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:
parent
1160fe120b
commit
6918fb93f7
21
CHANGES.md
21
CHANGES.md
@ -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.
|
||||
|
@ -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": [
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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.'
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -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 */
|
||||
);
|
||||
|
||||
|
@ -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
125
lib/do_rbac/do_reset.js
Normal 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;
|
@ -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);
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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',
|
||||
'',
|
||||
|
@ -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' +
|
||||
|
@ -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');
|
||||
|
30
lib/rbac.js
30
lib/rbac.js
@ -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();
|
||||
|
289
lib/tritonapi.js
289
lib/tritonapi.js
@ -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;
|
||||
|
@ -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);
|
||||
|
Reference in New Issue
Block a user