joyent/node-triton#108 support passphrase protected keys
Reviewed by: Trent Mick <trent.mick@joyent.com> Approved by: Trent Mick <trent.mick@joyent.com>
This commit is contained in:
parent
696439f1ae
commit
ad7d608011
77
CHANGES.md
77
CHANGES.md
@ -7,6 +7,83 @@ Known issues:
|
|||||||
|
|
||||||
## not yet released
|
## not yet released
|
||||||
|
|
||||||
|
- **BREAKING CHANGE for module usage of node-triton.**
|
||||||
|
To implement joyent/node-triton#108, the way a TritonApi client is
|
||||||
|
setup for use has changed from being (unrealistically) sync to async.
|
||||||
|
|
||||||
|
Client preparation is now a multi-step process:
|
||||||
|
|
||||||
|
1. create the client object;
|
||||||
|
2. initialize it (mainly involves finding the SSH key identified by the
|
||||||
|
`keyId`); and,
|
||||||
|
3. optionally unlock the SSH key (if it is passphrase-protected and not in
|
||||||
|
an ssh-agent).
|
||||||
|
|
||||||
|
`createClient` has changed to take a callback argument. It will create and
|
||||||
|
init the client (steps 1 and 2) and takes an optional `unlockKeyFn` parameter
|
||||||
|
to handle step 3. A new `mod_triton.promptPassphraseUnlockKey` export can be
|
||||||
|
used for `unlockKeyFn` for command-line tools to handle prompting for a
|
||||||
|
passphrase on stdin, if required. Therefore what used to be:
|
||||||
|
|
||||||
|
var mod_triton = require('triton');
|
||||||
|
try {
|
||||||
|
var client = mod_triton.createClient({ # No longer works.
|
||||||
|
profileName: 'env'
|
||||||
|
});
|
||||||
|
} catch (initErr) {
|
||||||
|
// handle err
|
||||||
|
}
|
||||||
|
|
||||||
|
// use `client`
|
||||||
|
|
||||||
|
is now:
|
||||||
|
|
||||||
|
var mod_triton = require('triton');
|
||||||
|
mod_triton.createClient({
|
||||||
|
profileName: 'env',
|
||||||
|
unlockKeyFn: triton.promptPassphraseUnlockKey
|
||||||
|
}, function (err, client) {
|
||||||
|
if (err) {
|
||||||
|
// handle err
|
||||||
|
}
|
||||||
|
|
||||||
|
// use `client`
|
||||||
|
});
|
||||||
|
|
||||||
|
See [the examples/ directory](examples/) for more complete examples.
|
||||||
|
|
||||||
|
Low-level/raw handling of the three steps above is possible as follows
|
||||||
|
(error handling is elided):
|
||||||
|
|
||||||
|
var mod_bunyan = require('bunyan');
|
||||||
|
var mod_triton = require('triton');
|
||||||
|
|
||||||
|
// 1. create
|
||||||
|
var client = mod_triton.createTritonApiClient({
|
||||||
|
log: mod_bunyan.createLogger({name: 'my-tool'}),
|
||||||
|
config: {},
|
||||||
|
profile: mod_triton.loadProfile('env')
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. init
|
||||||
|
client.init(function (initErr) {
|
||||||
|
// 3. unlock key
|
||||||
|
// See top-comment in "lib/tritonapi.js".
|
||||||
|
});
|
||||||
|
|
||||||
|
- [joyent/node-triton#108] Support for passphrase-protected private keys.
|
||||||
|
Before this work, an encrypted private SSH key (i.e. protected by a
|
||||||
|
passphrase) would have to be loaded in an ssh-agent for the `triton`
|
||||||
|
CLI to use it. Now `triton` will prompt for the passphrase to unlock
|
||||||
|
the private key (in memory), if needed. For example:
|
||||||
|
|
||||||
|
$ triton package list
|
||||||
|
Enter passphrase for id_rsa:
|
||||||
|
SHORTID NAME MEMORY SWAP DISK VCPUS
|
||||||
|
14ad9d54 g4-highcpu-128M 128M 512M 3G -
|
||||||
|
14ae2634 g4-highcpu-256M 256M 1G 5G -
|
||||||
|
...
|
||||||
|
|
||||||
- [joyent/node-triton#143] Fix duplicate output from 'triton rbac key ...'.
|
- [joyent/node-triton#143] Fix duplicate output from 'triton rbac key ...'.
|
||||||
|
|
||||||
## 4.15.0
|
## 4.15.0
|
||||||
|
55
README.md
55
README.md
@ -234,19 +234,27 @@ documentation](https://apidocs.joyent.com/docker) for more information.)
|
|||||||
## `TritonApi` Module Usage
|
## `TritonApi` Module Usage
|
||||||
|
|
||||||
Node-triton can also be used as a node module for your own node.js tooling.
|
Node-triton can also be used as a node module for your own node.js tooling.
|
||||||
A basic example:
|
A basic example appropriate for a command-line tool is:
|
||||||
|
|
||||||
var triton = require('triton');
|
```javascript
|
||||||
|
var mod_bunyan = require('bunyan');
|
||||||
|
var mod_triton = require('triton');
|
||||||
|
|
||||||
|
var log = mod_bunyan.createLogger({name: 'my-tool'});
|
||||||
|
|
||||||
|
// See the `createClient` block comment for full usage details:
|
||||||
|
// https://github.com/joyent/node-triton/blob/master/lib/index.js
|
||||||
|
mod_triton.createClient({
|
||||||
|
log: log,
|
||||||
|
// Use 'env' to pick up 'TRITON_/SDC_' env vars. Or manually specify a
|
||||||
|
// `profile` object.
|
||||||
|
profileName: 'env',
|
||||||
|
unlockKeyFn: mod_triton.promptPassphraseUnlockKey
|
||||||
|
}, function (err, client) {
|
||||||
|
if (err) {
|
||||||
|
// handle err
|
||||||
|
}
|
||||||
|
|
||||||
// See `createClient` block comment for full usage details:
|
|
||||||
// https://github.com/joyent/node-triton/blob/master/lib/index.js
|
|
||||||
var client = triton.createClient({
|
|
||||||
profile: {
|
|
||||||
url: URL,
|
|
||||||
account: ACCOUNT,
|
|
||||||
keyId: KEY_ID
|
|
||||||
}
|
|
||||||
});
|
|
||||||
client.listImages(function (err, images) {
|
client.listImages(function (err, images) {
|
||||||
client.close(); // Remember to close the client to close TCP conn.
|
client.close(); // Remember to close the client to close TCP conn.
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -255,7 +263,14 @@ A basic example:
|
|||||||
console.log(JSON.stringify(images, null, 4));
|
console.log(JSON.stringify(images, null, 4));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
See the following for more details:
|
||||||
|
- The block-comment for `createClient` in [lib/index.js](lib/index.js).
|
||||||
|
- Some module-usage examples in [examples/](examples/).
|
||||||
|
- The lower-level details in the top-comment in
|
||||||
|
[lib/tritonapi.js](lib/tritonapi.js).
|
||||||
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
@ -280,24 +295,6 @@ are in "etc/defaults.json" and can be overriden for the CLI in
|
|||||||
catching up and is much more friendly to use.
|
catching up and is much more friendly to use.
|
||||||
|
|
||||||
|
|
||||||
## cloudapi2.js differences with node-smartdc/lib/cloudapi.js
|
|
||||||
|
|
||||||
The old node-smartdc module included an lib for talking directly to the SDC
|
|
||||||
Cloud API (node-smartdc/lib/cloudapi.js). Part of this module (node-triton) is a
|
|
||||||
re-write of the Cloud API lib with some backward incompatibilities. The
|
|
||||||
differences and backward incompatibilities are discussed here.
|
|
||||||
|
|
||||||
- Currently no caching options in cloudapi2.js (this should be re-added in
|
|
||||||
some form). The `noCache` option to many of the cloudapi.js methods will not
|
|
||||||
be re-added, it was a wart.
|
|
||||||
- The leading `account` option to each cloudapi.js method has been dropped. It
|
|
||||||
was redundant for the constructor `account` option.
|
|
||||||
- "account" is now "user" in the CloudAPI constructor.
|
|
||||||
- All (all? at least at the time of this writing) methods in cloudapi2.js have
|
|
||||||
a signature of `function (options, callback)` instead of the sometimes
|
|
||||||
haphazard extra arguments.
|
|
||||||
|
|
||||||
|
|
||||||
## Development Hooks
|
## Development Hooks
|
||||||
|
|
||||||
Before commiting be sure to, at least:
|
Before commiting be sure to, at least:
|
||||||
|
@ -1,42 +1,45 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
/**
|
/**
|
||||||
* Example using cloudapi2.js to call cloudapi's GetAccount endpoint.
|
* Example creating a Triton API client and using it to get account info.
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* ./example-get-account.js | bunyan
|
* ./example-get-account.js
|
||||||
|
*
|
||||||
|
* # With trace-level logging
|
||||||
|
* LOG_LEVEL=trace ./example-get-account.js 2>&1 | bunyan
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var p = console.log;
|
|
||||||
var auth = require('smartdc-auth');
|
|
||||||
var bunyan = require('bunyan');
|
var bunyan = require('bunyan');
|
||||||
var cloudapi = require('../lib/cloudapi2');
|
var path = require('path');
|
||||||
|
var triton = require('../'); // typically `require('triton');`
|
||||||
|
|
||||||
var log = bunyan.createLogger({
|
var log = bunyan.createLogger({
|
||||||
name: 'example-get-account',
|
name: path.basename(__filename),
|
||||||
level: 'trace'
|
level: process.env.LOG_LEVEL || 'info',
|
||||||
})
|
stream: process.stderr
|
||||||
|
|
||||||
var ACCOUNT = process.env.SDC_ACCOUNT || 'bob';
|
|
||||||
var USER = process.env.SDC_USER;
|
|
||||||
var KEY_ID = process.env.SDC_KEY_ID || 'b4:f0:b4:6c:18:3b:44:63:b4:4e:58:22:74:43:d4:bc';
|
|
||||||
|
|
||||||
var sign = auth.cliSigner({
|
|
||||||
keyId: KEY_ID,
|
|
||||||
user: ACCOUNT,
|
|
||||||
log: log
|
|
||||||
});
|
|
||||||
var client = cloudapi.createClient({
|
|
||||||
url: 'https://us-sw-1.api.joyent.com',
|
|
||||||
account: ACCOUNT,
|
|
||||||
user: USER,
|
|
||||||
version: '*',
|
|
||||||
sign: sign,
|
|
||||||
agent: false, // don't want KeepAlive
|
|
||||||
log: log
|
|
||||||
});
|
});
|
||||||
|
|
||||||
log.info('start')
|
triton.createClient({
|
||||||
client.getAccount(function (err, account) {
|
log: log,
|
||||||
p('getAccount: err', err)
|
// Use 'env' to pick up 'TRITON_/SDC_' env vars. Or manually specify a
|
||||||
p('getAccount: account', account)
|
// `profile` object.
|
||||||
|
profileName: 'env',
|
||||||
|
unlockKeyFn: triton.promptPassphraseUnlockKey
|
||||||
|
}, function createdClient(err, client) {
|
||||||
|
if (err) {
|
||||||
|
console.error('error creating Triton client: %s\n%s', err, err.stack);
|
||||||
|
process.exitStatus = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Eventually the top-level TritonApi will have `.getAccount()`.
|
||||||
|
client.cloudapi.getAccount(function (err, account) {
|
||||||
|
client.close(); // Remember to close the client to close TCP conn.
|
||||||
|
if (err) {
|
||||||
|
console.error('getAccount error: %s\n%s', err, err.stack);
|
||||||
|
process.exitStatus = 1;
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify(account, null, 4));
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,46 +1,46 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
/**
|
/**
|
||||||
* Example using cloudapi2.js to call cloudapi's ListMachines endpoint.
|
* Example creating a Triton API client and using it to list instances.
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* ./example-list-images.js | bunyan
|
* ./example-list-instances.js
|
||||||
|
*
|
||||||
|
* # With trace-level logging
|
||||||
|
* LOG_LEVEL=trace ./example-list-instances.js 2>&1 | bunyan
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var p = console.log;
|
|
||||||
var bunyan = require('bunyan');
|
var bunyan = require('bunyan');
|
||||||
|
var path = require('path');
|
||||||
var triton = require('../'); // typically `require('triton');`
|
var triton = require('../'); // typically `require('triton');`
|
||||||
|
|
||||||
|
|
||||||
var URL = process.env.SDC_URL || 'https://us-sw-1.api.joyent.com';
|
|
||||||
var ACCOUNT = process.env.SDC_ACCOUNT || 'bob';
|
|
||||||
var KEY_ID = process.env.SDC_KEY_ID || 'b4:f0:b4:6c:18:3b:44:63:b4:4e:58:22:74:43:d4:bc';
|
|
||||||
|
|
||||||
|
|
||||||
var log = bunyan.createLogger({
|
var log = bunyan.createLogger({
|
||||||
name: 'test-list-instances',
|
name: path.basename(__filename),
|
||||||
level: process.env.LOG_LEVEL || 'trace'
|
level: process.env.LOG_LEVEL || 'info',
|
||||||
|
stream: process.stderr
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
triton.createClient({
|
||||||
* More details on `createClient` options here:
|
|
||||||
* https://github.com/joyent/node-triton/blob/master/lib/index.js#L18-L61
|
|
||||||
* For example, if you want to use an existing `triton` CLI profile, you can
|
|
||||||
* pass that profile name in.
|
|
||||||
*/
|
|
||||||
var client = triton.createClient({
|
|
||||||
log: log,
|
log: log,
|
||||||
profile: {
|
// Use 'env' to pick up 'TRITON_/SDC_' env vars. Or manually specify a
|
||||||
url: URL,
|
// `profile` object.
|
||||||
account: ACCOUNT,
|
profileName: 'env',
|
||||||
keyId: KEY_ID
|
unlockKeyFn: triton.promptPassphraseUnlockKey
|
||||||
}
|
}, function createdClient(err, client) {
|
||||||
});
|
|
||||||
// TODO: Eventually the top-level TritonApi will have `.listInstances()` to use.
|
|
||||||
client.cloudapi.listMachines(function (err, insts) {
|
|
||||||
client.close(); // Remember to close the client to close TCP conn.
|
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('listInstances err:', err);
|
console.error('error creating Triton client: %s\n%s', err, err.stack);
|
||||||
} else {
|
process.exitStatus = 1;
|
||||||
console.log(JSON.stringify(insts, null, 4));
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Eventually the top-level TritonApi will have `.listInstances()`.
|
||||||
|
client.cloudapi.listMachines(function (err, insts) {
|
||||||
|
client.close(); // Remember to close the client to close TCP conn.
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
console.error('listInstances error: %s\n%s', err, err.stack);
|
||||||
|
process.exitStatus = 1;
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify(insts, null, 4));
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
65
lib/cli.js
65
lib/cli.js
@ -27,7 +27,7 @@ var vasync = require('vasync');
|
|||||||
var common = require('./common');
|
var common = require('./common');
|
||||||
var mod_config = require('./config');
|
var mod_config = require('./config');
|
||||||
var errors = require('./errors');
|
var errors = require('./errors');
|
||||||
var tritonapi = require('./tritonapi');
|
var lib_tritonapi = require('./tritonapi');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -158,7 +158,7 @@ var OPTIONS = [
|
|||||||
help: 'A cloudapi API version, or semver range, to attempt to use. ' +
|
help: 'A cloudapi API version, or semver range, to attempt to use. ' +
|
||||||
'This is passed in the "Accept-Version" header. ' +
|
'This is passed in the "Accept-Version" header. ' +
|
||||||
'See `triton cloudapi /--ping` to list supported versions. ' +
|
'See `triton cloudapi /--ping` to list supported versions. ' +
|
||||||
'The default is "' + tritonapi.CLOUDAPI_ACCEPT_VERSION + '". ' +
|
'The default is "' + lib_tritonapi.CLOUDAPI_ACCEPT_VERSION + '". ' +
|
||||||
'*This is intended for development use only. It could cause ' +
|
'*This is intended for development use only. It could cause ' +
|
||||||
'`triton` processing of responses to break.*',
|
'`triton` processing of responses to break.*',
|
||||||
hidden: true
|
hidden: true
|
||||||
@ -302,16 +302,16 @@ CLI.prototype.init = function (opts, args, callback) {
|
|||||||
return self._profile;
|
return self._profile;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.__defineGetter__('tritonapi', function getTritonapi() {
|
try {
|
||||||
if (self._tritonapi === undefined) {
|
self.tritonapi = lib_tritonapi.createClient({
|
||||||
self._tritonapi = tritonapi.createClient({
|
log: self.log,
|
||||||
log: self.log,
|
profile: self.profile,
|
||||||
profile: self.profile,
|
config: self.config
|
||||||
config: self.config
|
});
|
||||||
});
|
} catch (createErr) {
|
||||||
}
|
callback(createErr);
|
||||||
return self._tritonapi;
|
return;
|
||||||
});
|
}
|
||||||
|
|
||||||
if (process.env.TRITON_COMPLETE) {
|
if (process.env.TRITON_COMPLETE) {
|
||||||
/*
|
/*
|
||||||
@ -326,21 +326,21 @@ CLI.prototype.init = function (opts, args, callback) {
|
|||||||
* Example usage:
|
* Example usage:
|
||||||
* TRITON_COMPLETE=images triton -p my-profile create
|
* TRITON_COMPLETE=images triton -p my-profile create
|
||||||
*/
|
*/
|
||||||
this._emitCompletions(process.env.TRITON_COMPLETE, function (err) {
|
self._emitCompletions(process.env.TRITON_COMPLETE, function (err) {
|
||||||
callback(err || false);
|
callback(err || false);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Cmdln class handles `opts.help`.
|
// Cmdln class handles `opts.help`.
|
||||||
Cmdln.prototype.init.apply(this, arguments);
|
Cmdln.prototype.init.call(self, opts, args, callback);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
CLI.prototype.fini = function fini(subcmd, err, cb) {
|
CLI.prototype.fini = function fini(subcmd, err, cb) {
|
||||||
this.log.trace({err: err, subcmd: subcmd}, 'cli fini');
|
this.log.trace({err: err, subcmd: subcmd}, 'cli fini');
|
||||||
if (this._tritonapi) {
|
if (this.tritonapi) {
|
||||||
this._tritonapi.close();
|
this.tritonapi.close();
|
||||||
delete this._tritonapi;
|
delete this.tritonapi;
|
||||||
}
|
}
|
||||||
cb();
|
cb();
|
||||||
};
|
};
|
||||||
@ -361,7 +361,7 @@ CLI.prototype._emitCompletions = function _emitCompletions(type, cb) {
|
|||||||
|
|
||||||
var cacheFile = path.join(this.tritonapi.cacheDir, type + '.completions');
|
var cacheFile = path.join(this.tritonapi.cacheDir, type + '.completions');
|
||||||
var ttl = 5 * 60 * 1000; // timeout of cache file info (ms)
|
var ttl = 5 * 60 * 1000; // timeout of cache file info (ms)
|
||||||
var cloudapi = this.tritonapi.cloudapi;
|
var tritonapi = this.tritonapi;
|
||||||
|
|
||||||
vasync.pipeline({arg: {}, funcs: [
|
vasync.pipeline({arg: {}, funcs: [
|
||||||
function tryCacheFile(arg, next) {
|
function tryCacheFile(arg, next) {
|
||||||
@ -377,13 +377,25 @@ CLI.prototype._emitCompletions = function _emitCompletions(type, cb) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
function initAuth(args, next) {
|
||||||
|
tritonapi.init(function (initErr) {
|
||||||
|
if (initErr) {
|
||||||
|
next(initErr);
|
||||||
|
}
|
||||||
|
if (tritonapi.keyPair.isLocked()) {
|
||||||
|
next(new errors.TritonError(
|
||||||
|
'cannot unlock keys during completion'));
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
function gather(arg, next) {
|
function gather(arg, next) {
|
||||||
var completions;
|
var completions;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'packages':
|
case 'packages':
|
||||||
cloudapi.listPackages({}, function (err, pkgs) {
|
tritonapi.cloudapi.listPackages({}, function (err, pkgs) {
|
||||||
if (err) {
|
if (err) {
|
||||||
next(err);
|
next(err);
|
||||||
return;
|
return;
|
||||||
@ -402,7 +414,7 @@ CLI.prototype._emitCompletions = function _emitCompletions(type, cb) {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'images':
|
case 'images':
|
||||||
cloudapi.listImages({}, function (err, imgs) {
|
tritonapi.cloudapi.listImages({}, function (err, imgs) {
|
||||||
if (err) {
|
if (err) {
|
||||||
next(err);
|
next(err);
|
||||||
return;
|
return;
|
||||||
@ -424,7 +436,7 @@ CLI.prototype._emitCompletions = function _emitCompletions(type, cb) {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'instances':
|
case 'instances':
|
||||||
cloudapi.listMachines({}, function (err, insts) {
|
tritonapi.cloudapi.listMachines({}, function (err, insts) {
|
||||||
if (err) {
|
if (err) {
|
||||||
next(err);
|
next(err);
|
||||||
return;
|
return;
|
||||||
@ -449,7 +461,7 @@ CLI.prototype._emitCompletions = function _emitCompletions(type, cb) {
|
|||||||
* on that is that with the additional prefixes, there would
|
* on that is that with the additional prefixes, there would
|
||||||
* be too many.
|
* be too many.
|
||||||
*/
|
*/
|
||||||
cloudapi.listMachines({}, function (err, insts) {
|
tritonapi.cloudapi.listMachines({}, function (err, insts) {
|
||||||
if (err) {
|
if (err) {
|
||||||
next(err);
|
next(err);
|
||||||
return;
|
return;
|
||||||
@ -470,7 +482,7 @@ CLI.prototype._emitCompletions = function _emitCompletions(type, cb) {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'networks':
|
case 'networks':
|
||||||
cloudapi.listNetworks({}, function (err, nets) {
|
tritonapi.cloudapi.listNetworks({}, function (err, nets) {
|
||||||
if (err) {
|
if (err) {
|
||||||
next(err);
|
next(err);
|
||||||
return;
|
return;
|
||||||
@ -489,7 +501,8 @@ CLI.prototype._emitCompletions = function _emitCompletions(type, cb) {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'fwrules':
|
case 'fwrules':
|
||||||
cloudapi.listFirewallRules({}, function (err, fwrules) {
|
tritonapi.cloudapi.listFirewallRules({}, function (err,
|
||||||
|
fwrules) {
|
||||||
if (err) {
|
if (err) {
|
||||||
next(err);
|
next(err);
|
||||||
return;
|
return;
|
||||||
@ -503,7 +516,7 @@ CLI.prototype._emitCompletions = function _emitCompletions(type, cb) {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'keys':
|
case 'keys':
|
||||||
cloudapi.listKeys({}, function (err, keys) {
|
tritonapi.cloudapi.listKeys({}, function (err, keys) {
|
||||||
if (err) {
|
if (err) {
|
||||||
next(err);
|
next(err);
|
||||||
return;
|
return;
|
||||||
@ -602,7 +615,7 @@ CLI.prototype.tritonapiFromProfileName =
|
|||||||
'tritonapiFromProfileName: loaded profile');
|
'tritonapiFromProfileName: loaded profile');
|
||||||
}
|
}
|
||||||
|
|
||||||
return tritonapi.createClient({
|
return lib_tritonapi.createClient({
|
||||||
log: this.log,
|
log: this.log,
|
||||||
profile: profile,
|
profile: profile,
|
||||||
config: this.config
|
config: this.config
|
||||||
|
@ -41,6 +41,7 @@ var os = require('os');
|
|||||||
var querystring = require('querystring');
|
var querystring = require('querystring');
|
||||||
var vasync = require('vasync');
|
var vasync = require('vasync');
|
||||||
var auth = require('smartdc-auth');
|
var auth = require('smartdc-auth');
|
||||||
|
var EventEmitter = require('events').EventEmitter;
|
||||||
|
|
||||||
var bunyannoop = require('./bunyannoop');
|
var bunyannoop = require('./bunyannoop');
|
||||||
var common = require('./common');
|
var common = require('./common');
|
||||||
@ -64,10 +65,7 @@ var OS_PLATFORM = os.platform();
|
|||||||
*
|
*
|
||||||
* @param options {Object}
|
* @param options {Object}
|
||||||
* - {String} url (required) Cloud API base url
|
* - {String} url (required) Cloud API base url
|
||||||
* - {String} account (required) The account login name.
|
* - Authentication options (see below)
|
||||||
* - {Function} sign (required) An http-signature auth signing function
|
|
||||||
* - {String} user (optional) The RBAC user login name.
|
|
||||||
* - {Array of String} roles (optional) RBAC role(s) to take up.
|
|
||||||
* - {String} version (optional) Used for the accept-version header. This
|
* - {String} version (optional) Used for the accept-version header. This
|
||||||
* defaults to '*', meaning that over time you could experience breaking
|
* defaults to '*', meaning that over time you could experience breaking
|
||||||
* changes. Specifying a value is strongly recommended. E.g. '~7.1'.
|
* changes. Specifying a value is strongly recommended. E.g. '~7.1'.
|
||||||
@ -78,6 +76,28 @@ var OS_PLATFORM = os.platform();
|
|||||||
* {Boolean} agent Set to `false` to not get KeepAlive. You want
|
* {Boolean} agent Set to `false` to not get KeepAlive. You want
|
||||||
* this for CLIs.
|
* this for CLIs.
|
||||||
* TODO doc the backoff/retry available options
|
* TODO doc the backoff/retry available options
|
||||||
|
*
|
||||||
|
* Authentication options can be given in two ways - either with a
|
||||||
|
* smartdc-auth KeyPair (the preferred method), or with a signer function
|
||||||
|
* (deprecated, retained for compatibility).
|
||||||
|
*
|
||||||
|
* Either (prefered):
|
||||||
|
* - {String} account (required) The account login name this cloudapi
|
||||||
|
* client will operate upon.
|
||||||
|
* - {Object} principal (required)
|
||||||
|
* - {String} account (required) The account login name for
|
||||||
|
* authentication.
|
||||||
|
* - {Object} keyPair (required) A smartdc-auth KeyPair object
|
||||||
|
* - {String} user (optional) RBAC sub-user login name
|
||||||
|
* - {Array of String} roles (optional) RBAC role(s) to take up.
|
||||||
|
*
|
||||||
|
* Or (backwards compatible):
|
||||||
|
* - {String} account (required) The account login name used both for
|
||||||
|
* authentication and as the account being operated upon.
|
||||||
|
* - {Function} sign (required) An http-signature auth signing function.
|
||||||
|
* - {String} user (optional) The RBAC user login name.
|
||||||
|
* - {Array of String} roles (optional) RBAC role(s) to take up.
|
||||||
|
*
|
||||||
* @throws {TypeError} on bad input.
|
* @throws {TypeError} on bad input.
|
||||||
* @constructor
|
* @constructor
|
||||||
*
|
*
|
||||||
@ -90,17 +110,30 @@ function CloudApi(options) {
|
|||||||
assert.object(options, 'options');
|
assert.object(options, 'options');
|
||||||
assert.string(options.url, 'options.url');
|
assert.string(options.url, 'options.url');
|
||||||
assert.string(options.account, 'options.account');
|
assert.string(options.account, 'options.account');
|
||||||
assert.func(options.sign, 'options.sign');
|
|
||||||
assert.optionalString(options.user, 'options.user');
|
|
||||||
assert.optionalArrayOfString(options.roles, 'options.roles');
|
assert.optionalArrayOfString(options.roles, 'options.roles');
|
||||||
assert.optionalString(options.version, 'options.version');
|
assert.optionalString(options.version, 'options.version');
|
||||||
assert.optionalObject(options.log, 'options.log');
|
assert.optionalObject(options.log, 'options.log');
|
||||||
|
|
||||||
|
assert.optionalObject(options.principal, 'options.principal');
|
||||||
|
this.principal = options.principal;
|
||||||
|
if (options.principal === undefined) {
|
||||||
|
this.principal = {};
|
||||||
|
this.principal.account = options.account;
|
||||||
|
assert.optionalString(options.user, 'options.user');
|
||||||
|
if (options.user !== undefined)
|
||||||
|
this.principal.user = options.user;
|
||||||
|
assert.func(options.sign, 'options.sign');
|
||||||
|
this.principal.sign = options.sign;
|
||||||
|
} else {
|
||||||
|
assert.string(this.principal.account, 'principal.account');
|
||||||
|
assert.object(this.principal.keyPair, 'principal.keyPair');
|
||||||
|
assert.optionalString(this.principal.user, 'principal.user');
|
||||||
|
}
|
||||||
|
|
||||||
this.url = options.url;
|
this.url = options.url;
|
||||||
this.account = options.account;
|
this.account = options.account;
|
||||||
this.user = options.user; // optional RBAC subuser
|
|
||||||
this.roles = options.roles;
|
this.roles = options.roles;
|
||||||
this.sign = options.sign;
|
|
||||||
this.log = options.log || new bunyannoop.BunyanNoopLogger();
|
this.log = options.log || new bunyannoop.BunyanNoopLogger();
|
||||||
if (!options.version) {
|
if (!options.version) {
|
||||||
options.version = '*';
|
options.version = '*';
|
||||||
@ -128,16 +161,33 @@ CloudApi.prototype.close = function close(callback) {
|
|||||||
this.client.close();
|
this.client.close();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
CloudApi.prototype._getAuthHeaders =
|
||||||
|
function _getAuthHeaders(method, path, callback) {
|
||||||
|
|
||||||
CloudApi.prototype._getAuthHeaders = function _getAuthHeaders(callback) {
|
assert.string(method, 'method');
|
||||||
|
assert.string(path, 'path');
|
||||||
assert.func(callback, 'callback');
|
assert.func(callback, 'callback');
|
||||||
var self = this;
|
|
||||||
|
|
||||||
var headers = {};
|
var headers = {};
|
||||||
|
|
||||||
var rs = auth.requestSigner({
|
var rs;
|
||||||
sign: self.sign
|
if (this.principal.sign !== undefined) {
|
||||||
});
|
rs = auth.requestSigner({
|
||||||
|
sign: this.principal.sign
|
||||||
|
});
|
||||||
|
} else if (this.principal.keyPair !== undefined) {
|
||||||
|
try {
|
||||||
|
rs = this.principal.keyPair.createRequestSigner({
|
||||||
|
user: this.principal.account,
|
||||||
|
subuser: this.principal.user
|
||||||
|
});
|
||||||
|
} catch (signerErr) {
|
||||||
|
callback(new errors.SigningError(signerErr));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rs.writeTarget(method, path);
|
||||||
headers.date = rs.writeDateHeader();
|
headers.date = rs.writeDateHeader();
|
||||||
|
|
||||||
// TODO: token auth support
|
// TODO: token auth support
|
||||||
@ -222,14 +272,8 @@ CloudApi.prototype._request = function _request(opts, cb) {
|
|||||||
|
|
||||||
var method = (opts.method || 'GET').toLowerCase();
|
var method = (opts.method || 'GET').toLowerCase();
|
||||||
assert.ok(['get', 'post', 'put', 'delete', 'head'].indexOf(method) >= 0,
|
assert.ok(['get', 'post', 'put', 'delete', 'head'].indexOf(method) >= 0,
|
||||||
'invalid method given');
|
'invalid HTTP method given');
|
||||||
switch (method) {
|
var clientFnName = (method === 'delete' ? 'del' : method);
|
||||||
case 'delete':
|
|
||||||
method = 'del';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.roles && self.roles.length > 0) {
|
if (self.roles && self.roles.length > 0) {
|
||||||
if (opts.path.indexOf('?') !== -1) {
|
if (opts.path.indexOf('?') !== -1) {
|
||||||
@ -239,7 +283,7 @@ CloudApi.prototype._request = function _request(opts, cb) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self._getAuthHeaders(function (err, headers) {
|
self._getAuthHeaders(method, opts.path, function (err, headers) {
|
||||||
if (err) {
|
if (err) {
|
||||||
cb(err);
|
cb(err);
|
||||||
return;
|
return;
|
||||||
@ -252,9 +296,9 @@ CloudApi.prototype._request = function _request(opts, cb) {
|
|||||||
headers: headers
|
headers: headers
|
||||||
};
|
};
|
||||||
if (opts.data)
|
if (opts.data)
|
||||||
self.client[method](reqOpts, opts.data, cb);
|
self.client[clientFnName](reqOpts, opts.data, cb);
|
||||||
else
|
else
|
||||||
self.client[method](reqOpts, cb);
|
self.client[clientFnName](reqOpts, cb);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ var assert = require('assert-plus');
|
|||||||
var child_process = require('child_process');
|
var child_process = require('child_process');
|
||||||
var crypto = require('crypto');
|
var crypto = require('crypto');
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
|
var getpass = require('getpass');
|
||||||
var os = require('os');
|
var os = require('os');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var read = require('read');
|
var read = require('read');
|
||||||
@ -678,6 +679,99 @@ function promptField(field, cb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A utility method to unlock a private key on a TritonApi client instance,
|
||||||
|
* if necessary.
|
||||||
|
*
|
||||||
|
* If the client's key is locked, this will prompt for the passphrase on the
|
||||||
|
* TTY (via the `getpass` module) and attempt to unlock.
|
||||||
|
*
|
||||||
|
* @param opts {Object}
|
||||||
|
* - opts.tritonapi {Object} An `.init()`ialized TritonApi instance.
|
||||||
|
* @param cb {Function} `function (err)`
|
||||||
|
*/
|
||||||
|
function promptPassphraseUnlockKey(opts, cb) {
|
||||||
|
assert.object(opts.tritonapi, 'opts.tritonapi');
|
||||||
|
|
||||||
|
var kp = opts.tritonapi.keyPair;
|
||||||
|
if (!kp) {
|
||||||
|
cb(new errors.InternalError('TritonApi instance given to '
|
||||||
|
+ 'promptPassphraseUnlockKey is not initialized'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!kp.isLocked()) {
|
||||||
|
cb();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyDesc;
|
||||||
|
if (kp.source !== undefined) {
|
||||||
|
keyDesc = kp.source;
|
||||||
|
} else if (kp.comment !== undefined && kp.comment.length > 1) {
|
||||||
|
keyDesc = kp.getPublicKey().type.toUpperCase() +
|
||||||
|
' key for ' + kp.comment;
|
||||||
|
} else {
|
||||||
|
keyDesc = kp.getPublicKey().type.toUpperCase() +
|
||||||
|
' key ' + kp.getKeyId();
|
||||||
|
}
|
||||||
|
var getpassOpts = {
|
||||||
|
prompt: 'Enter passphrase for ' + keyDesc
|
||||||
|
};
|
||||||
|
|
||||||
|
var tryPass = function (err, pass) {
|
||||||
|
if (err) {
|
||||||
|
cb(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
kp.unlock(pass);
|
||||||
|
} catch (unlockErr) {
|
||||||
|
getpassOpts.prompt = 'Bad passphrase, try again for ' + keyDesc;
|
||||||
|
getpass.getPass(getpassOpts, tryPass);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cb(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
getpass.getPass(getpassOpts, tryPass);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A utility for the `triton` CLI subcommands to `init()`ialize a
|
||||||
|
* `tritonapi` instance and ensure that the profile's key is unlocked
|
||||||
|
* (prompting on a TTY if necessary). This is typically the CLI's
|
||||||
|
* `tritonapi` instance, but a `tritonapi` can also be passed in
|
||||||
|
* directly.
|
||||||
|
*
|
||||||
|
* @param opts.cli {Object}
|
||||||
|
* @param opts.tritonapi {Object}
|
||||||
|
* @param cb {Function} `function (err)`
|
||||||
|
*/
|
||||||
|
function cliSetupTritonApi(opts, cb) {
|
||||||
|
assert.optionalObject(opts.cli, 'opts.cli');
|
||||||
|
assert.optionalObject(opts.tritonapi, 'opts.tritonapi');
|
||||||
|
var tritonapi = opts.tritonapi || opts.cli.tritonapi;
|
||||||
|
assert.object(tritonapi, 'tritonapi');
|
||||||
|
|
||||||
|
tritonapi.init(function (initErr) {
|
||||||
|
if (initErr) {
|
||||||
|
cb(initErr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
promptPassphraseUnlockKey({
|
||||||
|
tritonapi: tritonapi
|
||||||
|
}, function (keyErr) {
|
||||||
|
cb(keyErr);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edit the given text in $EDITOR (defaulting to `vi`) and return the edited
|
* Edit the given text in $EDITOR (defaulting to `vi`) and return the edited
|
||||||
* text.
|
* text.
|
||||||
@ -984,6 +1078,8 @@ module.exports = {
|
|||||||
promptYesNo: promptYesNo,
|
promptYesNo: promptYesNo,
|
||||||
promptEnter: promptEnter,
|
promptEnter: promptEnter,
|
||||||
promptField: promptField,
|
promptField: promptField,
|
||||||
|
promptPassphraseUnlockKey: promptPassphraseUnlockKey,
|
||||||
|
cliSetupTritonApi: cliSetupTritonApi,
|
||||||
editInEditor: editInEditor,
|
editInEditor: editInEditor,
|
||||||
ansiStylize: ansiStylize,
|
ansiStylize: ansiStylize,
|
||||||
indent: indent,
|
indent: indent,
|
||||||
|
@ -21,28 +21,34 @@ function do_get(subcmd, opts, args, callback) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.top.tritonapi.cloudapi.getAccount(function (err, account) {
|
var tritonapi = this.top.tritonapi;
|
||||||
if (err) {
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
callback(err);
|
if (setupErr) {
|
||||||
return;
|
callback(setupErr);
|
||||||
}
|
}
|
||||||
|
tritonapi.cloudapi.getAccount(function (err, account) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (opts.json) {
|
if (opts.json) {
|
||||||
console.log(JSON.stringify(account));
|
console.log(JSON.stringify(account));
|
||||||
} else {
|
} else {
|
||||||
// pretty print
|
// pretty print
|
||||||
var dates = ['updated', 'created'];
|
var dates = ['updated', 'created'];
|
||||||
Object.keys(account).forEach(function (key) {
|
Object.keys(account).forEach(function (key) {
|
||||||
var val = account[key];
|
var val = account[key];
|
||||||
if (dates.indexOf(key) >= 0) {
|
if (dates.indexOf(key) >= 0) {
|
||||||
console.log('%s: %s (%s)', key, val,
|
console.log('%s: %s (%s)', key, val,
|
||||||
common.longAgo(new Date(val)));
|
common.longAgo(new Date(val)));
|
||||||
} else {
|
} else {
|
||||||
console.log('%s: %s', key, val);
|
console.log('%s: %s', key, val);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
callback();
|
callback();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,9 @@ function do_update(subcmd, opts, args, callback) {
|
|||||||
var log = this.log;
|
var log = this.log;
|
||||||
var tritonapi = this.top.tritonapi;
|
var tritonapi = this.top.tritonapi;
|
||||||
|
|
||||||
vasync.pipeline({arg: {}, funcs: [
|
vasync.pipeline({arg: {cli: this.top}, funcs: [
|
||||||
|
common.cliSetupTritonApi,
|
||||||
|
|
||||||
function gatherDataArgs(ctx, next) {
|
function gatherDataArgs(ctx, next) {
|
||||||
if (opts.file) {
|
if (opts.file) {
|
||||||
next();
|
next();
|
||||||
|
@ -31,36 +31,42 @@ function do_datacenters(subcmd, opts, args, callback) {
|
|||||||
|
|
||||||
var columns = opts.o.split(',');
|
var columns = opts.o.split(',');
|
||||||
var sort = opts.s.split(',');
|
var sort = opts.s.split(',');
|
||||||
|
var tritonapi = this.tritonapi;
|
||||||
|
|
||||||
this.tritonapi.cloudapi.listDatacenters(function (err, datacenters) {
|
common.cliSetupTritonApi({cli: this}, function onSetup(setupErr) {
|
||||||
if (err) {
|
if (setupErr) {
|
||||||
callback(err);
|
callback(setupErr);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
tritonapi.cloudapi.listDatacenters(function (err, datacenters) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (opts.json) {
|
if (opts.json) {
|
||||||
console.log(JSON.stringify(datacenters));
|
console.log(JSON.stringify(datacenters));
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
* datacenters are returned in the form of:
|
* datacenters are returned in the form of:
|
||||||
* {name: 'url', name2: 'url2', ...}
|
* {name: 'url', name2: 'url2', ...}
|
||||||
* we "normalize" them for use by tabula by making them an array
|
* we "normalize" them for use by tabula by making them an array
|
||||||
*/
|
*/
|
||||||
var dcs = [];
|
var dcs = [];
|
||||||
Object.keys(datacenters).forEach(function (key) {
|
Object.keys(datacenters).forEach(function (key) {
|
||||||
dcs.push({
|
dcs.push({
|
||||||
name: key,
|
name: key,
|
||||||
url: datacenters[key]
|
url: datacenters[key]
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
tabula(dcs, {
|
||||||
tabula(dcs, {
|
skipHeader: opts.H,
|
||||||
skipHeader: opts.H,
|
columns: columns,
|
||||||
columns: columns,
|
sort: sort,
|
||||||
sort: sort,
|
dottedLookup: true
|
||||||
dottedLookup: true
|
});
|
||||||
});
|
}
|
||||||
}
|
callback();
|
||||||
callback();
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,15 +45,21 @@ function do_create(subcmd, opts, args, cb) {
|
|||||||
createOpts.description = opts.description;
|
createOpts.description = opts.description;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.top.tritonapi.cloudapi.createFirewallRule(createOpts,
|
var tritonapi = this.top.tritonapi;
|
||||||
function (err, fwrule) {
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
if (err) {
|
if (setupErr) {
|
||||||
cb(err);
|
cb(setupErr);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
console.log('Created firewall rule %s%s', fwrule.id,
|
tritonapi.cloudapi.createFirewallRule(
|
||||||
(!fwrule.enabled ? ' (disabled)' : ''));
|
createOpts, function (err, fwrule) {
|
||||||
cb();
|
if (err) {
|
||||||
|
cb(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('Created firewall rule %s%s', fwrule.id,
|
||||||
|
(!fwrule.enabled ? ' (disabled)' : ''));
|
||||||
|
cb();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,10 +31,11 @@ function do_delete(subcmd, opts, args, cb) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var cli = this.top;
|
var tritonapi = this.top.tritonapi;
|
||||||
var ruleIds = args;
|
var ruleIds = args;
|
||||||
|
|
||||||
vasync.pipeline({funcs: [
|
vasync.pipeline({arg: {cli: this.top}, funcs: [
|
||||||
|
common.cliSetupTritonApi,
|
||||||
function confirm(_, next) {
|
function confirm(_, next) {
|
||||||
if (opts.force) {
|
if (opts.force) {
|
||||||
return next();
|
return next();
|
||||||
@ -61,8 +62,8 @@ function do_delete(subcmd, opts, args, cb) {
|
|||||||
vasync.forEachParallel({
|
vasync.forEachParallel({
|
||||||
inputs: ruleIds,
|
inputs: ruleIds,
|
||||||
func: function deleteOne(id, nextId) {
|
func: function deleteOne(id, nextId) {
|
||||||
cli.tritonapi.deleteFirewallRule({
|
tritonapi.deleteFirewallRule({
|
||||||
id: id
|
id: id
|
||||||
}, function (err) {
|
}, function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
nextId(err);
|
nextId(err);
|
||||||
|
@ -30,22 +30,26 @@ function do_disable(subcmd, opts, args, cb) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var cli = this.top;
|
var tritonapi = this.top.tritonapi;
|
||||||
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
vasync.forEachParallel({
|
if (setupErr) {
|
||||||
inputs: args,
|
cb(setupErr);
|
||||||
func: function disableOne(id, nextId) {
|
|
||||||
cli.tritonapi.disableFirewallRule({ id: id }, function (err) {
|
|
||||||
if (err) {
|
|
||||||
nextId(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Disabled firewall rule %s', id);
|
|
||||||
nextId();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, cb);
|
vasync.forEachParallel({
|
||||||
|
inputs: args,
|
||||||
|
func: function disableOne(id, nextId) {
|
||||||
|
tritonapi.disableFirewallRule({ id: id }, function (err) {
|
||||||
|
if (err) {
|
||||||
|
nextId(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Disabled firewall rule %s', id);
|
||||||
|
nextId();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, cb);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,22 +30,26 @@ function do_enable(subcmd, opts, args, cb) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var cli = this.top;
|
var tritonapi = this.top.tritonapi;
|
||||||
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
vasync.forEachParallel({
|
if (setupErr) {
|
||||||
inputs: args,
|
cb(setupErr);
|
||||||
func: function enableOne(id, nextId) {
|
|
||||||
cli.tritonapi.enableFirewallRule({ id: id }, function (err) {
|
|
||||||
if (err) {
|
|
||||||
nextId(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Enabled firewall rule %s', id);
|
|
||||||
nextId();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, cb);
|
vasync.forEachParallel({
|
||||||
|
inputs: args,
|
||||||
|
func: function enableOne(id, nextId) {
|
||||||
|
tritonapi.enableFirewallRule({ id: id }, function (err) {
|
||||||
|
if (err) {
|
||||||
|
nextId(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Enabled firewall rule %s', id);
|
||||||
|
nextId();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, cb);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,21 +33,26 @@ function do_get(subcmd, opts, args, cb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var id = args[0];
|
var id = args[0];
|
||||||
var cli = this.top;
|
var tritonapi = this.top.tritonapi;
|
||||||
|
|
||||||
cli.tritonapi.getFirewallRule(id, function onRule(err, fwrule) {
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
if (err) {
|
if (setupErr) {
|
||||||
cb(err);
|
cb(setupErr);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
tritonapi.getFirewallRule(id, function onRule(err, fwrule) {
|
||||||
|
if (err) {
|
||||||
|
cb(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (opts.json) {
|
if (opts.json) {
|
||||||
console.log(JSON.stringify(fwrule));
|
console.log(JSON.stringify(fwrule));
|
||||||
} else {
|
} else {
|
||||||
console.log(JSON.stringify(fwrule, null, 4));
|
console.log(JSON.stringify(fwrule, null, 4));
|
||||||
}
|
}
|
||||||
|
|
||||||
cb();
|
cb();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,75 +54,81 @@ function do_instances(subcmd, opts, args, cb) {
|
|||||||
|
|
||||||
var tritonapi = this.top.tritonapi;
|
var tritonapi = this.top.tritonapi;
|
||||||
|
|
||||||
vasync.parallel({funcs: [
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
function getTheImages(next) {
|
if (setupErr) {
|
||||||
tritonapi.listImages({
|
cb(setupErr);
|
||||||
useCache: true,
|
|
||||||
state: 'all'
|
|
||||||
}, function (err, _imgs) {
|
|
||||||
if (err) {
|
|
||||||
next(err);
|
|
||||||
} else {
|
|
||||||
imgs = _imgs;
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function getTheMachines(next) {
|
|
||||||
tritonapi.listFirewallRuleInstances({
|
|
||||||
id: id
|
|
||||||
}, function (err, _insts) {
|
|
||||||
if (err) {
|
|
||||||
next(err);
|
|
||||||
} else {
|
|
||||||
insts = _insts;
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
]}, function (err, results) {
|
|
||||||
/*
|
|
||||||
* Error handling: vasync.parallel's `err` is always a MultiError. We
|
|
||||||
* want to prefer the `getTheMachines` err, e.g. if both get a
|
|
||||||
* self-signed cert error.
|
|
||||||
*/
|
|
||||||
if (err) {
|
|
||||||
err = results.operations[1].err || err;
|
|
||||||
return cb(err);
|
|
||||||
}
|
}
|
||||||
|
vasync.parallel({funcs: [
|
||||||
|
function getTheImages(next) {
|
||||||
|
tritonapi.listImages({
|
||||||
|
useCache: true,
|
||||||
|
state: 'all'
|
||||||
|
}, function (err, _imgs) {
|
||||||
|
if (err) {
|
||||||
|
next(err);
|
||||||
|
} else {
|
||||||
|
imgs = _imgs;
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function getTheMachines(next) {
|
||||||
|
tritonapi.listFirewallRuleInstances({
|
||||||
|
id: id
|
||||||
|
}, function (err, _insts) {
|
||||||
|
if (err) {
|
||||||
|
next(err);
|
||||||
|
} else {
|
||||||
|
insts = _insts;
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
]}, function (err, results) {
|
||||||
|
/*
|
||||||
|
* Error handling: vasync.parallel's `err` is always a
|
||||||
|
* MultiError. We want to prefer the `getTheMachines` err,
|
||||||
|
* e.g. if both get a self-signed cert error.
|
||||||
|
*/
|
||||||
|
if (err) {
|
||||||
|
err = results.operations[1].err || err;
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
// map "uuid" => "image_name"
|
// map "uuid" => "image_name"
|
||||||
var imgmap = {};
|
var imgmap = {};
|
||||||
imgs.forEach(function (img) {
|
imgs.forEach(function (img) {
|
||||||
imgmap[img.id] = format('%s@%s', img.name, img.version);
|
imgmap[img.id] = format('%s@%s', img.name, img.version);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add extra fields for nice output.
|
||||||
|
var now = new Date();
|
||||||
|
insts.forEach(function (inst) {
|
||||||
|
var created = new Date(inst.created);
|
||||||
|
inst.age = common.longAgo(created, now);
|
||||||
|
inst.img = imgmap[inst.image] ||
|
||||||
|
common.uuidToShortId(inst.image);
|
||||||
|
inst.shortid = inst.id.split('-', 1)[0];
|
||||||
|
var flags = [];
|
||||||
|
if (inst.docker) flags.push('D');
|
||||||
|
if (inst.firewall_enabled) flags.push('F');
|
||||||
|
if (inst.brand === 'kvm') flags.push('K');
|
||||||
|
inst.flags = flags.length ? flags.join('') : undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (opts.json) {
|
||||||
|
common.jsonStream(insts);
|
||||||
|
} else {
|
||||||
|
tabula(insts, {
|
||||||
|
skipHeader: opts.H,
|
||||||
|
columns: columns,
|
||||||
|
sort: sort,
|
||||||
|
dottedLookup: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cb();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add extra fields for nice output.
|
|
||||||
var now = new Date();
|
|
||||||
insts.forEach(function (inst) {
|
|
||||||
var created = new Date(inst.created);
|
|
||||||
inst.age = common.longAgo(created, now);
|
|
||||||
inst.img = imgmap[inst.image] || common.uuidToShortId(inst.image);
|
|
||||||
inst.shortid = inst.id.split('-', 1)[0];
|
|
||||||
var flags = [];
|
|
||||||
if (inst.docker) flags.push('D');
|
|
||||||
if (inst.firewall_enabled) flags.push('F');
|
|
||||||
if (inst.brand === 'kvm') flags.push('K');
|
|
||||||
inst.flags = flags.length ? flags.join('') : undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (opts.json) {
|
|
||||||
common.jsonStream(insts);
|
|
||||||
} else {
|
|
||||||
tabula(insts, {
|
|
||||||
skipHeader: opts.H,
|
|
||||||
columns: columns,
|
|
||||||
sort: sort,
|
|
||||||
dottedLookup: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
cb();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,40 +35,45 @@ function do_list(subcmd, opts, args, cb) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var cli = this.top;
|
var tritonapi = this.top.tritonapi;
|
||||||
cli.tritonapi.cloudapi.listFirewallRules({}, function onRules(err, rules) {
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
if (err) {
|
if (setupErr) {
|
||||||
cb(err);
|
cb(setupErr);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
tritonapi.cloudapi.listFirewallRules({}, function onRules(err, rules) {
|
||||||
if (opts.json) {
|
if (err) {
|
||||||
common.jsonStream(rules);
|
cb(err);
|
||||||
} else {
|
return;
|
||||||
var columns = COLUMNS_DEFAULT;
|
|
||||||
|
|
||||||
if (opts.o) {
|
|
||||||
columns = opts.o;
|
|
||||||
} else if (opts.long) {
|
|
||||||
columns = COLUMNS_LONG;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
columns = columns.toLowerCase().split(',');
|
if (opts.json) {
|
||||||
var sort = opts.s.toLowerCase().split(',');
|
common.jsonStream(rules);
|
||||||
|
} else {
|
||||||
|
var columns = COLUMNS_DEFAULT;
|
||||||
|
|
||||||
if (columns.indexOf('shortid') !== -1) {
|
if (opts.o) {
|
||||||
rules.forEach(function (rule) {
|
columns = opts.o;
|
||||||
rule.shortid = common.uuidToShortId(rule.id);
|
} else if (opts.long) {
|
||||||
|
columns = COLUMNS_LONG;
|
||||||
|
}
|
||||||
|
|
||||||
|
columns = columns.toLowerCase().split(',');
|
||||||
|
var sort = opts.s.toLowerCase().split(',');
|
||||||
|
|
||||||
|
if (columns.indexOf('shortid') !== -1) {
|
||||||
|
rules.forEach(function (rule) {
|
||||||
|
rule.shortid = common.uuidToShortId(rule.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
tabula(rules, {
|
||||||
|
skipHeader: opts.H,
|
||||||
|
columns: columns,
|
||||||
|
sort: sort
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
tabula(rules, {
|
|
||||||
skipHeader: opts.H,
|
|
||||||
columns: columns,
|
|
||||||
sort: sort
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
cb();
|
cb();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,9 @@ function do_update(subcmd, opts, args, cb) {
|
|||||||
|
|
||||||
var id = args.shift();
|
var id = args.shift();
|
||||||
|
|
||||||
vasync.pipeline({arg: {}, funcs: [
|
vasync.pipeline({arg: {cli: this.top}, funcs: [
|
||||||
|
common.cliSetupTritonApi,
|
||||||
|
|
||||||
function gatherDataArgs(ctx, next) {
|
function gatherDataArgs(ctx, next) {
|
||||||
if (opts.file) {
|
if (opts.file) {
|
||||||
next();
|
next();
|
||||||
|
@ -26,7 +26,6 @@ var mat = require('../metadataandtags');
|
|||||||
// ---- the command
|
// ---- the command
|
||||||
|
|
||||||
function do_create(subcmd, opts, args, cb) {
|
function do_create(subcmd, opts, args, cb) {
|
||||||
var self = this;
|
|
||||||
if (opts.help) {
|
if (opts.help) {
|
||||||
this.do_help('help', {}, [subcmd], cb);
|
this.do_help('help', {}, [subcmd], cb);
|
||||||
return;
|
return;
|
||||||
@ -37,9 +36,10 @@ function do_create(subcmd, opts, args, cb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var log = this.top.log;
|
var log = this.top.log;
|
||||||
var cloudapi = this.top.tritonapi.cloudapi;
|
var tritonapi = this.top.tritonapi;
|
||||||
|
|
||||||
vasync.pipeline({arg: {}, funcs: [
|
vasync.pipeline({arg: {cli: this.top}, funcs: [
|
||||||
|
common.cliSetupTritonApi,
|
||||||
function loadTags(ctx, next) {
|
function loadTags(ctx, next) {
|
||||||
mat.tagsFromCreateOpts(opts, log, function (err, tags) {
|
mat.tagsFromCreateOpts(opts, log, function (err, tags) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -76,7 +76,7 @@ function do_create(subcmd, opts, args, cb) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.top.tritonapi.getInstance(id, function (err, inst) {
|
tritonapi.getInstance(id, function (err, inst) {
|
||||||
if (err) {
|
if (err) {
|
||||||
next(err);
|
next(err);
|
||||||
return;
|
return;
|
||||||
@ -113,20 +113,22 @@ function do_create(subcmd, opts, args, cb) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cloudapi.createImageFromMachine(createOpts, function (err, img) {
|
tritonapi.cloudapi.createImageFromMachine(
|
||||||
if (err) {
|
createOpts, function (err, img) {
|
||||||
next(new errors.TritonError(err, 'error creating image'));
|
if (err) {
|
||||||
return;
|
next(new errors.TritonError(err,
|
||||||
}
|
'error creating image'));
|
||||||
ctx.img = img;
|
return;
|
||||||
if (opts.json) {
|
}
|
||||||
console.log(JSON.stringify(img));
|
ctx.img = img;
|
||||||
} else {
|
if (opts.json) {
|
||||||
console.log('Creating image %s@%s (%s)',
|
console.log(JSON.stringify(img));
|
||||||
img.name, img.version, img.id);
|
} else {
|
||||||
}
|
console.log('Creating image %s@%s (%s)',
|
||||||
next();
|
img.name, img.version, img.id);
|
||||||
});
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
function maybeWait(ctx, next) {
|
function maybeWait(ctx, next) {
|
||||||
if (!opts.wait) {
|
if (!opts.wait) {
|
||||||
@ -147,8 +149,8 @@ function do_create(subcmd, opts, args, cb) {
|
|||||||
ctx.img.state = 'running';
|
ctx.img.state = 'running';
|
||||||
waitCb(null, ctx.img);
|
waitCb(null, ctx.img);
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
} : tritonapi.cloudapi.waitForImageStates.bind(
|
||||||
: cloudapi.waitForImageStates.bind(cloudapi));
|
tritonapi.cloudapi));
|
||||||
|
|
||||||
waiter({
|
waiter({
|
||||||
id: ctx.img.id,
|
id: ctx.img.id,
|
||||||
|
@ -26,7 +26,8 @@ function do_delete(subcmd, opts, args, cb) {
|
|||||||
}
|
}
|
||||||
var ids = args;
|
var ids = args;
|
||||||
|
|
||||||
vasync.pipeline({arg: {}, funcs: [
|
vasync.pipeline({arg: {cli: this.top}, funcs: [
|
||||||
|
common.cliSetupTritonApi,
|
||||||
/*
|
/*
|
||||||
* Lookup images, if not given UUIDs: we'll need to do it anyway
|
* Lookup images, if not given UUIDs: we'll need to do it anyway
|
||||||
* for the DeleteImage call(s), and doing so explicitly here allows
|
* for the DeleteImage call(s), and doing so explicitly here allows
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
var format = require('util').format;
|
var format = require('util').format;
|
||||||
|
|
||||||
|
var common = require('../common');
|
||||||
var errors = require('../errors');
|
var errors = require('../errors');
|
||||||
|
|
||||||
|
|
||||||
@ -24,17 +25,23 @@ function do_get(subcmd, opts, args, callback) {
|
|||||||
'incorrect number of args (%d)', args.length)));
|
'incorrect number of args (%d)', args.length)));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.top.tritonapi.getImage(args[0], function onRes(err, img) {
|
var tritonapi = this.top.tritonapi;
|
||||||
if (err) {
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
return callback(err);
|
if (setupErr) {
|
||||||
|
callback(setupErr);
|
||||||
}
|
}
|
||||||
|
tritonapi.getImage(args[0], function onRes(err, img) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
if (opts.json) {
|
if (opts.json) {
|
||||||
console.log(JSON.stringify(img));
|
console.log(JSON.stringify(img));
|
||||||
} else {
|
} else {
|
||||||
console.log(JSON.stringify(img, null, 4));
|
console.log(JSON.stringify(img, null, 4));
|
||||||
}
|
}
|
||||||
callback();
|
callback();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,42 +63,48 @@ function do_list(subcmd, opts, args, callback) {
|
|||||||
listOpts.state = 'all';
|
listOpts.state = 'all';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.top.tritonapi.listImages(listOpts, function onRes(err, imgs, res) {
|
var tritonapi = this.top.tritonapi;
|
||||||
if (err) {
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
return callback(err);
|
if (setupErr) {
|
||||||
|
callback(setupErr);
|
||||||
}
|
}
|
||||||
|
tritonapi.listImages(listOpts, function onRes(err, imgs, res) {
|
||||||
if (opts.json) {
|
if (err) {
|
||||||
common.jsonStream(imgs);
|
return callback(err);
|
||||||
} else {
|
|
||||||
// Add some convenience fields
|
|
||||||
// Added fields taken from imgapi-cli.git.
|
|
||||||
for (var i = 0; i < imgs.length; i++) {
|
|
||||||
var img = imgs[i];
|
|
||||||
img.shortid = img.id.split('-', 1)[0];
|
|
||||||
if (img.published_at) {
|
|
||||||
// Just the date.
|
|
||||||
img.pubdate = img.published_at.slice(0, 10);
|
|
||||||
// Normalize on no milliseconds.
|
|
||||||
img.pub = img.published_at.replace(/\.\d+Z$/, 'Z');
|
|
||||||
}
|
|
||||||
if (img.files && img.files[0]) {
|
|
||||||
img.size = img.files[0].size;
|
|
||||||
}
|
|
||||||
var flags = [];
|
|
||||||
if (img.origin) flags.push('I');
|
|
||||||
if (img['public']) flags.push('P');
|
|
||||||
if (img.state !== 'active') flags.push('X');
|
|
||||||
img.flags = flags.length ? flags.join('') : undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tabula(imgs, {
|
if (opts.json) {
|
||||||
skipHeader: opts.H,
|
common.jsonStream(imgs);
|
||||||
columns: columns,
|
} else {
|
||||||
sort: sort
|
// Add some convenience fields
|
||||||
});
|
// Added fields taken from imgapi-cli.git.
|
||||||
}
|
for (var i = 0; i < imgs.length; i++) {
|
||||||
callback();
|
var img = imgs[i];
|
||||||
|
img.shortid = img.id.split('-', 1)[0];
|
||||||
|
if (img.published_at) {
|
||||||
|
// Just the date.
|
||||||
|
img.pubdate = img.published_at.slice(0, 10);
|
||||||
|
// Normalize on no milliseconds.
|
||||||
|
img.pub = img.published_at.replace(/\.\d+Z$/, 'Z');
|
||||||
|
}
|
||||||
|
if (img.files && img.files[0]) {
|
||||||
|
img.size = img.files[0].size;
|
||||||
|
}
|
||||||
|
var flags = [];
|
||||||
|
if (img.origin) flags.push('I');
|
||||||
|
if (img['public']) flags.push('P');
|
||||||
|
if (img.state !== 'active') flags.push('X');
|
||||||
|
img.flags = flags.length ? flags.join('') : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
tabula(imgs, {
|
||||||
|
skipHeader: opts.H,
|
||||||
|
columns: columns,
|
||||||
|
sort: sort
|
||||||
|
});
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
var vasync = require('vasync');
|
var vasync = require('vasync');
|
||||||
|
|
||||||
|
var common = require('../common');
|
||||||
var distractions = require('../distractions');
|
var distractions = require('../distractions');
|
||||||
var errors = require('../errors');
|
var errors = require('../errors');
|
||||||
|
|
||||||
@ -34,7 +35,8 @@ function do_wait(subcmd, opts, args, cb) {
|
|||||||
var done = 0;
|
var done = 0;
|
||||||
var imgFromId = {};
|
var imgFromId = {};
|
||||||
|
|
||||||
vasync.pipeline({funcs: [
|
vasync.pipeline({arg: {cli: this.top}, funcs: [
|
||||||
|
common.cliSetupTritonApi,
|
||||||
function getImgs(_, next) {
|
function getImgs(_, next) {
|
||||||
vasync.forEachParallel({
|
vasync.forEachParallel({
|
||||||
inputs: ids,
|
inputs: ids,
|
||||||
|
124
lib/do_info.js
124
lib/do_info.js
@ -28,69 +28,75 @@ function do_info(subcmd, opts, args, callback) {
|
|||||||
|
|
||||||
var out = {};
|
var out = {};
|
||||||
var i = 0;
|
var i = 0;
|
||||||
|
var tritonapi = this.tritonapi;
|
||||||
|
|
||||||
this.tritonapi.cloudapi.getAccount(cb.bind('account')); i++;
|
common.cliSetupTritonApi({cli: this}, function onSetup(setupErr) {
|
||||||
this.tritonapi.cloudapi.listMachines(cb.bind('machines')); i++;
|
if (setupErr) {
|
||||||
|
callback(setupErr);
|
||||||
function cb(err, data) {
|
|
||||||
if (err) {
|
|
||||||
callback(err);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
out[this.toString()] = data;
|
tritonapi.cloudapi.getAccount(cb.bind('account')); i++;
|
||||||
if (--i === 0)
|
tritonapi.cloudapi.listMachines(cb.bind('machines')); i++;
|
||||||
done();
|
|
||||||
}
|
|
||||||
|
|
||||||
function done() {
|
function cb(err, data) {
|
||||||
// parse name
|
if (err) {
|
||||||
var name;
|
callback(err);
|
||||||
if (out.account.firstName && out.account.lastName)
|
return;
|
||||||
name = format('%s %s', out.account.firstName,
|
}
|
||||||
out.account.lastName);
|
out[this.toString()] = data;
|
||||||
else if (out.account.firstName)
|
if (--i === 0)
|
||||||
name = out.account.firstName;
|
done();
|
||||||
|
|
||||||
// parse machine states and accounting
|
|
||||||
var states = {};
|
|
||||||
var disk = 0;
|
|
||||||
var memory = 0;
|
|
||||||
out.machines.forEach(function (machine) {
|
|
||||||
var state = machine.state;
|
|
||||||
states[state] = states[state] || 0;
|
|
||||||
states[state]++;
|
|
||||||
memory += machine.memory;
|
|
||||||
disk += machine.disk;
|
|
||||||
});
|
|
||||||
disk *= 1000 * 1000;
|
|
||||||
memory *= 1000 * 1000;
|
|
||||||
|
|
||||||
var data = {};
|
|
||||||
data.login = out.account.login;
|
|
||||||
if (name)
|
|
||||||
data.name = name;
|
|
||||||
data.email = out.account.email;
|
|
||||||
data.url = self.tritonapi.cloudapi.url;
|
|
||||||
data.totalDisk = disk;
|
|
||||||
data.totalMemory = memory;
|
|
||||||
|
|
||||||
if (opts.json) {
|
|
||||||
data.totalInstances = out.machines.length;
|
|
||||||
data.instances = states;
|
|
||||||
console.log(JSON.stringify(data));
|
|
||||||
} else {
|
|
||||||
data.totalDisk = common.humanSizeFromBytes(disk);
|
|
||||||
data.totalMemory = common.humanSizeFromBytes(memory);
|
|
||||||
Object.keys(data).forEach(function (key) {
|
|
||||||
console.log('%s: %s', key, data[key]);
|
|
||||||
});
|
|
||||||
console.log('instances: %d', out.machines.length);
|
|
||||||
Object.keys(states).forEach(function (key) {
|
|
||||||
console.log(' %s: %d', key, states[key]);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
callback();
|
|
||||||
}
|
function done() {
|
||||||
|
// parse name
|
||||||
|
var name;
|
||||||
|
if (out.account.firstName && out.account.lastName)
|
||||||
|
name = format('%s %s', out.account.firstName,
|
||||||
|
out.account.lastName);
|
||||||
|
else if (out.account.firstName)
|
||||||
|
name = out.account.firstName;
|
||||||
|
|
||||||
|
// parse machine states and accounting
|
||||||
|
var states = {};
|
||||||
|
var disk = 0;
|
||||||
|
var memory = 0;
|
||||||
|
out.machines.forEach(function (machine) {
|
||||||
|
var state = machine.state;
|
||||||
|
states[state] = states[state] || 0;
|
||||||
|
states[state]++;
|
||||||
|
memory += machine.memory;
|
||||||
|
disk += machine.disk;
|
||||||
|
});
|
||||||
|
disk *= 1000 * 1000;
|
||||||
|
memory *= 1000 * 1000;
|
||||||
|
|
||||||
|
var data = {};
|
||||||
|
data.login = out.account.login;
|
||||||
|
if (name)
|
||||||
|
data.name = name;
|
||||||
|
data.email = out.account.email;
|
||||||
|
data.url = self.tritonapi.cloudapi.url;
|
||||||
|
data.totalDisk = disk;
|
||||||
|
data.totalMemory = memory;
|
||||||
|
|
||||||
|
if (opts.json) {
|
||||||
|
data.totalInstances = out.machines.length;
|
||||||
|
data.instances = states;
|
||||||
|
console.log(JSON.stringify(data));
|
||||||
|
} else {
|
||||||
|
data.totalDisk = common.humanSizeFromBytes(disk);
|
||||||
|
data.totalMemory = common.humanSizeFromBytes(memory);
|
||||||
|
Object.keys(data).forEach(function (key) {
|
||||||
|
console.log('%s: %s', key, data[key]);
|
||||||
|
});
|
||||||
|
console.log('instances: %d', out.machines.length);
|
||||||
|
Object.keys(states).forEach(function (key) {
|
||||||
|
console.log(' %s: %d', key, states[key]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
do_info.options = [
|
do_info.options = [
|
||||||
|
@ -27,7 +27,6 @@ var sortDefault = 'id,time';
|
|||||||
|
|
||||||
|
|
||||||
function do_audit(subcmd, opts, args, cb) {
|
function do_audit(subcmd, opts, args, cb) {
|
||||||
var self = this;
|
|
||||||
if (opts.help) {
|
if (opts.help) {
|
||||||
this.do_help('help', {}, [subcmd], cb);
|
this.do_help('help', {}, [subcmd], cb);
|
||||||
return;
|
return;
|
||||||
@ -51,23 +50,25 @@ function do_audit(subcmd, opts, args, cb) {
|
|||||||
|
|
||||||
var arg = args[0];
|
var arg = args[0];
|
||||||
var uuid;
|
var uuid;
|
||||||
|
var tritonapi = this.top.tritonapi;
|
||||||
|
|
||||||
if (common.isUUID(arg)) {
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
uuid = arg;
|
if (common.isUUID(arg)) {
|
||||||
go1();
|
uuid = arg;
|
||||||
} else {
|
|
||||||
self.top.tritonapi.getInstance(arg, function (err, inst) {
|
|
||||||
if (err) {
|
|
||||||
cb(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uuid = inst.id;
|
|
||||||
go1();
|
go1();
|
||||||
});
|
} else {
|
||||||
}
|
tritonapi.getInstance(arg, function (err, inst) {
|
||||||
|
if (err) {
|
||||||
|
cb(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uuid = inst.id;
|
||||||
|
go1();
|
||||||
|
});
|
||||||
|
}});
|
||||||
|
|
||||||
function go1() {
|
function go1() {
|
||||||
self.top.tritonapi.cloudapi.machineAudit(uuid, function (err, audit) {
|
tritonapi.cloudapi.machineAudit(uuid, function (err, audit) {
|
||||||
if (err) {
|
if (err) {
|
||||||
cb(err);
|
cb(err);
|
||||||
return;
|
return;
|
||||||
|
@ -22,7 +22,6 @@ var mat = require('../metadataandtags');
|
|||||||
|
|
||||||
|
|
||||||
function do_create(subcmd, opts, args, cb) {
|
function do_create(subcmd, opts, args, cb) {
|
||||||
var self = this;
|
|
||||||
if (opts.help) {
|
if (opts.help) {
|
||||||
this.do_help('help', {}, [subcmd], cb);
|
this.do_help('help', {}, [subcmd], cb);
|
||||||
return;
|
return;
|
||||||
@ -31,9 +30,10 @@ function do_create(subcmd, opts, args, cb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var log = this.top.log;
|
var log = this.top.log;
|
||||||
var cloudapi = this.top.tritonapi.cloudapi;
|
var tritonapi = this.top.tritonapi;
|
||||||
|
|
||||||
vasync.pipeline({arg: {}, funcs: [
|
vasync.pipeline({arg: {cli: this.top}, funcs: [
|
||||||
|
common.cliSetupTritonApi,
|
||||||
/* BEGIN JSSTYLED */
|
/* BEGIN JSSTYLED */
|
||||||
/*
|
/*
|
||||||
* Parse --affinity options for validity to `ctx.affinities`.
|
* Parse --affinity options for validity to `ctx.affinities`.
|
||||||
@ -158,7 +158,7 @@ function do_create(subcmd, opts, args, cb) {
|
|||||||
nearFar.push(aff.val);
|
nearFar.push(aff.val);
|
||||||
nextAff();
|
nextAff();
|
||||||
} else {
|
} else {
|
||||||
self.top.tritonapi.getInstance({
|
tritonapi.getInstance({
|
||||||
id: aff.val,
|
id: aff.val,
|
||||||
fields: ['id']
|
fields: ['id']
|
||||||
}, function (err, inst) {
|
}, function (err, inst) {
|
||||||
@ -222,7 +222,7 @@ function do_create(subcmd, opts, args, cb) {
|
|||||||
name: args[0],
|
name: args[0],
|
||||||
useCache: true
|
useCache: true
|
||||||
};
|
};
|
||||||
self.top.tritonapi.getImage(_opts, function (err, img) {
|
tritonapi.getImage(_opts, function (err, img) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return next(err);
|
return next(err);
|
||||||
}
|
}
|
||||||
@ -243,7 +243,7 @@ function do_create(subcmd, opts, args, cb) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.top.tritonapi.getPackage(id, function (err, pkg) {
|
tritonapi.getPackage(id, function (err, pkg) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return next(err);
|
return next(err);
|
||||||
}
|
}
|
||||||
@ -261,7 +261,7 @@ function do_create(subcmd, opts, args, cb) {
|
|||||||
vasync.forEachPipeline({
|
vasync.forEachPipeline({
|
||||||
inputs: opts.network,
|
inputs: opts.network,
|
||||||
func: function getOneNetwork(name, nextNet) {
|
func: function getOneNetwork(name, nextNet) {
|
||||||
self.top.tritonapi.getNetwork(name, function (err, net) {
|
tritonapi.getNetwork(name, function (err, net) {
|
||||||
if (err) {
|
if (err) {
|
||||||
nextNet(err);
|
nextNet(err);
|
||||||
} else {
|
} else {
|
||||||
@ -316,7 +316,7 @@ function do_create(subcmd, opts, args, cb) {
|
|||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
cloudapi.createMachine(createOpts, function (err, inst) {
|
tritonapi.cloudapi.createMachine(createOpts, function (err, inst) {
|
||||||
if (err) {
|
if (err) {
|
||||||
next(new errors.TritonError(err,
|
next(new errors.TritonError(err,
|
||||||
'error creating instance'));
|
'error creating instance'));
|
||||||
@ -352,8 +352,8 @@ function do_create(subcmd, opts, args, cb) {
|
|||||||
ctx.inst.state = 'running';
|
ctx.inst.state = 'running';
|
||||||
waitCb(null, ctx.inst);
|
waitCb(null, ctx.inst);
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
} : tritonapi.cloudapi.waitForMachineStates.bind(
|
||||||
: cloudapi.waitForMachineStates.bind(cloudapi));
|
tritonapi.cloudapi));
|
||||||
|
|
||||||
waiter({
|
waiter({
|
||||||
id: ctx.inst.id,
|
id: ctx.inst.id,
|
||||||
|
@ -14,6 +14,7 @@ var assert = require('assert-plus');
|
|||||||
var format = require('util').format;
|
var format = require('util').format;
|
||||||
var vasync = require('vasync');
|
var vasync = require('vasync');
|
||||||
|
|
||||||
|
var common = require('../common');
|
||||||
var errors = require('../errors');
|
var errors = require('../errors');
|
||||||
|
|
||||||
|
|
||||||
@ -50,28 +51,33 @@ function do_disable_firewall(subcmd, opts, args, cb) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
vasync.forEachParallel({
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
inputs: args,
|
if (setupErr) {
|
||||||
func: function disableOne(name, nextInst) {
|
cb(setupErr);
|
||||||
cli.tritonapi.disableInstanceFirewall({
|
|
||||||
id: name
|
|
||||||
}, function (err, fauxInst) {
|
|
||||||
if (err) {
|
|
||||||
nextInst(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Disabling firewall for instance "%s"', name);
|
|
||||||
|
|
||||||
if (opts.wait) {
|
|
||||||
wait(name, fauxInst.id, nextInst);
|
|
||||||
} else {
|
|
||||||
nextInst();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, function (err) {
|
vasync.forEachParallel({
|
||||||
cb(err);
|
inputs: args,
|
||||||
|
func: function disableOne(name, nextInst) {
|
||||||
|
cli.tritonapi.disableInstanceFirewall({
|
||||||
|
id: name
|
||||||
|
}, function (err, fauxInst) {
|
||||||
|
if (err) {
|
||||||
|
nextInst(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Disabling firewall for instance "%s"', name);
|
||||||
|
|
||||||
|
if (opts.wait) {
|
||||||
|
wait(name, fauxInst.id, nextInst);
|
||||||
|
} else {
|
||||||
|
nextInst();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, function (err) {
|
||||||
|
cb(err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ var assert = require('assert-plus');
|
|||||||
var format = require('util').format;
|
var format = require('util').format;
|
||||||
var vasync = require('vasync');
|
var vasync = require('vasync');
|
||||||
|
|
||||||
|
var common = require('../common');
|
||||||
var errors = require('../errors');
|
var errors = require('../errors');
|
||||||
|
|
||||||
|
|
||||||
@ -50,28 +51,33 @@ function do_enable_firewall(subcmd, opts, args, cb) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
vasync.forEachParallel({
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
inputs: args,
|
if (setupErr) {
|
||||||
func: function enableOne(name, nextInst) {
|
cb(setupErr);
|
||||||
cli.tritonapi.enableInstanceFirewall({
|
|
||||||
id: name
|
|
||||||
}, function (err, fauxInst) {
|
|
||||||
if (err) {
|
|
||||||
nextInst(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Enabling firewall for instance "%s"', name);
|
|
||||||
|
|
||||||
if (opts.wait) {
|
|
||||||
wait(name, fauxInst.id, nextInst);
|
|
||||||
} else {
|
|
||||||
nextInst();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, function (err) {
|
vasync.forEachParallel({
|
||||||
cb(err);
|
inputs: args,
|
||||||
|
func: function enableOne(name, nextInst) {
|
||||||
|
cli.tritonapi.enableInstanceFirewall({
|
||||||
|
id: name
|
||||||
|
}, function (err, fauxInst) {
|
||||||
|
if (err) {
|
||||||
|
nextInst(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Enabling firewall for instance "%s"', name);
|
||||||
|
|
||||||
|
if (opts.wait) {
|
||||||
|
wait(name, fauxInst.id, nextInst);
|
||||||
|
} else {
|
||||||
|
nextInst();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, function (err) {
|
||||||
|
cb(err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,41 +41,46 @@ function do_fwrules(subcmd, opts, args, cb) {
|
|||||||
var id = args[0];
|
var id = args[0];
|
||||||
|
|
||||||
var cli = this.top;
|
var cli = this.top;
|
||||||
cli.tritonapi.listInstanceFirewallRules({
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
id: id
|
if (setupErr) {
|
||||||
}, function onRules(err, rules) {
|
cb(setupErr);
|
||||||
if (err) {
|
|
||||||
cb(err);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
cli.tritonapi.listInstanceFirewallRules({
|
||||||
if (opts.json) {
|
id: id
|
||||||
common.jsonStream(rules);
|
}, function onRules(err, rules) {
|
||||||
} else {
|
if (err) {
|
||||||
var columns = COLUMNS_DEFAULT;
|
cb(err);
|
||||||
|
return;
|
||||||
if (opts.o) {
|
|
||||||
columns = opts.o;
|
|
||||||
} else if (opts.long) {
|
|
||||||
columns = COLUMNS_LONG;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
columns = columns.toLowerCase().split(',');
|
if (opts.json) {
|
||||||
var sort = opts.s.toLowerCase().split(',');
|
common.jsonStream(rules);
|
||||||
|
} else {
|
||||||
|
var columns = COLUMNS_DEFAULT;
|
||||||
|
|
||||||
if (columns.indexOf('shortid') !== -1) {
|
if (opts.o) {
|
||||||
rules.forEach(function (rule) {
|
columns = opts.o;
|
||||||
rule.shortid = common.normShortId(rule.id);
|
} else if (opts.long) {
|
||||||
|
columns = COLUMNS_LONG;
|
||||||
|
}
|
||||||
|
|
||||||
|
columns = columns.toLowerCase().split(',');
|
||||||
|
var sort = opts.s.toLowerCase().split(',');
|
||||||
|
|
||||||
|
if (columns.indexOf('shortid') !== -1) {
|
||||||
|
rules.forEach(function (rule) {
|
||||||
|
rule.shortid = common.normShortId(rule.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
tabula(rules, {
|
||||||
|
skipHeader: opts.H,
|
||||||
|
columns: columns,
|
||||||
|
sort: sort
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
cb();
|
||||||
tabula(rules, {
|
});
|
||||||
skipHeader: opts.H,
|
|
||||||
columns: columns,
|
|
||||||
sort: sort
|
|
||||||
});
|
|
||||||
}
|
|
||||||
cb();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,15 +19,21 @@ function do_get(subcmd, opts, args, cb) {
|
|||||||
return cb(new Error('invalid args: ' + args));
|
return cb(new Error('invalid args: ' + args));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.top.tritonapi.getInstance(args[0], function (err, inst) {
|
var tritonapi = this.top.tritonapi;
|
||||||
if (inst) {
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
if (opts.json) {
|
if (setupErr) {
|
||||||
console.log(JSON.stringify(inst));
|
cb(setupErr);
|
||||||
} else {
|
|
||||||
console.log(JSON.stringify(inst, null, 4));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
cb(err);
|
tritonapi.getInstance(args[0], function (err, inst) {
|
||||||
|
if (inst) {
|
||||||
|
if (opts.json) {
|
||||||
|
console.log(JSON.stringify(inst));
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify(inst, null, 4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cb(err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
var format = require('util').format;
|
var format = require('util').format;
|
||||||
|
|
||||||
|
var common = require('../common');
|
||||||
var errors = require('../errors');
|
var errors = require('../errors');
|
||||||
|
|
||||||
|
|
||||||
@ -29,20 +30,25 @@ function do_ip(subcmd, opts, args, cb) {
|
|||||||
|
|
||||||
var cli = this.top;
|
var cli = this.top;
|
||||||
|
|
||||||
cli.tritonapi.getInstance(args[0], function (err, inst) {
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
if (err) {
|
if (setupErr) {
|
||||||
cb(err);
|
cb(setupErr);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
cli.tritonapi.getInstance(args[0], function (err, inst) {
|
||||||
|
if (err) {
|
||||||
|
cb(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!inst.primaryIp) {
|
if (!inst.primaryIp) {
|
||||||
cb(new errors.TritonError(format(
|
cb(new errors.TritonError(format(
|
||||||
'primaryIp not found for instance "%s"', args[0])));
|
'primaryIp not found for instance "%s"', args[0])));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(inst.primaryIp);
|
console.log(inst.primaryIp);
|
||||||
cb();
|
cb();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,84 +74,93 @@ function do_list(subcmd, opts, args, callback) {
|
|||||||
var imgs = [];
|
var imgs = [];
|
||||||
var insts;
|
var insts;
|
||||||
|
|
||||||
vasync.parallel({funcs: [
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
function getTheImages(next) {
|
if (setupErr) {
|
||||||
self.top.tritonapi.listImages({
|
callback(setupErr);
|
||||||
state: 'all',
|
}
|
||||||
useCache: true
|
vasync.parallel({funcs: [
|
||||||
}, function (err, _imgs) {
|
function getTheImages(next) {
|
||||||
if (err) {
|
self.top.tritonapi.listImages({
|
||||||
if (err.statusCode === 403) {
|
state: 'all',
|
||||||
/*
|
useCache: true
|
||||||
* This could be a authorization error due to RBAC
|
}, function (err, _imgs) {
|
||||||
* on a subuser. We don't want to fail `triton inst ls`
|
if (err) {
|
||||||
* if the subuser can ListMachines, but not ListImages.
|
if (err.statusCode === 403) {
|
||||||
*/
|
/*
|
||||||
log.debug(err,
|
* This could be a authorization error due
|
||||||
'authz error listing images for insts info');
|
* to RBAC on a subuser. We don't want to
|
||||||
next();
|
* fail `triton inst ls` if the subuser
|
||||||
|
* can ListMachines, but not ListImages.
|
||||||
|
*/
|
||||||
|
log.debug(
|
||||||
|
err,
|
||||||
|
'authz error listing images for insts info');
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
next(err);
|
imgs = _imgs;
|
||||||
|
next();
|
||||||
}
|
}
|
||||||
} else {
|
});
|
||||||
imgs = _imgs;
|
},
|
||||||
next();
|
function getTheMachines(next) {
|
||||||
}
|
self.top.tritonapi.cloudapi.listMachines(
|
||||||
});
|
listOpts,
|
||||||
},
|
|
||||||
function getTheMachines(next) {
|
|
||||||
self.top.tritonapi.cloudapi.listMachines(listOpts,
|
|
||||||
function (err, _insts) {
|
function (err, _insts) {
|
||||||
if (err) {
|
if (err) {
|
||||||
next(err);
|
next(err);
|
||||||
} else {
|
} else {
|
||||||
insts = _insts;
|
insts = _insts;
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
]}, function (err, results) {
|
||||||
|
/*
|
||||||
|
* Error handling: vasync.parallel's `err` is always a
|
||||||
|
* MultiError. We want to prefer the `getTheMachines` err,
|
||||||
|
* e.g. if both get a self-signed cert error.
|
||||||
|
*/
|
||||||
|
if (err) {
|
||||||
|
err = results.operations[1].err || err;
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// map "uuid" => "image_name"
|
||||||
|
var imgmap = {};
|
||||||
|
imgs.forEach(function (img) {
|
||||||
|
imgmap[img.id] = format('%s@%s', img.name, img.version);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
]}, function (err, results) {
|
|
||||||
/*
|
|
||||||
* Error handling: vasync.parallel's `err` is always a MultiError. We
|
|
||||||
* want to prefer the `getTheMachines` err, e.g. if both get a
|
|
||||||
* self-signed cert error.
|
|
||||||
*/
|
|
||||||
if (err) {
|
|
||||||
err = results.operations[1].err || err;
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// map "uuid" => "image_name"
|
// Add extra fields for nice output.
|
||||||
var imgmap = {};
|
var now = new Date();
|
||||||
imgs.forEach(function (img) {
|
insts.forEach(function (inst) {
|
||||||
imgmap[img.id] = format('%s@%s', img.name, img.version);
|
var created = new Date(inst.created);
|
||||||
});
|
inst.age = common.longAgo(created, now);
|
||||||
|
inst.img = imgmap[inst.image] ||
|
||||||
// Add extra fields for nice output.
|
common.uuidToShortId(inst.image);
|
||||||
var now = new Date();
|
inst.shortid = inst.id.split('-', 1)[0];
|
||||||
insts.forEach(function (inst) {
|
var flags = [];
|
||||||
var created = new Date(inst.created);
|
if (inst.docker) flags.push('D');
|
||||||
inst.age = common.longAgo(created, now);
|
if (inst.firewall_enabled) flags.push('F');
|
||||||
inst.img = imgmap[inst.image] || common.uuidToShortId(inst.image);
|
if (inst.brand === 'kvm') flags.push('K');
|
||||||
inst.shortid = inst.id.split('-', 1)[0];
|
inst.flags = flags.length ? flags.join('') : undefined;
|
||||||
var flags = [];
|
|
||||||
if (inst.docker) flags.push('D');
|
|
||||||
if (inst.firewall_enabled) flags.push('F');
|
|
||||||
if (inst.brand === 'kvm') flags.push('K');
|
|
||||||
inst.flags = flags.length ? flags.join('') : undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (opts.json) {
|
|
||||||
common.jsonStream(insts);
|
|
||||||
} else {
|
|
||||||
tabula(insts, {
|
|
||||||
skipHeader: opts.H,
|
|
||||||
columns: columns,
|
|
||||||
sort: sort,
|
|
||||||
dottedLookup: true
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
callback();
|
if (opts.json) {
|
||||||
|
common.jsonStream(insts);
|
||||||
|
} else {
|
||||||
|
tabula(insts, {
|
||||||
|
skipHeader: opts.H,
|
||||||
|
columns: columns,
|
||||||
|
sort: sort,
|
||||||
|
dottedLookup: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +47,8 @@ function do_create(subcmd, opts, args, cb) {
|
|||||||
createOpts.name = opts.name;
|
createOpts.name = opts.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
vasync.pipeline({arg: {}, funcs: [
|
vasync.pipeline({arg: {cli: this.top}, funcs: [
|
||||||
|
common.cliSetupTritonApi,
|
||||||
function createSnapshot(ctx, next) {
|
function createSnapshot(ctx, next) {
|
||||||
ctx.start = Date.now();
|
ctx.start = Date.now();
|
||||||
|
|
||||||
|
@ -61,7 +61,8 @@ function do_delete(subcmd, opts, args, cb) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
vasync.pipeline({funcs: [
|
vasync.pipeline({arg: {cli: this.top}, funcs: [
|
||||||
|
common.cliSetupTritonApi,
|
||||||
function confirm(_, next) {
|
function confirm(_, next) {
|
||||||
if (opts.force) {
|
if (opts.force) {
|
||||||
return next();
|
return next();
|
||||||
|
@ -36,22 +36,27 @@ function do_get(subcmd, opts, args, cb) {
|
|||||||
var name = args[1];
|
var name = args[1];
|
||||||
var cli = this.top;
|
var cli = this.top;
|
||||||
|
|
||||||
cli.tritonapi.getInstanceSnapshot({
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
id: id,
|
if (setupErr) {
|
||||||
name: name
|
cb(setupErr);
|
||||||
}, function onSnapshot(err, snapshot) {
|
|
||||||
if (err) {
|
|
||||||
cb(err);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
cli.tritonapi.getInstanceSnapshot({
|
||||||
|
id: id,
|
||||||
|
name: name
|
||||||
|
}, function onSnapshot(err, snapshot) {
|
||||||
|
if (err) {
|
||||||
|
cb(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (opts.json) {
|
if (opts.json) {
|
||||||
console.log(JSON.stringify(snapshot));
|
console.log(JSON.stringify(snapshot));
|
||||||
} else {
|
} else {
|
||||||
console.log(JSON.stringify(snapshot, null, 4));
|
console.log(JSON.stringify(snapshot, null, 4));
|
||||||
}
|
}
|
||||||
|
|
||||||
cb();
|
cb();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,35 +40,40 @@ function do_list(subcmd, opts, args, cb) {
|
|||||||
var cli = this.top;
|
var cli = this.top;
|
||||||
var machineId = args[0];
|
var machineId = args[0];
|
||||||
|
|
||||||
cli.tritonapi.listInstanceSnapshots({
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
id: machineId
|
if (setupErr) {
|
||||||
}, function onSnapshots(err, snapshots) {
|
cb(setupErr);
|
||||||
if (err) {
|
|
||||||
cb(err);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
cli.tritonapi.listInstanceSnapshots({
|
||||||
if (opts.json) {
|
id: machineId
|
||||||
common.jsonStream(snapshots);
|
}, function onSnapshots(err, snapshots) {
|
||||||
} else {
|
if (err) {
|
||||||
var columns = COLUMNS_DEFAULT;
|
cb(err);
|
||||||
|
return;
|
||||||
if (opts.o) {
|
|
||||||
columns = opts.o;
|
|
||||||
} else if (opts.long) {
|
|
||||||
columns = COLUMNS_DEFAULT;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
columns = columns.split(',');
|
if (opts.json) {
|
||||||
var sort = opts.s.split(',');
|
common.jsonStream(snapshots);
|
||||||
|
} else {
|
||||||
|
var columns = COLUMNS_DEFAULT;
|
||||||
|
|
||||||
tabula(snapshots, {
|
if (opts.o) {
|
||||||
skipHeader: opts.H,
|
columns = opts.o;
|
||||||
columns: columns,
|
} else if (opts.long) {
|
||||||
sort: sort
|
columns = COLUMNS_DEFAULT;
|
||||||
});
|
}
|
||||||
}
|
|
||||||
cb();
|
columns = columns.split(',');
|
||||||
|
var sort = opts.s.split(',');
|
||||||
|
|
||||||
|
tabula(snapshots, {
|
||||||
|
skipHeader: opts.H,
|
||||||
|
columns: columns,
|
||||||
|
sort: sort
|
||||||
|
});
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,52 +38,59 @@ function do_ssh(subcmd, opts, args, callback) {
|
|||||||
id = id.substr(i + 1);
|
id = id.substr(i + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
cli.tritonapi.getInstance(id, function (err, inst) {
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
if (err) {
|
if (setupErr) {
|
||||||
callback(err);
|
callback(setupErr);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
cli.tritonapi.getInstance(id, function (err, inst) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var ip = inst.primaryIp;
|
var ip = inst.primaryIp;
|
||||||
if (!ip) {
|
if (!ip) {
|
||||||
callback(new Error('primaryIp not found for instance'));
|
callback(new Error('primaryIp not found for instance'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
args = ['-l', user, ip].concat(args);
|
args = ['-l', user, ip].concat(args);
|
||||||
|
|
||||||
/*
|
|
||||||
* By default we disable ControlMaster (aka mux, aka SSH connection
|
|
||||||
* multiplexing) because of
|
|
||||||
* https://github.com/joyent/node-triton/issues/52
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
if (!opts.no_disable_mux) {
|
|
||||||
/*
|
/*
|
||||||
* A simple `-o ControlMaster=no` doesn't work. With just that
|
* By default we disable ControlMaster (aka mux, aka SSH connection
|
||||||
* option, a `ControlPath` option (from ~/.ssh/config) will still
|
* multiplexing) because of
|
||||||
* be used if it exists. Our hack is to set a ControlPath we
|
* https://github.com/joyent/node-triton/issues/52
|
||||||
* know should not exist. Using '/dev/null' wasn't a good
|
*
|
||||||
* alternative because `ssh` tries "$ControlPath.$somerandomnum"
|
|
||||||
* and also because Windows.
|
|
||||||
*/
|
*/
|
||||||
var nullSshControlPath = path.resolve(
|
if (!opts.no_disable_mux) {
|
||||||
cli.tritonapi.config._configDir, 'tmp', 'nullSshControlPath');
|
/*
|
||||||
args = [
|
* A simple `-o ControlMaster=no` doesn't work. With
|
||||||
'-o', 'ControlMaster=no',
|
* just that option, a `ControlPath` option (from
|
||||||
'-o', 'ControlPath='+nullSshControlPath
|
* ~/.ssh/config) will still be used if it exists. Our
|
||||||
].concat(args);
|
* hack is to set a ControlPath we know should not
|
||||||
}
|
* exist. Using '/dev/null' wasn't a good alternative
|
||||||
|
* because `ssh` tries "$ControlPath.$somerandomnum"
|
||||||
|
* and also because Windows.
|
||||||
|
*/
|
||||||
|
var nullSshControlPath = path.resolve(
|
||||||
|
cli.tritonapi.config._configDir, 'tmp',
|
||||||
|
'nullSshControlPath');
|
||||||
|
args = [
|
||||||
|
'-o', 'ControlMaster=no',
|
||||||
|
'-o', 'ControlPath='+nullSshControlPath
|
||||||
|
].concat(args);
|
||||||
|
}
|
||||||
|
|
||||||
self.top.log.info({args: args}, 'forking ssh');
|
self.top.log.info({args: args}, 'forking ssh');
|
||||||
var child = spawn('ssh', args, {stdio: 'inherit'});
|
var child = spawn('ssh', args, {stdio: 'inherit'});
|
||||||
child.on('close', function (code) {
|
child.on('close', function (code) {
|
||||||
/*
|
/*
|
||||||
* Once node 0.10 support is dropped we could instead:
|
* Once node 0.10 support is dropped we could instead:
|
||||||
* process.exitCode = code;
|
* process.exitCode = code;
|
||||||
* callback();
|
* callback();
|
||||||
*/
|
*/
|
||||||
process.exit(code);
|
process.exit(code);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
var vasync = require('vasync');
|
var vasync = require('vasync');
|
||||||
|
|
||||||
|
var common = require('../../common');
|
||||||
var errors = require('../../errors');
|
var errors = require('../../errors');
|
||||||
|
|
||||||
|
|
||||||
@ -29,41 +30,46 @@ function do_delete(subcmd, opts, args, cb) {
|
|||||||
}
|
}
|
||||||
var waitTimeoutMs = opts.wait_timeout * 1000; /* seconds to ms */
|
var waitTimeoutMs = opts.wait_timeout * 1000; /* seconds to ms */
|
||||||
|
|
||||||
if (opts.all) {
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
self.top.tritonapi.deleteAllInstanceTags({
|
if (setupErr) {
|
||||||
id: args[0],
|
cb(setupErr);
|
||||||
wait: opts.wait,
|
}
|
||||||
waitTimeout: waitTimeoutMs
|
if (opts.all) {
|
||||||
}, function (err) {
|
self.top.tritonapi.deleteAllInstanceTags({
|
||||||
console.log('Deleted all tags on instance %s', args[0]);
|
id: args[0],
|
||||||
cb(err);
|
wait: opts.wait,
|
||||||
});
|
waitTimeout: waitTimeoutMs
|
||||||
} else {
|
}, function (err) {
|
||||||
// Uniq'ify the given names.
|
console.log('Deleted all tags on instance %s', args[0]);
|
||||||
var names = {};
|
cb(err);
|
||||||
args.slice(1).forEach(function (arg) { names[arg] = true; });
|
});
|
||||||
names = Object.keys(names);
|
} else {
|
||||||
|
// Uniq'ify the given names.
|
||||||
|
var names = {};
|
||||||
|
args.slice(1).forEach(function (arg) { names[arg] = true; });
|
||||||
|
names = Object.keys(names);
|
||||||
|
|
||||||
// TODO: Instead of waiting for each delete, let's delete them all then
|
// TODO: Instead of waiting for each delete, let's delete
|
||||||
// wait for the set.
|
// them all then wait for the set.
|
||||||
vasync.forEachPipeline({
|
vasync.forEachPipeline({
|
||||||
inputs: names,
|
inputs: names,
|
||||||
func: function deleteOne(name, next) {
|
func: function deleteOne(name, next) {
|
||||||
self.top.tritonapi.deleteInstanceTag({
|
self.top.tritonapi.deleteInstanceTag({
|
||||||
id: args[0],
|
id: args[0],
|
||||||
tag: name,
|
tag: name,
|
||||||
wait: opts.wait,
|
wait: opts.wait,
|
||||||
waitTimeout: waitTimeoutMs
|
waitTimeout: waitTimeoutMs
|
||||||
}, function (err) {
|
}, function (err) {
|
||||||
if (!err) {
|
if (!err) {
|
||||||
console.log('Deleted tag %s on instance %s',
|
console.log('Deleted tag %s on instance %s',
|
||||||
name, args[0]);
|
name, args[0]);
|
||||||
}
|
}
|
||||||
next(err);
|
next(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, cb);
|
}, cb);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
do_delete.options = [
|
do_delete.options = [
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
* `triton instance tag get ...`
|
* `triton instance tag get ...`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
var common = require('../../common');
|
||||||
var errors = require('../../errors');
|
var errors = require('../../errors');
|
||||||
|
|
||||||
|
|
||||||
@ -23,20 +24,25 @@ function do_get(subcmd, opts, args, cb) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.top.tritonapi.getInstanceTag({
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
id: args[0],
|
if (setupErr) {
|
||||||
tag: args[1]
|
cb(setupErr);
|
||||||
}, function (err, value) {
|
|
||||||
if (err) {
|
|
||||||
cb(err);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (opts.json) {
|
self.top.tritonapi.getInstanceTag({
|
||||||
console.log(JSON.stringify(value));
|
id: args[0],
|
||||||
} else {
|
tag: args[1]
|
||||||
console.log(value);
|
}, function (err, value) {
|
||||||
}
|
if (err) {
|
||||||
cb();
|
cb(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (opts.json) {
|
||||||
|
console.log(JSON.stringify(value));
|
||||||
|
} else {
|
||||||
|
console.log(value);
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
* `triton instance tag list ...`
|
* `triton instance tag list ...`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
var common = require('../../common');
|
||||||
var errors = require('../../errors');
|
var errors = require('../../errors');
|
||||||
|
|
||||||
function do_list(subcmd, opts, args, cb) {
|
function do_list(subcmd, opts, args, cb) {
|
||||||
@ -22,17 +23,23 @@ function do_list(subcmd, opts, args, cb) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.top.tritonapi.listInstanceTags({id: args[0]}, function (err, tags) {
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
if (err) {
|
if (setupErr) {
|
||||||
cb(err);
|
cb(setupErr);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (opts.json) {
|
self.top.tritonapi.listInstanceTags(
|
||||||
console.log(JSON.stringify(tags));
|
{id: args[0]}, function (err, tags) {
|
||||||
} else {
|
if (err) {
|
||||||
console.log(JSON.stringify(tags, null, 4));
|
cb(err);
|
||||||
}
|
return;
|
||||||
cb();
|
}
|
||||||
|
if (opts.json) {
|
||||||
|
console.log(JSON.stringify(tags));
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify(tags, null, 4));
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
var vasync = require('vasync');
|
var vasync = require('vasync');
|
||||||
|
|
||||||
|
var common = require('../../common');
|
||||||
var errors = require('../../errors');
|
var errors = require('../../errors');
|
||||||
var mat = require('../../metadataandtags');
|
var mat = require('../../metadataandtags');
|
||||||
|
|
||||||
@ -27,7 +28,8 @@ function do_replace_all(subcmd, opts, args, cb) {
|
|||||||
}
|
}
|
||||||
var log = self.log;
|
var log = self.log;
|
||||||
|
|
||||||
vasync.pipeline({arg: {}, funcs: [
|
vasync.pipeline({arg: {cli: this.top}, funcs: [
|
||||||
|
common.cliSetupTritonApi,
|
||||||
function gatherTags(ctx, next) {
|
function gatherTags(ctx, next) {
|
||||||
mat.tagsFromSetArgs(opts, args.slice(1), log, function (err, tags) {
|
mat.tagsFromSetArgs(opts, args.slice(1), log, function (err, tags) {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
var vasync = require('vasync');
|
var vasync = require('vasync');
|
||||||
|
|
||||||
|
var common = require('../../common');
|
||||||
var errors = require('../../errors');
|
var errors = require('../../errors');
|
||||||
var mat = require('../../metadataandtags');
|
var mat = require('../../metadataandtags');
|
||||||
|
|
||||||
@ -27,7 +28,8 @@ function do_set(subcmd, opts, args, cb) {
|
|||||||
}
|
}
|
||||||
var log = self.log;
|
var log = self.log;
|
||||||
|
|
||||||
vasync.pipeline({arg: {}, funcs: [
|
vasync.pipeline({arg: {cli: this.top}, funcs: [
|
||||||
|
common.cliSetupTritonApi,
|
||||||
function gatherTags(ctx, next) {
|
function gatherTags(ctx, next) {
|
||||||
mat.tagsFromSetArgs(opts, args.slice(1), log, function (err, tags) {
|
mat.tagsFromSetArgs(opts, args.slice(1), log, function (err, tags) {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
var vasync = require('vasync');
|
var vasync = require('vasync');
|
||||||
|
|
||||||
|
var common = require('../common');
|
||||||
var distractions = require('../distractions');
|
var distractions = require('../distractions');
|
||||||
var errors = require('../errors');
|
var errors = require('../errors');
|
||||||
|
|
||||||
@ -34,7 +35,8 @@ function do_wait(subcmd, opts, args, cb) {
|
|||||||
var done = 0;
|
var done = 0;
|
||||||
var instFromId = {};
|
var instFromId = {};
|
||||||
|
|
||||||
vasync.pipeline({funcs: [
|
vasync.pipeline({arg: {cli: this.top}, funcs: [
|
||||||
|
common.cliSetupTritonApi,
|
||||||
function getInsts(_, next) {
|
function getInsts(_, next) {
|
||||||
vasync.forEachParallel({
|
vasync.forEachParallel({
|
||||||
inputs: ids,
|
inputs: ids,
|
||||||
|
@ -83,8 +83,6 @@ function gen_do_ACTION(opts) {
|
|||||||
function _doTheAction(action, subcmd, opts, args, callback) {
|
function _doTheAction(action, subcmd, opts, args, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var now = Date.now();
|
|
||||||
|
|
||||||
var command, state;
|
var command, state;
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'start':
|
case 'start':
|
||||||
@ -116,7 +114,17 @@ function _doTheAction(action, subcmd, opts, args, callback) {
|
|||||||
callback(new errors.UsageError('missing INST arg(s)'));
|
callback(new errors.UsageError('missing INST arg(s)'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
|
if (setupErr) {
|
||||||
|
callback(setupErr);
|
||||||
|
}
|
||||||
|
_doOnEachInstance(self, action, command, state, args, opts, callback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function _doOnEachInstance(self, action, command, state, instances,
|
||||||
|
opts, callback) {
|
||||||
|
var now = Date.now();
|
||||||
vasync.forEachParallel({
|
vasync.forEachParallel({
|
||||||
func: function (arg, cb) {
|
func: function (arg, cb) {
|
||||||
var alias, uuid;
|
var alias, uuid;
|
||||||
@ -190,7 +198,7 @@ function _doTheAction(action, subcmd, opts, args, callback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
inputs: args
|
inputs: instances
|
||||||
}, function (err, results) {
|
}, function (err, results) {
|
||||||
var e = err ? (new Error('command failure')) : null;
|
var e = err ? (new Error('command failure')) : null;
|
||||||
callback(e);
|
callback(e);
|
||||||
|
@ -40,7 +40,8 @@ function do_add(subcmd, opts, args, cb) {
|
|||||||
var filePath = args[0];
|
var filePath = args[0];
|
||||||
var cli = this.top;
|
var cli = this.top;
|
||||||
|
|
||||||
vasync.pipeline({arg: {}, funcs: [
|
vasync.pipeline({arg: {cli: this.top}, funcs: [
|
||||||
|
common.cliSetupTritonApi,
|
||||||
function gatherDataStdin(ctx, next) {
|
function gatherDataStdin(ctx, next) {
|
||||||
if (filePath !== '-') {
|
if (filePath !== '-') {
|
||||||
return next();
|
return next();
|
||||||
|
@ -35,7 +35,8 @@ function do_delete(subcmd, opts, args, cb) {
|
|||||||
|
|
||||||
var cli = this.top;
|
var cli = this.top;
|
||||||
|
|
||||||
vasync.pipeline({funcs: [
|
vasync.pipeline({arg: {cli: this.top}, funcs: [
|
||||||
|
common.cliSetupTritonApi,
|
||||||
function confirm(_, next) {
|
function confirm(_, next) {
|
||||||
if (opts.yes) {
|
if (opts.yes) {
|
||||||
return next();
|
return next();
|
||||||
|
@ -35,22 +35,27 @@ function do_get(subcmd, opts, args, cb) {
|
|||||||
var id = args[0];
|
var id = args[0];
|
||||||
var cli = this.top;
|
var cli = this.top;
|
||||||
|
|
||||||
cli.tritonapi.cloudapi.getKey({
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
// Currently `cloudapi.getUserKey` isn't picky about the `name` being
|
if (setupErr) {
|
||||||
// passed in as the `opts.fingerprint` arg.
|
cb(setupErr);
|
||||||
fingerprint: id
|
|
||||||
}, function onKey(err, key) {
|
|
||||||
if (err) {
|
|
||||||
cb(err);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
cli.tritonapi.cloudapi.getKey({
|
||||||
|
// Currently `cloudapi.getUserKey` isn't picky about the
|
||||||
|
// `name` being passed in as the `opts.fingerprint` arg.
|
||||||
|
fingerprint: id
|
||||||
|
}, function onKey(err, key) {
|
||||||
|
if (err) {
|
||||||
|
cb(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (opts.json) {
|
if (opts.json) {
|
||||||
console.log(JSON.stringify(key));
|
console.log(JSON.stringify(key));
|
||||||
} else {
|
} else {
|
||||||
console.log(common.chomp(key.key));
|
console.log(common.chomp(key.key));
|
||||||
}
|
}
|
||||||
cb();
|
cb();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,37 +37,42 @@ function do_list(subcmd, opts, args, cb) {
|
|||||||
|
|
||||||
var cli = this.top;
|
var cli = this.top;
|
||||||
|
|
||||||
cli.tritonapi.cloudapi.listKeys({}, function onKeys(err, keys) {
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
if (err) {
|
if (setupErr) {
|
||||||
cb(err);
|
cb(setupErr);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
cli.tritonapi.cloudapi.listKeys({}, function onKeys(err, keys) {
|
||||||
if (opts.json) {
|
if (err) {
|
||||||
common.jsonStream(keys);
|
cb(err);
|
||||||
} else if (opts.authorized_keys) {
|
return;
|
||||||
keys.forEach(function (key) {
|
|
||||||
console.log(common.chomp(key.key));
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
var columns = COLUMNS_DEFAULT;
|
|
||||||
|
|
||||||
if (opts.o) {
|
|
||||||
columns = opts.o;
|
|
||||||
} else if (opts.long) {
|
|
||||||
columns = COLUMNS_LONG;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
columns = columns.split(',');
|
if (opts.json) {
|
||||||
var sort = opts.s.split(',');
|
common.jsonStream(keys);
|
||||||
|
} else if (opts.authorized_keys) {
|
||||||
|
keys.forEach(function (key) {
|
||||||
|
console.log(common.chomp(key.key));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
var columns = COLUMNS_DEFAULT;
|
||||||
|
|
||||||
tabula(keys, {
|
if (opts.o) {
|
||||||
skipHeader: false,
|
columns = opts.o;
|
||||||
columns: columns,
|
} else if (opts.long) {
|
||||||
sort: sort
|
columns = COLUMNS_LONG;
|
||||||
});
|
}
|
||||||
}
|
|
||||||
cb();
|
columns = columns.split(',');
|
||||||
|
var sort = opts.s.split(',');
|
||||||
|
|
||||||
|
tabula(keys, {
|
||||||
|
skipHeader: false,
|
||||||
|
columns: columns,
|
||||||
|
sort: sort
|
||||||
|
});
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,17 +25,24 @@ function do_get(subcmd, opts, args, cb) {
|
|||||||
'incorrect number of args (%d)', args.length)));
|
'incorrect number of args (%d)', args.length)));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.top.tritonapi.getNetwork(args[0], function (err, net) {
|
var tritonapi = this.top.tritonapi;
|
||||||
if (err) {
|
|
||||||
return cb(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.json) {
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
console.log(JSON.stringify(net));
|
if (setupErr) {
|
||||||
} else {
|
cb(setupErr);
|
||||||
console.log(JSON.stringify(net, null, 4));
|
|
||||||
}
|
}
|
||||||
cb();
|
tritonapi.getNetwork(args[0], function (err, net) {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.json) {
|
||||||
|
console.log(JSON.stringify(net));
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify(net, null, 4));
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,28 +49,34 @@ function do_list(subcmd, opts, args, callback) {
|
|||||||
columns = columns.split(',');
|
columns = columns.split(',');
|
||||||
|
|
||||||
var sort = opts.s.split(',');
|
var sort = opts.s.split(',');
|
||||||
|
var tritonapi = this.top.tritonapi;
|
||||||
|
|
||||||
this.top.tritonapi.cloudapi.listNetworks(function (err, networks) {
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
if (err) {
|
if (setupErr) {
|
||||||
callback(err);
|
callback(setupErr);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
tritonapi.cloudapi.listNetworks(function (err, networks) {
|
||||||
if (opts.json) {
|
if (err) {
|
||||||
common.jsonStream(networks);
|
callback(err);
|
||||||
} else {
|
return;
|
||||||
for (var i = 0; i < networks.length; i++) {
|
|
||||||
var net = networks[i];
|
|
||||||
net.shortid = net.id.split('-', 1)[0];
|
|
||||||
net.vlan = net.vlan_id;
|
|
||||||
}
|
}
|
||||||
tabula(networks, {
|
|
||||||
skipHeader: opts.H,
|
if (opts.json) {
|
||||||
columns: columns,
|
common.jsonStream(networks);
|
||||||
sort: sort
|
} else {
|
||||||
});
|
for (var i = 0; i < networks.length; i++) {
|
||||||
}
|
var net = networks[i];
|
||||||
callback();
|
net.shortid = net.id.split('-', 1)[0];
|
||||||
|
net.vlan = net.vlan_id;
|
||||||
|
}
|
||||||
|
tabula(networks, {
|
||||||
|
skipHeader: opts.H,
|
||||||
|
columns: columns,
|
||||||
|
sort: sort
|
||||||
|
});
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
var format = require('util').format;
|
var format = require('util').format;
|
||||||
|
|
||||||
|
var common = require('../common');
|
||||||
var errors = require('../errors');
|
var errors = require('../errors');
|
||||||
|
|
||||||
|
|
||||||
@ -24,17 +25,23 @@ function do_get(subcmd, opts, args, callback) {
|
|||||||
'incorrect number of args (%d)', args.length)));
|
'incorrect number of args (%d)', args.length)));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.top.tritonapi.getPackage(args[0], function onRes(err, pkg) {
|
var tritonapi = this.top.tritonapi;
|
||||||
if (err) {
|
common.cliSetupTritonApi({cli: this.top}, function onSetup(setupErr) {
|
||||||
return callback(err);
|
if (setupErr) {
|
||||||
|
callback(setupErr);
|
||||||
}
|
}
|
||||||
|
tritonapi.getPackage(args[0], function onRes(err, pkg) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
if (opts.json) {
|
if (opts.json) {
|
||||||
console.log(JSON.stringify(pkg));
|
console.log(JSON.stringify(pkg));
|
||||||
} else {
|
} else {
|
||||||
console.log(JSON.stringify(pkg, null, 4));
|
console.log(JSON.stringify(pkg, null, 4));
|
||||||
}
|
}
|
||||||
callback();
|
callback();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,7 +70,7 @@ do_get.help = [
|
|||||||
'',
|
'',
|
||||||
'Where PACKAGE is a package id (full UUID), exact name, or short id.',
|
'Where PACKAGE is a package id (full UUID), exact name, or short id.',
|
||||||
'',
|
'',
|
||||||
'Note: Currently this dumps prettified JSON by default. That might change',
|
'Note: Currently this dumps perttified JSON by default. That might change',
|
||||||
'in the future. Use "-j" to explicitly get JSON output.'
|
'in the future. Use "-j" to explicitly get JSON output.'
|
||||||
/* END JSSTYLED */
|
/* END JSSTYLED */
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
var tabula = require('tabula');
|
var tabula = require('tabula');
|
||||||
|
var vasync = require('vasync');
|
||||||
|
|
||||||
var common = require('../common');
|
var common = require('../common');
|
||||||
|
|
||||||
@ -68,73 +69,89 @@ function do_list(subcmd, opts, args, callback) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.top.tritonapi.cloudapi.listPackages(listOpts, function (err, pkgs) {
|
var context = {
|
||||||
if (err) {
|
cli: this.top
|
||||||
callback(err);
|
};
|
||||||
return;
|
vasync.pipeline({arg: context, funcs: [
|
||||||
}
|
common.cliSetupTritonApi,
|
||||||
if (opts.json) {
|
|
||||||
common.jsonStream(pkgs);
|
|
||||||
} else {
|
|
||||||
for (i = 0; i < pkgs.length; i++) {
|
|
||||||
var pkg = pkgs[i];
|
|
||||||
pkg.shortid = pkg.id.split('-', 1)[0];
|
|
||||||
|
|
||||||
/*
|
function getThem(arg, next) {
|
||||||
* We take a slightly "smarter" view of "group" for default
|
arg.cli.tritonapi.cloudapi.listPackages(listOpts,
|
||||||
* sorting, to accomodate usage in the JPC. More recent
|
function (err, pkgs) {
|
||||||
* common usage is for packages to have "foo-*" naming.
|
if (err) {
|
||||||
* JPC includes package sets of yore *and* recent that don't
|
next(err);
|
||||||
* use the "group" field. We secondarily separate those
|
return;
|
||||||
* on a possible "foo-" prefix.
|
|
||||||
*/
|
|
||||||
pkg._groupPlus = (pkg.group || (pkg.name.indexOf('-') === -1
|
|
||||||
? '' : pkg.name.split('-', 1)[0]));
|
|
||||||
|
|
||||||
if (!opts.p) {
|
|
||||||
pkg.memoryHuman = common.humanSizeFromBytes({
|
|
||||||
precision: 1,
|
|
||||||
narrow: true
|
|
||||||
}, pkg.memory * 1024 * 1024);
|
|
||||||
pkg.swapHuman = common.humanSizeFromBytes({
|
|
||||||
precision: 1,
|
|
||||||
narrow: true
|
|
||||||
}, pkg.swap * 1024 * 1024);
|
|
||||||
pkg.diskHuman = common.humanSizeFromBytes({
|
|
||||||
precision: 1,
|
|
||||||
narrow: true
|
|
||||||
}, pkg.disk * 1024 * 1024);
|
|
||||||
pkg.vcpusHuman = pkg.vcpus === 0 ? '-' : pkg.vcpus;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!opts.p) {
|
|
||||||
columns = columns.map(function (c) {
|
|
||||||
switch (c.lookup || c) {
|
|
||||||
case 'memory':
|
|
||||||
return {lookup: 'memoryHuman', name: 'MEMORY',
|
|
||||||
align: 'right'};
|
|
||||||
case 'swap':
|
|
||||||
return {lookup: 'swapHuman', name: 'SWAP',
|
|
||||||
align: 'right'};
|
|
||||||
case 'disk':
|
|
||||||
return {lookup: 'diskHuman', name: 'DISK',
|
|
||||||
align: 'right'};
|
|
||||||
case 'vcpus':
|
|
||||||
return {lookup: 'vcpusHuman', name: 'VCPUS',
|
|
||||||
align: 'right'};
|
|
||||||
default:
|
|
||||||
return c;
|
|
||||||
}
|
}
|
||||||
|
arg.pkgs = pkgs;
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
function display(arg, next) {
|
||||||
|
if (opts.json) {
|
||||||
|
common.jsonStream(arg.pkgs);
|
||||||
|
} else {
|
||||||
|
for (i = 0; i < arg.pkgs.length; i++) {
|
||||||
|
var pkg = arg.pkgs[i];
|
||||||
|
pkg.shortid = pkg.id.split('-', 1)[0];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We take a slightly "smarter" view of "group" for default
|
||||||
|
* sorting, to accomodate usage in the JPC. More recent
|
||||||
|
* common usage is for packages to have "foo-*" naming.
|
||||||
|
* JPC includes package sets of yore *and* recent that don't
|
||||||
|
* use the "group" field. We secondarily separate those
|
||||||
|
* on a possible "foo-" prefix.
|
||||||
|
*/
|
||||||
|
pkg._groupPlus = (pkg.group || (pkg.name.indexOf('-') === -1
|
||||||
|
? '' : pkg.name.split('-', 1)[0]));
|
||||||
|
|
||||||
|
if (!opts.p) {
|
||||||
|
pkg.memoryHuman = common.humanSizeFromBytes({
|
||||||
|
precision: 1,
|
||||||
|
narrow: true
|
||||||
|
}, pkg.memory * 1024 * 1024);
|
||||||
|
pkg.swapHuman = common.humanSizeFromBytes({
|
||||||
|
precision: 1,
|
||||||
|
narrow: true
|
||||||
|
}, pkg.swap * 1024 * 1024);
|
||||||
|
pkg.diskHuman = common.humanSizeFromBytes({
|
||||||
|
precision: 1,
|
||||||
|
narrow: true
|
||||||
|
}, pkg.disk * 1024 * 1024);
|
||||||
|
pkg.vcpusHuman = pkg.vcpus === 0 ? '-' : pkg.vcpus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!opts.p) {
|
||||||
|
columns = columns.map(function (c) {
|
||||||
|
switch (c.lookup || c) {
|
||||||
|
case 'memory':
|
||||||
|
return {lookup: 'memoryHuman', name: 'MEMORY',
|
||||||
|
align: 'right'};
|
||||||
|
case 'swap':
|
||||||
|
return {lookup: 'swapHuman', name: 'SWAP',
|
||||||
|
align: 'right'};
|
||||||
|
case 'disk':
|
||||||
|
return {lookup: 'diskHuman', name: 'DISK',
|
||||||
|
align: 'right'};
|
||||||
|
case 'vcpus':
|
||||||
|
return {lookup: 'vcpusHuman', name: 'VCPUS',
|
||||||
|
align: 'right'};
|
||||||
|
default:
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
tabula(arg.pkgs, {
|
||||||
|
skipHeader: opts.H,
|
||||||
|
columns: columns,
|
||||||
|
sort: sort
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
tabula(pkgs, {
|
next();
|
||||||
skipHeader: opts.H,
|
|
||||||
columns: columns,
|
|
||||||
sort: sort
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
callback();
|
]}, callback);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
do_list.options = [
|
do_list.options = [
|
||||||
|
@ -9,6 +9,7 @@ var format = require('util').format;
|
|||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var sshpk = require('sshpk');
|
var sshpk = require('sshpk');
|
||||||
var vasync = require('vasync');
|
var vasync = require('vasync');
|
||||||
|
var auth = require('smartdc-auth');
|
||||||
|
|
||||||
var common = require('../common');
|
var common = require('../common');
|
||||||
var errors = require('../errors');
|
var errors = require('../errors');
|
||||||
@ -101,17 +102,15 @@ function _createProfile(opts, cb) {
|
|||||||
'create profile: stdout is not a TTY'));
|
'create profile: stdout is not a TTY'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var kr = new auth.KeyRing();
|
||||||
|
var keyChoices = {};
|
||||||
|
|
||||||
var defaults = {};
|
var defaults = {};
|
||||||
if (ctx.copy) {
|
if (ctx.copy) {
|
||||||
defaults = ctx.copy;
|
defaults = ctx.copy;
|
||||||
delete defaults.name; // we don't copy a profile name
|
delete defaults.name; // we don't copy a profile name
|
||||||
} else {
|
} else {
|
||||||
defaults.url = 'https://us-sw-1.api.joyent.com';
|
defaults.url = 'https://us-sw-1.api.joyent.com';
|
||||||
|
|
||||||
var possibleDefaultFp = '~/.ssh/id_rsa';
|
|
||||||
if (fs.existsSync(common.tildeSync(possibleDefaultFp))) {
|
|
||||||
defaults.keyId = possibleDefaultFp;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var fields = [ {
|
var fields = [ {
|
||||||
@ -156,11 +155,10 @@ function _createProfile(opts, cb) {
|
|||||||
valCb();
|
valCb();
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
desc: 'The fingerprint of the SSH key you have registered ' +
|
desc: 'The fingerprint of the SSH key you want to use, or ' +
|
||||||
'for your account. Alternatively, You may enter a local ' +
|
'its index in the list above. If the key you want to ' +
|
||||||
'path to a public or private SSH key to have the ' +
|
'use is not listed, make sure it is either saved in your ' +
|
||||||
'fingerprint calculated for you.',
|
'SSH keys directory or loaded into the SSH agent.',
|
||||||
default: defaults.keyId,
|
|
||||||
key: 'keyId',
|
key: 'keyId',
|
||||||
validate: function validateKeyId(value, valCb) {
|
validate: function validateKeyId(value, valCb) {
|
||||||
// First try as a fingerprint.
|
// First try as a fingerprint.
|
||||||
@ -170,44 +168,14 @@ function _createProfile(opts, cb) {
|
|||||||
} catch (fpErr) {
|
} catch (fpErr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try as a local path.
|
// Try as a list index
|
||||||
var keyPath = common.tildeSync(value);
|
if (keyChoices[value] !== undefined) {
|
||||||
fs.stat(keyPath, function (statErr, stats) {
|
return valCb(null, keyChoices[value]);
|
||||||
if (statErr || !stats.isFile()) {
|
}
|
||||||
return valCb(new Error(format(
|
|
||||||
'"%s" is neither a valid fingerprint, ' +
|
|
||||||
'nor an existing file', value)));
|
|
||||||
}
|
|
||||||
fs.readFile(keyPath, function (readErr, keyData) {
|
|
||||||
if (readErr) {
|
|
||||||
return valCb(readErr);
|
|
||||||
}
|
|
||||||
var keyType = (keyPath.slice(-4) === '.pub'
|
|
||||||
? 'ssh' : 'pem');
|
|
||||||
try {
|
|
||||||
var key = sshpk.parseKey(keyData, keyType);
|
|
||||||
} catch (keyErr) {
|
|
||||||
return valCb(keyErr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
valCb(new Error(format(
|
||||||
* Save the user's explicit given key path. We will
|
'"%s" is neither a valid fingerprint, not an index ' +
|
||||||
* using it later for Docker setup. Trying to use
|
'from the list of available keys', value)));
|
||||||
* the same format as node-smartdc's loadSSHKey
|
|
||||||
* `keyPaths` param.
|
|
||||||
*/
|
|
||||||
ctx.keyPaths = {};
|
|
||||||
if (keyType === 'ssh') {
|
|
||||||
ctx.keyPaths.public = keyPath;
|
|
||||||
} else {
|
|
||||||
ctx.keyPaths.private = keyPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
var newVal = key.fingerprint('md5').toString();
|
|
||||||
console.log('Fingerprint: %s', newVal);
|
|
||||||
valCb(null, newVal);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} ];
|
} ];
|
||||||
|
|
||||||
@ -234,11 +202,50 @@ function _createProfile(opts, cb) {
|
|||||||
vasync.forEachPipeline({
|
vasync.forEachPipeline({
|
||||||
inputs: fields,
|
inputs: fields,
|
||||||
func: function getField(field, nextField) {
|
func: function getField(field, nextField) {
|
||||||
if (field.key !== 'name') console.log();
|
if (field.key !== 'name')
|
||||||
common.promptField(field, function (err, value) {
|
console.log();
|
||||||
data[field.key] = value;
|
if (field.key === 'keyId') {
|
||||||
nextField(err);
|
kr.list(function (err, pairs) {
|
||||||
});
|
if (err) {
|
||||||
|
nextField(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var choice = 1;
|
||||||
|
console.log('Available SSH keys:');
|
||||||
|
Object.keys(pairs).forEach(function (keyId) {
|
||||||
|
var valid = pairs[keyId].filter(function (kp) {
|
||||||
|
return (kp.canSign());
|
||||||
|
});
|
||||||
|
if (valid.length < 1)
|
||||||
|
return;
|
||||||
|
var pub = valid[0].getPublicKey();
|
||||||
|
console.log(
|
||||||
|
' %d. %d-bit %s key with fingerprint %s',
|
||||||
|
choice, pub.size, pub.type.toUpperCase(),
|
||||||
|
keyId);
|
||||||
|
pairs[keyId].forEach(function (kp) {
|
||||||
|
var comment = kp.comment ||
|
||||||
|
kp.getPublicKey().comment;
|
||||||
|
console.log(' * [in %s] %s %s %s',
|
||||||
|
kp.plugin, comment,
|
||||||
|
(kp.source ? kp.source : ''),
|
||||||
|
(kp.isLocked() ? '[locked]' : ''));
|
||||||
|
});
|
||||||
|
console.log();
|
||||||
|
keyChoices[choice] = keyId;
|
||||||
|
++choice;
|
||||||
|
});
|
||||||
|
common.promptField(field, function (err2, value) {
|
||||||
|
data[field.key] = value;
|
||||||
|
nextField(err2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
common.promptField(field, function (err, value) {
|
||||||
|
data[field.key] = value;
|
||||||
|
nextField(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, function (err) {
|
}, function (err) {
|
||||||
console.log();
|
console.log();
|
||||||
|
@ -8,6 +8,7 @@ var assert = require('assert-plus');
|
|||||||
var auth = require('smartdc-auth');
|
var auth = require('smartdc-auth');
|
||||||
var format = require('util').format;
|
var format = require('util').format;
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
|
var getpass = require('getpass');
|
||||||
var https = require('https');
|
var https = require('https');
|
||||||
var mkdirp = require('mkdirp');
|
var mkdirp = require('mkdirp');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
@ -143,14 +144,38 @@ function profileDockerSetup(opts, cb) {
|
|||||||
assert.optionalObject(opts.keyPaths, 'opts.keyPaths');
|
assert.optionalObject(opts.keyPaths, 'opts.keyPaths');
|
||||||
assert.func(cb, 'cb');
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
var implicit = Boolean(opts.implicit);
|
|
||||||
var cli = opts.cli;
|
var cli = opts.cli;
|
||||||
var log = cli.log;
|
|
||||||
var tritonapi = cli.tritonapiFromProfileName({profileName: opts.name});
|
var tritonapi = cli.tritonapiFromProfileName({profileName: opts.name});
|
||||||
|
|
||||||
|
var implicit = Boolean(opts.implicit);
|
||||||
|
var log = cli.log;
|
||||||
|
|
||||||
var profile = tritonapi.profile;
|
var profile = tritonapi.profile;
|
||||||
var dockerHost;
|
var dockerHost;
|
||||||
|
|
||||||
vasync.pipeline({arg: {}, funcs: [
|
vasync.pipeline({arg: {tritonapi: tritonapi}, funcs: [
|
||||||
|
function dockerKeyWarning(arg, next) {
|
||||||
|
console.log(wordwrap(
|
||||||
|
'\nWARNING: Docker uses TLS-based authentication with a ' +
|
||||||
|
'different security model from SSH keys. As a result, the ' +
|
||||||
|
'Docker client cannot currently support encrypted ' +
|
||||||
|
'(password protected) keys or SSH agents. If you ' +
|
||||||
|
'continue, the Triton CLI will attempt to format a copy ' +
|
||||||
|
'of your SSH *private* key as an unencrypted TLS cert ' +
|
||||||
|
'and place the copy in ~/.triton/docker for use by the ' +
|
||||||
|
'Docker client.'));
|
||||||
|
common.promptYesNo({msg: 'Continue? [y/n] '}, function (answer) {
|
||||||
|
if (answer !== 'y') {
|
||||||
|
console.error('Aborting');
|
||||||
|
next(true);
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
common.cliSetupTritonApi,
|
||||||
|
|
||||||
function checkCloudapiStatus(arg, next) {
|
function checkCloudapiStatus(arg, next) {
|
||||||
tritonapi.cloudapi.ping({}, function (err, pong, res) {
|
tritonapi.cloudapi.ping({}, function (err, pong, res) {
|
||||||
if (!res) {
|
if (!res) {
|
||||||
@ -222,69 +247,16 @@ function profileDockerSetup(opts, cb) {
|
|||||||
next();
|
next();
|
||||||
},
|
},
|
||||||
|
|
||||||
function findSshPrivKey_keyPaths(arg, next) {
|
function checkSshPrivKey(arg, next) {
|
||||||
if (!opts.keyPaths) {
|
try {
|
||||||
next();
|
tritonapi.keyPair.getPrivateKey();
|
||||||
|
} catch (e) {
|
||||||
|
next(new errors.SetupError(format('could not obtain SSH ' +
|
||||||
|
'private key for keypair with fingerprint "%s" ' +
|
||||||
|
'to create Docker certificate.', profile.keyId)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
next();
|
||||||
var privKeyPath = opts.keyPaths.private;
|
|
||||||
if (!privKeyPath) {
|
|
||||||
assert.string(opts.keyPaths.public);
|
|
||||||
assert.ok(opts.keyPaths.public.slice(-4) === '.pub');
|
|
||||||
privKeyPath = opts.keyPaths.public.slice(0, -4);
|
|
||||||
if (!fs.existsSync(privKeyPath)) {
|
|
||||||
cb(new errors.SetupError(format('could not find SSH '
|
|
||||||
+ 'private key file from public key file "%s": "%s" '
|
|
||||||
+ 'does not exist', opts.keyPaths.public,
|
|
||||||
privKeyPath)));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
arg.sshKeyPaths = {
|
|
||||||
private: privKeyPath,
|
|
||||||
public: opts.keyPaths.public
|
|
||||||
};
|
|
||||||
|
|
||||||
fs.readFile(privKeyPath, function (readErr, keyData) {
|
|
||||||
if (readErr) {
|
|
||||||
cb(readErr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
arg.sshPrivKey = sshpk.parseKey(keyData, 'pem');
|
|
||||||
} catch (keyErr) {
|
|
||||||
cb(keyErr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
log.trace({sshKeyPaths: arg.sshKeyPaths},
|
|
||||||
'profileDockerSetup: findSshPrivKey_keyPaths');
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function findSshPrivKey_keyId(arg, next) {
|
|
||||||
if (opts.keyPaths) {
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: keyPaths here is using a non-#master of node-smartdc-auth.
|
|
||||||
// Change back to a smartdc-auth release when
|
|
||||||
// https://github.com/joyent/node-smartdc-auth/pull/5 is in.
|
|
||||||
auth.loadSSHKey(profile.keyId, function (err, key, keyPaths) {
|
|
||||||
if (err) {
|
|
||||||
// TODO: better error message here.
|
|
||||||
next(err);
|
|
||||||
} else {
|
|
||||||
assert.ok(key, 'key from auth.loadSSHKey');
|
|
||||||
log.trace({keyId: profile.keyId, sshKeyPaths: keyPaths},
|
|
||||||
'profileDockerSetup: findSshPrivKey');
|
|
||||||
arg.sshKeyPaths = keyPaths;
|
|
||||||
arg.sshPrivKey = key;
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -348,31 +320,32 @@ function profileDockerSetup(opts, cb) {
|
|||||||
},
|
},
|
||||||
function genClientCert_key(arg, next) {
|
function genClientCert_key(arg, next) {
|
||||||
arg.keyPath = path.resolve(arg.dockerCertPath, 'key.pem');
|
arg.keyPath = path.resolve(arg.dockerCertPath, 'key.pem');
|
||||||
common.execPlus({
|
var data = tritonapi.keyPair.getPrivateKey().toBuffer('pkcs1');
|
||||||
cmd: format('openssl rsa -in %s -out %s -outform pem',
|
fs.writeFile(arg.keyPath, data, function (err) {
|
||||||
arg.sshKeyPaths.private, arg.keyPath),
|
if (err) {
|
||||||
log: log
|
next(new errors.SetupError(err, format(
|
||||||
}, next);
|
'error writing file %s', arg.keyPath)));
|
||||||
},
|
} else {
|
||||||
function genClientCert_csr(arg, next) {
|
next();
|
||||||
arg.csrPath = path.resolve(arg.dockerCertPath, 'csr.pem');
|
}
|
||||||
common.execPlus({
|
});
|
||||||
cmd: format('openssl req -new -key %s -out %s -subj "/CN=%s"',
|
|
||||||
arg.keyPath, arg.csrPath, profile.account),
|
|
||||||
log: log
|
|
||||||
}, next);
|
|
||||||
},
|
},
|
||||||
function genClientCert_cert(arg, next) {
|
function genClientCert_cert(arg, next) {
|
||||||
arg.certPath = path.resolve(arg.dockerCertPath, 'cert.pem');
|
arg.certPath = path.resolve(arg.dockerCertPath, 'cert.pem');
|
||||||
common.execPlus({
|
|
||||||
cmd: format(
|
var privKey = tritonapi.keyPair.getPrivateKey();
|
||||||
'openssl x509 -req -days 365 -in %s -signkey %s -out %s',
|
var id = sshpk.identityFromDN('CN=' + profile.account);
|
||||||
arg.csrPath, arg.keyPath, arg.certPath),
|
var cert = sshpk.createSelfSignedCertificate(id, privKey);
|
||||||
log: log
|
var data = cert.toBuffer('pem');
|
||||||
}, next);
|
|
||||||
},
|
fs.writeFile(arg.certPath, data, function (err) {
|
||||||
function genClientCert_deleteCsr(arg, next) {
|
if (err) {
|
||||||
rimraf(arg.csrPath, next);
|
next(new errors.SetupError(err, format(
|
||||||
|
'error writing file %s', arg.keyPath)));
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
function getServerCa(arg, next) {
|
function getServerCa(arg, next) {
|
||||||
|
@ -31,38 +31,44 @@ function do_services(subcmd, opts, args, callback) {
|
|||||||
|
|
||||||
var columns = opts.o.split(',');
|
var columns = opts.o.split(',');
|
||||||
var sort = opts.s.split(',');
|
var sort = opts.s.split(',');
|
||||||
|
var tritonapi = this.tritonapi;
|
||||||
|
|
||||||
this.tritonapi.cloudapi.listServices(function (err, services) {
|
common.cliSetupTritonApi({cli: this}, function onSetup(setupErr) {
|
||||||
if (err) {
|
if (setupErr) {
|
||||||
callback(err);
|
callback(setupErr);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
tritonapi.cloudapi.listServices(function (err, services) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (opts.json) {
|
if (opts.json) {
|
||||||
console.log(JSON.stringify(services));
|
console.log(JSON.stringify(services));
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
* services are returned in the form of:
|
* services are returned in the form of:
|
||||||
* {name: 'endpoint', name2: 'endpoint2', ...}
|
* {name: 'endpoint', name2: 'endpoint2', ...}
|
||||||
* we "normalize" them for use by tabula and JSON stream
|
* we "normalize" them for use by tabula and JSON stream
|
||||||
* by making them an array
|
* by making them an array
|
||||||
*/
|
*/
|
||||||
var svcs = [];
|
var svcs = [];
|
||||||
Object.keys(services).forEach(function (key) {
|
Object.keys(services).forEach(function (key) {
|
||||||
svcs.push({
|
svcs.push({
|
||||||
name: key,
|
name: key,
|
||||||
endpoint: services[key]
|
endpoint: services[key]
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
tabula(svcs, {
|
||||||
tabula(svcs, {
|
skipHeader: opts.H,
|
||||||
skipHeader: opts.H,
|
columns: columns,
|
||||||
columns: columns,
|
sort: sort,
|
||||||
sort: sort,
|
dottedLookup: true
|
||||||
dottedLookup: true
|
});
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
|
||||||
callback();
|
callback();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
236
lib/index.js
236
lib/index.js
@ -5,64 +5,112 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2015 Joyent, Inc.
|
* Copyright 2016 Joyent, Inc.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var assert = require('assert-plus');
|
var assert = require('assert-plus');
|
||||||
|
var vasync = require('vasync');
|
||||||
|
|
||||||
var bunyannoop = require('./bunyannoop');
|
var bunyannoop = require('./bunyannoop');
|
||||||
|
var mod_common = require('./common');
|
||||||
var mod_config = require('./config');
|
var mod_config = require('./config');
|
||||||
var tritonapi = require('./tritonapi');
|
var mod_cloudapi2 = require('./cloudapi2');
|
||||||
|
var mod_tritonapi = require('./tritonapi');
|
||||||
|
|
||||||
|
|
||||||
|
/* BEGIN JSSTYLED */
|
||||||
/**
|
/**
|
||||||
* A convenience wrapper around `tritonapi.createClient` to for simpler usage.
|
* A convenience wrapper around `tritonapi.TritonApi` for simpler usage.
|
||||||
|
* Conveniences are:
|
||||||
|
* - It wraps up the 3-step process of TritonApi client preparation into
|
||||||
|
* this one call.
|
||||||
|
* - It accepts optional `profileName` and `configDir` parameters that will
|
||||||
|
* load a profile by name and load a config, respectively.
|
||||||
*
|
*
|
||||||
* Minimally this only requires that one of `profileName` or `profile` be
|
* Client preparation is a 3-step process:
|
||||||
* specified. Examples:
|
|
||||||
*
|
*
|
||||||
* var triton = require('triton');
|
* 1. create the client object;
|
||||||
* var client = triton.createClient({
|
* 2. initialize it (mainly involves finding the SSH key identified by the
|
||||||
|
* `keyId`); and,
|
||||||
|
* 3. optionally unlock the SSH key (if it is passphrase-protected and not in
|
||||||
|
* an ssh-agent).
|
||||||
|
*
|
||||||
|
* The simplest usage that handles all of these is:
|
||||||
|
*
|
||||||
|
* var mod_triton = require('triton');
|
||||||
|
* mod_triton.createClient({
|
||||||
|
* profileName: 'env',
|
||||||
|
* unlockKeyFn: triton.promptPassphraseUnlockKey
|
||||||
|
* }, function (err, client) {
|
||||||
|
* if (err) {
|
||||||
|
* // handle err
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // use `client`
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* Minimally, only of `profileName` or `profile` is required. Examples:
|
||||||
|
*
|
||||||
|
* // Manually specify profile parameters.
|
||||||
|
* mod_triton.createClient({
|
||||||
* profile: {
|
* profile: {
|
||||||
* url: "<cloudapi url>",
|
* url: "<cloudapi url>",
|
||||||
* account: "<account login for this cloud>",
|
* account: "<account login for this cloud>",
|
||||||
* keyId: "<ssh key fingerprint for one of account's keys>"
|
* keyId: "<ssh key fingerprint for one of account's keys>"
|
||||||
* }
|
* }
|
||||||
* });
|
* }, function (err, client) { ... });
|
||||||
* --
|
*
|
||||||
* // Loading a profile from the environment (the `TRITON_*` and/or
|
* // Loading a profile from the environment (the `TRITON_*` and/or
|
||||||
* // `SDC_*` environment variables).
|
* // `SDC_*` environment variables).
|
||||||
* var client = triton.createClient({profileName: 'env'});
|
* triton.createClient({profileName: 'env'},
|
||||||
* --
|
* function (err, client) { ... });
|
||||||
* var client = triton.createClient({
|
*
|
||||||
* configDir: '~/.triton', // use the CLI's config dir ...
|
* // Use one of the named profiles from the `triton` CLI.
|
||||||
* profileName: 'east1' // ... to find named profiles
|
* triton.createClient({
|
||||||
* });
|
* configDir: '~/.triton',
|
||||||
* --
|
* profileName: 'east1'
|
||||||
|
* }, function (err, client) { ... });
|
||||||
|
*
|
||||||
* // The same thing using the underlying APIs.
|
* // The same thing using the underlying APIs.
|
||||||
* var client = triton.createClient({
|
* triton.createClient({
|
||||||
* config: triton.loadConfig({configDir: '~/.triton'},
|
* config: triton.loadConfig({configDir: '~/.triton'}),
|
||||||
* profile: triton.loadProfile({name: 'east1', configDir: '~/.triton'})
|
* profile: triton.loadProfile({name: 'east1', configDir: '~/.triton'})
|
||||||
* });
|
* }, function (err, client) { ... });
|
||||||
*
|
|
||||||
* A more complete example an app using triton internally might want:
|
|
||||||
*
|
|
||||||
* var triton = require('triton');
|
|
||||||
* var bunyan = require('bunyan');
|
|
||||||
*
|
|
||||||
* var appConfig = {
|
|
||||||
* // However the app handles its config.
|
|
||||||
* };
|
|
||||||
* var log = bunyan.createLogger({name: 'myapp', component: 'triton'});
|
|
||||||
* var client = triton.createClient({
|
|
||||||
* log: log,
|
|
||||||
* profile: appConfig.tritonProfile
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
*
|
*
|
||||||
* TODO: The story for an app wanting to specify some Triton config but NOT
|
* TODO: The story for an app wanting to specify some Triton config but NOT
|
||||||
* have to have a triton $configDir/config.json is poor.
|
* have to have a triton $configDir/config.json is poor.
|
||||||
*
|
*
|
||||||
|
*
|
||||||
|
* # What is that `unlockKeyFn` about?
|
||||||
|
*
|
||||||
|
* Triton uses HTTP-Signature auth: an SSH private key is used to sign requests.
|
||||||
|
* The server-side authenticates by verifying that signature using the
|
||||||
|
* previously uploaded public key. For the client to sign a request it needs an
|
||||||
|
* unlocked private key: an SSH private key that (a) is not
|
||||||
|
* passphrase-protected, (b) is loaded in an ssh-agent, or (c) for which we
|
||||||
|
* have a passphrase.
|
||||||
|
*
|
||||||
|
* If `createClient` finds that its key is locked, it will use `unlockKeyFn`
|
||||||
|
* as follows to attempt to unlock it:
|
||||||
|
*
|
||||||
|
* unlockKeyFn({
|
||||||
|
* tritonapi: client
|
||||||
|
* }, function (unlockErr) {
|
||||||
|
* // ...
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* This package exports a convenience `promptPassphraseUnlockKey` function that
|
||||||
|
* will prompt the user for a passphrase on stdin. Your tooling can use this
|
||||||
|
* function, provide your own, or skip key unlocking.
|
||||||
|
*
|
||||||
|
* The failure mode for a locked key is an error like this:
|
||||||
|
*
|
||||||
|
* SigningError: error signing request: SSH private key id_rsa is locked (encrypted/password-protected). It must be unlocked before use.
|
||||||
|
* at SigningError._TritonBaseVError (/Users/trentm/tmp/node-triton/lib/errors.js:55:12)
|
||||||
|
* at new SigningError (/Users/trentm/tmp/node-triton/lib/errors.js:173:23)
|
||||||
|
* at CloudApi._getAuthHeaders (/Users/trentm/tmp/node-triton/lib/cloudapi2.js:185:22)
|
||||||
|
*
|
||||||
|
*
|
||||||
* @param opts {Object}:
|
* @param opts {Object}:
|
||||||
* - @param profile {Object} A *Triton profile* object that includes the
|
* - @param profile {Object} A *Triton profile* object that includes the
|
||||||
* information required to connect to a CloudAPI -- minimally this:
|
* information required to connect to a CloudAPI -- minimally this:
|
||||||
@ -91,14 +139,24 @@ var tritonapi = require('./tritonapi');
|
|||||||
* One may not specify both `configDir` and `config`.
|
* One may not specify both `configDir` and `config`.
|
||||||
* - @param log {Bunyan Logger} Optional. A Bunyan logger. If not provided,
|
* - @param log {Bunyan Logger} Optional. A Bunyan logger. If not provided,
|
||||||
* a stub that does no logging will be used.
|
* a stub that does no logging will be used.
|
||||||
|
* - @param {Function} unlockKeyFn - Optional. A function to handle
|
||||||
|
* unlocking the SSH key found for this profile, if necessary. It must
|
||||||
|
* be of the form `function (opts, cb)` where `opts.tritonapi` is the
|
||||||
|
* initialized TritonApi client. If the caller is a command-line
|
||||||
|
* interface, then `triton.promptPassphraseUnlockKey` can be used to
|
||||||
|
* prompt on stdin for the SSH key passphrase, if needed.
|
||||||
|
* @param {Function} cb - `function (err, client)`
|
||||||
*/
|
*/
|
||||||
function createClient(opts) {
|
/* END JSSTYLED */
|
||||||
|
function createClient(opts, cb) {
|
||||||
assert.object(opts, 'opts');
|
assert.object(opts, 'opts');
|
||||||
assert.optionalObject(opts.profile, 'opts.profile');
|
assert.optionalObject(opts.profile, 'opts.profile');
|
||||||
assert.optionalString(opts.profileName, 'opts.profileName');
|
assert.optionalString(opts.profileName, 'opts.profileName');
|
||||||
assert.optionalObject(opts.config, 'opts.config');
|
assert.optionalObject(opts.config, 'opts.config');
|
||||||
assert.optionalString(opts.configDir, 'opts.configDir');
|
assert.optionalString(opts.configDir, 'opts.configDir');
|
||||||
assert.optionalObject(opts.log, 'opts.log');
|
assert.optionalObject(opts.log, 'opts.log');
|
||||||
|
assert.optionalFunc(opts.unlockKeyFn, 'opts.unlockKeyFn');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
assert.ok(!(opts.profile && opts.profileName),
|
assert.ok(!(opts.profile && opts.profileName),
|
||||||
'cannot specify both opts.profile and opts.profileName');
|
'cannot specify both opts.profile and opts.profileName');
|
||||||
@ -113,42 +171,87 @@ function createClient(opts) {
|
|||||||
'must provide opts.configDir for opts.profileName!="env"');
|
'must provide opts.configDir for opts.profileName!="env"');
|
||||||
}
|
}
|
||||||
|
|
||||||
var log = opts.log;
|
var log;
|
||||||
if (!opts.log) {
|
var client;
|
||||||
log = new bunyannoop.BunyanNoopLogger();
|
|
||||||
}
|
|
||||||
|
|
||||||
var config = opts.config;
|
vasync.pipeline({funcs: [
|
||||||
if (!config) {
|
function theSyncPart(_, next) {
|
||||||
config = mod_config.loadConfig({configDir: opts.configDir});
|
log = opts.log || new bunyannoop.BunyanNoopLogger();
|
||||||
}
|
|
||||||
|
|
||||||
var profile = opts.profile;
|
var config;
|
||||||
if (!profile) {
|
if (opts.config) {
|
||||||
profile = mod_config.loadProfile({
|
config = opts.config;
|
||||||
name: opts.profileName,
|
} else {
|
||||||
configDir: config._configDir
|
try {
|
||||||
});
|
config = mod_config.loadConfig(
|
||||||
}
|
{configDir: opts.configDir});
|
||||||
// Don't require one to arbitrarily have a profile.name if manually
|
} catch (configErr) {
|
||||||
// creating it.
|
next(configErr);
|
||||||
if (!profile.name) {
|
return;
|
||||||
// TODO: might want this to be hash or slug of profile params.
|
}
|
||||||
profile.name = '_';
|
}
|
||||||
}
|
|
||||||
mod_config.validateProfile(profile);
|
|
||||||
|
|
||||||
var client = tritonapi.createClient({
|
var profile;
|
||||||
log: log,
|
if (opts.profile) {
|
||||||
config: config,
|
profile = opts.profile;
|
||||||
profile: profile
|
/*
|
||||||
|
* Don't require one to arbitrarily have a profile.name if
|
||||||
|
* manually creating it.
|
||||||
|
*/
|
||||||
|
if (!profile.name) {
|
||||||
|
// TODO: might want this to be a hash/slug of params.
|
||||||
|
profile.name = '_';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
profile = mod_config.loadProfile({
|
||||||
|
name: opts.profileName,
|
||||||
|
configDir: config._configDir
|
||||||
|
});
|
||||||
|
} catch (profileErr) {
|
||||||
|
next(profileErr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
mod_config.validateProfile(profile);
|
||||||
|
} catch (valErr) {
|
||||||
|
next(valErr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client = mod_tritonapi.createClient({
|
||||||
|
log: log,
|
||||||
|
config: config,
|
||||||
|
profile: profile
|
||||||
|
});
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
function initTheClient(_, next) {
|
||||||
|
client.init(next);
|
||||||
|
},
|
||||||
|
function optionallyUnlockKey(_, next) {
|
||||||
|
if (!opts.unlockKeyFn) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.unlockKeyFn({tritonapi: client}, next);
|
||||||
|
}
|
||||||
|
]}, function (err) {
|
||||||
|
log.trace({err: err}, 'createClient complete');
|
||||||
|
if (err) {
|
||||||
|
cb(err);
|
||||||
|
} else {
|
||||||
|
cb(null, client);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return client;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
createClient: createClient,
|
createClient: createClient,
|
||||||
|
promptPassphraseUnlockKey: mod_common.promptPassphraseUnlockKey,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `createClient` provides convenience parameters to not *have* to call
|
* `createClient` provides convenience parameters to not *have* to call
|
||||||
@ -159,7 +262,10 @@ module.exports = {
|
|||||||
loadProfile: mod_config.loadProfile,
|
loadProfile: mod_config.loadProfile,
|
||||||
loadAllProfiles: mod_config.loadAllProfiles,
|
loadAllProfiles: mod_config.loadAllProfiles,
|
||||||
|
|
||||||
createTritonApiClient: tritonapi.createClient,
|
/*
|
||||||
// For those wanting a lower-level raw CloudAPI client.
|
* For those wanting a lower-level TritonApi createClient, or an
|
||||||
createCloudApiClient: require('./cloudapi2').createClient
|
* even *lower*-level raw CloudAPI client.
|
||||||
|
*/
|
||||||
|
createTritonApiClient: mod_tritonapi.createClient,
|
||||||
|
createCloudApiClient: mod_cloudapi2.createClient
|
||||||
};
|
};
|
||||||
|
176
lib/tritonapi.js
176
lib/tritonapi.js
@ -6,10 +6,81 @@
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2016 Joyent, Inc.
|
* Copyright 2016 Joyent, Inc.
|
||||||
*
|
|
||||||
* Core TritonApi client driver class.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* BEGIN JSSTYLED */
|
||||||
|
/*
|
||||||
|
* Core `TritonApi` client class. A TritonApi client object is a wrapper around
|
||||||
|
* a lower-level `CloudApi` client that makes raw calls to
|
||||||
|
* [Cloud API](https://apidocs.joyent.com/cloudapi/). The wrapper provides
|
||||||
|
* some conveniences, for example:
|
||||||
|
* - referring to resources by "shortId" (8-char UUID prefixes) or "name"
|
||||||
|
* (e.g. an VM instance has a unique name for an account, but the raw
|
||||||
|
* Cloud API only supports lookup by full UUID);
|
||||||
|
* - filling in of image details for instances which only have an "image_uuid"
|
||||||
|
* in Cloud API responses;
|
||||||
|
* - support for waiting for async operations to complete via "wait" parameters;
|
||||||
|
* - profile handling.
|
||||||
|
*
|
||||||
|
* Preparing a TritonApi is a three-step process. (Note: Some users might
|
||||||
|
* prefer to use the `createClient` convenience function in "index.js" that
|
||||||
|
* wraps up all three steps into a single call.)
|
||||||
|
*
|
||||||
|
* 1. Create the client object.
|
||||||
|
* 2. Initialize it (mainly involves finding the SSH key identified by the
|
||||||
|
* `keyId`).
|
||||||
|
* 3. Optionally, unlock the SSH key (if it is passphrase-protected and not in
|
||||||
|
* an ssh-agent). If you know that your key is not passphrase-protected
|
||||||
|
* or is an ssh-agent, then you can skip this step. The failure mode for
|
||||||
|
* a locked key looks like this:
|
||||||
|
* SigningError: error signing request: SSH private key id_rsa is locked (encrypted/password-protected). It must be unlocked before use.
|
||||||
|
* at SigningError._TritonBaseVError (/Users/trentm/tmp/node-triton/lib/errors.js:55:12)
|
||||||
|
* at new SigningError (/Users/trentm/tmp/node-triton/lib/errors.js:173:23)
|
||||||
|
* at CloudApi._getAuthHeaders (/Users/trentm/tmp/node-triton/lib/cloudapi2.js:185:22)
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* var mod_triton = require('triton');
|
||||||
|
*
|
||||||
|
* // 1. Create the TritonApi instance.
|
||||||
|
* var client = mod_triton.createTritonApiClient({
|
||||||
|
* log: log,
|
||||||
|
* profile: profile, // See mod_triton.loadProfile
|
||||||
|
* config: config // See mod_triton.loadConfig
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // 2. Call `init` to setup the profile. This involves finding the SSH
|
||||||
|
* // key identified by the profile's keyId.
|
||||||
|
* client.init(function (initErr) {
|
||||||
|
* if (initErr) boom(initErr);
|
||||||
|
*
|
||||||
|
* // 3. Unlock the SSH key, if necessary. Possibilities are:
|
||||||
|
* // (a) Skip this step. If the key is locked, you will get a
|
||||||
|
* // "SigningError" at first attempt to sign. See example above.
|
||||||
|
* // (b) The key is not locked.
|
||||||
|
* // `client.keyPair.isLocked() === false`
|
||||||
|
* // (c) You have a passphrase for the key:
|
||||||
|
* if (client.keyPair.isLocked()) {
|
||||||
|
* // This throws if the passphrase is incorrect.
|
||||||
|
* client.keyPair.unlock(passphrase);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // (d) Or you use a function that will prompt for a passphrase
|
||||||
|
* // and unlock with that. E.g., `promptPassphraseUnlockKey`
|
||||||
|
* // is one provided by this package that with prompt on stdin.
|
||||||
|
* mod_triton.promptPassphraseUnlockKey({
|
||||||
|
* tritonapi: client
|
||||||
|
* }, function (unlockErr) {
|
||||||
|
* if (unlockErr) boom(unlockErr);
|
||||||
|
*
|
||||||
|
* // 4. Now you can finally make an API call. For example:
|
||||||
|
* client.listImages(function (err, imgs) {
|
||||||
|
* // ...
|
||||||
|
* });
|
||||||
|
* });
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
/* END JSSTYLED */
|
||||||
|
|
||||||
var assert = require('assert-plus');
|
var assert = require('assert-plus');
|
||||||
var auth = require('smartdc-auth');
|
var auth = require('smartdc-auth');
|
||||||
var EventEmitter = require('events').EventEmitter;
|
var EventEmitter = require('events').EventEmitter;
|
||||||
@ -24,6 +95,7 @@ var restifyBunyanSerializers =
|
|||||||
require('restify-clients/lib/helpers/bunyan').serializers;
|
require('restify-clients/lib/helpers/bunyan').serializers;
|
||||||
var tabula = require('tabula');
|
var tabula = require('tabula');
|
||||||
var vasync = require('vasync');
|
var vasync = require('vasync');
|
||||||
|
var sshpk = require('sshpk');
|
||||||
|
|
||||||
var cloudapi = require('./cloudapi2');
|
var cloudapi = require('./cloudapi2');
|
||||||
var common = require('./common');
|
var common = require('./common');
|
||||||
@ -116,6 +188,14 @@ function _stepFwRuleId(arg, next) {
|
|||||||
/**
|
/**
|
||||||
* Create a TritonApi client.
|
* Create a TritonApi client.
|
||||||
*
|
*
|
||||||
|
* Public properties (TODO: doc all of these):
|
||||||
|
* - profile
|
||||||
|
* - config
|
||||||
|
* - log
|
||||||
|
* - cacheDir (only available if configured with a configDir)
|
||||||
|
* - keyPair (available after init)
|
||||||
|
* - cloudapi (available after init)
|
||||||
|
*
|
||||||
* @param opts {Object}
|
* @param opts {Object}
|
||||||
* - log {Bunyan Logger}
|
* - log {Bunyan Logger}
|
||||||
* ...
|
* ...
|
||||||
@ -128,6 +208,7 @@ function TritonApi(opts) {
|
|||||||
|
|
||||||
this.profile = opts.profile;
|
this.profile = opts.profile;
|
||||||
this.config = opts.config;
|
this.config = opts.config;
|
||||||
|
this.keyPair = null;
|
||||||
|
|
||||||
// Make sure a given bunyan logger has reasonable client_re[qs] serializers.
|
// Make sure a given bunyan logger has reasonable client_re[qs] serializers.
|
||||||
// Note: This was fixed in restify, then broken again in
|
// Note: This was fixed in restify, then broken again in
|
||||||
@ -147,29 +228,43 @@ function TritonApi(opts) {
|
|||||||
this.config.cacheDir,
|
this.config.cacheDir,
|
||||||
common.profileSlug(this.profile));
|
common.profileSlug(this.profile));
|
||||||
this.log.trace({cacheDir: this.cacheDir}, 'cache dir');
|
this.log.trace({cacheDir: this.cacheDir}, 'cache dir');
|
||||||
// TODO perhaps move this to an async .init()
|
|
||||||
if (!fs.existsSync(this.cacheDir)) {
|
|
||||||
try {
|
|
||||||
mkdirp.sync(this.cacheDir);
|
|
||||||
} catch (e) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.cloudapi = this._cloudapiFromProfile(this.profile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TritonApi.prototype.close = function close() {
|
TritonApi.prototype.close = function close() {
|
||||||
this.cloudapi.close();
|
if (this.cloudapi) {
|
||||||
delete this.cloudapi;
|
this.cloudapi.close();
|
||||||
|
delete this.cloudapi;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
TritonApi.prototype._cloudapiFromProfile =
|
TritonApi.prototype.init = function init(cb) {
|
||||||
function _cloudapiFromProfile(profile)
|
var self = this;
|
||||||
{
|
if (this.cacheDir) {
|
||||||
|
fs.exists(this.cacheDir, function (exists) {
|
||||||
|
if (!exists) {
|
||||||
|
mkdirp(self.cacheDir, function (err) {
|
||||||
|
if (err) {
|
||||||
|
cb(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self._setupProfile(cb);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self._setupProfile(cb);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self._setupProfile(cb);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TritonApi.prototype._setupProfile = function _setupProfile(cb) {
|
||||||
|
var self = this;
|
||||||
|
var profile = this.profile;
|
||||||
|
|
||||||
assert.object(profile, 'profile');
|
assert.object(profile, 'profile');
|
||||||
assert.string(profile.account, 'profile.account');
|
assert.string(profile.account, 'profile.account');
|
||||||
assert.optionalString(profile.actAsAccount, 'profile.actAsAccount');
|
assert.optionalString(profile.actAsAccount, 'profile.actAsAccount');
|
||||||
@ -185,32 +280,39 @@ TritonApi.prototype._cloudapiFromProfile =
|
|||||||
? true : !profile.insecure);
|
? true : !profile.insecure);
|
||||||
var acceptVersion = profile.acceptVersion || CLOUDAPI_ACCEPT_VERSION;
|
var acceptVersion = profile.acceptVersion || CLOUDAPI_ACCEPT_VERSION;
|
||||||
|
|
||||||
var sign;
|
var opts = {
|
||||||
if (profile.privKey) {
|
|
||||||
sign = auth.privateKeySigner({
|
|
||||||
user: profile.account,
|
|
||||||
subuser: profile.user,
|
|
||||||
keyId: profile.keyId,
|
|
||||||
key: profile.privKey
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
sign = auth.cliSigner({
|
|
||||||
keyId: profile.keyId,
|
|
||||||
user: profile.account,
|
|
||||||
subuser: profile.user
|
|
||||||
});
|
|
||||||
}
|
|
||||||
var client = cloudapi.createClient({
|
|
||||||
url: profile.url,
|
url: profile.url,
|
||||||
account: profile.actAsAccount || profile.account,
|
account: profile.actAsAccount || profile.account,
|
||||||
user: profile.user,
|
principal: {
|
||||||
|
account: profile.account,
|
||||||
|
user: profile.user
|
||||||
|
},
|
||||||
roles: profile.roles,
|
roles: profile.roles,
|
||||||
version: acceptVersion,
|
version: acceptVersion,
|
||||||
rejectUnauthorized: rejectUnauthorized,
|
rejectUnauthorized: rejectUnauthorized,
|
||||||
sign: sign,
|
|
||||||
log: this.log
|
log: this.log
|
||||||
});
|
};
|
||||||
return client;
|
|
||||||
|
if (profile.privKey) {
|
||||||
|
var key = sshpk.parsePrivateKey(profile.privKey);
|
||||||
|
this.keyPair =
|
||||||
|
opts.principal.keyPair =
|
||||||
|
auth.KeyPair.fromPrivateKey(key);
|
||||||
|
this.cloudapi = cloudapi.createClient(opts);
|
||||||
|
cb(null);
|
||||||
|
} else {
|
||||||
|
var kr = new auth.KeyRing();
|
||||||
|
var fp = sshpk.parseFingerprint(profile.keyId);
|
||||||
|
kr.findSigningKeyPair(fp, function (err, kp) {
|
||||||
|
if (err) {
|
||||||
|
cb(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.keyPair = opts.principal.keyPair = kp;
|
||||||
|
self.cloudapi = cloudapi.createClient(opts);
|
||||||
|
cb(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
"bunyan": "1.5.1",
|
"bunyan": "1.5.1",
|
||||||
"cmdln": "4.1.2",
|
"cmdln": "4.1.2",
|
||||||
"extsprintf": "1.0.2",
|
"extsprintf": "1.0.2",
|
||||||
|
"getpass": "0.1.6",
|
||||||
"lomstream": "1.1.0",
|
"lomstream": "1.1.0",
|
||||||
"mkdirp": "0.5.1",
|
"mkdirp": "0.5.1",
|
||||||
"node-uuid": "1.4.3",
|
"node-uuid": "1.4.3",
|
||||||
@ -19,8 +20,9 @@
|
|||||||
"restify-errors": "3.0.0",
|
"restify-errors": "3.0.0",
|
||||||
"rimraf": "2.4.4",
|
"rimraf": "2.4.4",
|
||||||
"semver": "5.1.0",
|
"semver": "5.1.0",
|
||||||
"smartdc-auth": "git+https://github.com/joyent/node-smartdc-auth.git#05d9077",
|
"smartdc-auth": "2.5.2",
|
||||||
"sshpk": "1.7.x",
|
"sshpk": "1.10.1",
|
||||||
|
"sshpk-agent": "1.4.2",
|
||||||
"strsplit": "1.0.0",
|
"strsplit": "1.0.0",
|
||||||
"tabula": "1.7.0",
|
"tabula": "1.7.0",
|
||||||
"vasync": "1.6.3",
|
"vasync": "1.6.3",
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2015, Joyent, Inc.
|
* Copyright 2016 Joyent, Inc.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -29,9 +29,11 @@ test('TritonApi images', function (tt) {
|
|||||||
|
|
||||||
var client;
|
var client;
|
||||||
tt.test(' setup: client', function (t) {
|
tt.test(' setup: client', function (t) {
|
||||||
client = h.createClient();
|
h.createClient(function (err, client_) {
|
||||||
t.ok(client, 'client');
|
t.error(err);
|
||||||
t.end();
|
client = client_;
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var testOpts = {};
|
var testOpts = {};
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2015, Joyent, Inc.
|
* Copyright 2016 Joyent, Inc.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -15,24 +15,26 @@
|
|||||||
var h = require('./helpers');
|
var h = require('./helpers');
|
||||||
var test = require('tape');
|
var test = require('tape');
|
||||||
|
|
||||||
var common = require('../../lib/common');
|
|
||||||
|
|
||||||
|
|
||||||
// --- Globals
|
// --- Globals
|
||||||
|
|
||||||
|
|
||||||
var CLIENT;
|
var CLIENT;
|
||||||
var INST;
|
var INST;
|
||||||
|
|
||||||
|
|
||||||
// --- Tests
|
// --- Tests
|
||||||
|
|
||||||
|
|
||||||
test('TritonApi packages', function (tt) {
|
test('TritonApi packages', function (tt) {
|
||||||
tt.test(' setup', function (t) {
|
|
||||||
CLIENT = h.createClient();
|
|
||||||
t.ok(CLIENT, 'client');
|
|
||||||
|
|
||||||
|
tt.test(' setup', function (t) {
|
||||||
|
h.createClient(function (err, client_) {
|
||||||
|
t.error(err);
|
||||||
|
CLIENT = client_;
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tt.test(' setup: inst', function (t) {
|
||||||
CLIENT.cloudapi.listMachines(function (err, insts) {
|
CLIENT.cloudapi.listMachines(function (err, insts) {
|
||||||
if (h.ifErr(t, err))
|
if (h.ifErr(t, err))
|
||||||
return t.end();
|
return t.end();
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2015, Joyent, Inc.
|
* Copyright 2016 Joyent, Inc.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -15,28 +15,28 @@
|
|||||||
var h = require('./helpers');
|
var h = require('./helpers');
|
||||||
var test = require('tape');
|
var test = require('tape');
|
||||||
|
|
||||||
var common = require('../../lib/common');
|
|
||||||
|
|
||||||
|
|
||||||
// --- Globals
|
// --- Globals
|
||||||
|
|
||||||
|
|
||||||
var CLIENT;
|
var CLIENT;
|
||||||
var NET;
|
var NET;
|
||||||
|
|
||||||
|
|
||||||
// --- Tests
|
// --- Tests
|
||||||
|
|
||||||
|
|
||||||
test('TritonApi networks', function (tt) {
|
test('TritonApi networks', function (tt) {
|
||||||
tt.test(' setup', function (t) {
|
tt.test(' setup', function (t) {
|
||||||
CLIENT = h.createClient();
|
h.createClient(function (err, client_) {
|
||||||
t.ok(CLIENT, 'client');
|
t.error(err);
|
||||||
|
CLIENT = client_;
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tt.test(' setup: net', function (t) {
|
||||||
var opts = {
|
var opts = {
|
||||||
account: CLIENT.profile.account
|
account: CLIENT.profile.account
|
||||||
};
|
};
|
||||||
|
|
||||||
CLIENT.cloudapi.listNetworks(opts, function (err, nets) {
|
CLIENT.cloudapi.listNetworks(opts, function (err, nets) {
|
||||||
if (h.ifErr(t, err))
|
if (h.ifErr(t, err))
|
||||||
return t.end();
|
return t.end();
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2015, Joyent, Inc.
|
* Copyright 2016 Joyent, Inc.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -30,9 +30,14 @@ var PKG;
|
|||||||
|
|
||||||
test('TritonApi packages', function (tt) {
|
test('TritonApi packages', function (tt) {
|
||||||
tt.test(' setup', function (t) {
|
tt.test(' setup', function (t) {
|
||||||
CLIENT = h.createClient();
|
h.createClient(function (err, client_) {
|
||||||
t.ok(CLIENT, 'client');
|
t.error(err);
|
||||||
|
CLIENT = client_;
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tt.test(' setup: pkg', function (t) {
|
||||||
CLIENT.cloudapi.listPackages(function (err, pkgs) {
|
CLIENT.cloudapi.listPackages(function (err, pkgs) {
|
||||||
if (h.ifErr(t, err))
|
if (h.ifErr(t, err))
|
||||||
return t.end();
|
return t.end();
|
||||||
|
@ -248,12 +248,14 @@ function jsonStreamParse(s) {
|
|||||||
/*
|
/*
|
||||||
* Create a TritonApi client using the CLI.
|
* Create a TritonApi client using the CLI.
|
||||||
*/
|
*/
|
||||||
function createClient() {
|
function createClient(cb) {
|
||||||
return mod_triton.createClient({
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
|
mod_triton.createClient({
|
||||||
log: LOG,
|
log: LOG,
|
||||||
profile: CONFIG.profile,
|
profile: CONFIG.profile,
|
||||||
configDir: '~/.triton' // piggy-back on Triton CLI config dir
|
configDir: '~/.triton' // piggy-back on Triton CLI config dir
|
||||||
});
|
}, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user