TRITON-30 Add UpdateNetworkIP to node-triton
Reviewed by: Trent Mick <trentm@gmail.com> Approved by: Trent Mick <trentm@gmail.com>
This commit is contained in:
parent
da5f3bade8
commit
ab564177b5
@ -8,6 +8,11 @@ Known issues:
|
|||||||
|
|
||||||
(nothing yet)
|
(nothing yet)
|
||||||
|
|
||||||
|
## 5.6.0
|
||||||
|
|
||||||
|
- [TRITON-30] Add UpdateNetworkIP to node-triton, e.g.
|
||||||
|
`triton network ip update`
|
||||||
|
|
||||||
## 5.5.0
|
## 5.5.0
|
||||||
|
|
||||||
- [PUBAPI-1452] Add ip subcommand to network, e.g.
|
- [PUBAPI-1452] Add ip subcommand to network, e.g.
|
||||||
|
@ -389,7 +389,7 @@ CloudApi.prototype.getNetwork = function getNetwork(id, cb) {
|
|||||||
/**
|
/**
|
||||||
* <http://apidocs.joyent.com/cloudapi/#ListNetworkIPs>
|
* <http://apidocs.joyent.com/cloudapi/#ListNetworkIPs>
|
||||||
*
|
*
|
||||||
* @param {String} - UUID
|
* @param {String} id - The network UUID. Required.
|
||||||
* @param {Function} callback of the form `function (err, ips, res)`
|
* @param {Function} callback of the form `function (err, ips, res)`
|
||||||
*/
|
*/
|
||||||
CloudApi.prototype.listNetworkIps = function listNetworkIps(id, cb) {
|
CloudApi.prototype.listNetworkIps = function listNetworkIps(id, cb) {
|
||||||
@ -406,13 +406,14 @@ CloudApi.prototype.listNetworkIps = function listNetworkIps(id, cb) {
|
|||||||
* <http://apidocs.joyent.com/cloudapi/#GetNetworkIP>
|
* <http://apidocs.joyent.com/cloudapi/#GetNetworkIP>
|
||||||
*
|
*
|
||||||
* @param {Object} opts
|
* @param {Object} opts
|
||||||
* - {String} opts.id The network UUID, name, or shortID. Required.
|
* - {String} id - The network UUID. Required.
|
||||||
* - {String} opts.ip The IP. Required.
|
* - {String} ip - The IP. Required.
|
||||||
* @param {Function} callback of the form `function (err, ip, res)`
|
* @param {Function} callback of the form `function (err, ip, res)`
|
||||||
*/
|
*/
|
||||||
CloudApi.prototype.getNetworkIp = function getNetworkIp(opts, cb) {
|
CloudApi.prototype.getNetworkIp = function getNetworkIp(opts, cb) {
|
||||||
assert.uuid(opts.id, 'id');
|
assert.object(opts, 'opts');
|
||||||
assert.string(opts.ip, 'ip');
|
assert.uuid(opts.id, 'opts.id');
|
||||||
|
assert.string(opts.ip, 'opts.ip');
|
||||||
assert.func(cb, 'cb');
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
var endpoint = this._path(format('/%s/networks/%s/ips/%s',
|
var endpoint = this._path(format('/%s/networks/%s/ips/%s',
|
||||||
@ -422,6 +423,42 @@ CloudApi.prototype.getNetworkIp = function getNetworkIp(opts, cb) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <http://apidocs.joyent.com/cloudapi/#UpdateNetworkIP>
|
||||||
|
*
|
||||||
|
* @param {Object} opts
|
||||||
|
* - {String} id - The network UUID. Required.
|
||||||
|
* - {String} ip - The IP. Required.
|
||||||
|
* - {Boolean} reserved - Reserve the IP. Required.
|
||||||
|
* @param {Function} callback of the form `function (err, body, res)`
|
||||||
|
*/
|
||||||
|
CloudApi.prototype.updateNetworkIp = function updateNetworkIp(opts, cb) {
|
||||||
|
assert.object(opts, 'opts');
|
||||||
|
assert.uuid(opts.id, 'opts.id');
|
||||||
|
assert.string(opts.ip, 'opts.ip');
|
||||||
|
assert.bool(opts.reserved, 'opts.reserved');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
|
var endpoint = this._path(format('/%s/networks/%s/ips/%s',
|
||||||
|
this.account, opts.id, opts.ip));
|
||||||
|
var data = {
|
||||||
|
reserved: opts.reserved
|
||||||
|
};
|
||||||
|
|
||||||
|
this._request({
|
||||||
|
method: 'PUT',
|
||||||
|
path: endpoint,
|
||||||
|
data: data
|
||||||
|
}, function (err, req, res, body) {
|
||||||
|
cb(err, body, res);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// <updatable network ip field> -> <expected typeof>
|
||||||
|
CloudApi.prototype.UPDATE_NETWORK_IP_FIELDS = {
|
||||||
|
reserved: 'boolean'
|
||||||
|
};
|
||||||
|
|
||||||
// ---- datacenters
|
// ---- datacenters
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1301,7 +1301,91 @@ function argvFromLine(line) {
|
|||||||
return argv;
|
return argv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read stdin in and callback with it as a string
|
||||||
|
*
|
||||||
|
* @param {Function} cb - callback in the form `function (str) {}`
|
||||||
|
*/
|
||||||
|
function readStdin(cb) {
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
|
var stdin = '';
|
||||||
|
process.stdin.setEncoding('utf8');
|
||||||
|
process.stdin.resume();
|
||||||
|
process.stdin.on('data', function stdinOnData(chunk) {
|
||||||
|
stdin += chunk;
|
||||||
|
});
|
||||||
|
process.stdin.on('end', function stdinOnEnd() {
|
||||||
|
cb(stdin);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Validate an object of values against an object of types.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* var input = {
|
||||||
|
* foo: 'hello',
|
||||||
|
* bar: 42,
|
||||||
|
* baz: true
|
||||||
|
* };
|
||||||
|
* var valid = {
|
||||||
|
* foo: 'string',
|
||||||
|
* bar: 'number',
|
||||||
|
* baz: 'boolean'
|
||||||
|
* }
|
||||||
|
* validateObject(input, valid);
|
||||||
|
* // no error is thrown
|
||||||
|
*
|
||||||
|
* All keys in `input` are check for their matching counterparts in `valid`.
|
||||||
|
* If the key is not found in `valid`, or the type specified for the key in
|
||||||
|
* `valid` doesn't match the type of the value in `input` an error is thrown.
|
||||||
|
* Also an error is thrown (optionally, enabled by default) if the input object
|
||||||
|
* is empty. Note that any keys found in `valid` not found in `input` are not
|
||||||
|
* considered an error.
|
||||||
|
*
|
||||||
|
* @param {Object} input - Required. Input object of values.
|
||||||
|
* @param {Object} valid - Required. Validation object of types.
|
||||||
|
* @param {Object} opts: Optional
|
||||||
|
* - @param {Boolean} allowEmptyInput - don't consider an empty
|
||||||
|
* input object an error
|
||||||
|
* @throws {Error} if the input object contains a key not found in the
|
||||||
|
* validation object
|
||||||
|
*/
|
||||||
|
function validateObject(input, valid, opts) {
|
||||||
|
opts = opts || {};
|
||||||
|
|
||||||
|
assert.object(input, 'input');
|
||||||
|
assert.object(valid, 'valid');
|
||||||
|
assert.object(opts, 'opts');
|
||||||
|
assert.optionalBool(opts.allowEmptyInput, 'opts.allowEmptyInput');
|
||||||
|
|
||||||
|
var validFields = Object.keys(valid).sort().join(', ');
|
||||||
|
var i = 0;
|
||||||
|
|
||||||
|
Object.keys(input).forEach(function (key) {
|
||||||
|
var value = input[key];
|
||||||
|
var type = valid[key];
|
||||||
|
|
||||||
|
if (!type) {
|
||||||
|
throw new errors.UsageError(format('unknown or ' +
|
||||||
|
'unupdateable field: %s (updateable fields are: %s)',
|
||||||
|
key, validFields));
|
||||||
|
}
|
||||||
|
assert.string(type, 'type');
|
||||||
|
|
||||||
|
if (typeof (value) !== type) {
|
||||||
|
throw new errors.UsageError(format('field "%s" must be ' +
|
||||||
|
'of type "%s", but got a value of type "%s"',
|
||||||
|
key, type, typeof (value)));
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (i === 0 && !opts.allowEmptyInput) {
|
||||||
|
throw new errors.UsageError('Input object must not be empty');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//---- exports
|
//---- exports
|
||||||
|
|
||||||
@ -1339,6 +1423,8 @@ module.exports = {
|
|||||||
objFromKeyValueArgs: objFromKeyValueArgs,
|
objFromKeyValueArgs: objFromKeyValueArgs,
|
||||||
argvFromLine: argvFromLine,
|
argvFromLine: argvFromLine,
|
||||||
jsonPredFromKv: jsonPredFromKv,
|
jsonPredFromKv: jsonPredFromKv,
|
||||||
monotonicTimeDiffMs: monotonicTimeDiffMs
|
monotonicTimeDiffMs: monotonicTimeDiffMs,
|
||||||
|
readStdin: readStdin,
|
||||||
|
validateObject: validateObject
|
||||||
};
|
};
|
||||||
// vim: set softtabstop=4 shiftwidth=4:
|
// vim: set softtabstop=4 shiftwidth=4:
|
||||||
|
193
lib/do_network/do_ip/do_update.js
Normal file
193
lib/do_network/do_ip/do_update.js
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2017 Joyent, Inc.
|
||||||
|
*
|
||||||
|
* `triton network ip update ...`
|
||||||
|
*/
|
||||||
|
|
||||||
|
var format = require('util').format;
|
||||||
|
var fs = require('fs');
|
||||||
|
|
||||||
|
var vasync = require('vasync');
|
||||||
|
|
||||||
|
var common = require('../../common');
|
||||||
|
var errors = require('../../errors');
|
||||||
|
var UPDATE_NETWORK_IP_FIELDS
|
||||||
|
= require('../../cloudapi2').CloudApi.prototype.UPDATE_NETWORK_IP_FIELDS;
|
||||||
|
|
||||||
|
function do_update(subcmd, opts, args, callback) {
|
||||||
|
if (opts.help) {
|
||||||
|
this.do_help('help', {}, [subcmd], callback);
|
||||||
|
return;
|
||||||
|
} else if (args.length < 2) {
|
||||||
|
callback(new errors.UsageError(format(
|
||||||
|
'incorrect number of args (%d)', args.length)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var log = this.log;
|
||||||
|
var tritonapi = this.top.tritonapi;
|
||||||
|
var updateIpOpts = {
|
||||||
|
id: args.shift(),
|
||||||
|
ip: args.shift()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (args.length === 0 && !opts.file) {
|
||||||
|
callback(new errors.UsageError(
|
||||||
|
'FIELD=VALUE arguments or "-f FILE" must be specified'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vasync.pipeline({arg: {cli: this.top}, funcs: [
|
||||||
|
common.cliSetupTritonApi,
|
||||||
|
|
||||||
|
function gatherDataArgs(ctx, next) {
|
||||||
|
if (opts.file) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ctx.data = common.objFromKeyValueArgs(args, {
|
||||||
|
disableDotted: true,
|
||||||
|
typeHintFromKey: UPDATE_NETWORK_IP_FIELDS
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
|
||||||
|
function gatherDataFile(ctx, next) {
|
||||||
|
if (!opts.file || opts.file === '-') {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var input = fs.readFileSync(opts.file, 'utf8');
|
||||||
|
try {
|
||||||
|
ctx.data = JSON.parse(input);
|
||||||
|
} catch (err) {
|
||||||
|
next(new errors.TritonError(format(
|
||||||
|
'invalid JSON for network IP update in "%s": %s',
|
||||||
|
opts.file, err)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
|
||||||
|
function gatherDataStdin(ctx, next) {
|
||||||
|
if (opts.file !== '-') {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
common.readStdin(function gotStdin(stdin) {
|
||||||
|
try {
|
||||||
|
ctx.data = JSON.parse(stdin);
|
||||||
|
} catch (err) {
|
||||||
|
log.trace({stdin: stdin},
|
||||||
|
'invalid network IP update JSON on stdin');
|
||||||
|
next(new errors.TritonError(format(
|
||||||
|
'invalid JSON for network IP update on stdin: %s',
|
||||||
|
err)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
function validateIt(ctx, next) {
|
||||||
|
try {
|
||||||
|
common.validateObject(ctx.data, UPDATE_NETWORK_IP_FIELDS);
|
||||||
|
} catch (e) {
|
||||||
|
next(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
|
||||||
|
function updateNetworkIP(ctx, next) {
|
||||||
|
Object.keys(ctx.data).forEach(function (key) {
|
||||||
|
updateIpOpts[key] = ctx.data[key];
|
||||||
|
});
|
||||||
|
|
||||||
|
tritonapi.updateNetworkIp(updateIpOpts, function (err, body, res) {
|
||||||
|
if (err) {
|
||||||
|
next(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.json) {
|
||||||
|
console.log(JSON.stringify(body));
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Updated network %s IP %s (fields: %s)',
|
||||||
|
updateIpOpts.id, updateIpOpts.ip,
|
||||||
|
Object.keys(ctx.data).join(', '));
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
]}, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
do_update.options = [
|
||||||
|
{
|
||||||
|
names: ['help', 'h'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'Show this help.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['file', 'f'],
|
||||||
|
type: 'string',
|
||||||
|
helpArg: 'FILE',
|
||||||
|
help: 'A file holding a JSON file of updates, or "-" to read ' +
|
||||||
|
'JSON from stdin.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['json', 'j'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'JSON output.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
do_update.synopses = [
|
||||||
|
'{{name}} {{cmd}} NETWORK IP [FIELD=VALUE ...]',
|
||||||
|
'{{name}} {{cmd}} NETWORK IP -f JSON-FILE'
|
||||||
|
];
|
||||||
|
|
||||||
|
do_update.help = [
|
||||||
|
/* BEGIN JSSTYLED */
|
||||||
|
'Update a network ip.',
|
||||||
|
'',
|
||||||
|
'{{usage}}',
|
||||||
|
'',
|
||||||
|
'{{options}}',
|
||||||
|
'Where NETWORK is a network id, and IP is the ip address you want to update.',
|
||||||
|
'',
|
||||||
|
'Updateable fields:',
|
||||||
|
' ' + Object.keys(UPDATE_NETWORK_IP_FIELDS).sort().map(function (field) {
|
||||||
|
return field + ' (' + UPDATE_NETWORK_IP_FIELDS[field] + ')';
|
||||||
|
}).join('\n '),
|
||||||
|
|
||||||
|
''
|
||||||
|
/* END JSSTYLED */
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
do_update.completionArgtypes = [
|
||||||
|
'tritonnetwork',
|
||||||
|
'tritonnetworkip',
|
||||||
|
'tritonupdatenetworkipfield'
|
||||||
|
];
|
||||||
|
|
||||||
|
module.exports = do_update;
|
@ -32,7 +32,8 @@ function IpCLI(top) {
|
|||||||
helpSubcmds: [
|
helpSubcmds: [
|
||||||
'help',
|
'help',
|
||||||
'list',
|
'list',
|
||||||
'get'
|
'get',
|
||||||
|
'update'
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -45,5 +46,6 @@ IpCLI.prototype.init = function init(opts, args, cb) {
|
|||||||
|
|
||||||
IpCLI.prototype.do_list = require('./do_list');
|
IpCLI.prototype.do_list = require('./do_list');
|
||||||
IpCLI.prototype.do_get = require('./do_get');
|
IpCLI.prototype.do_get = require('./do_get');
|
||||||
|
IpCLI.prototype.do_update = require('./do_update');
|
||||||
|
|
||||||
module.exports = IpCLI;
|
module.exports = IpCLI;
|
||||||
|
@ -890,7 +890,7 @@ TritonApi.prototype.getNetwork = function getNetwork(name, cb) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List an network's IPs.
|
* List a network's IPs.
|
||||||
*
|
*
|
||||||
* @param {String} name The network UUID, name, or short ID. Required.
|
* @param {String} name The network UUID, name, or short ID. Required.
|
||||||
* @param {Function} cb `function (err, ip, res)`
|
* @param {Function} cb `function (err, ip, res)`
|
||||||
@ -929,11 +929,11 @@ TritonApi.prototype.listNetworkIps = function listNetworkIps(name, cb) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an network IP.
|
* Get a network IP.
|
||||||
*
|
*
|
||||||
* @param {Object} opts
|
* @param {Object} opts
|
||||||
* - {String} opts.id The network UUID, name, or shortID. Required.
|
* - {String} id - The network UUID, name, or shortID. Required.
|
||||||
* - {String} opts.ip The IP. Required.
|
* - {String} ip - The IP. Required.
|
||||||
* @param {Function} cb `function (err, ip, res)`
|
* @param {Function} cb `function (err, ip, res)`
|
||||||
* On failure `err` is an error instance, else it is null.
|
* On failure `err` is an error instance, else it is null.
|
||||||
* On success: `ip` is an ip object
|
* On success: `ip` is an ip object
|
||||||
@ -970,6 +970,53 @@ TritonApi.prototype.getNetworkIp = function getNetworkIp(opts, cb) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modify a network IP.
|
||||||
|
*
|
||||||
|
* @param {Object} opts
|
||||||
|
* - {String} id - The network UUID, name, or shortID. Required.
|
||||||
|
* - {String} ip - The IP. Required.
|
||||||
|
* # The updateable fields
|
||||||
|
* - {Boolean} reserved - Reserve the IP. Required.
|
||||||
|
* @param {Function} cb `function (err, ip, res)`
|
||||||
|
* On failure `err` is an error instance, else it is null.
|
||||||
|
* On success: `obj` is an ip object
|
||||||
|
*/
|
||||||
|
TritonApi.prototype.updateNetworkIp = function updateNetworkIp(opts, cb) {
|
||||||
|
assert.object(opts, 'opts');
|
||||||
|
assert.string(opts.id, 'opts.id');
|
||||||
|
assert.string(opts.ip, 'opts.ip');
|
||||||
|
assert.bool(opts.reserved, 'opts.reserved');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
var res;
|
||||||
|
var body;
|
||||||
|
|
||||||
|
vasync.pipeline({arg: {client: self, id: opts.id}, funcs: [
|
||||||
|
_stepNetId,
|
||||||
|
|
||||||
|
function updateIp(arg, next) {
|
||||||
|
opts.id = arg.netId;
|
||||||
|
self.cloudapi.updateNetworkIp(opts, function (err, _body, _res) {
|
||||||
|
res = _res;
|
||||||
|
body = _body;
|
||||||
|
|
||||||
|
if (err && err.restCode === 'ResourceNotFound' &&
|
||||||
|
err.exitStatus !== 3) {
|
||||||
|
// Wrap with *our* ResourceNotFound for exitStatus=3.
|
||||||
|
err = new errors.ResourceNotFoundError(err,
|
||||||
|
format('IP %s was not found in network %s',
|
||||||
|
opts.ip, opts.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
next(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
]}, function (err) {
|
||||||
|
cb(err, body, res);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an instance.
|
* Get an instance.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "triton",
|
"name": "triton",
|
||||||
"description": "Joyent Triton CLI and client (https://www.joyent.com/triton)",
|
"description": "Joyent Triton CLI and client (https://www.joyent.com/triton)",
|
||||||
"version": "5.5.0",
|
"version": "5.6.0",
|
||||||
"author": "Joyent (joyent.com)",
|
"author": "Joyent (joyent.com)",
|
||||||
"homepage": "https://github.com/joyent/node-triton",
|
"homepage": "https://github.com/joyent/node-triton",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
Reference in New Issue
Block a user