joyent/node-triton#54 a start at 'triton rbac info', add 'triton rbac instance-role-tags'
This commit is contained in:
		
							parent
							
								
									74b8f3e42e
								
							
						
					
					
						commit
						4e45e4061f
					
				| @ -18,6 +18,10 @@ | |||||||
|     - `triton rbac policy ...` to show, create, edit and delete policies. |     - `triton rbac policy ...` to show, create, edit and delete policies. | ||||||
|     - `triton rbac keys` to list all RBAC user SSH keys. |     - `triton rbac keys` to list all RBAC user SSH keys. | ||||||
|     - `triton rbac key ...` to show, create, edit and delete user keys. |     - `triton rbac key ...` to show, create, edit and delete user keys. | ||||||
|  |     - `triton rbac info` will dump a summary of the full current RBAC | ||||||
|  |       configuration. This command is still in development. | ||||||
|  |     - `triton rbac instance-role-tags ...` to list and manage role tags | ||||||
|  |       on an instance. | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| ## 2.1.4 | ## 2.1.4 | ||||||
|  | |||||||
| @ -224,7 +224,7 @@ CloudApi.prototype._request = function _request(options, callback) { | |||||||
|     assert.optionalObject(options.data, 'options.data'); |     assert.optionalObject(options.data, 'options.data'); | ||||||
| 
 | 
 | ||||||
|     var method = (options.method || 'GET').toLowerCase(); |     var method = (options.method || 'GET').toLowerCase(); | ||||||
|     assert.ok(['get', 'post', 'delete', 'head'].indexOf(method) >= 0, |     assert.ok(['get', 'post', 'put', 'delete', 'head'].indexOf(method) >= 0, | ||||||
|         'invalid method given'); |         'invalid method given'); | ||||||
|     switch (method) { |     switch (method) { | ||||||
|     case 'delete': |     case 'delete': | ||||||
| @ -1170,6 +1170,69 @@ CloudApi.prototype.deletePolicy = function deletePolicy(opts, cb) { | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  * <http://apidocs.joyent.com/cloudapi/#SetRoleTags>
 | ||||||
|  |  * Set RBAC role-tags on a resource. | ||||||
|  |  * | ||||||
|  |  * @param {Object} opts (object): | ||||||
|  |  *      - {String} resource (required) The resource URL. E.g. | ||||||
|  |  *        '/:account/machines/:uuid' to tag a particular machine instance. | ||||||
|  |  *      - {Array} roleTags (required) the array of role tags to set. Each | ||||||
|  |  *        role tag string is the name of a RBAC role. See `ListRoles`. | ||||||
|  |  * @param {Function} cb of the form `function (err, body, res)` | ||||||
|  |  *      Where `body` is of the form `{name: <resource url>,
 | ||||||
|  |  *      'role-tag': <array of added role tags>}`.
 | ||||||
|  |  * @throws {AssertionError, TypeError} on invalid inputs | ||||||
|  |  */ | ||||||
|  | CloudApi.prototype.setRoleTags = function setRoleTags(opts, cb) { | ||||||
|  |     assert.object(opts, 'opts'); | ||||||
|  |     assert.string(opts.resource, 'opts.resource'); | ||||||
|  |     assert.arrayOfString(opts.roleTags, 'opts.roleTags'); | ||||||
|  |     assert.func(cb, 'cb'); | ||||||
|  | 
 | ||||||
|  |     // Validate `resource`.
 | ||||||
|  |     // XXX Do we need to massage '/my' to '/:account' as old cloudapi.js does?
 | ||||||
|  |     var resourceRe = new RegExp('^/[^/]{2,}/[^/]+'); | ||||||
|  |     if (! resourceRe.test(opts.resource)) { | ||||||
|  |         throw new TypeError(format('invalid resource "%s": must match ' + | ||||||
|  |             '"/:account/:type..."', opts.resource)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     var validResources = [ | ||||||
|  |         'machines', | ||||||
|  |         'packages', | ||||||
|  |         'images', | ||||||
|  |         'fwrules', | ||||||
|  |         'networks', | ||||||
|  |         // TODO: validate/test role tags on these rbac resources
 | ||||||
|  |         'users', | ||||||
|  |         'roles', | ||||||
|  |         'policies', | ||||||
|  |         // TODO: validate, test
 | ||||||
|  |         'keys', | ||||||
|  |         'datacenters', | ||||||
|  |         'analytics', | ||||||
|  |         'instrumentations' | ||||||
|  |     ]; | ||||||
|  |     var parts = opts.resource.split('/'); | ||||||
|  |     if (validResources.indexOf(parts[2]) === -1) { | ||||||
|  |         throw new TypeError(format('invalid resource "%s": resource type ' + | ||||||
|  |             'must be one of: %s', opts.resource, validResources.join(', '))); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     this._request({ | ||||||
|  |         method: 'PUT', | ||||||
|  |         path: opts.resource, | ||||||
|  |         data: { | ||||||
|  |             'role-tag': opts.roleTags | ||||||
|  |         } | ||||||
|  |     }, function (err, req, res, body) { | ||||||
|  |         cb(err, body, res); | ||||||
|  |     }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| // --- Exports
 | // --- Exports
 | ||||||
| 
 | 
 | ||||||
| module.exports.createClient = function (options) { | module.exports.createClient = function (options) { | ||||||
|  | |||||||
							
								
								
									
										272
									
								
								lib/do_rbac/do_info.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										272
									
								
								lib/do_rbac/do_info.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,272 @@ | |||||||
|  | /* | ||||||
|  |  * 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 2015 Joyent, Inc. | ||||||
|  |  * | ||||||
|  |  * `triton rbac info ...` | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | /* BEGIN JSSTYLED */ | ||||||
|  | /* | ||||||
|  | 
 | ||||||
|  | Sample output for development/discussion: | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | users ($numUsers): | ||||||
|  |     bob ($fullname, **no ssh keys**): roles web* | ||||||
|  |     carl (Carl Fogel): roles eng, operator*, cibot*, monbot* | ||||||
|  |     ... | ||||||
|  | roles ($numRoles): | ||||||
|  |     eng: policies write, read                   # include users here? | ||||||
|  |     ops: policies delete, read, write | ||||||
|  |     support: policies read | ||||||
|  | policies ($numPolicies): | ||||||
|  |     delete ($desc): | ||||||
|  |         can deletemachine | ||||||
|  |     read (cloudapi read-only actions): | ||||||
|  |         can listmachines, getmachine and listimages | ||||||
|  |     write (cloudapi write (non-delete) actions): | ||||||
|  |         can createmachine, updatemachine, stopmachine and startmachine | ||||||
|  | resources:   # or call this 'resources'? role-tags? | ||||||
|  |     # some dump of all resources (perhaps not default to *all*) and their | ||||||
|  |     # role-tags | ||||||
|  |     instance foo0 ($uuid): role-tags eng | ||||||
|  |     image bar@1.2.3 ($uuid): role-tags ops | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Ideas: | ||||||
|  | - red warning about users with no keys | ||||||
|  | - `triton rbac info -u bob`   Show everything from bob's p.o.v. | ||||||
|  | - `triton rbac info -r readonly`   Show everything from this role's p.o.v. | ||||||
|  |     `... --instance foo0`, etc. | ||||||
|  | - `-t|--role-tags` to include the role tag info. Perhaps with arg for which? | ||||||
|  |   E.g. do we traverse all machines, images, networks? That could too much... | ||||||
|  |   Might need cloudapi support for returning those optionally. | ||||||
|  |     ListImages?fields=*,role_tags   # perhaps don't support '*' | ||||||
|  | */ | ||||||
|  | /* END JSSTYLED */ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | var assert = require('assert-plus'); | ||||||
|  | var format = require('util').format; | ||||||
|  | var tabula = require('tabula'); | ||||||
|  | var vasync = require('vasync'); | ||||||
|  | 
 | ||||||
|  | var common = require('../common'); | ||||||
|  | var errors = require('../errors'); | ||||||
|  | 
 | ||||||
|  | var ansiStylize = common.ansiStylize; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  |  * Gather RBAC users, policies, roles and add those to the given `ctx` object. | ||||||
|  |  */ | ||||||
|  | function gatherRbacBasicInfo(ctx, cb) { | ||||||
|  |     assert.object(ctx.cloudapi, 'ctx.cloudapi'); | ||||||
|  |     assert.func(cb, 'cb'); | ||||||
|  | 
 | ||||||
|  |     vasync.parallel({funcs: [ | ||||||
|  |         function listUsers(next) { | ||||||
|  |             ctx.cloudapi.listUsers(function (err, users) { | ||||||
|  |                 ctx.users = users; | ||||||
|  |                 next(err); | ||||||
|  |             }); | ||||||
|  |         }, | ||||||
|  |         function listPolicies(next) { | ||||||
|  |             ctx.cloudapi.listPolicies(function (err, policies) { | ||||||
|  |                 ctx.policies = policies; | ||||||
|  |                 next(err); | ||||||
|  |             }); | ||||||
|  |         }, | ||||||
|  |         function listRoles(next) { | ||||||
|  |             ctx.cloudapi.listRoles(function (err, roles) { | ||||||
|  |                 ctx.roles = roles; | ||||||
|  |                 next(err); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     ]}, cb); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | function do_info(subcmd, opts, args, cb) { | ||||||
|  |     if (opts.help) { | ||||||
|  |         this.do_help('help', {}, [subcmd], cb); | ||||||
|  |         return; | ||||||
|  |     } else if (args.length !== 0) { | ||||||
|  |         cb(new errors.UsageError('invalid args: ' + args)); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     var log = this.log; | ||||||
|  | 
 | ||||||
|  |     var context = { | ||||||
|  |         tritonapi: this.top.tritonapi, | ||||||
|  |         cloudapi: this.top.tritonapi.cloudapi | ||||||
|  |     }; | ||||||
|  |     vasync.pipeline({arg: context, funcs: [ | ||||||
|  |         gatherRbacBasicInfo, | ||||||
|  |         function gatherUserKeys(ctx, next) { | ||||||
|  |             if (!opts.all) { | ||||||
|  |                 next(); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             // XXX Q! or concurrency forEachParallel
 | ||||||
|  |             vasync.forEachParallel({ | ||||||
|  |                 inputs: ctx.users, | ||||||
|  |                 func: function oneUser(user, nextUser) { | ||||||
|  |                     ctx.cloudapi.listUserKeys({userId: user.id}, | ||||||
|  |                             function (err, userKeys) { | ||||||
|  |                         user.keys = userKeys; | ||||||
|  |                         nextUser(err); | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |             }, next); | ||||||
|  |         }, | ||||||
|  |         function fillInUserRoles(ctx, next) { | ||||||
|  |             var i; | ||||||
|  |             var userFromLogin = {}; | ||||||
|  |             for (i = 0; i < ctx.users.length; i++) { | ||||||
|  |                 var user = ctx.users[i]; | ||||||
|  |                 user.default_roles = []; | ||||||
|  |                 user.roles = []; | ||||||
|  |                 userFromLogin[user.login] = user; | ||||||
|  |             } | ||||||
|  |             for (i = 0; i < ctx.roles.length; i++) { | ||||||
|  |                 var role = ctx.roles[i]; | ||||||
|  |                 role.default_members.forEach(function (login) { | ||||||
|  |                     userFromLogin[login].default_roles.push(role.name); | ||||||
|  |                 }); | ||||||
|  |                 role.members.forEach(function (login) { | ||||||
|  |                     userFromLogin[login].roles.push(role.name); | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |             next(); | ||||||
|  |         }, | ||||||
|  |         function printInfo(ctx, next) { | ||||||
|  |             var i; | ||||||
|  |             log.trace({ | ||||||
|  |                 users: ctx.users, | ||||||
|  |                 policies: ctx.policies, | ||||||
|  |                 roles: ctx.roles | ||||||
|  |             }, 'rbac info data'); | ||||||
|  | 
 | ||||||
|  |             console.log('users (%d):', ctx.users.length); | ||||||
|  |             tabula.sortArrayOfObjects(ctx.users, ['name']); | ||||||
|  |             for (i = 0; i < ctx.users.length; i++) { | ||||||
|  |                 var user = ctx.users[i]; | ||||||
|  | 
 | ||||||
|  |                 var userExtra = []; | ||||||
|  |                 if (user.firstName || user.lastName) { | ||||||
|  |                     userExtra.push(((user.firstName || '') + ' ' + | ||||||
|  |                         (user.lastName || '')).trim()); | ||||||
|  |                 } | ||||||
|  |                 if (user.keys && user.keys.length === 0) { | ||||||
|  |                     userExtra.push(ansiStylize('no ssh keys', 'red')); | ||||||
|  |                 } | ||||||
|  |                 if (userExtra.length > 0) { | ||||||
|  |                     userExtra = format(' (%s)', userExtra.join(', ')); | ||||||
|  |                 } else { | ||||||
|  |                     userExtra = ''; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 var roleInfo = []; | ||||||
|  |                 user.default_roles.sort(); | ||||||
|  |                 user.roles.sort(); | ||||||
|  |                 var roleSeen = {}; | ||||||
|  |                 user.default_roles.forEach(function (r) { | ||||||
|  |                     roleSeen[r] = true; | ||||||
|  |                     roleInfo.push(r); | ||||||
|  |                 }); | ||||||
|  |                 user.roles.forEach(function (r) { | ||||||
|  |                     if (!roleSeen[r]) { | ||||||
|  |                         roleInfo.push(r + '*'); // marker for non-default role
 | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  |                 if (roleInfo.length === 1) { | ||||||
|  |                     roleInfo = 'role ' + roleInfo.join(', '); | ||||||
|  |                 } else if (roleInfo.length > 0) { | ||||||
|  |                     roleInfo = 'roles ' + roleInfo.join(', '); | ||||||
|  |                 } else { | ||||||
|  |                     roleInfo = ansiStylize('no roles', 'red'); | ||||||
|  |                 } | ||||||
|  |                 console.log('    %s%s: %s', ansiStylize(user.login, 'bold'), | ||||||
|  |                     userExtra, roleInfo); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             console.log('roles (%d):', ctx.roles.length); | ||||||
|  |             tabula.sortArrayOfObjects(ctx.roles, ['name']); | ||||||
|  |             for (i = 0; i < ctx.roles.length; i++) { | ||||||
|  |                 var role = ctx.roles[i]; | ||||||
|  | 
 | ||||||
|  |                 var policyInfo; | ||||||
|  |                 if (role.policies.length === 1) { | ||||||
|  |                     policyInfo = 'policy ' + role.policies.join(', '); | ||||||
|  |                 } else if (role.policies.length > 0) { | ||||||
|  |                     policyInfo = 'policies ' + role.policies.join(', '); | ||||||
|  |                 } else { | ||||||
|  |                     policyInfo = ansiStylize('no policies', 'red'); | ||||||
|  |                 } | ||||||
|  |                 console.log('    %s: %s', ansiStylize(role.name, 'bold'), | ||||||
|  |                     policyInfo); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             console.log('policies (%d):', ctx.policies.length); | ||||||
|  |             tabula.sortArrayOfObjects(ctx.policies, ['name']); | ||||||
|  |             for (i = 0; i < ctx.policies.length; i++) { | ||||||
|  |                 var policy = ctx.policies[i]; | ||||||
|  |                 var noRules = ''; | ||||||
|  |                 if (policy.rules.length === 0) { | ||||||
|  |                     noRules = ' ' + ansiStylize('no rules', 'red'); | ||||||
|  |                 } | ||||||
|  |                 if (policy.description) { | ||||||
|  |                     console.log('    %s (%s) rules:%s', | ||||||
|  |                         ansiStylize(policy.name, 'bold'), | ||||||
|  |                         policy.description, noRules); | ||||||
|  |                 } else { | ||||||
|  |                     console.log('    %s rules:%s', | ||||||
|  |                         ansiStylize(policy.name, 'bold'), noRules); | ||||||
|  |                 } | ||||||
|  |                 policy.rules.forEach(function (r) { | ||||||
|  |                     console.log('        %s', r); | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             next(); | ||||||
|  |         } | ||||||
|  |     ]}, function (err) { | ||||||
|  |         cb(err); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | do_info.options = [ | ||||||
|  |     { | ||||||
|  |         names: ['help', 'h'], | ||||||
|  |         type: 'bool', | ||||||
|  |         help: 'Show this help.' | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         names: ['all', 'a'], | ||||||
|  |         type: 'bool', | ||||||
|  |         help: 'Include all info for a more full report. This requires more ' + | ||||||
|  |             'work to gather all info.' | ||||||
|  |     } | ||||||
|  | ]; | ||||||
|  | 
 | ||||||
|  | do_info.help = ( | ||||||
|  |     /* BEGIN JSSTYLED */ | ||||||
|  |     'Print an account RBAC summary.\n' + | ||||||
|  |     '\n' + | ||||||
|  |     'Usage:\n' + | ||||||
|  |     '    {{name}} info [<options>]\n' + | ||||||
|  |     '\n' + | ||||||
|  |     '{{options}}' | ||||||
|  |     /* END JSSTYLED */ | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | module.exports = do_info; | ||||||
							
								
								
									
										582
									
								
								lib/do_rbac/do_role_tags.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										582
									
								
								lib/do_rbac/do_role_tags.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,582 @@ | |||||||
|  | /* | ||||||
|  |  * 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 2015 Joyent, Inc. | ||||||
|  |  * | ||||||
|  |  * `triton rbac role-tags ...`    # hidden lower-level command | ||||||
|  |  * `triton rbac instance-role-tags ...` | ||||||
|  |  * `triton rbac image-role-tags ...` | ||||||
|  |  * `triton rbac package-role-tags ...` | ||||||
|  |  * `triton rbac network-role-tags ...` | ||||||
|  |  * etc. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | var assert = require('assert-plus'); | ||||||
|  | var format = require('util').format; | ||||||
|  | var fs = require('fs'); | ||||||
|  | var strsplit = require('strsplit'); | ||||||
|  | var vasync = require('vasync'); | ||||||
|  | 
 | ||||||
|  | var common = require('../common'); | ||||||
|  | var errors = require('../errors'); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // ---- internal support stuff
 | ||||||
|  | 
 | ||||||
|  | function _listRoleTags(opts, cb) { | ||||||
|  |     assert.object(opts.cli, 'opts.cli'); | ||||||
|  |     assert.string(opts.resourceId, 'opts.resourceId'); | ||||||
|  |     assert.optionalBool(opts.json, 'opts.json'); | ||||||
|  |     assert.func(cb, 'cb'); | ||||||
|  |     var cli = opts.cli; | ||||||
|  | 
 | ||||||
|  |     cli.tritonapi.getInstanceRoleTags(opts.resourceId, | ||||||
|  |             function (err, roleTags) { | ||||||
|  |         if (err) { | ||||||
|  |             cb(err); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (opts.json) { | ||||||
|  |             console.log(JSON.stringify(roleTags)); | ||||||
|  |         } else { | ||||||
|  |             roleTags.forEach(function (r) { | ||||||
|  |                 console.log(r); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |         cb(); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function _addRoleTags(opts, cb) { | ||||||
|  |     assert.object(opts.cli, 'opts.cli'); | ||||||
|  |     assert.string(opts.resourceId, 'opts.resourceId'); | ||||||
|  |     assert.arrayOfString(opts.roleTags, 'opts.roleTags'); | ||||||
|  |     assert.func(cb, 'cb'); | ||||||
|  |     var cli = opts.cli; | ||||||
|  |     var log = cli.log; | ||||||
|  | 
 | ||||||
|  |     vasync.pipeline({arg: {}, funcs: [ | ||||||
|  |         function getCurrRoleTags(ctx, next) { | ||||||
|  |             cli.tritonapi.getInstanceRoleTags(opts.resourceId, | ||||||
|  |                     function (err, roleTags, inst) { | ||||||
|  |                 if (err) { | ||||||
|  |                     next(err); | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |                 ctx.roleTags = roleTags; | ||||||
|  |                 ctx.inst = inst; | ||||||
|  |                 log.trace({inst: inst, roleTags: roleTags}, 'curr role tags'); | ||||||
|  |                 next(); | ||||||
|  |             }); | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         function addRoleTags(ctx, next) { | ||||||
|  |             var adding = []; | ||||||
|  |             for (var i = 0; i < opts.roleTags.length; i++) { | ||||||
|  |                 var r = opts.roleTags[i]; | ||||||
|  |                 if (ctx.roleTags.indexOf(r) === -1) { | ||||||
|  |                     ctx.roleTags.push(r); | ||||||
|  |                     adding.push(r); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             if (adding.length === 0) { | ||||||
|  |                 next(); | ||||||
|  |                 return; | ||||||
|  |             } else { | ||||||
|  |                 console.log('Adding %d role tag%s (%s) to instance "%s"', | ||||||
|  |                     adding.length, adding.length === 1 ? '' : 's', | ||||||
|  |                     adding.join(', '), ctx.inst.name); | ||||||
|  |             } | ||||||
|  |             cli.tritonapi.cloudapi.setRoleTags({ | ||||||
|  |                 resource: _resourceUrlFromId( | ||||||
|  |                     cli.tritonapi.profile.account, ctx.inst.id), | ||||||
|  |                 roleTags: ctx.roleTags | ||||||
|  |             }, next); | ||||||
|  |         } | ||||||
|  |     ]}, function (err) { | ||||||
|  |         cb(err); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // TODO: resource URL should be in tritonapi.js,
 | ||||||
|  | //      E.g. perhaps `TritonApi.setInstanceRoleTags`?
 | ||||||
|  | function _resourceUrlFromId(account, id) { | ||||||
|  |     return format('/%s/machines/%s', account, id); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | function _reprFromRoleTags(roleTags) { | ||||||
|  |     assert.arrayOfString(roleTags, 'roleTags'); | ||||||
|  | 
 | ||||||
|  |     if (roleTags.length === 0) { | ||||||
|  |         return ''; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Make this somewhat canonical by sorting.
 | ||||||
|  |     roleTags.sort(); | ||||||
|  |     return roleTags.join('\n') + '\n'; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | function _roleTagsFromRepr(repr) { | ||||||
|  |     assert.string(repr, 'repr'); | ||||||
|  | 
 | ||||||
|  |     var roleTags = []; | ||||||
|  |     var lines = repr.split(/\n/g); | ||||||
|  |     lines.forEach(function (line) { | ||||||
|  |         var commentIdx = line.indexOf('#'); | ||||||
|  |         if (commentIdx !== -1) { | ||||||
|  |             line = line.slice(0, commentIdx); | ||||||
|  |         } | ||||||
|  |         line = line.trim(); | ||||||
|  |         if (!line) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         roleTags.push(line); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     roleTags.sort(); | ||||||
|  |     return roleTags; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | function _editRoleTags(opts, cb) { | ||||||
|  |     assert.object(opts.cli, 'opts.cli'); | ||||||
|  |     assert.string(opts.resourceId, 'opts.resourceId'); | ||||||
|  |     assert.func(cb, 'cb'); | ||||||
|  |     var cli = opts.cli; | ||||||
|  | 
 | ||||||
|  |     var account = cli.tritonapi.profile.account; | ||||||
|  |     var id; | ||||||
|  |     var roleTags; | ||||||
|  |     var filename; | ||||||
|  |     var origText; | ||||||
|  | 
 | ||||||
|  |     function offerRetry(afterText) { | ||||||
|  |         common.promptEnter( | ||||||
|  |             'Press <Enter> to re-edit, Ctrl+C to abort.', | ||||||
|  |             function (aborted) { | ||||||
|  |                 if (aborted) { | ||||||
|  |                     console.log('\nAborting. No change made.'); | ||||||
|  |                     cb(); | ||||||
|  |                 } else { | ||||||
|  |                     editAttempt(afterText); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     function editAttempt(text) { | ||||||
|  |         common.editInEditor({ | ||||||
|  |             text: text, | ||||||
|  |             filename: filename | ||||||
|  |         }, function (err, afterText, changed) { | ||||||
|  |             if (err) { | ||||||
|  |                 return cb(new errors.TritonError(err)); | ||||||
|  |             } | ||||||
|  |             // We don't use this `changed` in case it is a second attempt.
 | ||||||
|  | 
 | ||||||
|  |             try { | ||||||
|  |                 var edited = _roleTagsFromRepr(afterText); | ||||||
|  | 
 | ||||||
|  |                 if (_reprFromRoleTags(edited) === origText) { | ||||||
|  |                     // This repr is the closest to a canonical form we have.
 | ||||||
|  |                     console.log('No change'); | ||||||
|  |                     cb(); | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |             } catch (textErr) { | ||||||
|  |                 console.error('Error with your changes: %s', textErr); | ||||||
|  |                 offerRetry(afterText); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Save changes.
 | ||||||
|  |             cli.tritonapi.cloudapi.setRoleTags({ | ||||||
|  |                 resource: _resourceUrlFromId(account, id), | ||||||
|  |                 roleTags: edited | ||||||
|  |             }, function (setErr) { | ||||||
|  |                 if (setErr) { | ||||||
|  |                     console.error('Error updating role tags with ' + | ||||||
|  |                         'your changes: %s', setErr); | ||||||
|  |                     offerRetry(afterText); | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |                 console.log('Edited role tags on instance "%s"', | ||||||
|  |                     opts.resourceId); | ||||||
|  |                 cb(); | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     cli.tritonapi.getInstanceRoleTags(opts.resourceId, | ||||||
|  |             function (err, roleTags_, inst) { | ||||||
|  |         if (err) { | ||||||
|  |             cb(err); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         id = inst.id; | ||||||
|  |         roleTags = roleTags_; | ||||||
|  |         filename = format('%s-inst-%s-roleTags.txt', | ||||||
|  |             cli.tritonapi.profile.account, | ||||||
|  |             opts.resourceId); | ||||||
|  |         origText = _reprFromRoleTags(roleTags); | ||||||
|  |         editAttempt(origText); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | function _setRoleTags(opts, cb) { | ||||||
|  |     assert.object(opts.cli, 'opts.cli'); | ||||||
|  |     assert.string(opts.resourceId, 'opts.resourceId'); | ||||||
|  |     assert.arrayOfString(opts.roleTags, 'opts.roleTags'); | ||||||
|  |     assert.optionalBool(opts.yes, 'opts.yes'); | ||||||
|  |     assert.func(cb, 'cb'); | ||||||
|  |     var cli = opts.cli; | ||||||
|  | 
 | ||||||
|  |     vasync.pipeline({arg: {}, funcs: [ | ||||||
|  |         // TODO: consider shorter path if the instance UUID is given
 | ||||||
|  |         //       (but what if the instance has a UUID for an *alias*)?
 | ||||||
|  |         function getResource(ctx, next) { | ||||||
|  |             cli.tritonapi.getInstance(opts.resourceId, function (err, inst) { | ||||||
|  |                 if (err) { | ||||||
|  |                     next(err); | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |                 ctx.inst = inst; | ||||||
|  |                 next(); | ||||||
|  |             }); | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         function confirm(ctx, next) { | ||||||
|  |             if (opts.yes) { | ||||||
|  |                 return next(); | ||||||
|  |             } | ||||||
|  |             var msg = format('Set role tags on instance "%s"? [y/n] ', | ||||||
|  |                 ctx.inst.name); | ||||||
|  |             common.promptYesNo({msg: msg}, function (answer) { | ||||||
|  |                 if (answer !== 'y') { | ||||||
|  |                     console.error('Aborting'); | ||||||
|  |                     next(true); // early abort signal
 | ||||||
|  |                 } else { | ||||||
|  |                     next(); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         function setThem(ctx, next) { | ||||||
|  |             console.log('Setting role tags on instance "%s"', ctx.inst.name); | ||||||
|  |             cli.tritonapi.cloudapi.setRoleTags({ | ||||||
|  |                 resource: _resourceUrlFromId( | ||||||
|  |                     cli.tritonapi.profile.account, ctx.inst.id), | ||||||
|  |                 roleTags: opts.roleTags | ||||||
|  |             }, next); | ||||||
|  |         } | ||||||
|  |     ]}, function (err) { | ||||||
|  |         cb(err); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | function _deleteRoleTags(opts, cb) { | ||||||
|  |     assert.object(opts.cli, 'opts.cli'); | ||||||
|  |     assert.string(opts.resourceId, 'opts.resourceId'); | ||||||
|  |     assert.arrayOfString(opts.roleTags, 'opts.roleTags'); | ||||||
|  |     assert.func(cb, 'cb'); | ||||||
|  |     var cli = opts.cli; | ||||||
|  |     var log = cli.log; | ||||||
|  | 
 | ||||||
|  |     vasync.pipeline({arg: {}, funcs: [ | ||||||
|  |         function getCurrRoleTags(ctx, next) { | ||||||
|  |             cli.tritonapi.getInstanceRoleTags(opts.resourceId, | ||||||
|  |                     function (err, roleTags, inst) { | ||||||
|  |                 if (err) { | ||||||
|  |                     next(err); | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |                 ctx.roleTags = roleTags; | ||||||
|  |                 ctx.inst = inst; | ||||||
|  |                 log.trace({inst: inst, roleTags: roleTags}, 'curr role tags'); | ||||||
|  |                 next(); | ||||||
|  |             }); | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         function determineToDelete(ctx, next) { | ||||||
|  |             ctx.toDelete = []; | ||||||
|  |             ctx.roleTagsToKeep = []; | ||||||
|  |             for (var i = 0; i < ctx.roleTags.length; i++) { | ||||||
|  |                 var r = ctx.roleTags[i]; | ||||||
|  |                 if (opts.roleTags.indexOf(r) !== -1) { | ||||||
|  |                     ctx.toDelete.push(r); | ||||||
|  |                 } else { | ||||||
|  |                     ctx.roleTagsToKeep.push(r); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             next(); | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         function confirm(ctx, next) { | ||||||
|  |             if (ctx.toDelete.length === 0 || opts.yes) { | ||||||
|  |                 return next(); | ||||||
|  |             } | ||||||
|  |             var msg = format( | ||||||
|  |                 'Delete %d role tag%s (%s) from instance "%s"? [y/n] ', | ||||||
|  |                 ctx.toDelete.length, ctx.toDelete.length === 1 ? '' : 's', | ||||||
|  |                 ctx.toDelete.join(', '), ctx.inst.name); | ||||||
|  |             common.promptYesNo({msg: msg}, function (answer) { | ||||||
|  |                 if (answer !== 'y') { | ||||||
|  |                     console.error('Aborting'); | ||||||
|  |                     next(true); // early abort signal
 | ||||||
|  |                 } else { | ||||||
|  |                     next(); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         function deleteRoleTags(ctx, next) { | ||||||
|  |             if (ctx.toDelete.length === 0) { | ||||||
|  |                 next(); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             console.log('Deleting %d role tag%s (%s) from instance "%s"', | ||||||
|  |                 ctx.toDelete.length, ctx.toDelete.length === 1 ? '' : 's', | ||||||
|  |                 ctx.toDelete.join(', '), ctx.inst.name); | ||||||
|  |             cli.tritonapi.cloudapi.setRoleTags({ | ||||||
|  |                 resource: _resourceUrlFromId( | ||||||
|  |                     cli.tritonapi.profile.account, ctx.inst.id), | ||||||
|  |                 roleTags: ctx.roleTagsToKeep | ||||||
|  |             }, next); | ||||||
|  |         } | ||||||
|  |     ]}, function (err) { | ||||||
|  |         cb(err); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | function _deleteAllRoleTags(opts, cb) { | ||||||
|  |     assert.object(opts.cli, 'opts.cli'); | ||||||
|  |     assert.string(opts.resourceId, 'opts.resourceId'); | ||||||
|  |     assert.func(cb, 'cb'); | ||||||
|  |     var cli = opts.cli; | ||||||
|  | 
 | ||||||
|  |     vasync.pipeline({arg: {}, funcs: [ | ||||||
|  |         // TODO: consider shorter path if the instance UUID is given
 | ||||||
|  |         //       (but what if the instance has a UUID for an *alias*)?
 | ||||||
|  |         function getResource(ctx, next) { | ||||||
|  |             cli.tritonapi.getInstance(opts.resourceId, function (err, inst) { | ||||||
|  |                 if (err) { | ||||||
|  |                     next(err); | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |                 ctx.inst = inst; | ||||||
|  |                 next(); | ||||||
|  |             }); | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         function confirm(ctx, next) { | ||||||
|  |             if (opts.yes) { | ||||||
|  |                 return next(); | ||||||
|  |             } | ||||||
|  |             var msg = format('Delete all role tags from instance "%s"? [y/n] ', | ||||||
|  |                 ctx.inst.name); | ||||||
|  |             common.promptYesNo({msg: msg}, function (answer) { | ||||||
|  |                 if (answer !== 'y') { | ||||||
|  |                     console.error('Aborting'); | ||||||
|  |                     next(true); // early abort signal
 | ||||||
|  |                 } else { | ||||||
|  |                     next(); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         }, | ||||||
|  | 
 | ||||||
|  |         function deleteAllRoleTags(ctx, next) { | ||||||
|  |             console.log('Deleting all role tags from instance "%s"', | ||||||
|  |                 ctx.inst.name); | ||||||
|  |             cli.tritonapi.cloudapi.setRoleTags({ | ||||||
|  |                 resource: _resourceUrlFromId( | ||||||
|  |                     cli.tritonapi.profile.account, ctx.inst.id), | ||||||
|  |                 roleTags: [] | ||||||
|  |             }, next); | ||||||
|  |         } | ||||||
|  |     ]}, function (err) { | ||||||
|  |         cb(err); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | function _roleTagsFromArrayOfString(arr) { | ||||||
|  |     assert.arrayOfString(arr, arr); | ||||||
|  |     var allRoleTags = []; | ||||||
|  |     for (var i = 0; i < arr.length; i++) { | ||||||
|  |         var roleTags = arr[i] | ||||||
|  |             /* JSSTYLED */ | ||||||
|  |             .split(/\s*,\s*/) | ||||||
|  |             .filter(function (r) { return r.trim(); }); | ||||||
|  |         allRoleTags = allRoleTags.concat(roleTags); | ||||||
|  |     } | ||||||
|  |     return allRoleTags; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // ---- `triton rbac instance-role-tags`
 | ||||||
|  | 
 | ||||||
|  | function do_instance_role_tags(subcmd, opts, args, cb) { | ||||||
|  |     if (opts.help) { | ||||||
|  |         this.do_help('help', {}, [subcmd], cb); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Which action?
 | ||||||
|  |     var actions = []; | ||||||
|  |     if (opts.add) { actions.push('add'); } | ||||||
|  |     if (opts.edit) { actions.push('edit'); } | ||||||
|  |     if (opts.set) { actions.push('set'); } | ||||||
|  |     if (opts['delete']) { actions.push('delete'); } | ||||||
|  |     if (opts.delete_all) { actions.push('deleteAll'); } | ||||||
|  |     var action; | ||||||
|  |     if (actions.length === 0) { | ||||||
|  |         action = 'list'; | ||||||
|  |     } else if (actions.length > 1) { | ||||||
|  |         return cb(new errors.UsageError( | ||||||
|  |             'only one action option may be used at once')); | ||||||
|  |     } else { | ||||||
|  |         action = actions[0]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Arg count validation.
 | ||||||
|  |     if (args.length === 0) { | ||||||
|  |         return cb(new errors.UsageError('INST argument is required')); | ||||||
|  |     } else if (args.length > 1) { | ||||||
|  |         return cb(new errors.UsageError('too many arguments')); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     switch (action) { | ||||||
|  |     case 'list': | ||||||
|  |         _listRoleTags({ | ||||||
|  |             cli: this.top, | ||||||
|  |             resourceId: args[0], | ||||||
|  |             json: opts.json | ||||||
|  |         }, cb); | ||||||
|  |         break; | ||||||
|  |     case 'add': | ||||||
|  |         _addRoleTags({ | ||||||
|  |             cli: this.top, | ||||||
|  |             resourceId: args[0], | ||||||
|  |             roleTags: _roleTagsFromArrayOfString(opts.add) | ||||||
|  |         }, cb); | ||||||
|  |         break; | ||||||
|  |     case 'edit': | ||||||
|  |         _editRoleTags({ | ||||||
|  |             cli: this.top, | ||||||
|  |             resourceId: args[0] | ||||||
|  |         }, cb); | ||||||
|  |         break; | ||||||
|  |     case 'set': | ||||||
|  |         _setRoleTags({ | ||||||
|  |             cli: this.top, | ||||||
|  |             resourceId: args[0], | ||||||
|  |             roleTags: _roleTagsFromArrayOfString(opts.set), | ||||||
|  |             yes: opts.yes | ||||||
|  |         }, cb); | ||||||
|  |         break; | ||||||
|  |     case 'delete': | ||||||
|  |         _deleteRoleTags({ | ||||||
|  |             cli: this.top, | ||||||
|  |             resourceId: args[0], | ||||||
|  |             roleTags: _roleTagsFromArrayOfString(opts['delete']), | ||||||
|  |             yes: opts.yes | ||||||
|  |         }, cb); | ||||||
|  |         break; | ||||||
|  |     case 'deleteAll': | ||||||
|  |         _deleteAllRoleTags({ | ||||||
|  |             cli: this.top, | ||||||
|  |             resourceId: args[0], | ||||||
|  |             yes: opts.yes | ||||||
|  |         }, cb); | ||||||
|  |         break; | ||||||
|  |     default: | ||||||
|  |         return cb(new errors.InternalError('unknown action: ' + action)); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | do_instance_role_tags.options = [ | ||||||
|  |     { | ||||||
|  |         names: ['help', 'h'], | ||||||
|  |         type: 'bool', | ||||||
|  |         help: 'Show this help.' | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         names: ['json', 'j'], | ||||||
|  |         type: 'bool', | ||||||
|  |         help: 'JSON stream output.' | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         names: ['yes', 'y'], | ||||||
|  |         type: 'bool', | ||||||
|  |         help: 'Answer yes to confirmations, e.g. confirmation of deletion.' | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         group: 'Action Options' | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         names: ['add', 'a'], | ||||||
|  |         type: 'arrayOfString', | ||||||
|  |         helpArg: 'ROLE[,ROLE...]', | ||||||
|  |         help: 'Add the given role tags. Can be specified multiple times.' | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         names: ['set', 's'], | ||||||
|  |         type: 'arrayOfString', | ||||||
|  |         helpArg: 'ROLE[,ROLE...]', | ||||||
|  |         help: 'Set role tags to the given value(s). Can be specified ' + | ||||||
|  |             'multiple times.' | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         names: ['edit', 'e'], | ||||||
|  |         type: 'bool', | ||||||
|  |         help: 'Edit role tags in your $EDITOR.' | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         names: ['delete', 'd'], | ||||||
|  |         type: 'arrayOfString', | ||||||
|  |         helpArg: 'ROLE[,ROLE...]', | ||||||
|  |         help: 'Delete the given role tags. Can be specified multiple times.' | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         names: ['delete-all', 'D'], | ||||||
|  |         type: 'bool', | ||||||
|  |         help: 'Delete all role tags from the given resource.' | ||||||
|  |     } | ||||||
|  | ]; | ||||||
|  | do_instance_role_tags.help = [ | ||||||
|  |     /* BEGIN JSSTYLED */ | ||||||
|  |     'List and manage role tags for the given instance.', | ||||||
|  |     '', | ||||||
|  |     'Usage:', | ||||||
|  |     '     {{name}} instance-role-tags INST                      # list role tags', | ||||||
|  |     '     {{name}} instance-role-tags -a ROLE[,ROLE...] INST    # add', | ||||||
|  |     '     {{name}} instance-role-tags -s ROLE[,ROLE...] INST    # set/replace', | ||||||
|  |     '     {{name}} instance-role-tags -e INST                   # edit in $EDITOR', | ||||||
|  |     '     {{name}} instance-role-tags -d ROLE[,ROLE...] INST    # delete', | ||||||
|  |     '     {{name}} instance-role-tags -D INST                   # delete all', | ||||||
|  |     '', | ||||||
|  |     '{{options}}', | ||||||
|  |     'Where "ROLE" is a role tag name (see `triton rbac roles`) and INST is', | ||||||
|  |     'an instance "id", "name" or short id.' | ||||||
|  |     /* END JSSTYLED */ | ||||||
|  | ].join('\n'); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | module.exports = { | ||||||
|  |     //do_role_tags: do_role_tags,
 | ||||||
|  |     do_instance_role_tags: do_instance_role_tags | ||||||
|  | }; | ||||||
| @ -28,7 +28,22 @@ function RbacCLI(top) { | |||||||
|         /* END JSSTYLED */ |         /* END JSSTYLED */ | ||||||
|         helpOpts: { |         helpOpts: { | ||||||
|             minHelpCol: 24 /* line up with option help */ |             minHelpCol: 24 /* line up with option help */ | ||||||
|         } |         }, | ||||||
|  |         helpSubcmds: [ | ||||||
|  |             'help', | ||||||
|  |             'info', | ||||||
|  |             { group: 'RBAC Resources' }, | ||||||
|  |             'users', | ||||||
|  |             'user', | ||||||
|  |             'keys', | ||||||
|  |             'key', | ||||||
|  |             'policies', | ||||||
|  |             'policy', | ||||||
|  |             'roles', | ||||||
|  |             'role', | ||||||
|  |             { group: 'Role Tags' }, | ||||||
|  |             'instance-role-tags' | ||||||
|  |         ] | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
| util.inherits(RbacCLI, Cmdln); | util.inherits(RbacCLI, Cmdln); | ||||||
| @ -40,17 +55,19 @@ RbacCLI.prototype.init = function init(opts, args, cb) { | |||||||
|     Cmdln.prototype.init.apply(this, arguments); |     Cmdln.prototype.init.apply(this, arguments); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | RbacCLI.prototype.do_info = require('./do_info'); | ||||||
| 
 | 
 | ||||||
| RbacCLI.prototype.do_users = require('./do_users'); | RbacCLI.prototype.do_users = require('./do_users'); | ||||||
| RbacCLI.prototype.do_user = require('./do_user'); | RbacCLI.prototype.do_user = require('./do_user'); | ||||||
| 
 | RbacCLI.prototype.do_keys = require('./do_keys'); | ||||||
|  | RbacCLI.prototype.do_key = require('./do_key'); | ||||||
|  | RbacCLI.prototype.do_policies = require('./do_policies'); | ||||||
|  | RbacCLI.prototype.do_policy = require('./do_policy'); | ||||||
| RbacCLI.prototype.do_roles = require('./do_roles'); | RbacCLI.prototype.do_roles = require('./do_roles'); | ||||||
| RbacCLI.prototype.do_role = require('./do_role'); | RbacCLI.prototype.do_role = require('./do_role'); | ||||||
| 
 | 
 | ||||||
| RbacCLI.prototype.do_policies = require('./do_policies'); | var doRoleTags = require('./do_role_tags'); | ||||||
| RbacCLI.prototype.do_policy = require('./do_policy'); | //RbacCLI.prototype.do_role_tags = doRoleTags.do_role_tags;
 | ||||||
| 
 | RbacCLI.prototype.do_instance_role_tags = doRoleTags.do_instance_role_tags; | ||||||
| RbacCLI.prototype.do_keys = require('./do_keys'); |  | ||||||
| RbacCLI.prototype.do_key = require('./do_key'); |  | ||||||
| 
 | 
 | ||||||
| module.exports = RbacCLI; | module.exports = RbacCLI; | ||||||
|  | |||||||
| @ -487,13 +487,16 @@ TritonApi.prototype.getNetwork = function getNetwork(name, cb) { | |||||||
|  * Get an instance by ID, exact name, or short ID, in that order. |  * Get an instance by ID, exact name, or short ID, in that order. | ||||||
|  * |  * | ||||||
|  * @param {String} name |  * @param {String} name | ||||||
|  * @param {Function} callback `function (err, inst)` |  * @param {Function} callback `function (err, inst, res)` | ||||||
|  |  *      Where, on success, `res` is the response object from a `GetMachine` call | ||||||
|  |  *      if one was made. | ||||||
|  */ |  */ | ||||||
| TritonApi.prototype.getInstance = function getInstance(name, cb) { | TritonApi.prototype.getInstance = function getInstance(name, cb) { | ||||||
|     var self = this; |     var self = this; | ||||||
|     assert.string(name, 'name'); |     assert.string(name, 'name'); | ||||||
|     assert.func(cb, 'cb'); |     assert.func(cb, 'cb'); | ||||||
| 
 | 
 | ||||||
|  |     var res; | ||||||
|     var shortId; |     var shortId; | ||||||
|     var inst; |     var inst; | ||||||
| 
 | 
 | ||||||
| @ -511,7 +514,8 @@ TritonApi.prototype.getInstance = function getInstance(name, cb) { | |||||||
|                     return next(); |                     return next(); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             self.cloudapi.getMachine(uuid, function (err, inst_) { |             self.cloudapi.getMachine(uuid, function (err, inst_, res_) { | ||||||
|  |                 res = res_; | ||||||
|                 inst = inst_; |                 inst = inst_; | ||||||
|                 if (err && err.restCode === 'ResourceNotFound') { |                 if (err && err.restCode === 'ResourceNotFound') { | ||||||
|                     // The CloudApi 404 error message sucks: "VM not found".
 |                     // The CloudApi 404 error message sucks: "VM not found".
 | ||||||
| @ -579,7 +583,7 @@ TritonApi.prototype.getInstance = function getInstance(name, cb) { | |||||||
|         if (err) { |         if (err) { | ||||||
|             cb(err); |             cb(err); | ||||||
|         } else if (inst) { |         } else if (inst) { | ||||||
|             cb(null, inst); |             cb(null, inst, res); | ||||||
|         } else { |         } else { | ||||||
|             cb(new errors.ResourceNotFoundError(format( |             cb(new errors.ResourceNotFoundError(format( | ||||||
|                 'no instance with name or short id "%s" was found', name))); |                 'no instance with name or short id "%s" was found', name))); | ||||||
| @ -588,6 +592,62 @@ TritonApi.prototype.getInstance = function getInstance(name, cb) { | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  * Get instance role tags. | ||||||
|  |  * | ||||||
|  |  * @param {String} name  The instance id, name, or short id. | ||||||
|  |  * @param {Function} callback `function (err, roleTags, inst)` | ||||||
|  |  */ | ||||||
|  | TritonApi.prototype.getInstanceRoleTags = | ||||||
|  | function getInstanceRoleTags(name, cb) { | ||||||
|  |     var self = this; | ||||||
|  |     assert.string(name, 'name'); | ||||||
|  |     assert.func(cb, 'cb'); | ||||||
|  | 
 | ||||||
|  |     var roleTags; | ||||||
|  |     var inst; | ||||||
|  | 
 | ||||||
|  |     vasync.pipeline({arg: {}, funcs: [ | ||||||
|  |         function getInst(ctx, next) { | ||||||
|  |             self.getInstance(name, function (err, inst_, res) { | ||||||
|  |                 if (err) { | ||||||
|  |                     next(err); | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |                 inst = inst_; | ||||||
|  |                 ctx.res = res; | ||||||
|  |                 next(); | ||||||
|  |             }); | ||||||
|  |         }, | ||||||
|  |         function getMachineIfNecessary(ctx, next) { | ||||||
|  |             // Sometimes `getInstance` returns a CloudAPI `GetMachine` res on
 | ||||||
|  |             // which there is a 'role-tag' header that we want. Sometimes not.
 | ||||||
|  |             if (ctx.res) { | ||||||
|  |                 next(); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             self.cloudapi.getMachine(inst.id, function (err, inst_, res) { | ||||||
|  |                 if (err) { | ||||||
|  |                     next(err); | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |                 ctx.res = res; | ||||||
|  |                 next(); | ||||||
|  |             }); | ||||||
|  |         }, | ||||||
|  |         function getRolesFromRes(ctx, next) { | ||||||
|  |             roleTags = (ctx.res.headers['role-tag'] || '') | ||||||
|  |                 /* JSSTYLED */ | ||||||
|  |                 .split(/\s*,\s*/) | ||||||
|  |                 .filter(function (r) { return r.trim(); }); | ||||||
|  |             next(); | ||||||
|  |         } | ||||||
|  |     ]}, function (err) { | ||||||
|  |         cb(err, roleTags, inst); | ||||||
|  |     }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Get an RBAC user by ID, login, or short ID, in that order. |  * Get an RBAC user by ID, login, or short ID, in that order. | ||||||
| @ -715,7 +775,8 @@ TritonApi.prototype.getUser = function getUser(opts, cb) { | |||||||
|                 next(); |                 next(); | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|             self.cloudapi.listUserKeys({id: ctx.user.id}, function (err, keys) { |             self.cloudapi.listUserKeys({userId: ctx.user.id}, | ||||||
|  |                     function (err, keys) { | ||||||
|                 if (err) { |                 if (err) { | ||||||
|                     next(err); |                     next(err); | ||||||
|                     return; |                     return; | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user