joyent/node-triton#54 a start at 'triton rbac info', add 'triton rbac instance-role-tags'

This commit is contained in:
Trent Mick 2015-11-09 15:09:37 -08:00
parent 74b8f3e42e
commit 4e45e4061f
6 changed files with 1011 additions and 12 deletions

View File

@ -18,6 +18,10 @@
- `triton rbac policy ...` to show, create, edit and delete policies.
- `triton rbac keys` to list all RBAC user SSH 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

View File

@ -224,7 +224,7 @@ CloudApi.prototype._request = function _request(options, callback) {
assert.optionalObject(options.data, 'options.data');
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');
switch (method) {
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
module.exports.createClient = function (options) {

272
lib/do_rbac/do_info.js Normal file
View 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
View 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
};

View File

@ -28,7 +28,22 @@ function RbacCLI(top) {
/* END JSSTYLED */
helpOpts: {
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);
@ -40,17 +55,19 @@ RbacCLI.prototype.init = function init(opts, args, cb) {
Cmdln.prototype.init.apply(this, arguments);
};
RbacCLI.prototype.do_info = require('./do_info');
RbacCLI.prototype.do_users = require('./do_users');
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_role = require('./do_role');
RbacCLI.prototype.do_policies = require('./do_policies');
RbacCLI.prototype.do_policy = require('./do_policy');
RbacCLI.prototype.do_keys = require('./do_keys');
RbacCLI.prototype.do_key = require('./do_key');
var doRoleTags = require('./do_role_tags');
//RbacCLI.prototype.do_role_tags = doRoleTags.do_role_tags;
RbacCLI.prototype.do_instance_role_tags = doRoleTags.do_instance_role_tags;
module.exports = RbacCLI;

View File

@ -487,13 +487,16 @@ TritonApi.prototype.getNetwork = function getNetwork(name, cb) {
* Get an instance by ID, exact name, or short ID, in that order.
*
* @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) {
var self = this;
assert.string(name, 'name');
assert.func(cb, 'cb');
var res;
var shortId;
var inst;
@ -511,7 +514,8 @@ TritonApi.prototype.getInstance = function getInstance(name, cb) {
return next();
}
}
self.cloudapi.getMachine(uuid, function (err, inst_) {
self.cloudapi.getMachine(uuid, function (err, inst_, res_) {
res = res_;
inst = inst_;
if (err && err.restCode === 'ResourceNotFound') {
// The CloudApi 404 error message sucks: "VM not found".
@ -579,7 +583,7 @@ TritonApi.prototype.getInstance = function getInstance(name, cb) {
if (err) {
cb(err);
} else if (inst) {
cb(null, inst);
cb(null, inst, res);
} else {
cb(new errors.ResourceNotFoundError(format(
'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.
@ -715,7 +775,8 @@ TritonApi.prototype.getUser = function getUser(opts, cb) {
next();
return;
}
self.cloudapi.listUserKeys({id: ctx.user.id}, function (err, keys) {
self.cloudapi.listUserKeys({userId: ctx.user.id},
function (err, keys) {
if (err) {
next(err);
return;