joyent/node-triton#54 first pass at 'triton rbac policy' and 'triton rbac policies'
This commit is contained in:
parent
6b1065b24d
commit
c7daecc6f3
@ -14,6 +14,8 @@
|
|||||||
- `triton rbac user ...` to show, create, edit and delete users.
|
- `triton rbac user ...` to show, create, edit and delete users.
|
||||||
- `triton rbac roles` to list all roles.
|
- `triton rbac roles` to list all roles.
|
||||||
- `triton rbac role ...` to show, create, edit and delete roles.
|
- `triton rbac role ...` to show, create, edit and delete roles.
|
||||||
|
- `triton rbac policies` to list all policies.
|
||||||
|
- `triton rbac policy ...` to show, create, edit and delete policies.
|
||||||
|
|
||||||
|
|
||||||
## 2.1.4
|
## 2.1.4
|
||||||
|
136
lib/cloudapi2.js
136
lib/cloudapi2.js
@ -460,7 +460,7 @@ CloudApi.prototype.getMachine = function getMachine(id, cb) {
|
|||||||
* delete a machine by id.
|
* delete a machine by id.
|
||||||
*
|
*
|
||||||
* @param {String} uuid (required) The machine id.
|
* @param {String} uuid (required) The machine id.
|
||||||
* @param {Function} callback of the form `function (err, machine, response)`
|
* @param {Function} callback of the form `function (err, res)`
|
||||||
*/
|
*/
|
||||||
CloudApi.prototype.deleteMachine = function deleteMachine(uuid, callback) {
|
CloudApi.prototype.deleteMachine = function deleteMachine(uuid, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
@ -471,8 +471,8 @@ CloudApi.prototype.deleteMachine = function deleteMachine(uuid, callback) {
|
|||||||
path: format('/%s/machines/%s', self.account, uuid),
|
path: format('/%s/machines/%s', self.account, uuid),
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
};
|
};
|
||||||
this._request(opts, function (err, req, res, body) {
|
this._request(opts, function (err, req, res) {
|
||||||
callback(err, body, res);
|
callback(err, res);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -809,9 +809,9 @@ CloudApi.prototype.updateUser = function updateUser(opts, cb) {
|
|||||||
/**
|
/**
|
||||||
* <http://apidocs.joyent.com/cloudapi/#DeleteUser>
|
* <http://apidocs.joyent.com/cloudapi/#DeleteUser>
|
||||||
*
|
*
|
||||||
* @param {Object} opts (object) user object containing:
|
* @param {Object} opts (object)
|
||||||
* - {String} id (required) for your user.
|
* - {String} id (required) for your user.
|
||||||
* @param {Function} cb of the form `function (err, user, res)`
|
* @param {Function} cb of the form `function (err, res)`
|
||||||
*/
|
*/
|
||||||
CloudApi.prototype.deleteUser = function deleteUser(opts, cb) {
|
CloudApi.prototype.deleteUser = function deleteUser(opts, cb) {
|
||||||
assert.object(opts, 'opts');
|
assert.object(opts, 'opts');
|
||||||
@ -821,8 +821,8 @@ CloudApi.prototype.deleteUser = function deleteUser(opts, cb) {
|
|||||||
this._request({
|
this._request({
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
path: format('/%s/users/%s', this.account, opts.id)
|
path: format('/%s/users/%s', this.account, opts.id)
|
||||||
}, function (err, req, res, body) {
|
}, function (err, req, res) {
|
||||||
cb(err, body, res);
|
cb(err, res);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -850,7 +850,7 @@ CloudApi.prototype.listRoles = function listRoles(opts, cb) {
|
|||||||
*
|
*
|
||||||
* @param {Object} opts
|
* @param {Object} opts
|
||||||
* - id {UUID|String} The role ID or name.
|
* - id {UUID|String} The role ID or name.
|
||||||
* @param {Function} callback of the form `function (err, user, res)`
|
* @param {Function} callback of the form `function (err, role, res)`
|
||||||
*/
|
*/
|
||||||
CloudApi.prototype.getRole = function getRole(opts, cb) {
|
CloudApi.prototype.getRole = function getRole(opts, cb) {
|
||||||
assert.object(opts, 'opts');
|
assert.object(opts, 'opts');
|
||||||
@ -930,9 +930,9 @@ CloudApi.prototype.updateRole = function updateRole(opts, cb) {
|
|||||||
/**
|
/**
|
||||||
* <http://apidocs.joyent.com/cloudapi/#DeleteRole>
|
* <http://apidocs.joyent.com/cloudapi/#DeleteRole>
|
||||||
*
|
*
|
||||||
* @param {Object} opts (object) user object containing:
|
* @param {Object} opts (object)
|
||||||
* - {String} id (required) of the role to delete.
|
* - {String} id (required) of the role to delete.
|
||||||
* @param {Function} cb of the form `function (err, user, res)`
|
* @param {Function} cb of the form `function (err, res)`
|
||||||
*/
|
*/
|
||||||
CloudApi.prototype.deleteRole = function deleteRole(opts, cb) {
|
CloudApi.prototype.deleteRole = function deleteRole(opts, cb) {
|
||||||
assert.object(opts, 'opts');
|
assert.object(opts, 'opts');
|
||||||
@ -942,11 +942,127 @@ CloudApi.prototype.deleteRole = function deleteRole(opts, cb) {
|
|||||||
this._request({
|
this._request({
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
path: format('/%s/roles/%s', this.account, opts.id)
|
path: format('/%s/roles/%s', this.account, opts.id)
|
||||||
|
}, function (err, req, res) {
|
||||||
|
cb(err, res);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <http://apidocs.joyent.com/cloudapi/#ListPolicies>
|
||||||
|
*
|
||||||
|
* @param opts {Object} Options (optional)
|
||||||
|
* @param cb {Function} Callback of the form `function (err, policies, res)`
|
||||||
|
*/
|
||||||
|
CloudApi.prototype.listPolicies = function listPolicies(opts, cb) {
|
||||||
|
if (cb === undefined) {
|
||||||
|
cb = opts;
|
||||||
|
opts = {};
|
||||||
|
}
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
assert.object(opts, 'opts');
|
||||||
|
|
||||||
|
var endpoint = format('/%s/policies', this.account);
|
||||||
|
this._passThrough(endpoint, opts, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <http://apidocs.joyent.com/cloudapi/#GetPolicy>
|
||||||
|
*
|
||||||
|
* @param {Object} opts
|
||||||
|
* - id {UUID|String} The policy ID or name.
|
||||||
|
* @param {Function} callback of the form `function (err, policy, res)`
|
||||||
|
*/
|
||||||
|
CloudApi.prototype.getPolicy = function getPolicy(opts, cb) {
|
||||||
|
assert.object(opts, 'opts');
|
||||||
|
assert.string(opts.id, 'opts.id');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
|
var endpoint = format('/%s/policies/%s', this.account, opts.id);
|
||||||
|
this._passThrough(endpoint, {}, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <http://apidocs.joyent.com/cloudapi/#CreatePolicy>
|
||||||
|
*
|
||||||
|
* @param {Object} opts (object) policy object containing:
|
||||||
|
* - {String} name (required) for the policy.
|
||||||
|
* - {Array} description (optional) for the policy.
|
||||||
|
* - {Array} rules (optional) for the policy.
|
||||||
|
* @param {Function} cb of the form `function (err, policy, res)`
|
||||||
|
*/
|
||||||
|
CloudApi.prototype.createPolicy = function createPolicy(opts, cb) {
|
||||||
|
assert.object(opts, 'opts');
|
||||||
|
assert.string(opts.name, 'opts.name');
|
||||||
|
// XXX strict on inputs
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
name: opts.name,
|
||||||
|
description: opts.description,
|
||||||
|
rules: opts.rules
|
||||||
|
};
|
||||||
|
|
||||||
|
this._request({
|
||||||
|
method: 'POST',
|
||||||
|
path: format('/%s/policies', this.account),
|
||||||
|
data: data
|
||||||
}, function (err, req, res, body) {
|
}, function (err, req, res, body) {
|
||||||
cb(err, body, res);
|
cb(err, body, res);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <http://apidocs.joyent.com/cloudapi/#UpdatePolicy>
|
||||||
|
*
|
||||||
|
* @param {Object} opts (object) policy object containing:
|
||||||
|
* - {UUID|String} id (required) The policy ID or name.
|
||||||
|
* - {String} name (optional)
|
||||||
|
* - {String} description (optional)
|
||||||
|
* - {Array} rules (optional)
|
||||||
|
* @param {Function} cb of the form `function (err, policy, res)`
|
||||||
|
*/
|
||||||
|
CloudApi.prototype.updatePolicy = function updatePolicy(opts, cb) {
|
||||||
|
assert.object(opts, 'opts');
|
||||||
|
assert.string(opts.id, 'opts.id');
|
||||||
|
// XXX strict on inputs
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
|
var update = {
|
||||||
|
name: opts.name,
|
||||||
|
description: opts.description,
|
||||||
|
rules: opts.rules
|
||||||
|
};
|
||||||
|
|
||||||
|
this._request({
|
||||||
|
method: 'POST',
|
||||||
|
path: format('/%s/policies/%s', this.account, opts.id),
|
||||||
|
data: update
|
||||||
|
}, function (err, req, res, body) {
|
||||||
|
cb(err, body, res);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <http://apidocs.joyent.com/cloudapi/#DeletePolicy>
|
||||||
|
*
|
||||||
|
* @param {Object} opts (object) user object containing:
|
||||||
|
* - {String} id (required) of the policy to delete.
|
||||||
|
* @param {Function} cb of the form `function (err, res)`
|
||||||
|
*/
|
||||||
|
CloudApi.prototype.deletePolicy = function deletePolicy(opts, cb) {
|
||||||
|
assert.object(opts, 'opts');
|
||||||
|
assert.string(opts.id, 'opts.id');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
|
this._request({
|
||||||
|
method: 'DELETE',
|
||||||
|
path: format('/%s/policies/%s', this.account, opts.id)
|
||||||
|
}, function (err, req, res) {
|
||||||
|
cb(err, res);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// --- Exports
|
// --- Exports
|
||||||
|
@ -686,6 +686,15 @@ function ansiStylize(str, color) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function indent(s, indentation) {
|
||||||
|
if (!indentation) {
|
||||||
|
indentation = ' ';
|
||||||
|
}
|
||||||
|
var lines = s.split(/\r?\n/g);
|
||||||
|
return indentation + lines.join('\n' + indentation);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//---- exports
|
//---- exports
|
||||||
|
|
||||||
@ -709,6 +718,7 @@ module.exports = {
|
|||||||
promptEnter: promptEnter,
|
promptEnter: promptEnter,
|
||||||
promptField: promptField,
|
promptField: promptField,
|
||||||
editInEditor: editInEditor,
|
editInEditor: editInEditor,
|
||||||
ansiStylize: ansiStylize
|
ansiStylize: ansiStylize,
|
||||||
|
indent: indent
|
||||||
};
|
};
|
||||||
// vim: set softtabstop=4 shiftwidth=4:
|
// vim: set softtabstop=4 shiftwidth=4:
|
||||||
|
105
lib/do_rbac/do_policies.js
Normal file
105
lib/do_rbac/do_policies.js
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* 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 policies ...`
|
||||||
|
*/
|
||||||
|
|
||||||
|
var tabula = require('tabula');
|
||||||
|
|
||||||
|
var common = require('../common');
|
||||||
|
var errors = require('../errors');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// columns default without -o
|
||||||
|
var columnsDefault = 'shortid,name,description,nrules';
|
||||||
|
|
||||||
|
// columns default with -l
|
||||||
|
var columnsDefaultLong = 'id,name,rules';
|
||||||
|
|
||||||
|
// sort default with -s
|
||||||
|
var sortDefault = 'name';
|
||||||
|
|
||||||
|
|
||||||
|
function do_policies(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 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.listPolicies(function (err, policies) {
|
||||||
|
if (err) {
|
||||||
|
cb(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.json) {
|
||||||
|
common.jsonStream(policies);
|
||||||
|
} else {
|
||||||
|
var i, j;
|
||||||
|
// Add some convenience fields
|
||||||
|
for (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('; ');
|
||||||
|
}
|
||||||
|
|
||||||
|
tabula(policies, {
|
||||||
|
skipHeader: opts.H,
|
||||||
|
columns: columns,
|
||||||
|
sort: sort
|
||||||
|
});
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
do_policies.options = [
|
||||||
|
{
|
||||||
|
names: ['help', 'h'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'Show this help.'
|
||||||
|
}
|
||||||
|
].concat(common.getCliTableOptions({
|
||||||
|
includeLong: true,
|
||||||
|
sortDefault: sortDefault
|
||||||
|
}));
|
||||||
|
|
||||||
|
do_policies.help = (
|
||||||
|
/* BEGIN JSSTYLED */
|
||||||
|
'List RBAC policies.\n' +
|
||||||
|
'\n' +
|
||||||
|
'Usage:\n' +
|
||||||
|
' {{name}} policies [<options>]\n' +
|
||||||
|
'\n' +
|
||||||
|
'{{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 */
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = do_policies;
|
539
lib/do_rbac/do_policy.js
Normal file
539
lib/do_rbac/do_policy.js
Normal file
@ -0,0 +1,539 @@
|
|||||||
|
/*
|
||||||
|
* 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 policy ...`
|
||||||
|
*/
|
||||||
|
|
||||||
|
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');
|
||||||
|
|
||||||
|
|
||||||
|
var UPDATABLE_ROLE_FIELDS = [
|
||||||
|
{key: 'name', required: true},
|
||||||
|
{key: 'description'},
|
||||||
|
// Want 'rules' last for multiple yamlish repr, see below.
|
||||||
|
{key: 'rules', array: true}
|
||||||
|
];
|
||||||
|
|
||||||
|
var CREATE_ROLE_FIELDS = [
|
||||||
|
{key: 'name', required: true},
|
||||||
|
{key: 'description'},
|
||||||
|
{key: 'rules', array: true}
|
||||||
|
];
|
||||||
|
|
||||||
|
function _showPolicy(opts, cb) {
|
||||||
|
assert.object(opts.cli, 'opts.cli');
|
||||||
|
assert.string(opts.id, 'opts.id');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
var cli = opts.cli;
|
||||||
|
|
||||||
|
cli.tritonapi.getPolicy({
|
||||||
|
id: opts.id
|
||||||
|
}, function onPolicy(err, policy) {
|
||||||
|
if (err) {
|
||||||
|
cb(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.json) {
|
||||||
|
console.log(JSON.stringify(policy));
|
||||||
|
} else {
|
||||||
|
console.log('name: %s', policy.name);
|
||||||
|
delete policy.name;
|
||||||
|
var rules = policy.rules;
|
||||||
|
delete policy.rules;
|
||||||
|
Object.keys(policy).forEach(function (key) {
|
||||||
|
console.log('%s: %s', key, policy[key]);
|
||||||
|
});
|
||||||
|
// Do rules last because it is the sole multiline field. The
|
||||||
|
// rules can tend to be long, so we want to use multiline output.
|
||||||
|
console.log('rules:');
|
||||||
|
if (rules && rules.length) {
|
||||||
|
console.log(' ' + rules.join('\n '));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function _yamlishFromPolicy(policy) {
|
||||||
|
assert.object(policy, 'policy');
|
||||||
|
|
||||||
|
var lines = [];
|
||||||
|
UPDATABLE_ROLE_FIELDS.forEach(function (field) {
|
||||||
|
var key = field.key;
|
||||||
|
var val = policy[key];
|
||||||
|
if (key === 'rules') {
|
||||||
|
lines.push('rules:');
|
||||||
|
if (val && val.length) {
|
||||||
|
lines.push(' ' + val.join('\n '));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lines.push(format('%s: %s', key, val));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return lines.join('\n') + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
function _stripYamlishLine(line) {
|
||||||
|
var commentIdx = line.indexOf('#');
|
||||||
|
if (commentIdx !== -1) {
|
||||||
|
line = line.slice(0, commentIdx);
|
||||||
|
}
|
||||||
|
return line.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _policyFromYamlish(yamlish) {
|
||||||
|
assert.string(yamlish, 'yamlish');
|
||||||
|
|
||||||
|
var policy = {};
|
||||||
|
var lines = yamlish.split(/\n/g);
|
||||||
|
for (var i = 0; i < lines.length; i++) {
|
||||||
|
var line = _stripYamlishLine(lines[i]);
|
||||||
|
if (!line) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var parts = strsplit(line, ':', 2);
|
||||||
|
var key = parts[0].trim();
|
||||||
|
var value = parts[1].trim();
|
||||||
|
if (key === 'rules') {
|
||||||
|
rules = [];
|
||||||
|
if (value) {
|
||||||
|
rules.push(value);
|
||||||
|
}
|
||||||
|
// Remaining lines are rules.
|
||||||
|
for (var j = i+1; j < lines.length; j++) {
|
||||||
|
var line = _stripYamlishLine(lines[j]);
|
||||||
|
if (!line) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
rules.push(line);
|
||||||
|
}
|
||||||
|
policy['rules'] = rules;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
policy[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return policy;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function _editPolicy(opts, cb) {
|
||||||
|
assert.object(opts.cli, 'opts.cli');
|
||||||
|
assert.string(opts.id, 'opts.id');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
var cli = opts.cli;
|
||||||
|
|
||||||
|
var policy;
|
||||||
|
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 to policy.');
|
||||||
|
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 editedPolicy = _policyFromYamlish(afterText);
|
||||||
|
editedPolicy.id = policy.id;
|
||||||
|
|
||||||
|
if (_yamlishFromPolicy(editedPolicy) === origText) {
|
||||||
|
// This YAMLish is the closest to a canonical form we have.
|
||||||
|
console.log('No change to policy');
|
||||||
|
cb();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (textErr) {
|
||||||
|
console.error('Error with your changes: %s', textErr);
|
||||||
|
offerRetry(afterText);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save changes.
|
||||||
|
cli.tritonapi.cloudapi.updatePolicy(editedPolicy,
|
||||||
|
function (uErr, updated) {
|
||||||
|
if (uErr) {
|
||||||
|
var prefix = 'Error updating policy with your changes:'
|
||||||
|
var errmsg = uErr.toString();
|
||||||
|
if (errmsg.indexOf('\n') !== -1) {
|
||||||
|
console.error(prefix + '\n' + common.indent(errmsg));
|
||||||
|
} else {
|
||||||
|
console.error(prefix + ' ' + errmsg);
|
||||||
|
}
|
||||||
|
offerRetry(afterText);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('Updated policy "%s" (%s)',
|
||||||
|
updated.name, updated.id);
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cli.tritonapi.getPolicy({
|
||||||
|
id: opts.id
|
||||||
|
}, function onPolicy(err, policy_) {
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
policy = policy_;
|
||||||
|
filename = format('%s-policy-%s.txt', cli.tritonapi.profile.account,
|
||||||
|
policy.name);
|
||||||
|
origText = _yamlishFromPolicy(policy);
|
||||||
|
editAttempt(origText);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function _deletePolicies(opts, cb) {
|
||||||
|
assert.object(opts.cli, 'opts.cli');
|
||||||
|
assert.arrayOfString(opts.ids, 'opts.ids');
|
||||||
|
assert.optionalBool(opts.yes, 'opts.yes');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
var cli = opts.cli;
|
||||||
|
|
||||||
|
if (opts.ids.length === 0) {
|
||||||
|
cb();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vasync.pipeline({funcs: [
|
||||||
|
function confirm(_, next) {
|
||||||
|
if (opts.yes) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
var msg;
|
||||||
|
if (opts.ids.length === 1) {
|
||||||
|
msg = 'Delete policy "' + opts.ids[0] + '"? [y/n] ';
|
||||||
|
} else {
|
||||||
|
msg = format('Delete %d policies (%s)? [y/n] ',
|
||||||
|
opts.ids.length, opts.ids.join(', '));
|
||||||
|
}
|
||||||
|
common.promptYesNo({msg: msg}, function (answer) {
|
||||||
|
if (answer !== 'y') {
|
||||||
|
console.error('Aborting');
|
||||||
|
next(true); // early abort signal
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function deleteThem(_, next) {
|
||||||
|
vasync.forEachPipeline({
|
||||||
|
inputs: opts.ids,
|
||||||
|
func: function deleteOne(id, nextId) {
|
||||||
|
cli.tritonapi.deletePolicy({id: id}, function (err) {
|
||||||
|
if (err) {
|
||||||
|
nextId(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('Deleted policy "%s"', id);
|
||||||
|
nextId();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, next);
|
||||||
|
}
|
||||||
|
]}, function (err) {
|
||||||
|
if (err === true) {
|
||||||
|
err = null;
|
||||||
|
}
|
||||||
|
cb(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function _addPolicy(opts, cb) {
|
||||||
|
assert.object(opts.cli, 'opts.cli');
|
||||||
|
assert.optionalString(opts.file, 'opts.file');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
var cli = opts.cli;
|
||||||
|
var log = cli.log;
|
||||||
|
|
||||||
|
var data;
|
||||||
|
|
||||||
|
vasync.pipeline({funcs: [
|
||||||
|
function gatherDataStdin(_, next) {
|
||||||
|
if (opts.file !== '-') {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
var stdin = '';
|
||||||
|
process.stdin.resume();
|
||||||
|
process.stdin.on('data', function (chunk) {
|
||||||
|
stdin += chunk;
|
||||||
|
});
|
||||||
|
process.stdin.on('end', function () {
|
||||||
|
try {
|
||||||
|
data = JSON.parse(stdin);
|
||||||
|
} catch (err) {
|
||||||
|
log.trace({stdin: stdin}, 'invalid policy JSON on stdin');
|
||||||
|
return next(new errors.TritonError(
|
||||||
|
format('invalid policy JSON on stdin: %s', err)));
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function gatherDataFile(_, next) {
|
||||||
|
if (!opts.file || opts.file === '-') {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
var input = fs.readFileSync(opts.file);
|
||||||
|
try {
|
||||||
|
data = JSON.parse(input);
|
||||||
|
} catch (err) {
|
||||||
|
return next(new errors.TritonError(format(
|
||||||
|
'invalid policy JSON in "%s": %s', opts.file, err)));
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
function gatherDataInteractive(_, next) {
|
||||||
|
if (opts.file) {
|
||||||
|
return next();
|
||||||
|
} else if (!process.stdin.isTTY) {
|
||||||
|
return next(new errors.UsageError('cannot interactively ' +
|
||||||
|
'create a policy: stdin is not a TTY'));
|
||||||
|
} else if (!process.stdout.isTTY) {
|
||||||
|
return next(new errors.UsageError('cannot interactively ' +
|
||||||
|
'create a policy: stdout is not a TTY'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: better validation of name, rules
|
||||||
|
// TODO: retries on failure
|
||||||
|
// TODO: on failure write out to a tmp file with cmd to add it
|
||||||
|
data = {};
|
||||||
|
vasync.forEachPipeline({
|
||||||
|
inputs: CREATE_ROLE_FIELDS,
|
||||||
|
func: function getField(field, nextField) {
|
||||||
|
if (field.key === 'rules') {
|
||||||
|
var rules = [];
|
||||||
|
var rulePrompt = {
|
||||||
|
key: 'rule',
|
||||||
|
desc: 'Enter one rule per line. Enter an empty ' +
|
||||||
|
'rule to finish rules. See ' +
|
||||||
|
// JSSTYLED
|
||||||
|
'<https://docs.joyent.com/public-cloud/rbac/rules> ' +
|
||||||
|
'for rule syntax and examples.'
|
||||||
|
};
|
||||||
|
var promptAnotherRule = function () {
|
||||||
|
common.promptField(rulePrompt, function (err, val) {
|
||||||
|
delete rulePrompt.desc; // only want first time
|
||||||
|
if (err) {
|
||||||
|
nextField(err);
|
||||||
|
} else if (!val) {
|
||||||
|
// Done rules.
|
||||||
|
data.rules = rules;
|
||||||
|
nextField();
|
||||||
|
} else {
|
||||||
|
rules.push(val);
|
||||||
|
promptAnotherRule();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
promptAnotherRule();
|
||||||
|
} else {
|
||||||
|
common.promptField(field, function (err, value) {
|
||||||
|
if (value) {
|
||||||
|
data[field.key] = value;
|
||||||
|
}
|
||||||
|
nextField(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, function (err) {
|
||||||
|
console.log();
|
||||||
|
next(err);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function validateData(_, next) {
|
||||||
|
var missing = [];
|
||||||
|
var dataCopy = common.objCopy(data);
|
||||||
|
CREATE_ROLE_FIELDS.forEach(function (field) {
|
||||||
|
if (dataCopy.hasOwnProperty(field.key)) {
|
||||||
|
delete dataCopy[field.key];
|
||||||
|
} else if (field.required) {
|
||||||
|
missing.push(field.key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var extra = Object.keys(dataCopy);
|
||||||
|
var issues = [];
|
||||||
|
if (missing.length) {
|
||||||
|
issues.push(format('%s missing required field%s: %s',
|
||||||
|
missing.length, (missing.length === 1 ? '' : 's'),
|
||||||
|
missing.join(', ')));
|
||||||
|
}
|
||||||
|
if (extra.length) {
|
||||||
|
issues.push(format('extraneous field%s: %s',
|
||||||
|
(extra.length === 1 ? '' : 's'), extra.join(', ')));
|
||||||
|
}
|
||||||
|
if (issues.length) {
|
||||||
|
next(new errors.TritonError(
|
||||||
|
'invalid policy data: ' + issues.join('; ')));
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function createIt(_, next) {
|
||||||
|
cli.tritonapi.cloudapi.createPolicy(data, function (err, policy) {
|
||||||
|
if (err) {
|
||||||
|
next(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('Created policy "%s"', policy.name);
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
]}, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function do_policy(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['delete']) { actions.push('delete'); }
|
||||||
|
var action;
|
||||||
|
if (actions.length === 0) {
|
||||||
|
action = 'show';
|
||||||
|
} 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 && ['show', 'edit'].indexOf(action) !== -1) {
|
||||||
|
return cb(new errors.UsageError('POLICY argument is required'));
|
||||||
|
} else if (action !== 'delete' && args.length > 1) {
|
||||||
|
return cb(new errors.UsageError('too many arguments'));
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case 'show':
|
||||||
|
_showPolicy({
|
||||||
|
cli: this.top,
|
||||||
|
id: args[0],
|
||||||
|
json: opts.json
|
||||||
|
}, cb);
|
||||||
|
break;
|
||||||
|
case 'edit':
|
||||||
|
_editPolicy({
|
||||||
|
cli: this.top,
|
||||||
|
id: args[0]
|
||||||
|
}, cb);
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
_deletePolicies({
|
||||||
|
cli: this.top,
|
||||||
|
ids: args,
|
||||||
|
yes: opts.yes
|
||||||
|
}, cb);
|
||||||
|
break;
|
||||||
|
case 'add':
|
||||||
|
_addPolicy({cli: this.top, file: args[0]}, cb);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return cb(new errors.InternalError('unknown action: ' + action));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
do_policy.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: ['edit', 'e'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'Edit the named policy in your $EDITOR.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['add', 'a'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'Add a new policy.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
names: ['delete', 'd'],
|
||||||
|
type: 'bool',
|
||||||
|
help: 'Delete the named policy.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
do_policy.help = [
|
||||||
|
/* BEGIN JSSTYLED */
|
||||||
|
'Show, add, edit and delete RBAC policies.',
|
||||||
|
'',
|
||||||
|
'Usage:',
|
||||||
|
' {{name}} policy POLICY # show policy POLICY',
|
||||||
|
' {{name}} policy -e|--edit POLICY # edit policy POLICY in $EDITOR',
|
||||||
|
' {{name}} policy -d|--delete [POLICY...] # delete policy POLICY',
|
||||||
|
'',
|
||||||
|
' {{name}} policy -a|--add [FILE]',
|
||||||
|
' # Add a new policy. FILE must be a file path to a JSON file',
|
||||||
|
' # with the policy data or "-" to pass the policy in on stdin.',
|
||||||
|
' # Or exclude FILE to interactively add.',
|
||||||
|
'',
|
||||||
|
'{{options}}',
|
||||||
|
'Where "POLICY" is a full policy "id", the policy "login" name or a "shortid", i.e.',
|
||||||
|
'an id prefix.',
|
||||||
|
'',
|
||||||
|
'Fields for creating a policy:',
|
||||||
|
CREATE_ROLE_FIELDS.map(function (field) {
|
||||||
|
return ' ' + field.key + (field.required ? ' (required)' : '');
|
||||||
|
}).join('\n')
|
||||||
|
/* END JSSTYLED */
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
module.exports = do_policy;
|
@ -49,13 +49,11 @@ function _arrayFromCSV(csv) {
|
|||||||
function _showRole(opts, cb) {
|
function _showRole(opts, cb) {
|
||||||
assert.object(opts.cli, 'opts.cli');
|
assert.object(opts.cli, 'opts.cli');
|
||||||
assert.string(opts.id, 'opts.id');
|
assert.string(opts.id, 'opts.id');
|
||||||
assert.optionalBool(opts.roles, 'opts.roles');
|
|
||||||
assert.func(cb, 'cb');
|
assert.func(cb, 'cb');
|
||||||
var cli = opts.cli;
|
var cli = opts.cli;
|
||||||
|
|
||||||
cli.tritonapi.getRole({
|
cli.tritonapi.getRole({
|
||||||
id: opts.id,
|
id: opts.id
|
||||||
roles: opts.roles
|
|
||||||
}, function onRole(err, role) {
|
}, function onRole(err, role) {
|
||||||
if (err) {
|
if (err) {
|
||||||
cb(err);
|
cb(err);
|
||||||
@ -222,8 +220,8 @@ function _deleteRoles(opts, cb) {
|
|||||||
if (opts.ids.length === 1) {
|
if (opts.ids.length === 1) {
|
||||||
msg = 'Delete role "' + opts.ids[0] + '"? [y/n] ';
|
msg = 'Delete role "' + opts.ids[0] + '"? [y/n] ';
|
||||||
} else {
|
} else {
|
||||||
msg = 'Delete %d roles (' + opts.ids.join(', ') + ')? [y/n] ';
|
msg = format('Delete %d roles (%s)? [y/n] ',
|
||||||
|
opts.ids.length, opts.ids.join(', '));
|
||||||
}
|
}
|
||||||
common.promptYesNo({msg: msg}, function (answer) {
|
common.promptYesNo({msg: msg}, function (answer) {
|
||||||
if (answer !== 'y') {
|
if (answer !== 'y') {
|
||||||
@ -405,12 +403,10 @@ function do_role(subcmd, opts, args, cb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Arg count validation.
|
// Arg count validation.
|
||||||
if (args.length > 1) {
|
if (args.length === 0 && ['show', 'edit'].indexOf(action) !== -1) {
|
||||||
return cb(new errors.UsageError('too many arguments'));
|
|
||||||
} else if (args.length === 0 &&
|
|
||||||
['show', 'edit'].indexOf(action) !== -1)
|
|
||||||
{
|
|
||||||
return cb(new errors.UsageError('ROLE argument is required'));
|
return cb(new errors.UsageError('ROLE argument is required'));
|
||||||
|
} else if (action !== 'delete' && args.length > 1) {
|
||||||
|
return cb(new errors.UsageError('too many arguments'));
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
@ -453,19 +449,6 @@ do_role.options = [
|
|||||||
type: 'bool',
|
type: 'bool',
|
||||||
help: 'JSON stream output.'
|
help: 'JSON stream output.'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
names: ['roles', 'r'],
|
|
||||||
type: 'bool',
|
|
||||||
help: 'Include "roles" and "default_roles" this user has.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
names: ['membership'],
|
|
||||||
type: 'bool',
|
|
||||||
help: 'Include "roles" and "default_roles" this user has. Included ' +
|
|
||||||
'for backward compat with `sdc-user get --membership ...` from ' +
|
|
||||||
'node-smartdc.',
|
|
||||||
hidden: true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
names: ['yes', 'y'],
|
names: ['yes', 'y'],
|
||||||
type: 'bool',
|
type: 'bool',
|
||||||
@ -477,17 +460,17 @@ do_role.options = [
|
|||||||
{
|
{
|
||||||
names: ['edit', 'e'],
|
names: ['edit', 'e'],
|
||||||
type: 'bool',
|
type: 'bool',
|
||||||
help: 'Edit the named user in your $EDITOR.'
|
help: 'Edit the named role in your $EDITOR.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
names: ['add', 'a'],
|
names: ['add', 'a'],
|
||||||
type: 'bool',
|
type: 'bool',
|
||||||
help: 'Add a new user.'
|
help: 'Add a new role.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
names: ['delete', 'd'],
|
names: ['delete', 'd'],
|
||||||
type: 'bool',
|
type: 'bool',
|
||||||
help: 'Delete the named user.'
|
help: 'Delete the named role.'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
do_role.help = [
|
do_role.help = [
|
||||||
|
@ -211,8 +211,8 @@ function _deleteUsers(opts, cb) {
|
|||||||
if (opts.ids.length === 1) {
|
if (opts.ids.length === 1) {
|
||||||
msg = 'Delete user "' + opts.ids[0] + '"? [y/n] ';
|
msg = 'Delete user "' + opts.ids[0] + '"? [y/n] ';
|
||||||
} else {
|
} else {
|
||||||
msg = 'Delete %d users (' + opts.ids.join(', ') + ')? [y/n] ';
|
msg = format('Delete %d users (%s)? [y/n] ',
|
||||||
|
opts.ids.length, opts.ids.join(', '));
|
||||||
}
|
}
|
||||||
common.promptYesNo({msg: msg}, function (answer) {
|
common.promptYesNo({msg: msg}, function (answer) {
|
||||||
if (answer !== 'y') {
|
if (answer !== 'y') {
|
||||||
@ -385,12 +385,10 @@ function do_user(subcmd, opts, args, cb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Arg count validation.
|
// Arg count validation.
|
||||||
if (args.length > 1) {
|
if (args.length === 0 && ['show', 'edit'].indexOf(action) !== -1) {
|
||||||
return cb(new errors.UsageError('too many arguments'));
|
|
||||||
} else if (args.length === 0 &&
|
|
||||||
['show', 'edit'].indexOf(action) !== -1)
|
|
||||||
{
|
|
||||||
return cb(new errors.UsageError('USER argument is required'));
|
return cb(new errors.UsageError('USER argument is required'));
|
||||||
|
} else if (action !== 'delete' && args.length > 1) {
|
||||||
|
return cb(new errors.UsageError('too many arguments'));
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
|
@ -47,4 +47,7 @@ RbacCLI.prototype.do_user = require('./do_user');
|
|||||||
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');
|
||||||
|
RbacCLI.prototype.do_policy = require('./do_policy');
|
||||||
|
|
||||||
module.exports = RbacCLI;
|
module.exports = RbacCLI;
|
||||||
|
145
lib/tritonapi.js
145
lib/tritonapi.js
@ -811,7 +811,7 @@ TritonApi.prototype.getRole = function getRole(opts, cb) {
|
|||||||
*
|
*
|
||||||
* @param {Object} opts
|
* @param {Object} opts
|
||||||
* - id {UUID|String} The role id (a UUID), name or short id.
|
* - id {UUID|String} The role id (a UUID), name or short id.
|
||||||
* @param {Function} callback of the form `function (err, user)`
|
* @param {Function} callback of the form `function (err)`
|
||||||
*/
|
*/
|
||||||
TritonApi.prototype.deleteRole = function deleteRole(opts, cb) {
|
TritonApi.prototype.deleteRole = function deleteRole(opts, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
@ -832,8 +832,12 @@ TritonApi.prototype.deleteRole = function deleteRole(opts, cb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.getRole({id: opts.id}, function (err, role) {
|
self.getRole({id: opts.id}, function (err, role) {
|
||||||
ctx.id = role.id;
|
if (err) {
|
||||||
next(err);
|
next(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.id = role.id;
|
||||||
|
next();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -846,6 +850,143 @@ TritonApi.prototype.deleteRole = function deleteRole(opts, cb) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an RBAC policy by ID, name, or short ID, in that order.
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
|
* @param {Function} callback of the form `function (err)`
|
||||||
|
*/
|
||||||
|
TritonApi.prototype.deletePolicy = function deletePolicy(opts, cb) {
|
||||||
|
var self = this;
|
||||||
|
assert.object(opts, 'opts');
|
||||||
|
assert.string(opts.id, 'opts.id');
|
||||||
|
assert.func(cb, 'cb');
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CloudAPI DeletePolicy only accepts a policy id (UUID).
|
||||||
|
*/
|
||||||
|
var context = {};
|
||||||
|
vasync.pipeline({arg: context, funcs: [
|
||||||
|
function getId(ctx, next) {
|
||||||
|
if (common.isUUID(opts.id)) {
|
||||||
|
ctx.id = opts.id;
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.getPolicy({id: opts.id}, function (err, policy) {
|
||||||
|
if (err) {
|
||||||
|
next(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.id = policy.id;
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
function deleteIt(ctx, next) {
|
||||||
|
self.cloudapi.deletePolicy({id: ctx.id}, next);
|
||||||
|
}
|
||||||
|
]}, function (err) {
|
||||||
|
cb(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
//---- exports
|
//---- exports
|
||||||
|
|
||||||
module.exports.createClient = function (options) {
|
module.exports.createClient = function (options) {
|
||||||
|
Reference in New Issue
Block a user