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) | ||||
| 
 | ||||
| ## 5.6.0 | ||||
| 
 | ||||
| - [TRITON-30] Add UpdateNetworkIP to node-triton, e.g. | ||||
|   `triton network ip update` | ||||
| 
 | ||||
| ## 5.5.0 | ||||
| 
 | ||||
| - [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>
 | ||||
|  * | ||||
|  * @param {String} - UUID | ||||
|  * @param {String} id - The network UUID. Required. | ||||
|  * @param {Function} callback of the form `function (err, ips, res)` | ||||
|  */ | ||||
| CloudApi.prototype.listNetworkIps = function listNetworkIps(id, cb) { | ||||
| @ -406,13 +406,14 @@ CloudApi.prototype.listNetworkIps = function listNetworkIps(id, cb) { | ||||
|  * <http://apidocs.joyent.com/cloudapi/#GetNetworkIP>
 | ||||
|  * | ||||
|  * @param {Object} opts | ||||
|  *      - {String} opts.id The network UUID, name, or shortID. Required. | ||||
|  *      - {String} opts.ip The IP. Required. | ||||
|  *      - {String} id - The network UUID. Required. | ||||
|  *      - {String} ip - The IP. Required. | ||||
|  * @param {Function} callback of the form `function (err, ip, res)` | ||||
|  */ | ||||
| CloudApi.prototype.getNetworkIp = function getNetworkIp(opts, cb) { | ||||
|     assert.uuid(opts.id, 'id'); | ||||
|     assert.string(opts.ip, 'ip'); | ||||
|     assert.object(opts, 'opts'); | ||||
|     assert.uuid(opts.id, 'opts.id'); | ||||
|     assert.string(opts.ip, 'opts.ip'); | ||||
|     assert.func(cb, 'cb'); | ||||
| 
 | ||||
|     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
 | ||||
| 
 | ||||
| /** | ||||
|  | ||||
| @ -1301,7 +1301,91 @@ function argvFromLine(line) { | ||||
|     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
 | ||||
| 
 | ||||
| @ -1339,6 +1423,8 @@ module.exports = { | ||||
|     objFromKeyValueArgs: objFromKeyValueArgs, | ||||
|     argvFromLine: argvFromLine, | ||||
|     jsonPredFromKv: jsonPredFromKv, | ||||
|     monotonicTimeDiffMs: monotonicTimeDiffMs | ||||
|     monotonicTimeDiffMs: monotonicTimeDiffMs, | ||||
|     readStdin: readStdin, | ||||
|     validateObject: validateObject | ||||
| }; | ||||
| // 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: [ | ||||
|             'help', | ||||
|             '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_get = require('./do_get'); | ||||
| IpCLI.prototype.do_update = require('./do_update'); | ||||
| 
 | ||||
| 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 {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 | ||||
|  *      - {String} opts.id The network UUID, name, or shortID. Required. | ||||
|  *      - {String} opts.ip The IP. Required. | ||||
|  *      - {String} id - The network UUID, name, or shortID. Required. | ||||
|  *      - {String} ip - The IP. Required. | ||||
|  * @param {Function} cb `function (err, ip, res)` | ||||
|  *      On failure `err` is an error instance, else it is null. | ||||
|  *      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. | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| { | ||||
|   "name": "triton", | ||||
|   "description": "Joyent Triton CLI and client (https://www.joyent.com/triton)", | ||||
|   "version": "5.5.0", | ||||
|   "version": "5.6.0", | ||||
|   "author": "Joyent (joyent.com)", | ||||
|   "homepage": "https://github.com/joyent/node-triton", | ||||
|   "dependencies": { | ||||
|  | ||||
		Reference in New Issue
	
	Block a user