Update Api code.
This commit is contained in:
parent
1192bf6a87
commit
0d4b9b4bce
477
src/api.js
477
src/api.js
@ -1,315 +1,200 @@
|
||||
'use strict';
|
||||
import debug from 'debug';
|
||||
debug = debug('xo:api');
|
||||
|
||||
import assign from 'lodash.assign';
|
||||
import Bluebird from 'bluebird';
|
||||
import forEach from 'lodash.foreach';
|
||||
import getKeys from 'lodash.keys';
|
||||
import isFunction from 'lodash.isfunction';
|
||||
import requireTree from 'require-tree';
|
||||
import schemaInspector from 'schema-inspector';
|
||||
import {coroutine} from 'bluebird';
|
||||
|
||||
import {
|
||||
InvalidParameters,
|
||||
MethodNotFound,
|
||||
NoSuchObject,
|
||||
Unauthorized,
|
||||
} from './api-errors';
|
||||
|
||||
//====================================================================
|
||||
|
||||
var assign = require('lodash.assign');
|
||||
var Bluebird = require('bluebird');
|
||||
var debug = require('debug')('xo:api');
|
||||
var forEach = require('lodash.foreach');
|
||||
var isArray = require('lodash.isarray');
|
||||
var isFunction = require('lodash.isfunction');
|
||||
var isObject = require('lodash.isobject');
|
||||
var isString = require('lodash.isstring');
|
||||
var keys = require('lodash.keys');
|
||||
var pick = require('lodash.pick');
|
||||
var requireTree = require('require-tree');
|
||||
var schemaInspector = require('schema-inspector');
|
||||
// FIXME: this function is specific to XO and should not be defined in
|
||||
// this file.
|
||||
let checkPermission = coroutine(function *(permission) {
|
||||
/* jshint validthis: true */
|
||||
|
||||
var apiErrors = require('./api-errors');
|
||||
var coroutine = require('./fibers-utils').$coroutine;
|
||||
var InvalidParameters = require('./api-errors').InvalidParameters;
|
||||
var MethodNotFound = require('./api-errors').MethodNotFound;
|
||||
var Unauthorized = require('./api-errors').Unauthorized;
|
||||
var wait = require('./fibers-utils').$wait;
|
||||
var wrap = require('./utils').wrap;
|
||||
let userId = this.session.get('user_id', undefined);
|
||||
|
||||
//====================================================================
|
||||
if (userId === undefined) {
|
||||
throw new Unauthorized();
|
||||
}
|
||||
|
||||
function $deprecated(fn)
|
||||
{
|
||||
return function (session, req) {
|
||||
console.warn(req.method +' is deprecated!');
|
||||
if (!permission) {
|
||||
return;
|
||||
}
|
||||
|
||||
return fn.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
let user = yield this.users.first(userId);
|
||||
|
||||
//====================================================================
|
||||
|
||||
// TODO: Helper functions that could be written:
|
||||
// - checkParams(req.params, param1, ..., paramN)
|
||||
|
||||
var helpers = {};
|
||||
|
||||
helpers.checkPermission = function (permission)
|
||||
{
|
||||
// TODO: Handle token permission.
|
||||
|
||||
var userId = this.session.get('user_id', undefined);
|
||||
|
||||
if (undefined === userId)
|
||||
{
|
||||
throw new Unauthorized();
|
||||
}
|
||||
|
||||
if (!permission)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var user = wait(this.users.first(userId));
|
||||
// The user MUST exist at this time.
|
||||
|
||||
if (!user.hasPermission(permission))
|
||||
{
|
||||
throw new Unauthorized();
|
||||
}
|
||||
};
|
||||
|
||||
// Checks and returns parameters.
|
||||
helpers.getParams = function (schema) {
|
||||
var params = this.request.params;
|
||||
|
||||
schema = {
|
||||
type: 'object',
|
||||
properties: schema,
|
||||
};
|
||||
|
||||
var result = schemaInspector.validate(schema, params);
|
||||
|
||||
if (!result.valid)
|
||||
{
|
||||
throw new InvalidParameters(result.error);
|
||||
}
|
||||
|
||||
return params;
|
||||
};
|
||||
|
||||
helpers.getUserPublicProperties = function (user) {
|
||||
// Handles both properties and wrapped models.
|
||||
var properties = user.properties || user;
|
||||
|
||||
return pick(properties, 'id', 'email', 'permission');
|
||||
};
|
||||
|
||||
helpers.getServerPublicProperties = function (server) {
|
||||
// Handles both properties and wrapped models.
|
||||
var properties = server.properties || server;
|
||||
|
||||
return pick(properties, 'id', 'host', 'username');
|
||||
};
|
||||
|
||||
// Deprecated!
|
||||
var errorClasses = {
|
||||
ALREADY_AUTHENTICATED: apiErrors.AlreadyAuthenticated,
|
||||
INVALID_CREDENTIAL: apiErrors.InvalidCredential,
|
||||
INVALID_PARAMS: apiErrors.InvalidParameters,
|
||||
NO_SUCH_OBJECT: apiErrors.NoSuchObject,
|
||||
NOT_IMPLEMENTED: apiErrors.NotImplementd,
|
||||
};
|
||||
helpers.throw = function (errorId, data) {
|
||||
throw new (errorClasses[errorId])(data);
|
||||
};
|
||||
|
||||
//====================================================================
|
||||
|
||||
function Api(xo)
|
||||
{
|
||||
if ( !(this instanceof Api) )
|
||||
{
|
||||
return new Api(xo);
|
||||
}
|
||||
|
||||
this.xo = xo;
|
||||
}
|
||||
|
||||
var execHelper = coroutine(function (session, request) {
|
||||
var ctx = Object.create(this.xo);
|
||||
assign(ctx, helpers, {
|
||||
session: session,
|
||||
request: request,
|
||||
});
|
||||
|
||||
var method = this.getMethod(request.method);
|
||||
|
||||
if (!method)
|
||||
{
|
||||
console.warn('Invalid method: '+ request.method);
|
||||
throw new MethodNotFound(request.method);
|
||||
}
|
||||
|
||||
if ('permission' in method)
|
||||
{
|
||||
helpers.checkPermission.call(ctx, method.permission);
|
||||
}
|
||||
|
||||
if (method.params)
|
||||
{
|
||||
helpers.getParams.call(ctx, method.params);
|
||||
}
|
||||
|
||||
return method.call(ctx, request.params);
|
||||
if (!user.hasPermission(permission)) {
|
||||
throw new Unauthorized();
|
||||
}
|
||||
});
|
||||
|
||||
Api.prototype.exec = function (session, request) {
|
||||
var method = request.method;
|
||||
|
||||
debug('%s(...)', method);
|
||||
|
||||
return Bluebird.try(execHelper, [session, request], this).then(
|
||||
function (result) {
|
||||
// If nothing was returned, consider this operation a success
|
||||
// and return true.
|
||||
if (result === undefined) {
|
||||
result = true;
|
||||
}
|
||||
|
||||
debug('%s(...) → %s', method, typeof result);
|
||||
|
||||
return result;
|
||||
},
|
||||
function (error) {
|
||||
debug('Error: %s(...) → %s', method, error);
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
Api.prototype.getMethod = function (name) {
|
||||
var parts = name.split('.');
|
||||
|
||||
var current = Api.fn;
|
||||
for (
|
||||
var i = 0, n = parts.length;
|
||||
(i < n) && (current = current[parts[i]]);
|
||||
++i
|
||||
)
|
||||
{
|
||||
/* jshint noempty:false */
|
||||
}
|
||||
|
||||
// Method found.
|
||||
if (isFunction(current))
|
||||
{
|
||||
return current;
|
||||
}
|
||||
|
||||
// It's a (deprecated) alias.
|
||||
if (isString(current))
|
||||
{
|
||||
return $deprecated(this.getMethod(current));
|
||||
}
|
||||
|
||||
// No entry found, looking for a catch-all method.
|
||||
current = Api.fn;
|
||||
var catchAll;
|
||||
for (i = 0; (i < n) && (current = current[parts[i]]); ++i)
|
||||
{
|
||||
catchAll = current.__catchAll || catchAll;
|
||||
}
|
||||
|
||||
return catchAll;
|
||||
};
|
||||
|
||||
module.exports = Api;
|
||||
|
||||
//====================================================================
|
||||
|
||||
var $register = function (path, fn, params) {
|
||||
var component, current;
|
||||
|
||||
if (params)
|
||||
{
|
||||
fn.params = params;
|
||||
}
|
||||
|
||||
if (!isArray(path))
|
||||
{
|
||||
path = path.split('.');
|
||||
}
|
||||
|
||||
current = Api.fn;
|
||||
for (var i = 0, n = path.length - 1; i < n; ++i)
|
||||
{
|
||||
component = path[i];
|
||||
current = (current[component] || (current[component] = {}));
|
||||
}
|
||||
|
||||
if (isFunction(fn))
|
||||
{
|
||||
current[path[n]] = fn;
|
||||
}
|
||||
else if (isObject(fn) && !isArray(fn))
|
||||
{
|
||||
// If it is not an function but an object, copies its
|
||||
// properties.
|
||||
|
||||
component = path[n];
|
||||
current = (current[component] || (current[component] = {}));
|
||||
|
||||
assign(current, fn);
|
||||
}
|
||||
else
|
||||
{
|
||||
current[path[n]] = wrap(fn);
|
||||
}
|
||||
};
|
||||
|
||||
Api.fn = requireTree('./api');
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
$register('system.getVersion', wrap('0.1'));
|
||||
function checkParams(method, params) {
|
||||
var schema = method.params;
|
||||
if (!schema) {
|
||||
return;
|
||||
}
|
||||
|
||||
$register('xo.getAllObjects', function () {
|
||||
return this.getObjects();
|
||||
});
|
||||
let result = schemaInspector.validate({
|
||||
type: 'object',
|
||||
properties: schema,
|
||||
}, params);
|
||||
|
||||
// Returns the list of available methods similar to XML-RPC
|
||||
// introspection (http://xmlrpc-c.sourceforge.net/introspection.html).
|
||||
(function () {
|
||||
var methods = {};
|
||||
if (!result.valid) {
|
||||
throw new InvalidParameters(result.error);
|
||||
}
|
||||
}
|
||||
|
||||
(function browse(container, path) {
|
||||
var n = path.length;
|
||||
forEach(container, function (content, key) {
|
||||
path[n] = key;
|
||||
if (isFunction(content))
|
||||
{
|
||||
methods[path.join('.')] = {
|
||||
description: content.description,
|
||||
params: content.params || {},
|
||||
permission: content.permission,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
browse(content, path);
|
||||
}
|
||||
});
|
||||
path.pop();
|
||||
})(Api.fn, []);
|
||||
//====================================================================
|
||||
|
||||
$register('system.listMethods', wrap(keys(methods)));
|
||||
$register('system.methodSignature', function (params) {
|
||||
var method = methods[params.name];
|
||||
function getMethodsInfo() {
|
||||
let methods = {};
|
||||
|
||||
if (!method)
|
||||
{
|
||||
this.throw('NO_SUCH_OBJECT');
|
||||
}
|
||||
forEach(this.api._methods, function (method, name) {
|
||||
this[name] = assign({}, {
|
||||
description: method.description,
|
||||
params: method.params || {},
|
||||
permission: method.permission,
|
||||
});
|
||||
}, methods);
|
||||
|
||||
// XML-RPC can have multiple signatures per method.
|
||||
return [
|
||||
// XML-RPC requires the method name.
|
||||
assign({name: params.name}, method)
|
||||
];
|
||||
}, {
|
||||
name: {
|
||||
description: 'method to describe',
|
||||
type: 'string',
|
||||
},
|
||||
});
|
||||
return methods;
|
||||
}
|
||||
getMethodsInfo.description = 'returns the signatures of all available API methods';
|
||||
|
||||
$register('system.getMethodsInfo', wrap(methods));
|
||||
})();
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
let getVersion = () => '0.1';
|
||||
getVersion.description = 'API version (unstable)';
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
function listMethods() {
|
||||
return getKeys(this.api._methods);
|
||||
}
|
||||
listMethods.description = 'returns the name of all available API methods';
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
function methodSignature({method: name}) {
|
||||
let method = this.api.getMethod(name);
|
||||
|
||||
if (!method) {
|
||||
throw new NoSuchObject();
|
||||
}
|
||||
|
||||
// Return an array for compatibility with XML-RPC.
|
||||
return [
|
||||
// XML-RPC require the name of the method.
|
||||
assign({ name }, {
|
||||
description: method.description,
|
||||
params: method.params || {},
|
||||
permission: method.permission,
|
||||
})
|
||||
];
|
||||
}
|
||||
methodSignature.description = 'returns the signature of an API method';
|
||||
|
||||
//====================================================================
|
||||
|
||||
|
||||
export default class Api {
|
||||
constructor({context} = {}) {
|
||||
this._methods = Object.create(null);
|
||||
this.context = context;
|
||||
|
||||
this.addMethods({
|
||||
system: {
|
||||
getMethodsInfo,
|
||||
getVersion,
|
||||
listMethods,
|
||||
methodSignature,
|
||||
}
|
||||
});
|
||||
|
||||
// FIXME: this too is specific to XO and should be moved out of this file.
|
||||
this.addMethods(requireTree('./api'));
|
||||
}
|
||||
|
||||
addMethod(name, method) {
|
||||
this._methods[name] = method;
|
||||
}
|
||||
|
||||
addMethods(methods) {
|
||||
let base = '';
|
||||
forEach(methods, function addMethod(method, name) {
|
||||
name = base + name;
|
||||
|
||||
if (isFunction(method)) {
|
||||
this.addMethod(name, method);
|
||||
return;
|
||||
}
|
||||
|
||||
let oldBase = base;
|
||||
base = name + '.';
|
||||
forEach(method, addMethod, this);
|
||||
base = oldBase;
|
||||
}, this);
|
||||
}
|
||||
|
||||
call(session, name, params) {
|
||||
debug('%s(...)', name);
|
||||
|
||||
let method;
|
||||
let context;
|
||||
|
||||
return Bluebird.try(() => {
|
||||
method = this.getMethod(name);
|
||||
if (!method) {
|
||||
throw new MethodNotFound(name);
|
||||
}
|
||||
|
||||
context = Object.create(this.context);
|
||||
context.api = this; // Used by system.*().
|
||||
context.session = session;
|
||||
|
||||
if ('permission' in method) {
|
||||
return checkPermission.call(context, method.permission);
|
||||
}
|
||||
}).then(() => {
|
||||
checkParams(method, params);
|
||||
|
||||
return method.call(context, params);
|
||||
}).then(
|
||||
result => {
|
||||
// If nothing was returned, consider this operation a success
|
||||
// and return true.
|
||||
if (result === undefined) {
|
||||
result = true;
|
||||
}
|
||||
|
||||
debug('%s(...) → %s', name, typeof result);
|
||||
|
||||
return result;
|
||||
},
|
||||
error => {
|
||||
debug('Error: %s(...) → %s', name, error);
|
||||
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getMethod(name) {
|
||||
return this._methods[name];
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ get.params = {
|
||||
object: { type: 'string', optional: true },
|
||||
};
|
||||
|
||||
get.description = 'get existing ACLs'
|
||||
get.description = 'get existing ACLs';
|
||||
|
||||
export {get};
|
||||
|
||||
@ -56,7 +56,7 @@ let add = coroutine(function *({subject, object}) {
|
||||
yield this.acls.create(subject, object);
|
||||
} catch (error) {
|
||||
if (!(error instanceof ModelAlreadyExists)) {
|
||||
throw error
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1,8 +1,8 @@
|
||||
{$wait} = require '../fibers-utils'
|
||||
{$coroutine, $wait} = require '../fibers-utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
exports.set = (params) ->
|
||||
exports.set = $coroutine (params) ->
|
||||
try
|
||||
host = @getObject params.id, 'host'
|
||||
catch
|
||||
@ -33,7 +33,7 @@ exports.set.params =
|
||||
type: 'boolean'
|
||||
optional: true
|
||||
|
||||
exports.restart = ({id}) ->
|
||||
exports.restart = $coroutine ({id}) ->
|
||||
@checkPermission 'admin'
|
||||
|
||||
try
|
||||
@ -52,7 +52,7 @@ exports.restart.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.restart_agent = ({id}) ->
|
||||
exports.restart_agent = $coroutine ({id}) ->
|
||||
try
|
||||
host = @getObject id, 'host'
|
||||
catch
|
||||
@ -68,7 +68,7 @@ exports.restart_agent.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.start = ({id}) ->
|
||||
exports.start = $coroutine ({id}) ->
|
||||
try
|
||||
host = @getObject id, 'host'
|
||||
catch
|
||||
@ -84,7 +84,7 @@ exports.restart_agent.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.stop = ({id}) ->
|
||||
exports.stop = $coroutine ({id}) ->
|
||||
try
|
||||
host = @getObject id, 'host'
|
||||
catch
|
||||
@ -101,7 +101,7 @@ exports.stop.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.detach = ({id}) ->
|
||||
exports.detach = $coroutine ({id}) ->
|
||||
try
|
||||
host = @getObject id, 'host'
|
||||
catch
|
||||
@ -117,7 +117,7 @@ exports.detach.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.enable = ({id}) ->
|
||||
exports.enable = $coroutine ({id}) ->
|
||||
try
|
||||
host = @getObject id, 'host'
|
||||
catch
|
||||
@ -133,7 +133,7 @@ exports.stop.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.disable = ({id}) ->
|
||||
exports.disable = $coroutine ({id}) ->
|
||||
try
|
||||
host = @getObject id, 'host'
|
||||
catch
|
||||
|
@ -1,8 +1,8 @@
|
||||
{$wait} = require '../fibers-utils'
|
||||
{$coroutine, $wait} = require '../fibers-utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
exports.delete = ({id}) ->
|
||||
exports.delete = $coroutine ({id}) ->
|
||||
try
|
||||
message = @getObject id, 'message'
|
||||
catch
|
||||
|
@ -1,10 +1,10 @@
|
||||
# FIXME: too low level, should be removed.
|
||||
|
||||
{$wait} = require '../fibers-utils'
|
||||
{$coroutine, $wait} = require '../fibers-utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
exports.delete = ({id}) ->
|
||||
exports.delete = $coroutine ({id}) ->
|
||||
try
|
||||
VBD = @getObject id, 'PBD'
|
||||
catch
|
||||
@ -21,7 +21,7 @@ exports.delete.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.disconnect = ({id}) ->
|
||||
exports.disconnect = $coroutine ({id}) ->
|
||||
try
|
||||
PBD = @getObject id, 'PBD'
|
||||
catch
|
||||
@ -38,7 +38,7 @@ exports.disconnect.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.connect = ({id}) ->
|
||||
exports.connect = $coroutine ({id}) ->
|
||||
try
|
||||
PBD = @getObject id, 'PBD'
|
||||
catch
|
||||
|
@ -1,8 +1,8 @@
|
||||
{$wait} = require '../fibers-utils'
|
||||
{$coroutine, $wait} = require '../fibers-utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
exports.delete = ({id}) ->
|
||||
exports.delete = $coroutine ({id}) ->
|
||||
try
|
||||
PIF = @getObject id, 'PIF'
|
||||
catch
|
||||
@ -19,7 +19,7 @@ exports.delete.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.disconnect = ({id}) ->
|
||||
exports.disconnect = $coroutine ({id}) ->
|
||||
try
|
||||
PIF = @getObject id, 'PIF'
|
||||
catch
|
||||
@ -36,7 +36,7 @@ exports.disconnect.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.connect = ({id}) ->
|
||||
exports.connect = $coroutine ({id}) ->
|
||||
try
|
||||
PIF = @getObject id, 'PIF'
|
||||
catch
|
||||
|
@ -3,7 +3,7 @@ $debug = (require 'debug') 'xo:api:vm'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
exports.set = (params) ->
|
||||
exports.set = $coroutine (params) ->
|
||||
try
|
||||
pool = @getObject params.id, 'pool'
|
||||
catch
|
||||
@ -32,7 +32,7 @@ exports.set.params =
|
||||
optional: true
|
||||
|
||||
# FIXME
|
||||
exports.patch = ({pool}) ->
|
||||
exports.patch = $coroutine ({pool}) ->
|
||||
try
|
||||
pool = @getObject pool, 'pool'
|
||||
catch
|
||||
|
@ -1,4 +1,4 @@
|
||||
{$wait} = require '../fibers-utils'
|
||||
{$coroutine, $wait} = require '../fibers-utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
# Could we use tokens instead?
|
||||
|
||||
# Adds a new server.
|
||||
exports.add = ({host, username, password}) ->
|
||||
exports.add = $coroutine ({host, username, password}) ->
|
||||
server = $wait @servers.add {
|
||||
host
|
||||
username
|
||||
@ -25,7 +25,7 @@ exports.add.params =
|
||||
type: 'string'
|
||||
|
||||
# Removes an existing server.
|
||||
exports.remove = ({id}) ->
|
||||
exports.remove = $coroutine ({id}) ->
|
||||
# Throws an error if the server did not exist.
|
||||
@throw 'NO_SUCH_OBJECT' unless $wait @servers.remove id
|
||||
|
||||
@ -36,7 +36,7 @@ exports.remove.params =
|
||||
type: 'string'
|
||||
|
||||
# Returns all servers.
|
||||
exports.getAll = ->
|
||||
exports.getAll = $coroutine ->
|
||||
# Retrieves the servers.
|
||||
servers = $wait @servers.get()
|
||||
|
||||
@ -48,7 +48,7 @@ exports.getAll = ->
|
||||
exports.getAll.permission = 'admin'
|
||||
|
||||
# Changes the properties of an existing server.
|
||||
exports.set = ({id, host, username, password}) ->
|
||||
exports.set = $coroutine ({id, host, username, password}) ->
|
||||
# Retrieves the server.
|
||||
server = $wait @servers.first id
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
{$wait} = require '../fibers-utils'
|
||||
{$coroutine, $wait} = require '../fibers-utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
# Signs a user in with its email/password.
|
||||
exports.signInWithPassword = ({email, password}) ->
|
||||
exports.signInWithPassword = $coroutine ({email, password}) ->
|
||||
@throw 'ALREADY_AUTHENTICATED' if @session.has 'user_id'
|
||||
|
||||
# Gets the user.
|
||||
@ -24,7 +24,7 @@ exports.signInWithPassword.params = {
|
||||
}
|
||||
|
||||
# Signs a user in with a token.
|
||||
exports.signInWithToken = ({token}) ->
|
||||
exports.signInWithToken = $coroutine ({token}) ->
|
||||
@throw 'ALREADY_AUTHENTICATED' if @session.has 'user_id'
|
||||
|
||||
# Gets the token.
|
||||
@ -43,14 +43,14 @@ exports.signInWithToken.params = {
|
||||
token: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.signOut = ->
|
||||
exports.signOut = $coroutine ->
|
||||
@session.unset 'token_id'
|
||||
@session.unset 'user_id'
|
||||
|
||||
return true
|
||||
|
||||
# Gets the the currently signed in user.
|
||||
exports.getUser = ->
|
||||
exports.getUser = $coroutine ->
|
||||
id = @session.get 'user_id', null
|
||||
|
||||
# If the user is not signed in, returns null.
|
||||
|
@ -1,8 +1,8 @@
|
||||
{$wait} = require '../fibers-utils'
|
||||
{$coroutine, $wait} = require '../fibers-utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
exports.set = (params) ->
|
||||
exports.set = $coroutine (params) ->
|
||||
try
|
||||
SR = @getObject params.id, 'SR'
|
||||
catch
|
||||
@ -29,7 +29,7 @@ exports.set.params = {
|
||||
}
|
||||
|
||||
|
||||
exports.scan = ({id}) ->
|
||||
exports.scan = $coroutine ({id}) ->
|
||||
try
|
||||
SR = @getObject id, 'SR'
|
||||
catch
|
||||
|
@ -1,8 +1,8 @@
|
||||
{$wait} = require '../fibers-utils'
|
||||
{$coroutine, $wait} = require '../fibers-utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
exports.cancel = ({id}) ->
|
||||
exports.cancel = $coroutine ({id}) ->
|
||||
try
|
||||
task = @getObject id, 'task'
|
||||
catch
|
||||
|
@ -1,11 +1,11 @@
|
||||
{$wait} = require '../fibers-utils'
|
||||
{$coroutine, $wait} = require '../fibers-utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
# Creates a new token.
|
||||
#
|
||||
# TODO: Token permission.
|
||||
exports.create = ->
|
||||
exports.create = $coroutine ->
|
||||
userId = @session.get 'user_id'
|
||||
|
||||
# The user MUST be signed in and not with a token
|
||||
@ -17,7 +17,7 @@ exports.create = ->
|
||||
return token.id
|
||||
|
||||
# Deletes a token.
|
||||
exports.delete = ({token: tokenId}) ->
|
||||
exports.delete = $coroutine ({token: tokenId}) ->
|
||||
# Gets the token.
|
||||
token = $wait @tokens.first tokenId
|
||||
@throw 'NO_SUCH_OBJECT' unless token?
|
||||
|
@ -1,9 +1,9 @@
|
||||
{$wait} = require '../fibers-utils'
|
||||
{$coroutine, $wait} = require '../fibers-utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
# Creates a new user.
|
||||
exports.create = ({email, password, permission}) ->
|
||||
exports.create = $coroutine ({email, password, permission}) ->
|
||||
# Creates the user.
|
||||
user = $wait @users.create email, password, permission
|
||||
|
||||
@ -18,7 +18,7 @@ exports.create.params = {
|
||||
# Deletes an existing user.
|
||||
#
|
||||
# FIXME: a user should not be able to delete itself.
|
||||
exports.delete = ({id}) ->
|
||||
exports.delete = $coroutine ({id}) ->
|
||||
# The user cannot delete himself.
|
||||
@throw 'INVALID_PARAMS' if id is @session.get 'user_id'
|
||||
|
||||
@ -32,7 +32,7 @@ exports.delete.params = {
|
||||
}
|
||||
|
||||
# Changes the password of the current user.
|
||||
exports.changePassword = ({old, new: newP}) ->
|
||||
exports.changePassword = $coroutine ({old, new: newP}) ->
|
||||
# Gets the current user (which MUST exist).
|
||||
user = $wait @users.first @session.get 'user_id'
|
||||
|
||||
@ -53,7 +53,7 @@ exports.changePassword.params = {
|
||||
}
|
||||
|
||||
# Returns the user with a given identifier.
|
||||
exports.get = ({id}) ->
|
||||
exports.get = $coroutine ({id}) ->
|
||||
# Only an administrator can see another user.
|
||||
@checkPermission 'admin' unless @session.get 'user_id' is id
|
||||
|
||||
@ -69,7 +69,7 @@ exports.get.params = {
|
||||
}
|
||||
|
||||
# Returns all users.
|
||||
exports.getAll = ->
|
||||
exports.getAll = $coroutine ->
|
||||
# Retrieves the users.
|
||||
users = $wait @users.get()
|
||||
|
||||
@ -81,7 +81,7 @@ exports.getAll = ->
|
||||
exports.getAll.permission = 'admin'
|
||||
|
||||
# Changes the properties of an existing user.
|
||||
exports.set = ({id, email, password, permission}) ->
|
||||
exports.set = $coroutine ({id, email, password, permission}) ->
|
||||
# Retrieves the user.
|
||||
user = $wait @users.first id
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
# FIXME: too low level, should be removed.
|
||||
|
||||
{$wait} = require '../fibers-utils'
|
||||
{$coroutine, $wait} = require '../fibers-utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
exports.delete = ({id}) ->
|
||||
exports.delete = $coroutine ({id}) ->
|
||||
try
|
||||
VBD = @getObject id, 'VBD'
|
||||
catch
|
||||
@ -21,7 +21,7 @@ exports.delete.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.disconnect = ({id}) ->
|
||||
exports.disconnect = $coroutine ({id}) ->
|
||||
try
|
||||
VBD = @getObject id, 'VBD'
|
||||
catch
|
||||
@ -38,7 +38,7 @@ exports.disconnect.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.connect = ({id}) ->
|
||||
exports.connect = $coroutine ({id}) ->
|
||||
try
|
||||
VBD = @getObject id, 'VBD'
|
||||
catch
|
||||
|
@ -4,11 +4,11 @@ $isArray = require 'lodash.isarray'
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
|
||||
{$wait} = require '../fibers-utils'
|
||||
{$coroutine, $wait} = require '../fibers-utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
exports.delete = ({id}) ->
|
||||
exports.delete = $coroutine ({id}) ->
|
||||
try
|
||||
VDI = @getObject id, 'VDI'
|
||||
catch
|
||||
@ -26,7 +26,7 @@ exports.delete.params = {
|
||||
}
|
||||
|
||||
# FIXME: human readable strings should be handled.
|
||||
exports.set = (params) ->
|
||||
exports.set = $coroutine (params) ->
|
||||
try
|
||||
VDI = @getObject params.id
|
||||
catch
|
||||
@ -72,7 +72,7 @@ exports.set.params = {
|
||||
size: { type: 'integer', optional: true }
|
||||
}
|
||||
|
||||
exports.migrate = ({id, sr_id}) ->
|
||||
exports.migrate = $coroutine ({id, sr_id}) ->
|
||||
try
|
||||
VDI = @getObject id, 'VDI'
|
||||
SR = @getObject sr_id, 'SR'
|
||||
|
@ -1,8 +1,8 @@
|
||||
{$wait} = require '../fibers-utils'
|
||||
{$coroutine, $wait} = require '../fibers-utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
exports.delete = ({id}) ->
|
||||
exports.delete = $coroutine ({id}) ->
|
||||
try
|
||||
VIF = @getObject id, 'VIF'
|
||||
catch
|
||||
@ -19,7 +19,7 @@ exports.delete.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.disconnect = ({id}) ->
|
||||
exports.disconnect = $coroutine ({id}) ->
|
||||
try
|
||||
VIF = @getObject id, 'VIF'
|
||||
catch
|
||||
@ -36,7 +36,7 @@ exports.disconnect.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.connect = ({id}) ->
|
||||
exports.connect = $coroutine ({id}) ->
|
||||
try
|
||||
VIF = @getObject id, 'VIF'
|
||||
catch
|
||||
|
@ -31,7 +31,7 @@ $isVMRunning = do ->
|
||||
#=====================================================================
|
||||
|
||||
# FIXME: Make the method as atomic as possible.
|
||||
exports.create = ({
|
||||
exports.create = $coroutine ({
|
||||
installation
|
||||
name_label
|
||||
template
|
||||
@ -275,7 +275,7 @@ exports.delete.params = {
|
||||
}
|
||||
}
|
||||
|
||||
exports.ejectCd = ({id}) ->
|
||||
exports.ejectCd = $coroutine ({id}) ->
|
||||
try
|
||||
VM = @getObject id, 'VM'
|
||||
catch
|
||||
@ -301,7 +301,7 @@ exports.ejectCd.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.insertCd = ({id, cd_id, force}) ->
|
||||
exports.insertCd = $coroutine ({id, cd_id, force}) ->
|
||||
try
|
||||
VM = @getObject id, 'VM'
|
||||
VDI = @getObject cd_id, 'VDI'
|
||||
@ -350,7 +350,7 @@ exports.insertCd.params = {
|
||||
force: { type: 'boolean' }
|
||||
}
|
||||
|
||||
exports.migrate = ({id, host_id}) ->
|
||||
exports.migrate = $coroutine ({id, host_id}) ->
|
||||
try
|
||||
VM = @getObject id, 'VM'
|
||||
host = @getObject host_id, 'host'
|
||||
@ -374,7 +374,7 @@ exports.migrate.params = {
|
||||
host_id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.migrate_pool = ({
|
||||
exports.migrate_pool = $coroutine ({
|
||||
id
|
||||
target_host_id
|
||||
target_sr_id
|
||||
@ -464,7 +464,7 @@ exports.migrate_pool.params = {
|
||||
}
|
||||
|
||||
# FIXME: human readable strings should be handled.
|
||||
exports.set = (params) ->
|
||||
exports.set = $coroutine (params) ->
|
||||
try
|
||||
VM = @getObject params.id, ['VM', 'VM-snapshot']
|
||||
catch
|
||||
@ -559,7 +559,7 @@ exports.set.params = {
|
||||
memory: { type: 'integer', optional: true }
|
||||
}
|
||||
|
||||
exports.restart = ({id, force}) ->
|
||||
exports.restart = $coroutine ({id, force}) ->
|
||||
try
|
||||
VM = @getObject id, 'VM'
|
||||
catch
|
||||
@ -584,7 +584,7 @@ exports.restart.params = {
|
||||
force: { type: 'boolean' }
|
||||
}
|
||||
|
||||
exports.clone = ({id, name, full_copy}) ->
|
||||
exports.clone = $coroutine ({id, name, full_copy}) ->
|
||||
try
|
||||
VM = @getObject id, 'VM'
|
||||
catch
|
||||
@ -605,7 +605,7 @@ exports.clone.params = {
|
||||
}
|
||||
|
||||
# TODO: rename convertToTemplate()
|
||||
exports.convert = ({id}) ->
|
||||
exports.convert = $coroutine ({id}) ->
|
||||
try
|
||||
VM = @getObject id, 'VM'
|
||||
catch
|
||||
@ -620,7 +620,7 @@ exports.convert.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.snapshot = ({id, name}) ->
|
||||
exports.snapshot = $coroutine ({id, name}) ->
|
||||
try
|
||||
VM = @getObject id, 'VM'
|
||||
catch
|
||||
@ -635,7 +635,7 @@ exports.snapshot.params = {
|
||||
name: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.start = ({id}) ->
|
||||
exports.start = $coroutine ({id}) ->
|
||||
try
|
||||
VM = @getObject id, 'VM'
|
||||
catch
|
||||
@ -657,7 +657,7 @@ exports.start.params = {
|
||||
# - if !force → clean shutdown
|
||||
# - if force is true → hard shutdown
|
||||
# - if force is integer → clean shutdown and after force seconds, hard shutdown.
|
||||
exports.stop = ({id, force}) ->
|
||||
exports.stop = $coroutine ({id, force}) ->
|
||||
try
|
||||
VM = @getObject id, 'VM'
|
||||
catch
|
||||
@ -687,7 +687,7 @@ exports.stop.params = {
|
||||
force: { type: 'boolean', optional: true }
|
||||
}
|
||||
|
||||
exports.suspend = ({id}) ->
|
||||
exports.suspend = $coroutine ({id}) ->
|
||||
try
|
||||
VM = @getObject id, 'VM'
|
||||
catch
|
||||
@ -703,7 +703,7 @@ exports.suspend.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.resume = ({id, force}) ->
|
||||
exports.resume = $coroutine ({id, force}) ->
|
||||
try
|
||||
VM = @getObject id, 'VM'
|
||||
catch
|
||||
@ -724,7 +724,7 @@ exports.resume.params = {
|
||||
}
|
||||
|
||||
# revert a snapshot to its parent VM
|
||||
exports.revert = ({id}) ->
|
||||
exports.revert = $coroutine ({id}) ->
|
||||
try
|
||||
VM = @getObject id, 'VM-snapshot'
|
||||
catch
|
||||
@ -741,7 +741,7 @@ exports.revert.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.export = ({vm, compress}) ->
|
||||
exports.export = $coroutine ({vm, compress}) ->
|
||||
compress ?= true
|
||||
try
|
||||
VM = @getObject vm, ['VM', 'VM-snapshot']
|
||||
@ -782,7 +782,7 @@ exports.export = ({vm, compress}) ->
|
||||
|
||||
if snapshotRef?
|
||||
$debug 'deleting temp snapshot...'
|
||||
exports.delete.call this, id: snapshotRef, delete_disks: true
|
||||
$wait exports.delete.call this, id: snapshotRef, delete_disks: true
|
||||
|
||||
return
|
||||
|
||||
@ -809,7 +809,7 @@ exports.export.params = {
|
||||
|
||||
# FIXME
|
||||
# TODO: "sr_id" can be passed in URL to target a specific SR
|
||||
exports.import = ({host}) ->
|
||||
exports.import = $coroutine ({host}) ->
|
||||
try
|
||||
host = @getObject host, 'host'
|
||||
catch
|
||||
@ -841,7 +841,7 @@ exports.import.params = {
|
||||
#
|
||||
# FIXME: if position is used, all other disks after this position
|
||||
# should be shifted.
|
||||
exports.attachDisk = ({vm, vdi, position, mode, bootable}) ->
|
||||
exports.attachDisk = $coroutine ({vm, vdi, position, mode, bootable}) ->
|
||||
try
|
||||
VM = @getObject vm, 'VM'
|
||||
VDI = @getObject vdi, 'VDI'
|
||||
@ -886,7 +886,7 @@ exports.attachDisk.params = {
|
||||
#
|
||||
# FIXME: disk should be created using disk.create() and then attached
|
||||
# via vm.attachDisk().
|
||||
exports.addDisk = ({vm, name, size, sr, position, bootable}) ->
|
||||
exports.addDisk = $coroutine ({vm, name, size, sr, position, bootable}) ->
|
||||
try
|
||||
VM = @getObject vm, 'VM'
|
||||
SR = @getObject sr, 'SR'
|
||||
|
1
src/api/xo.js
Normal file
1
src/api/xo.js
Normal file
@ -0,0 +1 @@
|
||||
export var getAllObjects = () => this.getObjects();
|
69
src/index.js
69
src/index.js
@ -1,22 +1,32 @@
|
||||
import debug from 'debug';
|
||||
debug = debug('xo:main');
|
||||
|
||||
import Bluebird, {coroutine} from 'bluebird';
|
||||
import Bluebird from 'bluebird';
|
||||
Bluebird.longStackTraces();
|
||||
|
||||
import appConf from 'app-conf';
|
||||
import assign from 'lodash.assign';
|
||||
import bind from 'lodash.bind';
|
||||
import createConnectApp from 'connect';
|
||||
import eventToPromise from 'event-to-promise';
|
||||
import {readFile} from 'fs-promise';
|
||||
import forEach from 'lodash.foreach';
|
||||
import has from 'lodash.has';
|
||||
import isArray from 'lodash.isarray';
|
||||
import {createServer as createJsonRpcServer} from 'json-rpc';
|
||||
import pick from 'lodash.pick';
|
||||
import serveStatic from 'serve-static';
|
||||
import WebSocket from 'ws';
|
||||
import {
|
||||
AlreadyAuthenticated,
|
||||
InvalidCredential,
|
||||
InvalidParameters,
|
||||
NoSuchObject,
|
||||
NotImplementd,
|
||||
} from './api-errors';
|
||||
import {coroutine} from 'bluebird';
|
||||
import {createServer as createJsonRpcServer} from 'json-rpc';
|
||||
import {readFile} from 'fs-promise';
|
||||
|
||||
import API from './api';
|
||||
import Api from './api';
|
||||
import WebServer from 'http-server-plus';
|
||||
import wsProxy from './ws-proxy';
|
||||
import XO from './xo';
|
||||
@ -25,11 +35,11 @@ import XO from './xo';
|
||||
|
||||
let info = (...args) => {
|
||||
console.info('[Info]', ...args);
|
||||
}
|
||||
};
|
||||
|
||||
let warn = (...args) => {
|
||||
console.warn('[Warn]', ...args);
|
||||
}
|
||||
};
|
||||
|
||||
//====================================================================
|
||||
|
||||
@ -45,7 +55,7 @@ const DEFAULTS = {
|
||||
const DEPRECATED_ENTRIES = [
|
||||
'users',
|
||||
'servers',
|
||||
]
|
||||
];
|
||||
|
||||
let loadConfiguration = coroutine(function *() {
|
||||
let config = yield appConf.load('xo-server', {
|
||||
@ -114,14 +124,47 @@ let setUpStaticFiles = (connect, opts) => {
|
||||
debug('Setting up %s → %s', url, path);
|
||||
|
||||
connect.use(url, serveStatic(path));
|
||||
})
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
//====================================================================
|
||||
|
||||
let errorClasses = {
|
||||
ALREADY_AUTHENTICATED: AlreadyAuthenticated,
|
||||
INVALID_CREDENTIAL: InvalidCredential,
|
||||
INVALID_PARAMS: InvalidParameters,
|
||||
NO_SUCH_OBJECT: NoSuchObject,
|
||||
NOT_IMPLEMENTED: NotImplementd,
|
||||
};
|
||||
|
||||
let apiHelpers = {
|
||||
getUserPublicProperties(user) {
|
||||
// Handles both properties and wrapped models.
|
||||
let properties = user.properties || user;
|
||||
|
||||
return pick(properties, 'id', 'email', 'permission');
|
||||
},
|
||||
|
||||
getServerPublicProperties(server) {
|
||||
// Handles both properties and wrapped models.
|
||||
let properties = server.properties || server;
|
||||
|
||||
return pick(properties, 'id', 'host', 'username');
|
||||
},
|
||||
|
||||
throw(errorId, data) {
|
||||
throw new (errorClasses[errorId])(data);
|
||||
}
|
||||
};
|
||||
|
||||
let setUpApi = (webServer, xo) => {
|
||||
let api = new API(xo);
|
||||
let context = Object.create(xo);
|
||||
assign(xo, apiHelpers);
|
||||
|
||||
let api = new Api({
|
||||
context,
|
||||
});
|
||||
|
||||
let webSocketServer = new WebSocket.Server({
|
||||
server: webServer,
|
||||
@ -136,7 +179,7 @@ let setUpApi = (webServer, xo) => {
|
||||
// Create the JSON-RPC server for this connection.
|
||||
let jsonRpc = createJsonRpcServer(message => {
|
||||
if (message.type === 'request') {
|
||||
return api.exec(xoConnection, message);
|
||||
return api.call(xoConnection, message.method, message.params);
|
||||
}
|
||||
});
|
||||
|
||||
@ -172,7 +215,7 @@ let setUpApi = (webServer, xo) => {
|
||||
//====================================================================
|
||||
|
||||
let getVmConsoleUrl = (xo, id) => {
|
||||
let vm = xo.getObject(id, ['VM', 'VM-controller'])
|
||||
let vm = xo.getObject(id, ['VM', 'VM-controller']);
|
||||
if (!vm || vm.power_state !== 'Running') {
|
||||
return;
|
||||
}
|
||||
@ -198,12 +241,12 @@ let setUpConsoleProxy = (webServer, xo) => {
|
||||
});
|
||||
|
||||
webServer.on('upgrade', (req, res, head) => {
|
||||
let matches = CONSOLE_PROXY_PATH_RE.exec(req.url)
|
||||
let matches = CONSOLE_PROXY_PATH_RE.exec(req.url);
|
||||
if (!matches) {
|
||||
return;
|
||||
}
|
||||
|
||||
let url = getVmConsoleUrl(xo, matches[1])
|
||||
let url = getVmConsoleUrl(xo, matches[1]);
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user