joyent/node-triton#54 'triton rbac reset', 'triton [rbac] keys' default output changes, 'triton rbac apply' implicit usage of './rbac-user-keys' dir, drop shortIds for 'triton rbac ...'

This commit is contained in:
Trent Mick 2015-11-21 12:41:16 -08:00
parent 1160fe120b
commit 6918fb93f7
15 changed files with 282 additions and 335 deletions

View File

@ -2,8 +2,23 @@
## 3.0.1 (not yet released)
- #54 `triton rbac info` improvements: better help, use brackets to show
non-default roles.
- #54 RBAC support, see <https://docs.joyent.com/public-cloud/rbac> to start.
- `triton rbac info` improvements: better help, use brackets to show
non-default roles.
- `triton rbac reset`
- change `triton rbac user USER` output a little for the 'keys' (show
the key fingerprint and name instead of the key content), 'roles',
and 'default_roles' fields.
- #54 *Drop* support for shortIds for `triton rbac {users,roles,policies}`
commands. They all have unique *`name`* fields, just use that.
- #54 `triton rbac apply` will implicitly look for a user key file at
"./rbac-user-keys/$login.pub" if no `keys` field is provided in the
"rbac.json" config file.
- Change default `triton keys` and `triton rbac keys` output to be tabular.
Otherwise it is a little obtuse to see fingerprints (which is what currently
must be included in a profile). `triton [rbac] keys -A` can be used to
get the old behaviour (just the key content, i.e. output appropriate
for "~/.ssh/authorized\_keys").
## 3.0.0
@ -24,7 +39,7 @@
- `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 {instance,image,network,package,}role-tags ...` to list
- `triton rbac {instance,image,network,package,}role-tags ...` to list
and manage role tags on each of those resources.
- `triton rbac info` will dump a summary of the full current RBAC
state. This command is still in development.

View File

@ -1,6 +1,6 @@
{
"users": [
{ "login": "emma", "email": "emma@simple.example.com", "keys": "rbac-user-keys" },
{ "login": "emma", "email": "emma@simple.example.com" },
{ "login": "bert", "email": "bert@simple.example.com" }
],
"roles": [

View File

@ -734,6 +734,12 @@ function chomp(s) {
* using ASCII printable, non-space chars: ASCII 33-126 (inclusive).
*
* No idea if this is crypto-sound. Doubt it.
*
* This needs to pass UFDS's "insufficientPasswordQuality". This actually
* depends on the pwdcheckquality configurable JS function configured for
* the datacenter. By default that is from:
* https://github.com/joyent/sdc-ufds/blob/master/data/bootstrap.ldif.in
* which at the time of writing requires at least a number and a letter.
*/
function generatePassword(opts) {
assert.optionalObject(opts, 'opts');
@ -748,8 +754,15 @@ function generatePassword(opts) {
var num = Math.round(((buf[i] / 0xff) * (max - min)) + min);
chars.push(String.fromCharCode(num));
}
var pwd = chars.join('');
return chars.join('');
// "quality" checks
if (!/[a-zA-Z]+/.test(pwd) || !/[0-9]+/.test(pwd)) {
// Try again.
return generatePassword(opts);
} else {
return pwd;
}
}

View File

@ -60,8 +60,6 @@ var common = require('../common');
var errors = require('../errors');
var rbac = require('../rbac');
var ansiStylize = common.ansiStylize;
function do_info(subcmd, opts, args, cb) {
@ -74,6 +72,11 @@ function do_info(subcmd, opts, args, cb) {
}
var log = this.log;
var ansiStylize = common.ansiStylize;
if (!process.stdout.isTTY || opts.no_color) {
ansiStylize = function (s) { return s; };
}
var context = {
log: this.log,
tritonapi: this.top.tritonapi,
@ -200,6 +203,11 @@ do_info.options = [
type: 'bool',
help: 'Include all info for a more full report. This requires more ' +
'work to gather all info.'
},
{
names: ['no-color'],
type: 'bool',
help: 'Do not color some of the output with ANSI codes.'
}
];

View File

@ -10,10 +10,16 @@
* `triton rbac keys ...`
*/
var tabula = require('tabula');
var common = require('../common');
var errors = require('../errors');
var columnsDefault = 'fingerprint,name';
var columnsDefaultLong = 'fingerprint,name,key';
var sortDefault = 'name';
function do_keys(subcmd, opts, args, cb) {
if (opts.help) {
@ -27,6 +33,15 @@ function do_keys(subcmd, opts, args, cb) {
return;
}
var columns = columnsDefault;
if (opts.o) {
columns = opts.o;
} else if (opts.long) {
columns = columnsDefaultLong;
}
columns = columns.split(',');
var sort = opts.s.split(',');
this.top.tritonapi.cloudapi.listUserKeys({userId: args[0]},
function (err, userKeys) {
if (err) {
@ -36,10 +51,16 @@ function do_keys(subcmd, opts, args, cb) {
if (opts.json) {
common.jsonStream(userKeys);
} else {
} else if (opts.authorized_keys) {
userKeys.forEach(function (key) {
console.log(common.chomp(key.key));
});
} else {
tabula(userKeys, {
skipHeader: false,
columns: columns,
sort: sort
});
}
cb();
});
@ -50,13 +71,18 @@ do_keys.options = [
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['json', 'j'],
type: 'bool',
help: 'JSON output.'
}
];
].concat(common.getCliTableOptions({
includeLong: true,
sortDefault: sortDefault
})).concat([
{
names: ['authorized-keys', 'A'],
type: 'bool',
help: 'Just output public key data -- i.e. output appropriate for a ' +
'"~/.ssh/authorized_keys" file.'
}
]);
do_keys.help = (
/* BEGIN JSSTYLED */
@ -67,10 +93,7 @@ do_keys.help = (
'\n' +
'{{options}}' +
'\n' +
'Where "USER" is an RBAC user id, login or short id. By default this\n' +
'lists just the key content for each key -- in other words, content\n' +
'appropriate for a "~/.ssh/authorized_keys" file.\n' +
'Use `{{name}} keys -j USER` to see all fields.\n'
'Where "USER" is an RBAC user login or id (a UUID).\n'
/* END JSSTYLED */
);

View File

@ -18,10 +18,10 @@ var errors = require('../errors');
// columns default without -o
var columnsDefault = 'shortid,name,description,nrules';
var columnsDefault = 'name,description,nrules';
// columns default with -l
var columnsDefaultLong = 'id,name,rules';
var columnsDefaultLong = 'id,name,description,rules';
// sort default with -s
var sortDefault = 'name';
@ -43,7 +43,6 @@ function do_policies(subcmd, opts, args, cb) {
columns = columnsDefaultLong;
}
columns = columns.split(',');
var sort = opts.s.split(',');
this.top.tritonapi.cloudapi.listPolicies(function (err, policies) {
@ -58,7 +57,6 @@ function do_policies(subcmd, opts, args, cb) {
// Add some convenience fields
for (var i = 0; i < policies.length; i++) {
var role = policies[i];
role.shortid = role.id.split('-', 1)[0];
role.nrules = role.rules.length;
role.rules = role.rules.sort().join('; ');
}
@ -94,7 +92,6 @@ do_policies.help = (
'{{options}}' +
'\n' +
'Fields (most are self explanatory, the client adds some for convenience):\n' +
' shortid A short ID prefix.\n' +
' nrules The number of rules in this policy.\n'
/* END JSSTYLED */
);

125
lib/do_rbac/do_reset.js Normal file
View File

@ -0,0 +1,125 @@
/*
* 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 reset`
*/
var assert = require('assert-plus');
var format = require('util').format;
var vasync = require('vasync');
var common = require('../common');
var errors = require('../errors');
var rbac = require('../rbac');
var ansiStylize = common.ansiStylize;
function do_reset(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 context = {
log: this.log,
tritonapi: this.top.tritonapi,
cloudapi: this.top.tritonapi.cloudapi,
rbacDryRun: opts.dry_run
};
vasync.pipeline({arg: context, funcs: [
function emptyConfig(ctx, next) {
ctx.rbacConfig = {};
next();
},
rbac.loadRbacState,
rbac.createRbacUpdatePlan,
function confirmApply(ctx, next) {
if (opts.yes || ctx.rbacUpdatePlan.length === 0) {
next();
return;
}
ctx.log.info({rbacUpdatePlan: ctx.rbacUpdatePlan},
'rbacUpdatePlan');
var p = console.log;
p('');
p('This will make the following RBAC config changes:');
ctx.rbacUpdatePlan.forEach(function (c) {
var extra = '';
if (c.action === 'update') {
extra = format(' (%s)',
Object.keys(c.diff).map(function (field) {
return c.diff[field] + ' ' + field;
}).join(', '));
}
p(' %s %s %s%s',
{create: 'Create', 'delete': 'Delete',
update: 'Update'}[c.action],
c.desc || c.type,
c.id,
extra);
});
p('');
var msg = format('Would you like to continue%s? [y/N] ',
opts.dry_run ? ' (dry-run)' : '');
common.promptYesNo({msg: msg, default: 'n'}, function (answer) {
if (answer !== 'y') {
p('Aborting update');
return cb();
}
p('');
next();
});
},
rbac.executeRbacUpdatePlan
]}, function (err) {
cb(err);
});
}
do_reset.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['dry-run', 'n'],
type: 'bool',
help: 'Go through the motions without applying changes.'
},
{
names: ['yes', 'y'],
type: 'bool',
help: 'Answer "yes" to confirmation.'
}
];
do_reset.help = [
/* BEGIN JSSTYLED */
'Reset RBAC state for this account.',
'**Warning: This will delete all RBAC info for this account.**',
'',
'Usage:',
' {{name}} reset [<options>]',
'',
'{{options}}',
'Warning: Currently, RBAC state updates can take a few seconds to appear',
'as they are replicated across data centers. This can result in unexpected',
'no-op updates with consecutive quick re-runs of this command.'
/* END JSSTYLED */
].join('\n');
do_reset.hidden = true;
module.exports = do_reset;

View File

@ -52,9 +52,7 @@ function _showRole(opts, cb) {
assert.func(cb, 'cb');
var cli = opts.cli;
cli.tritonapi.getRole({
id: opts.id
}, function onRole(err, role) {
cli.cloudapi.getRole({id: opts.id}, function onRole(err, role) {
if (err) {
cb(err);
return;
@ -183,9 +181,7 @@ function _editRole(opts, cb) {
}
cli.tritonapi.getRole({
id: opts.id
}, function onRole(err, role_) {
cli.cloudapi.getRole({id: opts.id}, function onRole(err, role_) {
if (err) {
return cb(err);
}

View File

@ -17,13 +17,8 @@ var errors = require('../errors');
// columns default without -o
var columnsDefault = 'shortid,name,policies,members';
// columns default with -l
var columnsDefaultLong = 'shortid,name,policies,members,default_members';
// sort default with -s
var columnsDefault = 'name,policies,members,default_members';
var columnsDefaultLong = 'id,name,policies,members,default_members';
var sortDefault = 'name';
@ -43,7 +38,6 @@ function do_roles(subcmd, opts, args, cb) {
columns = columnsDefaultLong;
}
columns = columns.split(',');
var sort = opts.s.split(',');
this.top.tritonapi.cloudapi.listRoles(function (err, roles) {
@ -59,7 +53,6 @@ function do_roles(subcmd, opts, args, cb) {
// Add some convenience fields
for (i = 0; i < roles.length; i++) {
var role = roles[i];
role.shortid = role.id.split('-', 1)[0];
role.policies = role.policies.sort().join(',');
var defaultMap = {};
for (j = 0; j < role.default_members.length; j++) {
@ -114,7 +107,6 @@ do_roles.help = (
'{{options}}' +
'\n' +
'Fields (most are self explanatory, the client adds some for convenience):\n' +
' shortid A short ID prefix.\n' +
' members Non-default members (not in the "default_members")\n' +
' are shown in magenta.\n'
/* END JSSTYLED */

View File

@ -69,19 +69,26 @@ function _showUser(opts, cb) {
if (opts.json) {
console.log(JSON.stringify(user));
} else {
Object.keys(user).forEach(function (key) {
if (key === 'keys') {
var skip = {keys: true, roles: true, default_roles: true};
Object.keys(user).forEach(function (field) {
if (skip[field]) {
return;
}
console.log('%s: %s', key, user[key]);
console.log('%s: %s', field, user[field]);
});
if (opts.roles) {
console.log('roles:');
user.roles.forEach(
function (r) { console.log(' ' + r); });
console.log('default_roles:');
user.default_roles.forEach(
function (r) { console.log(' ' + r); });
}
if (opts.keys) {
console.log('keys:');
user.keys.forEach(function (key) {
process.stdout.write(' ' + key.key);
if (key.key && key.key.slice(-1) !== '\n') {
process.stdout.write('\n');
}
console.log(' %s%s', key.fingerprint,
key.name ? format(' (%s)', key.name) : '');
});
}
}
@ -495,7 +502,7 @@ do_user.help = [
'Show, add, edit and delete RBAC users.',
'',
'Usage:',
' {{name}} user USER # show user USER',
' {{name}} user [<options>] USER # show user USER',
' {{name}} user -e|--edit USER # edit user USER in $EDITOR',
' {{name}} user -d|--delete [USER...] # delete user USER',
'',

View File

@ -17,13 +17,8 @@ var errors = require('../errors');
// columns default without -o
var columnsDefault = 'shortid,login,email,name,cdate';
// columns default with -l
var columnsDefault = 'login,email,name,cdate';
var columnsDefaultLong = 'id,login,email,firstName,lastName,created';
// sort default with -s
var sortDefault = 'login';
@ -58,7 +53,6 @@ function do_users(subcmd, opts, args, cb) {
// Add some convenience fields
for (var i = 0; i < users.length; i++) {
var user = users[i];
user.shortid = user.id.split('-', 1)[0];
user.name = ((user.firstName || '') + ' ' +
(user.lastName || '')).trim() || undefined;
if (user.created) {
@ -98,7 +92,6 @@ do_users.help = (
' {{name}} users [<options>]\n' +
'\n' +
'Fields (most are self explanatory, the client adds some for convenience):\n' +
' shortid A short ID prefix.\n' +
' name "firstName lastName"\n' +
' cdate Just the date portion of "created"\n' +
' udate Just the date portion of "updated"\n' +

View File

@ -63,6 +63,7 @@ RbacCLI.prototype.init = function init(opts, args, cb) {
RbacCLI.prototype.do_info = require('./do_info');
RbacCLI.prototype.do_apply = require('./do_apply');
RbacCLI.prototype.do_reset = require('./do_reset');
RbacCLI.prototype.do_users = require('./do_users');
RbacCLI.prototype.do_user = require('./do_user');

View File

@ -21,6 +21,12 @@ var common = require('./common');
var errors = require('./errors');
// ---- globals
var DEFAULT_RBAC_USER_KEYS_DIR = 'rbac-user-keys';
// ---- internal support stuff
function _rbacStateBasics(ctx, cb) {
@ -251,6 +257,11 @@ function loadRbacConfig(ctx, cb) {
vasync.forEachPipeline({
inputs: ctx.rbacConfig.users || [],
func: function loadForOneUser(user, nextUser) {
var implicit = false;
if (!user.hasOwnProperty('keys')) {
user.keys = DEFAULT_RBAC_USER_KEYS_DIR;
implicit = true;
}
if (!user.keys || typeof (user.keys) !== 'string') {
nextUser();
return;
@ -260,6 +271,10 @@ function loadRbacConfig(ctx, cb) {
try {
var stat = fs.statSync(keysFile);
} catch (statErr) {
if (implicit) {
nextUser();
return;
}
throw new errors.TritonError(format(
'User %s keys not found in "%s": %s',
user.login, keysFile, statErr));
@ -269,12 +284,20 @@ function loadRbacConfig(ctx, cb) {
try {
stat = fs.statSync(keysFile);
} catch (statErr) {
if (implicit) {
nextUser();
return;
}
throw new errors.TritonError(format(
'User %s keys not found in "%s": %s',
user.login, keysFile, statErr));
}
}
if (!stat.isFile()) {
if (implicit) {
nextUser();
return;
}
throw new errors.TritonError(format(
'Expected "%s" to be a regular file', keysFile));
}
@ -564,7 +587,10 @@ function executeRbacUpdatePlan(ctx, cb) {
next(err);
return;
}
console.log('Created user %s', c.wantThing.login);
console.log('Created user %s (use `triton rbac passwd ' +
'%s` to change password)', c.wantThing.login,
c.wantThing.login);
next();
});
break;
@ -703,7 +729,7 @@ function executeRbacUpdatePlan(ctx, cb) {
next(err);
return;
}
console.log('Created role %s (%s members%s)',
console.log('Created role %s (%s member%s)',
role.name, role.members.length,
role.members.length === 1 ? '' : 's');
next();

View File

@ -778,10 +778,10 @@ TritonApi.prototype.setRoleTags = function setRoleTags(opts, cb) {
/**
* Get an RBAC user by ID, login, or short ID, in that order.
* Get an RBAC user by ID or login.
*
* @param {Object} opts
* - id {UUID|String} The user ID (a UUID), login or short id.
* - id {UUID|String} The user ID (a UUID) or login.
* - roles {Boolean} Optional. Whether to includes roles of which this
* user is a member. Default false.
* - keys {Boolean} Optional. Set to `true` to also (with a separate
@ -796,11 +796,6 @@ TritonApi.prototype.getUser = function getUser(opts, cb) {
assert.optionalBool(opts.keys, 'opts.keys');
assert.func(cb, 'cb');
/*
* CloudAPI GetUser supports a UUID or login, so we try that first.
* If that is a 404 and `opts.id` a valid shortid, then try to lookup
* via `listUsers`.
*/
var context = {};
vasync.pipeline({arg: context, funcs: [
function tryGetUser(ctx, next) {
@ -811,83 +806,11 @@ TritonApi.prototype.getUser = function getUser(opts, cb) {
self.cloudapi.getUser(getOpts, function (err, user) {
if (err) {
if (err.restCode === 'ResourceNotFound') {
ctx.notFoundErr = err;
next();
} else {
next(err);
}
} else {
ctx.user = user;
next();
}
});
},
function tryShortId(ctx, next) {
if (ctx.user) {
next();
return;
}
var shortId = common.normShortId(opts.id);
if (!shortId) {
next();
return;
}
self.cloudapi.listUsers(function (err, users) {
if (err) {
next(err);
return;
}
var shortIdMatches = [];
for (var i = 0; i < users.length; i++) {
var user = users[i];
// TODO: use this test in other shortId matching
if (user.id.slice(0, shortId.length) === shortId) {
shortIdMatches.push(user);
}
}
if (shortIdMatches.length === 1) {
ctx.user = shortIdMatches[0];
next();
} else if (shortIdMatches.length === 0) {
next(new errors.ResourceNotFoundError(format(
'user with login or id matching "%s" was not found',
opts.id)));
} else {
next(new errors.ResourceNotFoundError(
format('user with login "%s" was not found '
+ 'and "%s" is an ambiguous short id', opts.id)));
}
});
},
/*
* If we found the user via `listUsers` and `opts.roles` was requested
* then we need to re-getUser.
*/
function reGetUserIfNecessary(ctx, next) {
if (!ctx.user) {
// We must have gotten the `notFoundErr` above.
next(new errors.ResourceNotFoundError(ctx.notFoundErr, format(
'user with login or id "%s" was not found', opts.id)));
return;
} else if (!opts.roles || ctx.user.roles) {
next();
return;
}
var getOpts = {
id: ctx.user.id,
membership: opts.roles
};
self.cloudapi.getUser(getOpts, function (err, user) {
if (err) {
if (err.restCode === 'ResourceNotFound') {
next(new errors.ResourceNotFoundError(err, format(
'user with id "%s" was not found', opts.id)));
// TODO: feels like overkill to wrap this, ensure
// decent cloudapi error for this, then don't wrap.
next(new errors.ResourceNotFoundError(err,
format('user with login or id "%s" was not found',
opts.id)));
} else {
next(err);
}
@ -920,104 +843,11 @@ TritonApi.prototype.getUser = function getUser(opts, cb) {
};
/**
* Get an RBAC role by ID, name, or short ID, in that order.
* Delete an RBAC role by ID or name.
*
* @param {Object} opts
* - id {UUID|String} The RBAC role id (a UUID), name or short id.
* @param {Function} callback of the form `function (err, role)`
*/
TritonApi.prototype.getRole = function getRole(opts, cb) {
var self = this;
assert.object(opts, 'opts');
assert.string(opts.id, 'opts.id');
assert.func(cb, 'cb');
/*
* CloudAPI GetRole supports a UUID or name, so we try that first.
* If that is a 404 and `opts.id` a valid shortid, then try to lookup
* via `listRoles`.
*/
var context = {};
vasync.pipeline({arg: context, funcs: [
function tryGetRole(ctx, next) {
self.cloudapi.getRole({id: opts.id}, function (err, role) {
if (err) {
if (err.restCode === 'ResourceNotFound') {
ctx.notFoundErr = err;
next();
} else {
next(err);
}
} else {
ctx.role = role;
next();
}
});
},
function tryShortId(ctx, next) {
if (ctx.role) {
next();
return;
}
var shortId = common.normShortId(opts.id);
if (!shortId) {
next();
return;
}
self.cloudapi.listRoles(function (err, roles) {
if (err) {
next(err);
return;
}
var shortIdMatches = [];
for (var i = 0; i < roles.length; i++) {
var role = roles[i];
if (role.id.slice(0, shortId.length) === shortId) {
shortIdMatches.push(role);
}
}
if (shortIdMatches.length === 1) {
ctx.role = shortIdMatches[0];
next();
} else if (shortIdMatches.length === 0) {
next(new errors.ResourceNotFoundError(format(
'role with id or name matching "%s" was not found',
opts.id)));
} else {
next(new errors.ResourceNotFoundError(
format('role with name "%s" was not found '
+ 'and "%s" is an ambiguous short id', opts.id)));
}
});
},
function raiseEarlierNotFoundErrIfNotFound(ctx, next) {
if (!ctx.role) {
// We must have gotten the `notFoundErr` above.
next(new errors.ResourceNotFoundError(ctx.notFoundErr, format(
'role with name or id "%s" was not found', opts.id)));
} else {
next();
}
}
]}, function (err) {
cb(err, context.role);
});
};
/**
* Delete an RBAC role by ID, name, or short ID, in that order.
*
* @param {Object} opts
* - id {UUID|String} The role id (a UUID), name or short id.
* - id {UUID|String} The role id (a UUID) or name.
* @param {Function} callback of the form `function (err)`
*/
TritonApi.prototype.deleteRole = function deleteRole(opts, cb) {
@ -1038,7 +868,7 @@ TritonApi.prototype.deleteRole = function deleteRole(opts, cb) {
return;
}
self.getRole({id: opts.id}, function (err, role) {
self.cloudapi.getRole({id: opts.id}, function (err, role) {
if (err) {
next(err);
return;
@ -1057,104 +887,11 @@ TritonApi.prototype.deleteRole = function deleteRole(opts, cb) {
};
/**
* Get an RBAC policy by ID, name, or short ID, in that order.
* Delete an RBAC policy by ID or name.
*
* @param {Object} opts
* - id {UUID|String} The RBAC policy id (a UUID), name or short id.
* @param {Function} callback of the form `function (err, policy)`
*/
TritonApi.prototype.getPolicy = function getPolicy(opts, cb) {
var self = this;
assert.object(opts, 'opts');
assert.string(opts.id, 'opts.id');
assert.func(cb, 'cb');
/*
* CloudAPI GetPolicy supports a UUID or name, so we try that first.
* If that is a 404 and `opts.id` a valid shortid, then try to lookup
* via `listPolicies`.
*/
var context = {};
vasync.pipeline({arg: context, funcs: [
function tryGetIt(ctx, next) {
self.cloudapi.getPolicy({id: opts.id}, function (err, policy) {
if (err) {
if (err.restCode === 'ResourceNotFound') {
ctx.notFoundErr = err;
next();
} else {
next(err);
}
} else {
ctx.policy = policy;
next();
}
});
},
function tryShortId(ctx, next) {
if (ctx.policy) {
next();
return;
}
var shortId = common.normShortId(opts.id);
if (!shortId) {
next();
return;
}
self.cloudapi.listRoles(function (err, policies) {
if (err) {
next(err);
return;
}
var shortIdMatches = [];
for (var i = 0; i < policies.length; i++) {
var policy = policies[i];
if (policy.id.slice(0, shortId.length) === shortId) {
shortIdMatches.push(policy);
}
}
if (shortIdMatches.length === 1) {
ctx.policy = shortIdMatches[0];
next();
} else if (shortIdMatches.length === 0) {
next(new errors.ResourceNotFoundError(format(
'policy with id or name matching "%s" was not found',
opts.id)));
} else {
next(new errors.ResourceNotFoundError(
format('policy with name "%s" was not found '
+ 'and "%s" is an ambiguous short id', opts.id)));
}
});
},
function raiseEarlierNotFoundErrIfNotFound(ctx, next) {
if (!ctx.policy) {
// We must have gotten the `notFoundErr` above.
next(new errors.ResourceNotFoundError(ctx.notFoundErr, format(
'policy with name or id "%s" was not found', opts.id)));
} else {
next();
}
}
]}, function (err) {
cb(err, context.policy);
});
};
/**
* Delete an RBAC policy by ID, name, or short ID, in that order.
*
* @param {Object} opts
* - id {UUID|String} The policy id (a UUID), name or short id.
* - id {UUID|String} The policy id (a UUID) or name.
* @param {Function} callback of the form `function (err)`
*/
TritonApi.prototype.deletePolicy = function deletePolicy(opts, cb) {
@ -1175,7 +912,7 @@ TritonApi.prototype.deletePolicy = function deletePolicy(opts, cb) {
return;
}
self.getPolicy({id: opts.id}, function (err, policy) {
self.cloudapi.getPolicy({id: opts.id}, function (err, policy) {
if (err) {
next(err);
return;

View File

@ -82,11 +82,24 @@ var LOG = require('../lib/log');
/*
* Call the `triton` CLI with the given args.
*
* @param args {String|Array} Required. CLI arguments to `triton ...` (without
* the "triton"). This can be an array of args, or a string.
* @param opts {Object} Optional.
* - opts.cwd {String} cwd option to exec.
* @param cb {Function}
*/
function triton(args, cb) {
function triton(args, opts, cb) {
var command = [].concat(TRITON).concat(args);
if (typeof (args) === 'string')
command = command.join(' ');
if (cb === undefined) {
cb = opts;
opts = {};
}
assert.object(opts, 'opts');
assert.optionalString(opts.cwd, 'opts.cwd');
assert.func(cb, 'cb');
testcommon.execPlus({
command: command,
@ -101,7 +114,8 @@ function triton(args, cb) {
TRITON_ACCOUNT: CONFIG.profile.account,
TRITON_KEY_ID: CONFIG.profile.keyId,
TRITON_TLS_INSECURE: CONFIG.profile.insecure
}
},
cwd: opts.cwd
},
log: LOG
}, cb);