Use standard style.
This commit is contained in:
parent
b4a3b832dc
commit
0eec1c1f61
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
'use strict'
|
||||
|
||||
//====================================================================
|
||||
// ===================================================================
|
||||
|
||||
require('exec-promise')(require('../'));
|
||||
require('exec-promise')(require('../'))
|
||||
|
14
gulpfile.js
14
gulpfile.js
@ -1,4 +1,4 @@
|
||||
'use strict';
|
||||
'use strict'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@ -12,18 +12,18 @@ var watch = require('gulp-watch')
|
||||
|
||||
// ===================================================================
|
||||
|
||||
var SRC_DIR = __dirname + '/src';
|
||||
var DIST_DIR = __dirname + '/dist';
|
||||
var SRC_DIR = __dirname + '/src'
|
||||
var DIST_DIR = __dirname + '/dist'
|
||||
|
||||
var PRODUCTION = process.argv.indexOf('--production') !== -1
|
||||
|
||||
// ===================================================================
|
||||
|
||||
function src(patterns) {
|
||||
function src (patterns) {
|
||||
return PRODUCTION ?
|
||||
gulp.src(patterns, {
|
||||
base: SRC_DIR,
|
||||
cwd: SRC_DIR,
|
||||
cwd: SRC_DIR
|
||||
}) :
|
||||
watch(patterns, {
|
||||
base: SRC_DIR,
|
||||
@ -36,7 +36,7 @@ function src(patterns) {
|
||||
|
||||
// ===================================================================
|
||||
|
||||
gulp.task(function buildCoffee() {
|
||||
gulp.task(function buildCoffee () {
|
||||
return src('**/*.coffee')
|
||||
.pipe(sourceMaps.init())
|
||||
.pipe(coffee({
|
||||
@ -46,7 +46,7 @@ gulp.task(function buildCoffee() {
|
||||
.pipe(gulp.dest(DIST_DIR))
|
||||
})
|
||||
|
||||
gulp.task(function buildEs6() {
|
||||
gulp.task(function buildEs6 () {
|
||||
return src('**/*.js')
|
||||
.pipe(sourceMaps.init())
|
||||
.pipe(babel({
|
||||
|
10
package.json
10
package.json
@ -91,13 +91,19 @@
|
||||
"in-publish": "^1.1.1",
|
||||
"mocha": "^2.2.1",
|
||||
"node-inspector": "^0.9.2",
|
||||
"sinon": "^1.14.1"
|
||||
"sinon": "^1.14.1",
|
||||
"standard": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp build --production",
|
||||
"dev": "gulp build",
|
||||
"prepublish": "in-publish && npm run build || in-install",
|
||||
"start": "node bin/xo-server",
|
||||
"test": "mocha 'dist/**/*.spec.js'"
|
||||
"test": "standard && mocha 'dist/**/*.spec.js'"
|
||||
},
|
||||
"standard": {
|
||||
"ignore": [
|
||||
"dist/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -217,7 +217,6 @@ class $MappedCollection extends $EventEmitter
|
||||
|
||||
removeWithPredicate: (predicate, thisArg) ->
|
||||
items = ($filter @_byKey, predicate, thisArg)
|
||||
console.log('%s items to remove', items.length)
|
||||
@_removeItems items
|
||||
|
||||
set: (items, {add, update, remove} = {}) ->
|
||||
|
@ -1,54 +1,51 @@
|
||||
'use strict';
|
||||
import assign from 'lodash.assign'
|
||||
import {JsonRpcError} from 'json-rpc/errors'
|
||||
|
||||
//====================================================================
|
||||
|
||||
var assign = require('lodash.assign');
|
||||
var JsonRpcError = require('json-rpc/errors').JsonRpcError;
|
||||
var jsonRpcErrors = require('json-rpc/errors');
|
||||
var makeError = require('make-error');
|
||||
|
||||
//====================================================================
|
||||
|
||||
function exportError(constructor) {
|
||||
makeError(constructor, JsonRpcError);
|
||||
exports[constructor.name] = constructor;
|
||||
}
|
||||
|
||||
//====================================================================
|
||||
// ===================================================================
|
||||
|
||||
// Export standard JSON-RPC errors.
|
||||
assign(exports, jsonRpcErrors);
|
||||
export * from 'json-rpc/errors'
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
exportError(function NotImplemented() {
|
||||
NotImplemented.super.call(this, 'not implemented', 0);
|
||||
});
|
||||
export class NotImplemented extends JsonRpcError {
|
||||
constructor () {
|
||||
super('not implemented', 0)
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
exportError(function NoSuchObject() {
|
||||
NoSuchObject.super.call(this, 'no such object', 1);
|
||||
});
|
||||
export class NoSuchObject extends JsonRpcError {
|
||||
constructor () {
|
||||
super(this, 'no such object', 1)
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
exportError(function Unauthorized() {
|
||||
Unauthorized.super.call(
|
||||
export class Unauthorized extends JsonRpcError {
|
||||
constructor () {
|
||||
super(
|
||||
this,
|
||||
'not authenticated or not enough permissions',
|
||||
2
|
||||
);
|
||||
});
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
exportError(function InvalidCredential() {
|
||||
InvalidCredential.super.call(this, 'invalid credential', 3);
|
||||
});
|
||||
export class InvalidCredential extends JsonRpcError {
|
||||
constructor () {
|
||||
super(this, 'invalid credential', 3)
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
exportError(function AlreadyAuthenticated() {
|
||||
AlreadyAuthenticated.super.call(this, 'already authenticated', 4);
|
||||
});
|
||||
export class AlreadyAuthenticated extends JsonRpcError {
|
||||
constructor () {
|
||||
super(this, 'already authenticated', 4)
|
||||
}
|
||||
}
|
||||
|
290
src/api.js
290
src/api.js
@ -1,82 +1,83 @@
|
||||
import debug from 'debug';
|
||||
debug = debug('xo:api');
|
||||
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 map from 'lodash.map';
|
||||
import requireTree from 'require-tree';
|
||||
import schemaInspector from 'schema-inspector';
|
||||
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 map from 'lodash.map'
|
||||
import requireTree from 'require-tree'
|
||||
import schemaInspector from 'schema-inspector'
|
||||
|
||||
import {
|
||||
InvalidParameters,
|
||||
MethodNotFound,
|
||||
NoSuchObject,
|
||||
Unauthorized,
|
||||
} from './api-errors';
|
||||
Unauthorized
|
||||
} from './api-errors'
|
||||
|
||||
//====================================================================
|
||||
// ===================================================================
|
||||
|
||||
// FIXME: this function is specific to XO and should not be defined in
|
||||
// this file.
|
||||
function checkPermission(method) {
|
||||
function checkPermission (method) {
|
||||
/* jshint validthis: true */
|
||||
|
||||
let {permission} = method;
|
||||
const {permission} = method
|
||||
|
||||
// No requirement.
|
||||
if (permission === undefined) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
let {user} = this;
|
||||
const {user} = this
|
||||
if (!user) {
|
||||
throw new Unauthorized();
|
||||
throw new Unauthorized()
|
||||
}
|
||||
|
||||
// The only requirement is login.
|
||||
if (!permission) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
if (!user.hasPermission(permission)) {
|
||||
throw new Unauthorized();
|
||||
throw new Unauthorized()
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
function checkParams(method, params) {
|
||||
var schema = method.params;
|
||||
function checkParams (method, params) {
|
||||
var schema = method.params
|
||||
if (!schema) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
let result = schemaInspector.validate({
|
||||
const result = schemaInspector.validate({
|
||||
type: 'object',
|
||||
properties: schema,
|
||||
}, params);
|
||||
properties: schema
|
||||
}, params)
|
||||
|
||||
if (!result.valid) {
|
||||
throw new InvalidParameters(result.error);
|
||||
throw new InvalidParameters(result.error)
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
let checkAuthorization;
|
||||
// Forward declaration.
|
||||
let checkAuthorization
|
||||
|
||||
function authorized() {}
|
||||
function forbiddden() {
|
||||
throw new Unauthorized();
|
||||
}
|
||||
function checkMemberAuthorization(member) {
|
||||
function authorized () {}
|
||||
// function forbiddden () {
|
||||
// throw new Unauthorized()
|
||||
// }
|
||||
function checkMemberAuthorization (member) {
|
||||
return function (userId, object) {
|
||||
let memberObject = this.getObject(object[member]);
|
||||
return checkAuthorization.call(this, userId, memberObject);
|
||||
};
|
||||
const memberObject = this.getObject(object[member])
|
||||
return checkAuthorization.call(this, userId, memberObject)
|
||||
}
|
||||
}
|
||||
|
||||
const checkAuthorizationByTypes = {
|
||||
@ -92,129 +93,129 @@ const checkAuthorizationByTypes = {
|
||||
|
||||
// Access to a VDI is granted if the user has access to the
|
||||
// containing SR or to a linked VM.
|
||||
VDI(userId, vdi) {
|
||||
VDI (userId, vdi) {
|
||||
// Check authorization for each of the connected VMs.
|
||||
let promises = map(this.getObjects(vdi.$VBDs, 'VBD'), vbd => {
|
||||
let vm = this.getObject(vbd.VM, 'VM');
|
||||
return checkAuthorization.call(this, userId, vm);
|
||||
});
|
||||
const promises = map(this.getObjects(vdi.$VBDs, 'VBD'), vbd => {
|
||||
const vm = this.getObject(vbd.VM, 'VM')
|
||||
return checkAuthorization.call(this, userId, vm)
|
||||
})
|
||||
|
||||
// Check authorization for the containing SR.
|
||||
let sr = this.getObject(vdi.$SR, 'SR');
|
||||
promises.push(checkAuthorization.call(this, userId, sr));
|
||||
const sr = this.getObject(vdi.$SR, 'SR')
|
||||
promises.push(checkAuthorization.call(this, userId, sr))
|
||||
|
||||
// We need at least one success
|
||||
return Bluebird.any(promises).catch(function (aggregateError) {
|
||||
throw aggregateError[0];
|
||||
});
|
||||
throw aggregateError[0]
|
||||
})
|
||||
},
|
||||
|
||||
VIF(userId, vif) {
|
||||
let network = this.getObject(vif.$network);
|
||||
let vm = this.getObject(vif.$VM);
|
||||
VIF (userId, vif) {
|
||||
const network = this.getObject(vif.$network)
|
||||
const vm = this.getObject(vif.$VM)
|
||||
|
||||
return Bluebird.any([
|
||||
checkAuthorization.call(this, userId, network),
|
||||
checkAuthorization.call(this, userId, vm),
|
||||
]);
|
||||
checkAuthorization.call(this, userId, vm)
|
||||
])
|
||||
},
|
||||
|
||||
'VM-snapshot': checkMemberAuthorization('$snapshot_of'),
|
||||
};
|
||||
'VM-snapshot': checkMemberAuthorization('$snapshot_of')
|
||||
}
|
||||
|
||||
function defaultCheckAuthorization(userId, object) {
|
||||
function defaultCheckAuthorization (userId, object) {
|
||||
return this.acls.exists({
|
||||
subject: userId,
|
||||
object: object.id,
|
||||
object: object.id
|
||||
}).then(success => {
|
||||
if (!success) {
|
||||
throw new Unauthorized();
|
||||
throw new Unauthorized()
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
checkAuthorization = Bluebird.method(function (userId, object) {
|
||||
let fn = checkAuthorizationByTypes[object.type] || defaultCheckAuthorization;
|
||||
return fn.call(this, userId, object);
|
||||
});
|
||||
const fn = checkAuthorizationByTypes[object.type] || defaultCheckAuthorization
|
||||
return fn.call(this, userId, object)
|
||||
})
|
||||
|
||||
function resolveParams(method, params) {
|
||||
var resolve = method.resolve;
|
||||
function resolveParams (method, params) {
|
||||
var resolve = method.resolve
|
||||
if (!resolve) {
|
||||
return params;
|
||||
return params
|
||||
}
|
||||
|
||||
let {user} = this;
|
||||
const {user} = this
|
||||
if (!user) {
|
||||
throw new Unauthorized();
|
||||
throw new Unauthorized()
|
||||
}
|
||||
|
||||
let userId = user.get('id');
|
||||
let isAdmin = this.user.hasPermission('admin');
|
||||
const userId = user.get('id')
|
||||
const isAdmin = this.user.hasPermission('admin')
|
||||
|
||||
let promises = [];
|
||||
const promises = []
|
||||
try {
|
||||
forEach(resolve, ([param, types], key) => {
|
||||
let id = params[param];
|
||||
const id = params[param]
|
||||
if (id === undefined) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
let object = this.getObject(params[param], types);
|
||||
const object = this.getObject(params[param], types)
|
||||
|
||||
// This parameter has been handled, remove it.
|
||||
delete params[param];
|
||||
delete params[param]
|
||||
|
||||
// Register this new value.
|
||||
params[key] = object;
|
||||
params[key] = object
|
||||
|
||||
if (!isAdmin) {
|
||||
promises.push(checkAuthorization.call(this, userId, object));
|
||||
promises.push(checkAuthorization.call(this, userId, object))
|
||||
}
|
||||
});
|
||||
})
|
||||
} catch (error) {
|
||||
throw new NoSuchObject();
|
||||
throw new NoSuchObject()
|
||||
}
|
||||
|
||||
return Bluebird.all(promises).return(params);
|
||||
return Bluebird.all(promises).return(params)
|
||||
}
|
||||
|
||||
//====================================================================
|
||||
// ===================================================================
|
||||
|
||||
function getMethodsInfo() {
|
||||
let methods = {};
|
||||
function getMethodsInfo () {
|
||||
const methods = {}
|
||||
|
||||
forEach(this.api._methods, function (method, name) {
|
||||
this[name] = assign({}, {
|
||||
description: method.description,
|
||||
params: method.params || {},
|
||||
permission: method.permission,
|
||||
});
|
||||
}, methods);
|
||||
permission: method.permission
|
||||
})
|
||||
}, methods)
|
||||
|
||||
return methods;
|
||||
return methods
|
||||
}
|
||||
getMethodsInfo.description = 'returns the signatures of all available API methods';
|
||||
getMethodsInfo.description = 'returns the signatures of all available API methods'
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
let getVersion = () => '0.1';
|
||||
getVersion.description = 'API version (unstable)';
|
||||
const getVersion = () => '0.1'
|
||||
getVersion.description = 'API version (unstable)'
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
function listMethods() {
|
||||
return getKeys(this.api._methods);
|
||||
function listMethods () {
|
||||
return getKeys(this.api._methods)
|
||||
}
|
||||
listMethods.description = 'returns the name of all available API methods';
|
||||
listMethods.description = 'returns the name of all available API methods'
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
function methodSignature({method: name}) {
|
||||
let method = this.api.getMethod(name);
|
||||
function methodSignature ({method: name}) {
|
||||
const method = this.api.getMethod(name)
|
||||
|
||||
if (!method) {
|
||||
throw new NoSuchObject();
|
||||
throw new NoSuchObject()
|
||||
}
|
||||
|
||||
// Return an array for compatibility with XML-RPC.
|
||||
@ -223,105 +224,104 @@ function methodSignature({method: name}) {
|
||||
assign({ name }, {
|
||||
description: method.description,
|
||||
params: method.params || {},
|
||||
permission: method.permission,
|
||||
permission: method.permission
|
||||
})
|
||||
];
|
||||
]
|
||||
}
|
||||
methodSignature.description = 'returns the signature of an API method';
|
||||
|
||||
//====================================================================
|
||||
methodSignature.description = 'returns the signature of an API method'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export default class Api {
|
||||
constructor({context} = {}) {
|
||||
this._methods = Object.create(null);
|
||||
this.context = context;
|
||||
constructor ({context} = {}) {
|
||||
this._methods = Object.create(null)
|
||||
this.context = context
|
||||
|
||||
this.addMethods({
|
||||
system: {
|
||||
getMethodsInfo,
|
||||
getVersion,
|
||||
listMethods,
|
||||
methodSignature,
|
||||
methodSignature
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// FIXME: this too is specific to XO and should be moved out of this file.
|
||||
this.addMethods(requireTree('./api'));
|
||||
this.addMethods(requireTree('./api'))
|
||||
}
|
||||
|
||||
addMethod(name, method) {
|
||||
this._methods[name] = method;
|
||||
addMethod (name, method) {
|
||||
this._methods[name] = method
|
||||
}
|
||||
|
||||
addMethods(methods) {
|
||||
let base = '';
|
||||
forEach(methods, function addMethod(method, name) {
|
||||
name = base + name;
|
||||
addMethods (methods) {
|
||||
let base = ''
|
||||
forEach(methods, function addMethod (method, name) {
|
||||
name = base + name
|
||||
|
||||
if (isFunction(method)) {
|
||||
this.addMethod(name, method);
|
||||
return;
|
||||
this.addMethod(name, method)
|
||||
return
|
||||
}
|
||||
|
||||
let oldBase = base;
|
||||
base = name + '.';
|
||||
forEach(method, addMethod, this);
|
||||
base = oldBase;
|
||||
}, this);
|
||||
const oldBase = base
|
||||
base = name + '.'
|
||||
forEach(method, addMethod, this)
|
||||
base = oldBase
|
||||
}, this)
|
||||
}
|
||||
|
||||
call(session, name, params) {
|
||||
debug('%s(...)', name);
|
||||
call (session, name, params) {
|
||||
debug('%s(...)', name)
|
||||
|
||||
let method;
|
||||
let context;
|
||||
let method
|
||||
let context
|
||||
|
||||
return Bluebird.try(() => {
|
||||
method = this.getMethod(name);
|
||||
method = this.getMethod(name)
|
||||
if (!method) {
|
||||
throw new MethodNotFound(name);
|
||||
throw new MethodNotFound(name)
|
||||
}
|
||||
|
||||
context = Object.create(this.context);
|
||||
context.api = this; // Used by system.*().
|
||||
context.session = session;
|
||||
context = Object.create(this.context)
|
||||
context.api = this // Used by system.*().
|
||||
context.session = session
|
||||
|
||||
// FIXME: too coupled with XO.
|
||||
// Fetch and inject the current user.
|
||||
let userId = session.get('user_id', undefined);
|
||||
return userId === undefined ? null : context.users.first(userId);
|
||||
const userId = session.get('user_id', undefined)
|
||||
return userId === undefined ? null : context.users.first(userId)
|
||||
}).then(function (user) {
|
||||
context.user = user;
|
||||
context.user = user
|
||||
|
||||
return checkPermission.call(context, method);
|
||||
return checkPermission.call(context, method)
|
||||
}).then(() => {
|
||||
checkParams(method, params);
|
||||
checkParams(method, params)
|
||||
|
||||
return resolveParams.call(context, method, params);
|
||||
return resolveParams.call(context, method, params)
|
||||
}).then(params => {
|
||||
return method.call(context, 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;
|
||||
result = true
|
||||
}
|
||||
|
||||
debug('%s(...) → %s', name, typeof result);
|
||||
debug('%s(...) → %s', name, typeof result)
|
||||
|
||||
return result;
|
||||
return result
|
||||
},
|
||||
error => {
|
||||
debug('Error: %s(...) → %s', name, error);
|
||||
debug('Error: %s(...) → %s', name, error)
|
||||
|
||||
throw error;
|
||||
throw error
|
||||
}
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
getMethod(name) {
|
||||
return this._methods[name];
|
||||
getMethod (name) {
|
||||
return this._methods[name]
|
||||
}
|
||||
}
|
||||
|
@ -1,82 +1,82 @@
|
||||
import {coroutine} from 'bluebird';
|
||||
import {ModelAlreadyExists} from '../collection';
|
||||
import {coroutine} from 'bluebird'
|
||||
import {ModelAlreadyExists} from '../collection'
|
||||
|
||||
//====================================================================
|
||||
// ===================================================================
|
||||
|
||||
export const get = coroutine(function *({subject, object}) {
|
||||
const sieve = {};
|
||||
export const get = coroutine(function * ({subject, object}) {
|
||||
const sieve = {}
|
||||
try {
|
||||
if (subject !== undefined) {
|
||||
sieve.subject = (yield this.users.first(subject)).get('id');
|
||||
sieve.subject = (yield this.users.first(subject)).get('id')
|
||||
}
|
||||
if (object !== undefined) {
|
||||
sieve.object = this.getObject(object).id;
|
||||
sieve.object = this.getObject(object).id
|
||||
}
|
||||
} catch (error) {
|
||||
this.throw('NO_SUCH_OBJECT');
|
||||
this.throw('NO_SUCH_OBJECT')
|
||||
}
|
||||
|
||||
return this.acls.get(sieve);
|
||||
});
|
||||
return this.acls.get(sieve)
|
||||
})
|
||||
|
||||
get.permission = 'admin';
|
||||
get.permission = 'admin'
|
||||
|
||||
get.params = {
|
||||
subject: { type: 'string', optional: true },
|
||||
object: { type: 'string', optional: true },
|
||||
};
|
||||
object: { type: 'string', optional: true }
|
||||
}
|
||||
|
||||
get.description = 'get existing ACLs';
|
||||
get.description = 'get existing ACLs'
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export const getCurrent = coroutine(function *() {
|
||||
return this.acls.get({ subject: this.session.get('user_id') });
|
||||
});
|
||||
export const getCurrent = coroutine(function * () {
|
||||
return this.acls.get({ subject: this.session.get('user_id') })
|
||||
})
|
||||
|
||||
getCurrent.permission = '';
|
||||
getCurrent.permission = ''
|
||||
|
||||
getCurrent.description = 'get existing ACLs concerning current user';
|
||||
getCurrent.description = 'get existing ACLs concerning current user'
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export const add = coroutine(function *({subject, object}) {
|
||||
export const add = coroutine(function * ({subject, object}) {
|
||||
try {
|
||||
subject = (yield this.users.first(subject)).get('id');
|
||||
object = this.getObject(object).id;
|
||||
subject = (yield this.users.first(subject)).get('id')
|
||||
object = this.getObject(object).id
|
||||
} catch (error) {
|
||||
this.throw('NO_SUCH_OBJECT');
|
||||
this.throw('NO_SUCH_OBJECT')
|
||||
}
|
||||
|
||||
try {
|
||||
yield this.acls.create(subject, object);
|
||||
yield this.acls.create(subject, object)
|
||||
} catch (error) {
|
||||
if (!(error instanceof ModelAlreadyExists)) {
|
||||
throw error;
|
||||
throw error
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
add.permission = 'admin';
|
||||
add.permission = 'admin'
|
||||
|
||||
add.params = {
|
||||
subject: { type: 'string' },
|
||||
object: { type: 'string' },
|
||||
};
|
||||
object: { type: 'string' }
|
||||
}
|
||||
|
||||
add.description = 'add a new ACL entry';
|
||||
add.description = 'add a new ACL entry'
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export const remove = coroutine(function *({subject, object}) {
|
||||
yield this.acls.deconste(subject, object);
|
||||
});
|
||||
export const remove = coroutine(function * ({subject, object}) {
|
||||
yield this.acls.deconste(subject, object)
|
||||
})
|
||||
|
||||
remove.permission = 'admin';
|
||||
remove.permission = 'admin'
|
||||
|
||||
remove.params = {
|
||||
subject: { type: 'string' },
|
||||
object: { type: 'string' },
|
||||
};
|
||||
object: { type: 'string' }
|
||||
}
|
||||
|
||||
remove.description = 'remove an existing ACL entry';
|
||||
remove.description = 'remove an existing ACL entry'
|
||||
|
@ -1,10 +1,10 @@
|
||||
import {coroutine, wait} from '../fibers-utils';
|
||||
import {parseSize} from '../utils';
|
||||
import {coroutine, wait} from '../fibers-utils'
|
||||
import {parseSize} from '../utils'
|
||||
|
||||
//====================================================================
|
||||
// ===================================================================
|
||||
|
||||
export const create = coroutine(function ({name, size, sr}) {
|
||||
const xapi = this.getXAPI(sr);
|
||||
const xapi = this.getXAPI(sr)
|
||||
|
||||
const ref = wait(xapi.call('VDI.create', {
|
||||
name_label: name,
|
||||
@ -13,20 +13,20 @@ export const create = coroutine(function ({name, size, sr}) {
|
||||
sharable: false,
|
||||
SR: sr.ref,
|
||||
type: 'user',
|
||||
virtual_size: String(parseSize(size)),
|
||||
}));
|
||||
virtual_size: String(parseSize(size))
|
||||
}))
|
||||
|
||||
return wait(xapi.call('VDI.get_record', ref)).uuid;
|
||||
});
|
||||
return wait(xapi.call('VDI.get_record', ref)).uuid
|
||||
})
|
||||
|
||||
create.description = 'create a new disk on a SR';
|
||||
create.description = 'create a new disk on a SR'
|
||||
|
||||
create.params = {
|
||||
name: { type: 'string' },
|
||||
size: { type: 'string' },
|
||||
sr: { type: 'string' },
|
||||
};
|
||||
sr: { type: 'string' }
|
||||
}
|
||||
|
||||
create.resolve = {
|
||||
sr: ['sr', 'SR'],
|
||||
};
|
||||
sr: ['sr', 'SR']
|
||||
}
|
||||
|
@ -1,64 +1,63 @@
|
||||
import {deprecate} from 'util';
|
||||
import {deprecate} from 'util'
|
||||
|
||||
import {InvalidCredential, AlreadyAuthenticated} from '../api-errors';
|
||||
import {InvalidCredential, AlreadyAuthenticated} from '../api-errors'
|
||||
|
||||
import {coroutine, wait} from '../fibers-utils';
|
||||
import {coroutine, wait} from '../fibers-utils'
|
||||
|
||||
//====================================================================
|
||||
// ===================================================================
|
||||
|
||||
export const signIn = coroutine(function (credentials) {
|
||||
if (this.session.has('user_id')) {
|
||||
throw new AlreadyAuthenticated();
|
||||
throw new AlreadyAuthenticated()
|
||||
}
|
||||
|
||||
const user = wait(this.authenticateUser(credentials));
|
||||
const user = wait(this.authenticateUser(credentials))
|
||||
if (!user) {
|
||||
throw new InvalidCredential();
|
||||
throw new InvalidCredential()
|
||||
}
|
||||
this.session.set('user_id', user.get('id'));
|
||||
this.session.set('user_id', user.get('id'))
|
||||
|
||||
return this.getUserPublicProperties(user);
|
||||
});
|
||||
return this.getUserPublicProperties(user)
|
||||
})
|
||||
|
||||
signIn.description = 'sign in';
|
||||
signIn.description = 'sign in'
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export const signInWithPassword = deprecate(signIn, 'use session.signIn() instead');
|
||||
export const signInWithPassword = deprecate(signIn, 'use session.signIn() instead')
|
||||
|
||||
signInWithPassword.params = {
|
||||
email: { type: 'string' },
|
||||
password: { type: 'string' },
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
export const signInWithToken = deprecate(signIn, 'use session.signIn() instead');
|
||||
|
||||
signInWithToken.params = {
|
||||
token: { type: 'string' },
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
export function signOut() {
|
||||
this.session.unset('user_id');
|
||||
password: { type: 'string' }
|
||||
}
|
||||
|
||||
signOut.description = 'sign out the user from the current session';
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export const signInWithToken = deprecate(signIn, 'use session.signIn() instead')
|
||||
|
||||
signInWithToken.params = {
|
||||
token: { type: 'string' }
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export function signOut () {
|
||||
this.session.unset('user_id')
|
||||
}
|
||||
|
||||
signOut.description = 'sign out the user from the current session'
|
||||
|
||||
// This method requires the user to be signed in.
|
||||
signOut.permission = '';
|
||||
signOut.permission = ''
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export const getUser = coroutine(function () {
|
||||
const userId = this.session.get('user_id');
|
||||
const userId = this.session.get('user_id')
|
||||
|
||||
return userId === undefined ?
|
||||
null :
|
||||
this.getUserPublicProperties(wait(this.users.first(userId)))
|
||||
;
|
||||
});
|
||||
})
|
||||
|
||||
getUser.description = 'return the currently connected user';
|
||||
getUser.description = 'return the currently connected user'
|
||||
|
426
src/api/sr.js
426
src/api/sr.js
@ -1,93 +1,93 @@
|
||||
import forEach from 'lodash.foreach';
|
||||
import {coroutine, wait} from '../fibers-utils';
|
||||
import {ensureArray, parseXml} from '../utils';
|
||||
import forEach from 'lodash.foreach'
|
||||
import {coroutine, wait} from '../fibers-utils'
|
||||
import {ensureArray, parseXml} from '../utils'
|
||||
|
||||
//====================================================================
|
||||
// ===================================================================
|
||||
|
||||
export const set = coroutine(function (params) {
|
||||
const {SR} = params;
|
||||
const xapi = this.getXAPI();
|
||||
const {SR} = params
|
||||
const xapi = this.getXAPI()
|
||||
|
||||
forEach(['name_label', 'name_description'], param => {
|
||||
const value = params[param];
|
||||
const value = params[param]
|
||||
if (value === undefined) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
wait(xapi.call(`SR.set_${value}`, SR.ref, params[param]));
|
||||
});
|
||||
wait(xapi.call(`SR.set_${value}`, SR.ref, params[param]))
|
||||
})
|
||||
|
||||
return true;
|
||||
});
|
||||
return true
|
||||
})
|
||||
|
||||
set.params = {
|
||||
id: { type: 'string' },
|
||||
|
||||
name_label: { type: 'string', optional: true },
|
||||
|
||||
name_description: { type: 'string', optional: true },
|
||||
};
|
||||
name_description: { type: 'string', optional: true }
|
||||
}
|
||||
|
||||
set.resolve = {
|
||||
SR: ['id', 'SR'],
|
||||
};
|
||||
SR: ['id', 'SR']
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export const scan = coroutine(function ({SR}) {
|
||||
const xapi = this.getXAPI(SR);
|
||||
const xapi = this.getXAPI(SR)
|
||||
|
||||
wait(xapi.call('SR.scan', SR.ref));
|
||||
wait(xapi.call('SR.scan', SR.ref))
|
||||
|
||||
return true;
|
||||
});
|
||||
return true
|
||||
})
|
||||
|
||||
scan.params = {
|
||||
id: { type: 'string' },
|
||||
};
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
scan.resolve = {
|
||||
SR: ['id', 'SR'],
|
||||
};
|
||||
SR: ['id', 'SR']
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// TODO: find a way to call this "deconste" and not destroy
|
||||
export const destroy = coroutine(function ({SR}) {
|
||||
const xapi = this.getXAPI(SR);
|
||||
const xapi = this.getXAPI(SR)
|
||||
|
||||
wait(xapi.call('SR.destroy', SR.ref));
|
||||
wait(xapi.call('SR.destroy', SR.ref))
|
||||
|
||||
return true;
|
||||
});
|
||||
return true
|
||||
})
|
||||
|
||||
destroy.params = {
|
||||
id: { type: 'string' },
|
||||
};
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
destroy.resolve = {
|
||||
SR: ['id', 'SR'],
|
||||
};
|
||||
SR: ['id', 'SR']
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export const forget = coroutine(function ({SR}) {
|
||||
const xapi = this.getXAPI(SR);
|
||||
const xapi = this.getXAPI(SR)
|
||||
|
||||
wait(xapi.call('SR.forget', SR.ref));
|
||||
wait(xapi.call('SR.forget', SR.ref))
|
||||
|
||||
return true;
|
||||
});
|
||||
return true
|
||||
})
|
||||
|
||||
forget.params = {
|
||||
id: { type: 'string' },
|
||||
};
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
forget.resolve = {
|
||||
SR: ['id', 'SR'],
|
||||
};
|
||||
SR: ['id', 'SR']
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export const createIso = coroutine(function ({
|
||||
host,
|
||||
@ -95,14 +95,14 @@ export const createIso = coroutine(function ({
|
||||
nameDescription,
|
||||
path
|
||||
}) {
|
||||
const xapi = this.getXAPI(host);
|
||||
const xapi = this.getXAPI(host)
|
||||
|
||||
// FIXME: won't work for IPv6
|
||||
// Detect if NFS or local path for ISO files
|
||||
const deviceConfig = {location: path};
|
||||
const deviceConfig = {location: path}
|
||||
if (path.indexOf(':') === -1) { // not NFS share
|
||||
// TODO: legacy will be removed in XAPI soon by FileSR
|
||||
deviceConfig.legacy_mode = 'true';
|
||||
deviceConfig.legacy_mode = 'true'
|
||||
}
|
||||
const srRef = wait(xapi.call(
|
||||
'SR.create',
|
||||
@ -115,24 +115,24 @@ export const createIso = coroutine(function ({
|
||||
'iso', // SR content type ISO
|
||||
true,
|
||||
{}
|
||||
));
|
||||
))
|
||||
|
||||
const sr = wait(xapi.call('SR.get_record', srRef));
|
||||
return sr.uuid;
|
||||
});
|
||||
const sr = wait(xapi.call('SR.get_record', srRef))
|
||||
return sr.uuid
|
||||
})
|
||||
|
||||
createIso.params = {
|
||||
host: { type: 'string' },
|
||||
nameLabel: { type: 'string' },
|
||||
nameDescription: { type: 'string' },
|
||||
path: { type: 'string' }
|
||||
};
|
||||
}
|
||||
|
||||
createIso.resolve = {
|
||||
host: ['host', 'host'],
|
||||
};
|
||||
host: ['host', 'host']
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
// NFS SR
|
||||
|
||||
// This functions creates a NFS SR
|
||||
@ -145,16 +145,16 @@ export const createNfs = coroutine(function ({
|
||||
serverPath,
|
||||
nfsVersion
|
||||
}) {
|
||||
const xapi = this.getXAPI(host);
|
||||
const xapi = this.getXAPI(host)
|
||||
|
||||
const deviceConfig = {
|
||||
server,
|
||||
serverpath: serverPath,
|
||||
};
|
||||
serverpath: serverPath
|
||||
}
|
||||
|
||||
// if NFS version given
|
||||
if (nfsVersion) {
|
||||
deviceConfig.nfsversion = nfsVersion;
|
||||
deviceConfig.nfsversion = nfsVersion
|
||||
}
|
||||
|
||||
const srRef = wait(xapi.call(
|
||||
@ -168,11 +168,11 @@ export const createNfs = coroutine(function ({
|
||||
'user', // recommended by Citrix
|
||||
true,
|
||||
{}
|
||||
));
|
||||
))
|
||||
|
||||
const sr = wait(xapi.call('SR.get_record', srRef));
|
||||
return sr.uuid;
|
||||
});
|
||||
const sr = wait(xapi.call('SR.get_record', srRef))
|
||||
return sr.uuid
|
||||
})
|
||||
|
||||
createNfs.params = {
|
||||
host: { type: 'string' },
|
||||
@ -180,14 +180,14 @@ createNfs.params = {
|
||||
nameDescription: { type: 'string' },
|
||||
server: { type: 'string' },
|
||||
serverPath: { type: 'string' },
|
||||
nfsVersion: { type: 'string' , optional: true},
|
||||
};
|
||||
nfsVersion: { type: 'string', optional: true }
|
||||
}
|
||||
|
||||
createNfs.resolve = {
|
||||
host: ['host', 'host'],
|
||||
};
|
||||
host: ['host', 'host']
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
// Local LVM SR
|
||||
|
||||
// This functions creates a local LVM SR
|
||||
@ -198,11 +198,11 @@ export const createLvm = coroutine(function ({
|
||||
nameDescription,
|
||||
device
|
||||
}) {
|
||||
const xapi = this.getXAPI(host);
|
||||
const xapi = this.getXAPI(host)
|
||||
|
||||
const deviceConfig = {
|
||||
device
|
||||
};
|
||||
}
|
||||
|
||||
const srRef = wait(xapi.call(
|
||||
'SR.create',
|
||||
@ -215,24 +215,24 @@ export const createLvm = coroutine(function ({
|
||||
'user', // recommended by Citrix
|
||||
false,
|
||||
{}
|
||||
));
|
||||
))
|
||||
|
||||
const sr = wait(xapi.call('SR.get_record', srRef));
|
||||
return sr.uuid;
|
||||
});
|
||||
const sr = wait(xapi.call('SR.get_record', srRef))
|
||||
return sr.uuid
|
||||
})
|
||||
|
||||
createLvm.params = {
|
||||
host: { type: 'string' },
|
||||
nameLabel: { type: 'string' },
|
||||
nameDescription: { type: 'string' },
|
||||
device: { type: 'string' },
|
||||
};
|
||||
device: { type: 'string' }
|
||||
}
|
||||
|
||||
createLvm.resolve = {
|
||||
host: ['host', 'host'],
|
||||
};
|
||||
host: ['host', 'host']
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
// This function helps to detect all NFS shares (exports) on a NFS server
|
||||
// Return a table of exports with their paths and ACLs
|
||||
|
||||
@ -240,13 +240,13 @@ export const probeNfs = coroutine(function ({
|
||||
host,
|
||||
server
|
||||
}) {
|
||||
const xapi = this.getXAPI(host);
|
||||
const xapi = this.getXAPI(host)
|
||||
|
||||
const deviceConfig = {
|
||||
server,
|
||||
};
|
||||
server
|
||||
}
|
||||
|
||||
let xml;
|
||||
let xml
|
||||
|
||||
try {
|
||||
wait(xapi.call(
|
||||
@ -255,38 +255,38 @@ export const probeNfs = coroutine(function ({
|
||||
deviceConfig,
|
||||
'nfs',
|
||||
{}
|
||||
));
|
||||
))
|
||||
|
||||
throw new Error('the call above should have thrown an error')
|
||||
} catch (error) {
|
||||
if (error[0] !== 'SR_BACKEND_FAILURE_101') {
|
||||
throw error;
|
||||
throw error
|
||||
}
|
||||
|
||||
xml = parseXml(error[3]);
|
||||
xml = parseXml(error[3])
|
||||
}
|
||||
|
||||
const nfsExports = [];
|
||||
const nfsExports = []
|
||||
forEach(ensureArray(xml['nfs-exports'].Export), nfsExport => {
|
||||
nfsExports.push({
|
||||
path: nfsExport.Path.trim(),
|
||||
acl: nfsExport.Accesslist.trim()
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
return nfsExports;
|
||||
});
|
||||
return nfsExports
|
||||
})
|
||||
|
||||
probeNfs.params = {
|
||||
host: { type: 'string' },
|
||||
server: { type: 'string' },
|
||||
};
|
||||
server: { type: 'string' }
|
||||
}
|
||||
|
||||
probeNfs.resolve = {
|
||||
host: ['host', 'host'],
|
||||
};
|
||||
host: ['host', 'host']
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
// ISCSI SR
|
||||
|
||||
// This functions creates a iSCSI SR
|
||||
@ -303,23 +303,23 @@ export const createIscsi = coroutine(function ({
|
||||
chapUser,
|
||||
chapPassword
|
||||
}) {
|
||||
const xapi = this.getXAPI(host);
|
||||
const xapi = this.getXAPI(host)
|
||||
|
||||
const deviceConfig = {
|
||||
target,
|
||||
targetIQN: targetIqn,
|
||||
SCSIid: scsiId,
|
||||
};
|
||||
SCSIid: scsiId
|
||||
}
|
||||
|
||||
// if we give user and password
|
||||
if (chapUser && chapPassword) {
|
||||
deviceConfig.chapUser = chapUser;
|
||||
deviceConfig.chapPassword = chapPassword;
|
||||
deviceConfig.chapUser = chapUser
|
||||
deviceConfig.chapPassword = chapPassword
|
||||
}
|
||||
|
||||
// if we give another port than default iSCSI
|
||||
if (port) {
|
||||
deviceConfig.port = port;
|
||||
deviceConfig.port = port
|
||||
}
|
||||
|
||||
const srRef = wait(xapi.call(
|
||||
@ -333,57 +333,57 @@ export const createIscsi = coroutine(function ({
|
||||
'user', // recommended by Citrix
|
||||
true,
|
||||
{}
|
||||
));
|
||||
))
|
||||
|
||||
const sr = wait(xapi.call('SR.get_record', srRef));
|
||||
return sr.uuid;
|
||||
});
|
||||
const sr = wait(xapi.call('SR.get_record', srRef))
|
||||
return sr.uuid
|
||||
})
|
||||
|
||||
createIscsi.params = {
|
||||
host: { type: 'string' },
|
||||
nameLabel: { type: 'string' },
|
||||
nameDescription: { type: 'string' },
|
||||
target: { type: 'string' },
|
||||
port: { type: 'integer' , optional: true},
|
||||
port: { type: 'integer', optional: true},
|
||||
targetIqn: { type: 'string' },
|
||||
scsiId: { type: 'string' },
|
||||
chapUser: { type: 'string' , optional: true },
|
||||
chapPassword: { type: 'string' , optional: true },
|
||||
};
|
||||
chapUser: { type: 'string', optional: true },
|
||||
chapPassword: { type: 'string', optional: true }
|
||||
}
|
||||
|
||||
createIscsi.resolve = {
|
||||
host: ['host', 'host'],
|
||||
};
|
||||
host: ['host', 'host']
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
// This function helps to detect all iSCSI IQN on a Target (iSCSI "server")
|
||||
// Return a table of IQN or empty table if no iSCSI connection to the target
|
||||
|
||||
export const probeIscsiIqns = coroutine(function ({
|
||||
host,
|
||||
target:targetIp,
|
||||
target: targetIp,
|
||||
port,
|
||||
chapUser,
|
||||
chapPassword
|
||||
}) {
|
||||
const xapi = this.getXAPI(host);
|
||||
const xapi = this.getXAPI(host)
|
||||
|
||||
const deviceConfig = {
|
||||
target: targetIp,
|
||||
};
|
||||
target: targetIp
|
||||
}
|
||||
|
||||
// if we give user and password
|
||||
if (chapUser && chapPassword) {
|
||||
deviceConfig.chapUser = chapUser;
|
||||
deviceConfig.chapPassword = chapPassword;
|
||||
deviceConfig.chapUser = chapUser
|
||||
deviceConfig.chapPassword = chapPassword
|
||||
}
|
||||
|
||||
// if we give another port than default iSCSI
|
||||
if (port) {
|
||||
deviceConfig.port = port;
|
||||
deviceConfig.port = port
|
||||
}
|
||||
|
||||
let xml;
|
||||
let xml
|
||||
|
||||
try {
|
||||
wait(xapi.call(
|
||||
@ -392,76 +392,76 @@ export const probeIscsiIqns = coroutine(function ({
|
||||
deviceConfig,
|
||||
'lvmoiscsi',
|
||||
{}
|
||||
));
|
||||
))
|
||||
|
||||
throw new Error('the call above should have thrown an error')
|
||||
} catch (error) {
|
||||
if (error[0] === 'SR_BACKEND_FAILURE_141') {
|
||||
return [];
|
||||
return []
|
||||
}
|
||||
if (error[0] !== 'SR_BACKEND_FAILURE_96') {
|
||||
throw error;
|
||||
throw error
|
||||
}
|
||||
|
||||
xml = parseXml(error[3]);
|
||||
xml = parseXml(error[3])
|
||||
}
|
||||
|
||||
const targets = [];
|
||||
const targets = []
|
||||
forEach(ensureArray(xml['iscsi-target-iqns'].TGT), target => {
|
||||
// if the target is on another IP adress, do not display it
|
||||
if (target.IPAddress.trim() === targetIp) {
|
||||
targets.push({
|
||||
iqn: target.TargetIQN.trim(),
|
||||
ip: target.IPAddress.trim()
|
||||
});
|
||||
})
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
return targets;
|
||||
});
|
||||
return targets
|
||||
})
|
||||
|
||||
probeIscsiIqns.params = {
|
||||
host: { type: 'string' },
|
||||
target: { type: 'string' },
|
||||
port: { type: 'integer', optional: true },
|
||||
chapUser: { type: 'string' , optional: true },
|
||||
chapPassword: { type: 'string' , optional: true },
|
||||
};
|
||||
chapUser: { type: 'string', optional: true },
|
||||
chapPassword: { type: 'string', optional: true }
|
||||
}
|
||||
probeIscsiIqns.resolve = {
|
||||
host: ['host', 'host'],
|
||||
};
|
||||
host: ['host', 'host']
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
// This function helps to detect all iSCSI ID and LUNs on a Target
|
||||
// It will return a LUN table
|
||||
|
||||
export const probeIscsiLuns = coroutine(function ({
|
||||
host,
|
||||
target:targetIp,
|
||||
target: targetIp,
|
||||
port,
|
||||
targetIqn,
|
||||
chapUser,
|
||||
chapPassword
|
||||
}) {
|
||||
const xapi = this.getXAPI(host);
|
||||
const xapi = this.getXAPI(host)
|
||||
|
||||
const deviceConfig = {
|
||||
target: targetIp,
|
||||
targetIQN: targetIqn,
|
||||
};
|
||||
targetIQN: targetIqn
|
||||
}
|
||||
|
||||
// if we give user and password
|
||||
if (chapUser && chapPassword) {
|
||||
deviceConfig.chapUser = chapUser;
|
||||
deviceConfig.chapPassword = chapPassword;
|
||||
deviceConfig.chapUser = chapUser
|
||||
deviceConfig.chapPassword = chapPassword
|
||||
}
|
||||
|
||||
// if we give another port than default iSCSI
|
||||
if (port) {
|
||||
deviceConfig.port = port;
|
||||
deviceConfig.port = port
|
||||
}
|
||||
|
||||
let xml;
|
||||
let xml
|
||||
|
||||
try {
|
||||
wait(xapi.call(
|
||||
@ -470,18 +470,18 @@ export const probeIscsiLuns = coroutine(function ({
|
||||
deviceConfig,
|
||||
'lvmoiscsi',
|
||||
{}
|
||||
));
|
||||
))
|
||||
|
||||
throw new Error('the call above should have thrown an error')
|
||||
} catch (error) {
|
||||
if (error[0] !== 'SR_BACKEND_FAILURE_107') {
|
||||
throw error;
|
||||
throw error
|
||||
}
|
||||
|
||||
xml = parseXml(error[3]);
|
||||
xml = parseXml(error[3])
|
||||
}
|
||||
|
||||
const luns = [];
|
||||
const luns = []
|
||||
forEach(ensureArray(xml['iscsi-target'].LUN), lun => {
|
||||
luns.push({
|
||||
id: lun.LUNid.trim(),
|
||||
@ -489,67 +489,67 @@ export const probeIscsiLuns = coroutine(function ({
|
||||
serial: lun.serial.trim(),
|
||||
size: lun.size.trim(),
|
||||
scsiId: lun.SCSIid.trim()
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
return luns;
|
||||
});
|
||||
return luns
|
||||
})
|
||||
|
||||
probeIscsiLuns.params = {
|
||||
host: { type: 'string' },
|
||||
target: { type: 'string' },
|
||||
port: { type: 'integer' , optional: true},
|
||||
port: { type: 'integer', optional: true},
|
||||
targetIqn: { type: 'string' },
|
||||
chapUser: { type: 'string' , optional: true },
|
||||
chapPassword: { type: 'string' , optional: true },
|
||||
};
|
||||
chapUser: { type: 'string', optional: true },
|
||||
chapPassword: { type: 'string', optional: true }
|
||||
}
|
||||
|
||||
probeIscsiLuns.resolve = {
|
||||
host: ['host', 'host'],
|
||||
};
|
||||
host: ['host', 'host']
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
// This function helps to detect if this target already exists in XAPI
|
||||
// It returns a table of SR UUID, empty if no existing connections
|
||||
|
||||
export const probeIscsiExists = coroutine(function ({
|
||||
host,
|
||||
target:targetIp,
|
||||
target: targetIp,
|
||||
port,
|
||||
targetIqn,
|
||||
scsiId,
|
||||
chapUser,
|
||||
chapPassword
|
||||
}) {
|
||||
const xapi = this.getXAPI(host);
|
||||
const xapi = this.getXAPI(host)
|
||||
|
||||
const deviceConfig = {
|
||||
target: targetIp,
|
||||
targetIQN: targetIqn,
|
||||
SCSIid: scsiId,
|
||||
};
|
||||
SCSIid: scsiId
|
||||
}
|
||||
|
||||
// if we give user and password
|
||||
if (chapUser && chapPassword) {
|
||||
deviceConfig.chapUser = chapUser;
|
||||
deviceConfig.chapPassword = chapPassword;
|
||||
deviceConfig.chapUser = chapUser
|
||||
deviceConfig.chapPassword = chapPassword
|
||||
}
|
||||
|
||||
// if we give another port than default iSCSI
|
||||
if (port) {
|
||||
deviceConfig.port = port;
|
||||
deviceConfig.port = port
|
||||
}
|
||||
|
||||
const xml = parseXml(wait(xapi.call('SR.probe', host.ref, deviceConfig, 'lvmoiscsi', {})));
|
||||
const xml = parseXml(wait(xapi.call('SR.probe', host.ref, deviceConfig, 'lvmoiscsi', {})))
|
||||
|
||||
const srs = [];
|
||||
const srs = []
|
||||
forEach(ensureArray(xml['SRlist'].SR), sr => {
|
||||
// get the UUID of SR connected to this LUN
|
||||
srs.push({uuid: sr.UUID.trim()});
|
||||
});
|
||||
srs.push({uuid: sr.UUID.trim()})
|
||||
})
|
||||
|
||||
return srs;
|
||||
});
|
||||
return srs
|
||||
})
|
||||
|
||||
probeIscsiExists.params = {
|
||||
host: { type: 'string' },
|
||||
@ -557,15 +557,15 @@ probeIscsiExists.params = {
|
||||
port: { type: 'integer', optional: true },
|
||||
targetIqn: { type: 'string' },
|
||||
scsiId: { type: 'string' },
|
||||
chapUser: { type: 'string' , optional: true },
|
||||
chapPassword: { type: 'string' , optional: true },
|
||||
};
|
||||
chapUser: { type: 'string', optional: true },
|
||||
chapPassword: { type: 'string', optional: true }
|
||||
}
|
||||
|
||||
probeIscsiExists.resolve = {
|
||||
host: ['host', 'host'],
|
||||
};
|
||||
host: ['host', 'host']
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
// This function helps to detect if this NFS SR already exists in XAPI
|
||||
// It returns a table of SR UUID, empty if no existing connections
|
||||
|
||||
@ -574,36 +574,36 @@ export const probeNfsExists = coroutine(function ({
|
||||
server,
|
||||
serverPath,
|
||||
}) {
|
||||
const xapi = this.getXAPI(host);
|
||||
const xapi = this.getXAPI(host)
|
||||
|
||||
const deviceConfig = {
|
||||
server,
|
||||
serverpath: serverPath,
|
||||
};
|
||||
serverpath: serverPath
|
||||
}
|
||||
|
||||
const xml = parseXml(wait(xapi.call('SR.probe', host.ref, deviceConfig, 'nfs', {})));
|
||||
const xml = parseXml(wait(xapi.call('SR.probe', host.ref, deviceConfig, 'nfs', {})))
|
||||
|
||||
const srs = [];
|
||||
const srs = []
|
||||
|
||||
forEach(ensureArray(xml['SRlist'].SR), sr => {
|
||||
// get the UUID of SR connected to this LUN
|
||||
srs.push({uuid: sr.UUID.trim()});
|
||||
});
|
||||
srs.push({uuid: sr.UUID.trim()})
|
||||
})
|
||||
|
||||
return srs;
|
||||
});
|
||||
return srs
|
||||
})
|
||||
|
||||
probeNfsExists.params = {
|
||||
host: { type: 'string' },
|
||||
server: { type: 'string' },
|
||||
serverPath: { type: 'string' },
|
||||
};
|
||||
serverPath: { type: 'string' }
|
||||
}
|
||||
|
||||
probeNfsExists.resolve = {
|
||||
host: ['host', 'host'],
|
||||
};
|
||||
host: ['host', 'host']
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
// This function helps to reattach a forgotten NFS/iSCSI SR
|
||||
|
||||
export const reattach = coroutine(function ({
|
||||
@ -613,10 +613,10 @@ export const reattach = coroutine(function ({
|
||||
nameDescription,
|
||||
type,
|
||||
}) {
|
||||
const xapi = this.getXAPI(host);
|
||||
const xapi = this.getXAPI(host)
|
||||
|
||||
if (type === 'iscsi') {
|
||||
type = 'lvmoiscsi'; // the internal XAPI name
|
||||
type = 'lvmoiscsi' // the internal XAPI name
|
||||
}
|
||||
|
||||
const srRef = wait(xapi.call(
|
||||
@ -628,25 +628,25 @@ export const reattach = coroutine(function ({
|
||||
'user',
|
||||
true,
|
||||
{}
|
||||
));
|
||||
))
|
||||
|
||||
const sr = wait(xapi.call('SR.get_record', srRef));
|
||||
return sr.uuid;
|
||||
});
|
||||
const sr = wait(xapi.call('SR.get_record', srRef))
|
||||
return sr.uuid
|
||||
})
|
||||
|
||||
reattach.params = {
|
||||
host: { type: 'string' },
|
||||
uuid: { type: 'string' },
|
||||
nameLabel: { type: 'string' },
|
||||
nameDescription: { type: 'string' },
|
||||
type: { type: 'string' },
|
||||
};
|
||||
type: { type: 'string' }
|
||||
}
|
||||
|
||||
reattach.resolve = {
|
||||
host: ['host', 'host'],
|
||||
};
|
||||
host: ['host', 'host']
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
// This function helps to reattach a forgotten ISO SR
|
||||
|
||||
export const reattachIso = coroutine(function ({
|
||||
@ -656,10 +656,10 @@ export const reattachIso = coroutine(function ({
|
||||
nameDescription,
|
||||
type,
|
||||
}) {
|
||||
const xapi = this.getXAPI(host);
|
||||
const xapi = this.getXAPI(host)
|
||||
|
||||
if (type === 'iscsi') {
|
||||
type = 'lvmoiscsi'; // the internal XAPI name
|
||||
type = 'lvmoiscsi' // the internal XAPI name
|
||||
}
|
||||
|
||||
const srRef = wait(xapi.call(
|
||||
@ -671,20 +671,20 @@ export const reattachIso = coroutine(function ({
|
||||
'iso',
|
||||
true,
|
||||
{}
|
||||
));
|
||||
))
|
||||
|
||||
const sr = wait(xapi.call('SR.get_record', srRef));
|
||||
return sr.uuid;
|
||||
});
|
||||
const sr = wait(xapi.call('SR.get_record', srRef))
|
||||
return sr.uuid
|
||||
})
|
||||
|
||||
reattachIso.params = {
|
||||
host: { type: 'string' },
|
||||
uuid: { type: 'string' },
|
||||
nameLabel: { type: 'string' },
|
||||
nameDescription: { type: 'string' },
|
||||
type: { type: 'string' },
|
||||
};
|
||||
type: { type: 'string' }
|
||||
}
|
||||
|
||||
reattachIso.resolve = {
|
||||
host: ['host', 'host'],
|
||||
};
|
||||
host: ['host', 'host']
|
||||
}
|
||||
|
@ -1,175 +1,173 @@
|
||||
import Bluebird from 'bluebird';
|
||||
import isArray from 'lodash.isarray';
|
||||
import isObject from 'lodash.isobject';
|
||||
import makeError from 'make-error';
|
||||
import Model from './model';
|
||||
import {EventEmitter} from 'events';
|
||||
import {mapInPlace} from './utils';
|
||||
import Bluebird from 'bluebird'
|
||||
import isArray from 'lodash.isarray'
|
||||
import isObject from 'lodash.isobject'
|
||||
import Model from './model'
|
||||
import {BaseError} from 'make-error'
|
||||
import {EventEmitter} from 'events'
|
||||
import {mapInPlace} from './utils'
|
||||
|
||||
//====================================================================
|
||||
// ===================================================================
|
||||
|
||||
function ModelAlreadyExists(id) {
|
||||
ModelAlreadyExists.super.call(this, 'this model already exists: ' + id);
|
||||
export class ModelAlreadyExists {
|
||||
constructor (id) {
|
||||
super('this model already exists: ' + id)
|
||||
}
|
||||
}
|
||||
makeError(ModelAlreadyExists);
|
||||
export {ModelAlreadyExists};
|
||||
|
||||
//====================================================================
|
||||
// ===================================================================
|
||||
|
||||
export default class Collection extends EventEmitter {
|
||||
// Default value for Model.
|
||||
get Model() {
|
||||
return Model;
|
||||
get Model () {
|
||||
return Model
|
||||
}
|
||||
|
||||
// Make this property writable.
|
||||
set Model(Model) {
|
||||
set Model (Model) {
|
||||
Object.defineProperty(this, 'Model', {
|
||||
configurable: true,
|
||||
enumerale: true,
|
||||
value: Model,
|
||||
writable: true,
|
||||
});
|
||||
writable: true
|
||||
})
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
constructor () {
|
||||
super()
|
||||
}
|
||||
|
||||
add(models, opts) {
|
||||
let array = isArray(models);
|
||||
add (models, opts) {
|
||||
const array = isArray(models)
|
||||
if (!array) {
|
||||
models = [models];
|
||||
models = [models]
|
||||
|
||||
}
|
||||
|
||||
let {Model} = this;
|
||||
const {Model} = this
|
||||
mapInPlace(models, model => {
|
||||
if (!(model instanceof Model)) {
|
||||
model = new Model(model);
|
||||
model = new Model(model)
|
||||
}
|
||||
|
||||
let error = model.validate();
|
||||
const error = model.validate()
|
||||
if (error) {
|
||||
// TODO: Better system inspired by Backbone.js
|
||||
throw error;
|
||||
throw error
|
||||
}
|
||||
|
||||
return model.properties;
|
||||
});
|
||||
return model.properties
|
||||
})
|
||||
|
||||
return Bluebird.try(this._add, [models, opts], this).then(models => {
|
||||
this.emit('add', models);
|
||||
this.emit('add', models)
|
||||
|
||||
return array ? models : new this.Model(models[0]);
|
||||
});
|
||||
return array ? models : new this.Model(models[0])
|
||||
})
|
||||
}
|
||||
|
||||
first(properties) {
|
||||
first (properties) {
|
||||
if (!isObject(properties)) {
|
||||
properties = (properties !== undefined) ?
|
||||
{ id: properties } :
|
||||
{}
|
||||
;
|
||||
}
|
||||
|
||||
return Bluebird.try(this._first, [properties], this).then(
|
||||
model => model && new this.Model(model)
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
get(properties) {
|
||||
get (properties) {
|
||||
if (!isObject(properties)) {
|
||||
properties = (properties !== undefined) ?
|
||||
{ id: properties } :
|
||||
{}
|
||||
;
|
||||
}
|
||||
|
||||
return Bluebird.try(this._get, [properties], this);
|
||||
return Bluebird.try(this._get, [properties], this)
|
||||
}
|
||||
|
||||
remove(ids) {
|
||||
remove (ids) {
|
||||
if (!isArray(ids)) {
|
||||
ids = [ids];
|
||||
ids = [ids]
|
||||
}
|
||||
|
||||
return Bluebird.try(this._remove, [ids], this).then(() => {
|
||||
this.emit('remove', ids);
|
||||
return true;
|
||||
});
|
||||
this.emit('remove', ids)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
update(models) {
|
||||
var array = isArray(models);
|
||||
update (models) {
|
||||
var array = isArray(models)
|
||||
if (!isArray(models)) {
|
||||
models = [models];
|
||||
models = [models]
|
||||
}
|
||||
|
||||
let {Model} = this;
|
||||
const {Model} = this
|
||||
mapInPlace(models, model => {
|
||||
if (!(model instanceof Model)) {
|
||||
// TODO: Problems, we may be mixing in some default
|
||||
// properties which will overwrite existing ones.
|
||||
model = new Model(model);
|
||||
model = new Model(model)
|
||||
}
|
||||
|
||||
let id = model.get('id');
|
||||
const id = model.get('id')
|
||||
|
||||
// Missing models should be added not updated.
|
||||
if (id === undefined){
|
||||
if (id === undefined) {
|
||||
// FIXME: should not throw an exception but return a rejected promise.
|
||||
throw new Error('a model without an id cannot be updated');
|
||||
throw new Error('a model without an id cannot be updated')
|
||||
}
|
||||
|
||||
var error = model.validate();
|
||||
var error = model.validate()
|
||||
if (error !== undefined) {
|
||||
// TODO: Better system inspired by Backbone.js.
|
||||
throw error;
|
||||
throw error
|
||||
}
|
||||
|
||||
return model.properties;
|
||||
});
|
||||
return model.properties
|
||||
})
|
||||
|
||||
return Bluebird.try(this._update, [models], this).then(models => {
|
||||
this.emit('update', models);
|
||||
this.emit('update', models)
|
||||
|
||||
return array ? models : new this.Model(models[0]);
|
||||
});
|
||||
return array ? models : new this.Model(models[0])
|
||||
})
|
||||
}
|
||||
|
||||
// Methods to override in implementations.
|
||||
|
||||
_add() {
|
||||
throw new Error('not implemented');
|
||||
_add () {
|
||||
throw new Error('not implemented')
|
||||
}
|
||||
|
||||
_get() {
|
||||
throw new Error('not implemented');
|
||||
_get () {
|
||||
throw new Error('not implemented')
|
||||
}
|
||||
|
||||
_remove() {
|
||||
throw new Error('not implemented');
|
||||
_remove () {
|
||||
throw new Error('not implemented')
|
||||
}
|
||||
|
||||
_update() {
|
||||
throw new Error('not implemented');
|
||||
_update () {
|
||||
throw new Error('not implemented')
|
||||
}
|
||||
|
||||
// Methods which may be overridden in implementations.
|
||||
|
||||
count(properties) {
|
||||
return this.get(properties).get('count');
|
||||
count (properties) {
|
||||
return this.get(properties).get('count')
|
||||
}
|
||||
|
||||
exists(properties) {
|
||||
exists (properties) {
|
||||
/* jshint eqnull: true */
|
||||
return this.first(properties).then(model => model != null);
|
||||
return this.first(properties).then(model => model != null)
|
||||
}
|
||||
|
||||
_first(properties) {
|
||||
_first (properties) {
|
||||
return Bluebird.try(this.get, [properties], this).then(
|
||||
models => models.length ? models[0] : null
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,21 @@
|
||||
import Bluebird, {coroutine} from 'bluebird';
|
||||
import Collection, {ModelAlreadyExists} from '../collection';
|
||||
import difference from 'lodash.difference';
|
||||
import filter from 'lodash.filter';
|
||||
import forEach from 'lodash.foreach';
|
||||
import getKey from 'lodash.keys';
|
||||
import isEmpty from 'lodash.isempty';
|
||||
import map from 'lodash.map';
|
||||
import thenRedis from 'then-redis';
|
||||
import Bluebird, {coroutine} from 'bluebird'
|
||||
import Collection, {ModelAlreadyExists} from '../collection'
|
||||
import difference from 'lodash.difference'
|
||||
import filter from 'lodash.filter'
|
||||
import forEach from 'lodash.foreach'
|
||||
import getKey from 'lodash.keys'
|
||||
import isEmpty from 'lodash.isempty'
|
||||
import map from 'lodash.map'
|
||||
import thenRedis from 'then-redis'
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// ///////////////////////////////////////////////////////////////////
|
||||
// Data model:
|
||||
// - prefix +'_id': value of the last generated identifier;
|
||||
// - prefix +'_ids': set containing identifier of all models;
|
||||
// - prefix +'_'+ index +':' + value: set of identifiers which have
|
||||
// value for the given index.
|
||||
// - prefix +':'+ id: hash containing the properties of a model;
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// ///////////////////////////////////////////////////////////////////
|
||||
|
||||
// TODO: then-redis sends commands in order, we should use this
|
||||
// semantic to simplify the code.
|
||||
@ -26,124 +26,124 @@ import thenRedis from 'then-redis';
|
||||
// TODO: Remote events.
|
||||
|
||||
export default class Redis extends Collection {
|
||||
constructor({
|
||||
constructor ({
|
||||
connection,
|
||||
indexes = [],
|
||||
prefix,
|
||||
uri = 'tcp://localhost:6379',
|
||||
}) {
|
||||
super();
|
||||
super()
|
||||
|
||||
this.indexes = indexes;
|
||||
this.prefix = prefix;
|
||||
this.redis = connection || thenRedis.createClient(uri);
|
||||
this.indexes = indexes
|
||||
this.prefix = prefix
|
||||
this.redis = connection || thenRedis.createClient(uri)
|
||||
}
|
||||
|
||||
_extract(ids) {
|
||||
let prefix = this.prefix + ':';
|
||||
let {redis} = this;
|
||||
_extract (ids) {
|
||||
const prefix = this.prefix + ':'
|
||||
const {redis} = this
|
||||
|
||||
let models = [];
|
||||
const models = []
|
||||
return Bluebird.map(ids, id => {
|
||||
return redis.hgetall(prefix + id).then(model => {
|
||||
// If empty, consider it a no match.
|
||||
if (isEmpty(model)) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
// Mix the identifier in.
|
||||
model.id = id;
|
||||
model.id = id
|
||||
|
||||
models.push(model);
|
||||
});
|
||||
}).return(models);
|
||||
models.push(model)
|
||||
})
|
||||
}).return(models)
|
||||
}
|
||||
|
||||
_add(models, {replace = false} = {}) {
|
||||
_add (models, {replace = false} = {}) {
|
||||
// TODO: remove “replace” which is a temporary measure, implement
|
||||
// “set()” instead.
|
||||
|
||||
let {indexes, prefix, redis} = this;
|
||||
const {indexes, prefix, redis} = this
|
||||
|
||||
return Bluebird.map(models, coroutine(function *(model) {
|
||||
return Bluebird.map(models, coroutine(function * (model) {
|
||||
// Generate a new identifier if necessary.
|
||||
if (model.id === undefined) {
|
||||
model.id = String(yield redis.incr(prefix + '_id'));
|
||||
model.id = String(yield redis.incr(prefix + '_id'))
|
||||
}
|
||||
|
||||
let success = yield redis.sadd(prefix + '_ids', model.id);
|
||||
const success = yield redis.sadd(prefix + '_ids', model.id)
|
||||
|
||||
// The entry already exists an we are not in replace mode.
|
||||
if (!success && !replace) {
|
||||
throw new ModelAlreadyExists(model.id);
|
||||
throw new ModelAlreadyExists(model.id)
|
||||
}
|
||||
|
||||
// TODO: Remove existing fields.
|
||||
|
||||
let params = [];
|
||||
const params = []
|
||||
forEach(model, (value, name) => {
|
||||
// No need to store the identifier (already in the key).
|
||||
if (name === 'id') {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
params.push(name, value);
|
||||
});
|
||||
params.push(name, value)
|
||||
})
|
||||
|
||||
let promises = [
|
||||
redis.hmset(prefix + ':' + model.id, ...params),
|
||||
];
|
||||
const promises = [
|
||||
redis.hmset(prefix + ':' + model.id, ...params)
|
||||
]
|
||||
|
||||
// Update indexes.
|
||||
forEach(indexes, (index) => {
|
||||
let value = model[index];
|
||||
const value = model[index]
|
||||
if (value === undefined) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
let key = prefix + '_' + index + ':' + value;
|
||||
promises.push(redis.sadd(key, model.id));
|
||||
});
|
||||
const key = prefix + '_' + index + ':' + value
|
||||
promises.push(redis.sadd(key, model.id))
|
||||
})
|
||||
|
||||
yield Bluebird.all(promises);
|
||||
yield Bluebird.all(promises)
|
||||
|
||||
return model;
|
||||
}));
|
||||
return model
|
||||
}))
|
||||
}
|
||||
|
||||
_get(properties) {
|
||||
let {prefix, redis} = this;
|
||||
_get (properties) {
|
||||
const {prefix, redis} = this
|
||||
|
||||
if (isEmpty(properties)) {
|
||||
return redis.smembers(prefix + '_ids').then(ids => this._extract(ids));
|
||||
return redis.smembers(prefix + '_ids').then(ids => this._extract(ids))
|
||||
}
|
||||
|
||||
// Special treatment for the identifier.
|
||||
let id = properties.id;
|
||||
const id = properties.id
|
||||
if (id !== undefined) {
|
||||
delete properties.id
|
||||
return this._extract([id]).then(models => {
|
||||
return (models.length && !isEmpty(properties)) ?
|
||||
filter(models) :
|
||||
models
|
||||
;
|
||||
});
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
let {indexes} = this;
|
||||
const {indexes} = this
|
||||
|
||||
// Check for non indexed fields.
|
||||
let unfit = difference(getKey(properties), indexes);
|
||||
const unfit = difference(getKey(properties), indexes)
|
||||
if (unfit.length) {
|
||||
throw new Error('fields not indexed: ' + unfit.join());
|
||||
throw new Error('fields not indexed: ' + unfit.join())
|
||||
}
|
||||
|
||||
let keys = map(properties, (value, index) => prefix + '_' + index + ':' + value);
|
||||
return redis.sinter(...keys).then(ids => this._extract(ids));
|
||||
const keys = map(properties, (value, index) => prefix + '_' + index + ':' + value)
|
||||
return redis.sinter(...keys).then(ids => this._extract(ids))
|
||||
}
|
||||
|
||||
_remove(ids) {
|
||||
let {prefix, redis} = this;
|
||||
_remove (ids) {
|
||||
const {prefix, redis} = this
|
||||
|
||||
// TODO: handle indexes.
|
||||
|
||||
@ -152,11 +152,11 @@ export default class Redis extends Collection {
|
||||
redis.srem(prefix + '_ids', ...ids),
|
||||
|
||||
// Remove the models.
|
||||
redis.del(map(ids, id => prefix + ':' + id)),
|
||||
]);
|
||||
redis.del(map(ids, id => prefix + ':' + id))
|
||||
])
|
||||
}
|
||||
|
||||
_update(models) {
|
||||
return this._add(models, { replace: true });
|
||||
_update (models) {
|
||||
return this._add(models, { replace: true })
|
||||
}
|
||||
}
|
||||
|
@ -1,83 +1,79 @@
|
||||
'use strict';
|
||||
'use strict'
|
||||
|
||||
//====================================================================
|
||||
// ===================================================================
|
||||
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var inherits = require('util').inherits;
|
||||
var EventEmitter = require('events').EventEmitter
|
||||
var inherits = require('util').inherits
|
||||
|
||||
var assign = require('lodash.assign');
|
||||
var assign = require('lodash.assign')
|
||||
|
||||
//====================================================================
|
||||
// ===================================================================
|
||||
|
||||
var has = Object.prototype.hasOwnProperty;
|
||||
has = has.call.bind(has);
|
||||
var has = Object.prototype.hasOwnProperty
|
||||
has = has.call.bind(has)
|
||||
|
||||
function noop() {}
|
||||
function noop () {}
|
||||
|
||||
//====================================================================
|
||||
// ===================================================================
|
||||
|
||||
function Connection(opts) {
|
||||
EventEmitter.call(this);
|
||||
function Connection (opts) {
|
||||
EventEmitter.call(this)
|
||||
|
||||
this.data = Object.create(null);
|
||||
this.data = Object.create(null)
|
||||
|
||||
this._close = opts.close;
|
||||
this.notify = opts.notify;
|
||||
this._close = opts.close
|
||||
this.notify = opts.notify
|
||||
}
|
||||
inherits(Connection, EventEmitter);
|
||||
inherits(Connection, EventEmitter)
|
||||
|
||||
assign(Connection.prototype, {
|
||||
// Close the connection.
|
||||
close: function () {
|
||||
// Prevent errors when the connection is closed more than once.
|
||||
this.close = noop;
|
||||
this.close = noop
|
||||
|
||||
this._close();
|
||||
this._close()
|
||||
|
||||
this.emit('close');
|
||||
this.emit('close')
|
||||
|
||||
// Releases values AMAP to ease the garbage collecting.
|
||||
for (var key in this)
|
||||
{
|
||||
if (key !== 'close' && has(this, key))
|
||||
{
|
||||
delete this[key];
|
||||
for (var key in this) {
|
||||
if (key !== 'close' && has(this, key)) {
|
||||
delete this[key]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Gets the value for this key.
|
||||
get: function (key, defaultValue) {
|
||||
var data = this.data;
|
||||
var data = this.data
|
||||
|
||||
if (key in data)
|
||||
{
|
||||
return data[key];
|
||||
if (key in data) {
|
||||
return data[key]
|
||||
}
|
||||
|
||||
if (arguments.length >= 2)
|
||||
{
|
||||
return defaultValue;
|
||||
if (arguments.length >= 2) {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
throw new Error('no value for `'+ key +'`');
|
||||
throw new Error('no value for `' + key + '`')
|
||||
},
|
||||
|
||||
// Checks whether there is a value for this key.
|
||||
has: function (key) {
|
||||
return key in this.data;
|
||||
return key in this.data
|
||||
},
|
||||
|
||||
// Sets the value for this key.
|
||||
set: function (key, value) {
|
||||
this.data[key] = value;
|
||||
this.data[key] = value
|
||||
},
|
||||
|
||||
unset: function (key) {
|
||||
delete this.data[key];
|
||||
},
|
||||
});
|
||||
delete this.data[key]
|
||||
}
|
||||
})
|
||||
|
||||
//====================================================================
|
||||
// ===================================================================
|
||||
|
||||
module.exports = Connection;
|
||||
module.exports = Connection
|
||||
|
@ -1,182 +1,182 @@
|
||||
'use strict';
|
||||
'use strict'
|
||||
|
||||
//====================================================================
|
||||
// ===================================================================
|
||||
|
||||
/* jshint mocha: true */
|
||||
/* eslint-env mocha */
|
||||
|
||||
var expect = require('chai').expect;
|
||||
var expect = require('chai').expect
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
var Promise = require('bluebird');
|
||||
var Promise = require('bluebird')
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
var utils = require('./fibers-utils');
|
||||
var $coroutine = utils.$coroutine;
|
||||
var utils = require('./fibers-utils')
|
||||
var $coroutine = utils.$coroutine
|
||||
|
||||
//====================================================================
|
||||
// ===================================================================
|
||||
|
||||
describe('$coroutine', function () {
|
||||
it('creates a on which returns promises', function () {
|
||||
var fn = $coroutine(function () {});
|
||||
expect(fn().then).to.be.a('function');
|
||||
});
|
||||
var fn = $coroutine(function () {})
|
||||
expect(fn().then).to.be.a('function')
|
||||
})
|
||||
|
||||
it('creates a function which runs in a new fiber', function () {
|
||||
var previous = require('fibers').current;
|
||||
var previous = require('fibers').current
|
||||
|
||||
var fn = $coroutine(function () {
|
||||
var current = require('fibers').current;
|
||||
var current = require('fibers').current
|
||||
|
||||
expect(current).to.exists;
|
||||
expect(current).to.not.equal(previous);
|
||||
});
|
||||
expect(current).to.exists
|
||||
expect(current).to.not.equal(previous)
|
||||
})
|
||||
|
||||
fn();
|
||||
});
|
||||
fn()
|
||||
})
|
||||
|
||||
it('forwards all arguments (even this)', function () {
|
||||
var self = {};
|
||||
var arg1 = {};
|
||||
var arg2 = {};
|
||||
var self = {}
|
||||
var arg1 = {}
|
||||
var arg2 = {}
|
||||
|
||||
$coroutine(function (arg1_, arg2_) {
|
||||
expect(this).to.equal(self);
|
||||
expect(arg1_).to.equal(arg1);
|
||||
expect(arg2_).to.equal(arg2);
|
||||
}).call(self, arg1, arg2);
|
||||
});
|
||||
});
|
||||
expect(this).to.equal(self)
|
||||
expect(arg1_).to.equal(arg1)
|
||||
expect(arg2_).to.equal(arg2)
|
||||
}).call(self, arg1, arg2)
|
||||
})
|
||||
})
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
describe('$wait', function () {
|
||||
var $wait = utils.$wait;
|
||||
var $wait = utils.$wait
|
||||
|
||||
it('waits for a promise', function (done) {
|
||||
$coroutine(function () {
|
||||
var value = {};
|
||||
var promise = Promise.cast(value);
|
||||
var value = {}
|
||||
var promise = Promise.cast(value)
|
||||
|
||||
expect($wait(promise)).to.equal(value);
|
||||
expect($wait(promise)).to.equal(value)
|
||||
|
||||
done();
|
||||
})();
|
||||
});
|
||||
done()
|
||||
})()
|
||||
})
|
||||
|
||||
it('handles promise rejection', function (done) {
|
||||
$coroutine(function () {
|
||||
var promise = Promise.reject('an exception');
|
||||
var promise = Promise.reject('an exception')
|
||||
|
||||
expect(function () {
|
||||
$wait(promise);
|
||||
}).to.throw('an exception');
|
||||
$wait(promise)
|
||||
}).to.throw('an exception')
|
||||
|
||||
done();
|
||||
})();
|
||||
});
|
||||
done()
|
||||
})()
|
||||
})
|
||||
|
||||
it('waits for a continuable', function (done) {
|
||||
$coroutine(function () {
|
||||
var value = {};
|
||||
var value = {}
|
||||
var continuable = function (callback) {
|
||||
callback(null, value);
|
||||
};
|
||||
callback(null, value)
|
||||
}
|
||||
|
||||
expect($wait(continuable)).to.equal(value);
|
||||
expect($wait(continuable)).to.equal(value)
|
||||
|
||||
done();
|
||||
})();
|
||||
});
|
||||
done()
|
||||
})()
|
||||
})
|
||||
|
||||
it('handles continuable error', function (done) {
|
||||
$coroutine(function () {
|
||||
var continuable = function (callback) {
|
||||
callback('an exception');
|
||||
};
|
||||
callback('an exception')
|
||||
}
|
||||
|
||||
expect(function () {
|
||||
$wait(continuable);
|
||||
}).to.throw('an exception');
|
||||
$wait(continuable)
|
||||
}).to.throw('an exception')
|
||||
|
||||
done();
|
||||
})();
|
||||
});
|
||||
done()
|
||||
})()
|
||||
})
|
||||
|
||||
it('forwards scalar values', function (done) {
|
||||
$coroutine(function () {
|
||||
var value = 'a scalar value';
|
||||
expect($wait(value)).to.equal(value);
|
||||
var value = 'a scalar value'
|
||||
expect($wait(value)).to.equal(value)
|
||||
|
||||
value = [
|
||||
'foo',
|
||||
'bar',
|
||||
'baz',
|
||||
];
|
||||
expect($wait(value)).to.deep.equal(value);
|
||||
'baz'
|
||||
]
|
||||
expect($wait(value)).to.deep.equal(value)
|
||||
|
||||
value = [];
|
||||
expect($wait(value)).to.deep.equal(value);
|
||||
value = []
|
||||
expect($wait(value)).to.deep.equal(value)
|
||||
|
||||
value = {
|
||||
foo: 'foo',
|
||||
bar: 'bar',
|
||||
baz: 'baz',
|
||||
};
|
||||
expect($wait(value)).to.deep.equal(value);
|
||||
baz: 'baz'
|
||||
}
|
||||
expect($wait(value)).to.deep.equal(value)
|
||||
|
||||
value = {};
|
||||
expect($wait(value)).to.deep.equal(value);
|
||||
value = {}
|
||||
expect($wait(value)).to.deep.equal(value)
|
||||
|
||||
done();
|
||||
})();
|
||||
});
|
||||
done()
|
||||
})()
|
||||
})
|
||||
|
||||
it('handles arrays of promises/continuables', function (done) {
|
||||
$coroutine(function () {
|
||||
var value1 = {};
|
||||
var value2 = {};
|
||||
var value1 = {}
|
||||
var value2 = {}
|
||||
|
||||
var promise = Promise.cast(value1);
|
||||
var promise = Promise.cast(value1)
|
||||
var continuable = function (callback) {
|
||||
callback(null, value2);
|
||||
};
|
||||
callback(null, value2)
|
||||
}
|
||||
|
||||
var results = $wait([promise, continuable]);
|
||||
expect(results[0]).to.equal(value1);
|
||||
expect(results[1]).to.equal(value2);
|
||||
var results = $wait([promise, continuable])
|
||||
expect(results[0]).to.equal(value1)
|
||||
expect(results[1]).to.equal(value2)
|
||||
|
||||
done();
|
||||
})();
|
||||
});
|
||||
done()
|
||||
})()
|
||||
})
|
||||
|
||||
it('handles maps of promises/continuable', function (done) {
|
||||
$coroutine(function () {
|
||||
var value1 = {};
|
||||
var value2 = {};
|
||||
var value1 = {}
|
||||
var value2 = {}
|
||||
|
||||
var promise = Promise.cast(value1);
|
||||
var promise = Promise.cast(value1)
|
||||
var continuable = function (callback) {
|
||||
callback(null, value2);
|
||||
};
|
||||
callback(null, value2)
|
||||
}
|
||||
|
||||
var results = $wait({
|
||||
foo: promise,
|
||||
bar: continuable
|
||||
});
|
||||
expect(results.foo).to.equal(value1);
|
||||
expect(results.bar).to.equal(value2);
|
||||
})
|
||||
expect(results.foo).to.equal(value1)
|
||||
expect(results.bar).to.equal(value2)
|
||||
|
||||
done();
|
||||
})();
|
||||
});
|
||||
done()
|
||||
})()
|
||||
})
|
||||
|
||||
it('handles nested arrays/maps', function (done) {
|
||||
var promise = Promise.cast('a promise');
|
||||
var promise = Promise.cast('a promise')
|
||||
var continuable = function (callback) {
|
||||
callback(null, 'a continuable');
|
||||
};
|
||||
callback(null, 'a continuable')
|
||||
}
|
||||
|
||||
$coroutine(function () {
|
||||
expect($wait({
|
||||
@ -191,9 +191,9 @@ describe('$wait', function () {
|
||||
'a continuable',
|
||||
'a scalar'
|
||||
]
|
||||
});
|
||||
})
|
||||
|
||||
done();
|
||||
})();
|
||||
});
|
||||
});
|
||||
done()
|
||||
})()
|
||||
})
|
||||
})
|
||||
|
462
src/index.js
462
src/index.js
@ -1,186 +1,186 @@
|
||||
import createLogger from 'debug';
|
||||
let debug = createLogger('xo:main');
|
||||
let debugPlugin = createLogger('xo:plugin');
|
||||
import createLogger from 'debug'
|
||||
const debug = createLogger('xo:main')
|
||||
const debugPlugin = createLogger('xo:plugin')
|
||||
|
||||
import Bluebird from 'bluebird';
|
||||
Bluebird.longStackTraces();
|
||||
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 forEach from 'lodash.foreach';
|
||||
import has from 'lodash.has';
|
||||
import isArray from 'lodash.isarray';
|
||||
import isFunction from 'lodash.isfunction';
|
||||
import map from 'lodash.map';
|
||||
import pick from 'lodash.pick';
|
||||
import serveStatic from 'serve-static';
|
||||
import WebSocket from 'ws';
|
||||
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 forEach from 'lodash.foreach'
|
||||
import has from 'lodash.has'
|
||||
import isArray from 'lodash.isarray'
|
||||
import isFunction from 'lodash.isfunction'
|
||||
import map from 'lodash.map'
|
||||
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';
|
||||
NotImplemented
|
||||
} from './api-errors'
|
||||
import {coroutine} from 'bluebird'
|
||||
import {createServer as createJsonRpcServer} from 'json-rpc'
|
||||
import {readFile} from 'fs-promise'
|
||||
|
||||
import Api from './api';
|
||||
import WebServer from 'http-server-plus';
|
||||
import wsProxy from './ws-proxy';
|
||||
import XO from './xo';
|
||||
import Api from './api'
|
||||
import WebServer from 'http-server-plus'
|
||||
import wsProxy from './ws-proxy'
|
||||
import XO from './xo'
|
||||
|
||||
//====================================================================
|
||||
// ===================================================================
|
||||
|
||||
let info = (...args) => {
|
||||
console.info('[Info]', ...args);
|
||||
};
|
||||
const info = (...args) => {
|
||||
console.info('[Info]', ...args)
|
||||
}
|
||||
|
||||
let warn = (...args) => {
|
||||
console.warn('[Warn]', ...args);
|
||||
};
|
||||
const warn = (...args) => {
|
||||
console.warn('[Warn]', ...args)
|
||||
}
|
||||
|
||||
//====================================================================
|
||||
// ===================================================================
|
||||
|
||||
const DEFAULTS = {
|
||||
http: {
|
||||
listen: [
|
||||
{ port: 80 },
|
||||
{ port: 80 }
|
||||
],
|
||||
mounts: {},
|
||||
},
|
||||
};
|
||||
mounts: {}
|
||||
}
|
||||
}
|
||||
|
||||
const DEPRECATED_ENTRIES = [
|
||||
'users',
|
||||
'servers',
|
||||
];
|
||||
'servers'
|
||||
]
|
||||
|
||||
let loadConfiguration = coroutine(function *() {
|
||||
let config = yield appConf.load('xo-server', {
|
||||
const loadConfiguration = coroutine(function * () {
|
||||
const config = yield appConf.load('xo-server', {
|
||||
defaults: DEFAULTS,
|
||||
ignoreUnknownFormats: true,
|
||||
});
|
||||
ignoreUnknownFormats: true
|
||||
})
|
||||
|
||||
debug('Configuration loaded.');
|
||||
debug('Configuration loaded.')
|
||||
|
||||
// Print a message if deprecated entries are specified.
|
||||
forEach(DEPRECATED_ENTRIES, entry => {
|
||||
if (has(config, entry)) {
|
||||
warn(`${entry} configuration is deprecated.`);
|
||||
warn(`${entry} configuration is deprecated.`)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
return config;
|
||||
});
|
||||
return config
|
||||
})
|
||||
|
||||
//====================================================================
|
||||
// ===================================================================
|
||||
|
||||
let loadPlugin = Bluebird.method(function (pluginConf, pluginName) {
|
||||
debugPlugin('loading %s', pluginName);
|
||||
const loadPlugin = Bluebird.method(function (pluginConf, pluginName) {
|
||||
debugPlugin('loading %s', pluginName)
|
||||
|
||||
var pluginPath;
|
||||
var pluginPath
|
||||
try {
|
||||
pluginPath = require.resolve('xo-server-' + pluginName);
|
||||
pluginPath = require.resolve('xo-server-' + pluginName)
|
||||
} catch (e) {
|
||||
pluginPath = require.resolve(pluginName);
|
||||
pluginPath = require.resolve(pluginName)
|
||||
}
|
||||
|
||||
var plugin = require(pluginPath);
|
||||
var plugin = require(pluginPath)
|
||||
|
||||
if (isFunction(plugin)) {
|
||||
plugin = plugin(pluginConf);
|
||||
plugin = plugin(pluginConf)
|
||||
}
|
||||
|
||||
return plugin.load(this);
|
||||
});
|
||||
return plugin.load(this)
|
||||
})
|
||||
|
||||
let loadPlugins = function (plugins, xo) {
|
||||
const loadPlugins = function (plugins, xo) {
|
||||
return Bluebird.all(map(plugins, loadPlugin, xo)).then(() => {
|
||||
debugPlugin('all plugins loaded');
|
||||
});
|
||||
};
|
||||
debugPlugin('all plugins loaded')
|
||||
})
|
||||
}
|
||||
|
||||
//====================================================================
|
||||
// ===================================================================
|
||||
|
||||
let makeWebServerListen = coroutine(function *(opts) {
|
||||
const makeWebServerListen = coroutine(function * (opts) {
|
||||
// Read certificate and key if necessary.
|
||||
let {certificate, key} = opts;
|
||||
const {certificate, key} = opts
|
||||
if (certificate && key) {
|
||||
[opts.certificate, opts.key] = yield Bluebird.all([
|
||||
readFile(certificate),
|
||||
readFile(key),
|
||||
]);
|
||||
readFile(key)
|
||||
])
|
||||
}
|
||||
|
||||
try {
|
||||
let niceAddress = yield this.listen(opts);
|
||||
debug(`Web server listening on ${niceAddress}`);
|
||||
const niceAddress = yield this.listen(opts)
|
||||
debug(`Web server listening on ${niceAddress}`)
|
||||
} catch (error) {
|
||||
warn(`Web server could not listen on ${error.niceAddress}`);
|
||||
warn(`Web server could not listen on ${error.niceAddress}`)
|
||||
|
||||
let {code} = error;
|
||||
const {code} = error
|
||||
if (code === 'EACCES') {
|
||||
warn(' Access denied.');
|
||||
warn(' Ports < 1024 are often reserved to privileges users.');
|
||||
warn(' Access denied.')
|
||||
warn(' Ports < 1024 are often reserved to privileges users.')
|
||||
} else if (code === 'EADDRINUSE') {
|
||||
warn(' Address already in use.');
|
||||
warn(' Address already in use.')
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
let createWebServer = opts => {
|
||||
let webServer = new WebServer();
|
||||
const createWebServer = opts => {
|
||||
const webServer = new WebServer()
|
||||
|
||||
return Bluebird
|
||||
.bind(webServer).return(opts).map(makeWebServerListen)
|
||||
.return(webServer)
|
||||
;
|
||||
};
|
||||
|
||||
//====================================================================
|
||||
}
|
||||
|
||||
let setUpStaticFiles = (connect, opts) => {
|
||||
// ===================================================================
|
||||
|
||||
const setUpStaticFiles = (connect, opts) => {
|
||||
forEach(opts, (paths, url) => {
|
||||
if (!isArray(paths)) {
|
||||
paths = [paths];
|
||||
paths = [paths]
|
||||
}
|
||||
|
||||
forEach(paths, path => {
|
||||
debug('Setting up %s → %s', url, path);
|
||||
debug('Setting up %s → %s', url, path)
|
||||
|
||||
connect.use(url, serveStatic(path));
|
||||
});
|
||||
});
|
||||
};
|
||||
connect.use(url, serveStatic(path))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
//====================================================================
|
||||
// ===================================================================
|
||||
|
||||
let errorClasses = {
|
||||
const errorClasses = {
|
||||
ALREADY_AUTHENTICATED: AlreadyAuthenticated,
|
||||
INVALID_CREDENTIAL: InvalidCredential,
|
||||
INVALID_PARAMS: InvalidParameters,
|
||||
NO_SUCH_OBJECT: NoSuchObject,
|
||||
NOT_IMPLEMENTED: NotImplementd,
|
||||
};
|
||||
NOT_IMPLEMENTED: NotImplemented
|
||||
}
|
||||
|
||||
let apiHelpers = {
|
||||
getUserPublicProperties(user) {
|
||||
const apiHelpers = {
|
||||
getUserPublicProperties (user) {
|
||||
// Handles both properties and wrapped models.
|
||||
let properties = user.properties || user;
|
||||
const properties = user.properties || user
|
||||
|
||||
return pick(properties, 'id', 'email', 'permission');
|
||||
return pick(properties, 'id', 'email', 'permission')
|
||||
},
|
||||
|
||||
getServerPublicProperties(server) {
|
||||
getServerPublicProperties (server) {
|
||||
// Handles both properties and wrapped models.
|
||||
let properties = server.properties || server;
|
||||
const properties = server.properties || server
|
||||
|
||||
server = pick(properties, 'id', 'host', 'username');
|
||||
server = pick(properties, 'id', 'host', 'username')
|
||||
|
||||
// Injects connection status.
|
||||
const xapi = this._xapis[server.id]
|
||||
@ -189,231 +189,237 @@ let apiHelpers = {
|
||||
return server
|
||||
},
|
||||
|
||||
throw(errorId, data) {
|
||||
throw new (errorClasses[errorId])(data);
|
||||
throw (errorId, data) {
|
||||
throw new (errorClasses[errorId])(data)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let setUpApi = (webServer, xo) => {
|
||||
let context = Object.create(xo);
|
||||
assign(xo, apiHelpers);
|
||||
const setUpApi = (webServer, xo) => {
|
||||
const context = Object.create(xo)
|
||||
assign(xo, apiHelpers)
|
||||
|
||||
let api = new Api({
|
||||
context,
|
||||
});
|
||||
const api = new Api({
|
||||
context
|
||||
})
|
||||
|
||||
let webSocketServer = new WebSocket.Server({
|
||||
const webSocketServer = new WebSocket.Server({
|
||||
server: webServer,
|
||||
path: '/api/',
|
||||
});
|
||||
path: '/api/'
|
||||
})
|
||||
|
||||
webSocketServer.on('connection', connection => {
|
||||
debug('+ WebSocket connection');
|
||||
debug('+ WebSocket connection')
|
||||
|
||||
let xoConnection;
|
||||
let xoConnection
|
||||
|
||||
// Create the JSON-RPC server for this connection.
|
||||
let jsonRpc = createJsonRpcServer(message => {
|
||||
const jsonRpc = createJsonRpcServer(message => {
|
||||
if (message.type === 'request') {
|
||||
return api.call(xoConnection, message.method, message.params);
|
||||
return api.call(xoConnection, message.method, message.params)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// Create the abstract XO object for this connection.
|
||||
xoConnection = xo.createUserConnection({
|
||||
close: bind(connection.close, connection),
|
||||
notify: bind(jsonRpc.notify, jsonRpc),
|
||||
});
|
||||
notify: bind(jsonRpc.notify, jsonRpc)
|
||||
})
|
||||
|
||||
// Close the XO connection with this WebSocket.
|
||||
connection.once('close', () => {
|
||||
debug('- WebSocket connection');
|
||||
debug('- WebSocket connection')
|
||||
|
||||
xoConnection.close();
|
||||
});
|
||||
xoConnection.close()
|
||||
})
|
||||
|
||||
// Connect the WebSocket to the JSON-RPC server.
|
||||
connection.on('message', message => {
|
||||
jsonRpc.write(message);
|
||||
});
|
||||
jsonRpc.write(message)
|
||||
})
|
||||
|
||||
let onSend = error => {
|
||||
const onSend = error => {
|
||||
if (error) {
|
||||
warn('WebSocket send:', error.stack);
|
||||
warn('WebSocket send:', error.stack)
|
||||
}
|
||||
}
|
||||
};
|
||||
jsonRpc.on('data', data => {
|
||||
connection.send(JSON.stringify(data), onSend);
|
||||
});
|
||||
});
|
||||
};
|
||||
connection.send(JSON.stringify(data), onSend)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
//====================================================================
|
||||
// ===================================================================
|
||||
|
||||
let getVmConsoleUrl = (xo, id) => {
|
||||
let vm = xo.getObject(id, ['VM', 'VM-controller']);
|
||||
const getVmConsoleUrl = (xo, id) => {
|
||||
const vm = xo.getObject(id, ['VM', 'VM-controller'])
|
||||
if (!vm || vm.power_state !== 'Running') {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
let {sessionId} = xo.getXAPI(vm);
|
||||
const {sessionId} = xo.getXAPI(vm)
|
||||
|
||||
let url;
|
||||
let url
|
||||
forEach(vm.consoles, console => {
|
||||
if (console.protocol === 'rfb') {
|
||||
url = `${console.location}&session_id=${sessionId}`;
|
||||
return false;
|
||||
url = `${console.location}&session_id=${sessionId}`
|
||||
return false
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
return url;
|
||||
};
|
||||
return url
|
||||
}
|
||||
|
||||
const CONSOLE_PROXY_PATH_RE = /^\/consoles\/(.*)$/;
|
||||
const CONSOLE_PROXY_PATH_RE = /^\/consoles\/(.*)$/
|
||||
|
||||
let setUpConsoleProxy = (webServer, xo) => {
|
||||
let webSocketServer = new WebSocket.Server({
|
||||
noServer: true,
|
||||
});
|
||||
const setUpConsoleProxy = (webServer, xo) => {
|
||||
const webSocketServer = new WebSocket.Server({
|
||||
noServer: true
|
||||
})
|
||||
|
||||
webServer.on('upgrade', (req, res, head) => {
|
||||
let matches = CONSOLE_PROXY_PATH_RE.exec(req.url);
|
||||
const matches = CONSOLE_PROXY_PATH_RE.exec(req.url)
|
||||
if (!matches) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
let url = getVmConsoleUrl(xo, matches[1]);
|
||||
const url = getVmConsoleUrl(xo, matches[1])
|
||||
if (!url) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
// FIXME: lost connection due to VM restart is not detected.
|
||||
webSocketServer.handleUpgrade(req, res, head, connection => {
|
||||
wsProxy(connection, url);
|
||||
});
|
||||
});
|
||||
};
|
||||
wsProxy(connection, url)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
//====================================================================
|
||||
// ===================================================================
|
||||
|
||||
let registerPasswordAuthenticationProvider = (xo) => {
|
||||
let passwordAuthenticationProvider = coroutine(function *({
|
||||
const registerPasswordAuthenticationProvider = (xo) => {
|
||||
const passwordAuthenticationProvider = coroutine(function * ({
|
||||
email,
|
||||
password,
|
||||
}) {
|
||||
/* eslint no-throw-literal: 0 */
|
||||
|
||||
if (email === undefined || password === undefined) {
|
||||
throw null;
|
||||
throw null
|
||||
}
|
||||
|
||||
let user = yield xo.users.first({email});
|
||||
if (!user || !yield user.checkPassword(password)) {
|
||||
throw null;
|
||||
const user = yield xo.users.first({email})
|
||||
if (!user || !(yield user.checkPassword(password))) {
|
||||
throw null
|
||||
}
|
||||
return user;
|
||||
});
|
||||
return user
|
||||
})
|
||||
|
||||
xo.registerAuthenticationProvider(passwordAuthenticationProvider);
|
||||
};
|
||||
|
||||
let registerTokenAuthenticationProvider = (xo) => {
|
||||
let tokenAuthenticationProvider = coroutine(function *({
|
||||
token: tokenId,
|
||||
}) {
|
||||
if (!tokenId) {
|
||||
throw null;
|
||||
}
|
||||
|
||||
let token = yield xo.tokens.first(tokenId);
|
||||
if (!token) {
|
||||
throw null;
|
||||
}
|
||||
|
||||
return token.get('user_id');
|
||||
});
|
||||
|
||||
xo.registerAuthenticationProvider(tokenAuthenticationProvider);
|
||||
};
|
||||
|
||||
//====================================================================
|
||||
|
||||
let help;
|
||||
{
|
||||
let {name, version} = require('../package');
|
||||
help = () => `${name} v${version}`;
|
||||
xo.registerAuthenticationProvider(passwordAuthenticationProvider)
|
||||
}
|
||||
|
||||
//====================================================================
|
||||
const registerTokenAuthenticationProvider = (xo) => {
|
||||
const tokenAuthenticationProvider = coroutine(function * ({
|
||||
token: tokenId,
|
||||
}) {
|
||||
/* eslint no-throw-literal: 0 */
|
||||
|
||||
let main = coroutine(function *(args) {
|
||||
if (args.indexOf('--help') !== -1 || args.indexOf('-h') !== -1) {
|
||||
return help();
|
||||
if (!tokenId) {
|
||||
throw null
|
||||
}
|
||||
|
||||
let config = yield loadConfiguration();
|
||||
const token = yield xo.tokens.first(tokenId)
|
||||
if (!token) {
|
||||
throw null
|
||||
}
|
||||
|
||||
let webServer = yield createWebServer(config.http.listen);
|
||||
return token.get('user_id')
|
||||
})
|
||||
|
||||
xo.registerAuthenticationProvider(tokenAuthenticationProvider)
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
let help
|
||||
{
|
||||
/* eslint no-lone-blocks: 0 */
|
||||
|
||||
const {name, version} = require('../package')
|
||||
help = () => `${name} v${version}`
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const main = coroutine(function * (args) {
|
||||
if (args.indexOf('--help') !== -1 || args.indexOf('-h') !== -1) {
|
||||
return help()
|
||||
}
|
||||
|
||||
const config = yield loadConfiguration()
|
||||
|
||||
const webServer = yield createWebServer(config.http.listen)
|
||||
|
||||
// Now the web server is listening, drop privileges.
|
||||
try {
|
||||
let {user, group} = config;
|
||||
const {user, group} = config
|
||||
if (group) {
|
||||
process.setgid(group);
|
||||
debug('Group changed to', group);
|
||||
process.setgid(group)
|
||||
debug('Group changed to', group)
|
||||
}
|
||||
if (user) {
|
||||
process.setuid(user);
|
||||
debug('User changed to', user);
|
||||
process.setuid(user)
|
||||
debug('User changed to', user)
|
||||
}
|
||||
} catch (error) {
|
||||
warn('Failed to change user/group:', error);
|
||||
warn('Failed to change user/group:', error)
|
||||
}
|
||||
|
||||
// Create the main object which will connects to Xen servers and
|
||||
// manages all the models.
|
||||
let xo = new XO();
|
||||
const xo = new XO()
|
||||
xo.start({
|
||||
redis: {
|
||||
uri: config.redis && config.redis.uri,
|
||||
},
|
||||
});
|
||||
uri: config.redis && config.redis.uri
|
||||
}
|
||||
})
|
||||
|
||||
// Loads default authentication providers.
|
||||
registerPasswordAuthenticationProvider(xo);
|
||||
registerTokenAuthenticationProvider(xo);
|
||||
registerPasswordAuthenticationProvider(xo)
|
||||
registerTokenAuthenticationProvider(xo)
|
||||
|
||||
if (config.plugins) {
|
||||
yield loadPlugins(config.plugins, xo);
|
||||
yield loadPlugins(config.plugins, xo)
|
||||
}
|
||||
|
||||
// Connect is used to manage non WebSocket connections.
|
||||
let connect = createConnectApp();
|
||||
webServer.on('request', connect);
|
||||
const connect = createConnectApp()
|
||||
webServer.on('request', connect)
|
||||
|
||||
// Must be set up before the API.
|
||||
setUpConsoleProxy(webServer, xo);
|
||||
setUpConsoleProxy(webServer, xo)
|
||||
|
||||
// Must be set up before the API.
|
||||
connect.use(bind(xo.handleProxyRequest, xo));
|
||||
connect.use(bind(xo.handleProxyRequest, xo))
|
||||
|
||||
// Must be set up before the static files.
|
||||
setUpApi(webServer, xo);
|
||||
setUpApi(webServer, xo)
|
||||
|
||||
setUpStaticFiles(connect, config.http.mounts);
|
||||
setUpStaticFiles(connect, config.http.mounts)
|
||||
|
||||
if (!yield xo.users.exists()) {
|
||||
let email = 'admin@admin.net';
|
||||
let password = 'admin';
|
||||
if (!(yield xo.users.exists())) {
|
||||
const email = 'admin@admin.net'
|
||||
const password = 'admin'
|
||||
|
||||
xo.users.create(email, password, 'admin');
|
||||
info('Default user created:', email, ' with password', password);
|
||||
xo.users.create(email, password, 'admin')
|
||||
info('Default user created:', email, ' with password', password)
|
||||
}
|
||||
|
||||
// Handle gracefully shutdown.
|
||||
let closeWebServer = () => { webServer.close(); };
|
||||
process.on('SIGINT', closeWebServer);
|
||||
process.on('SIGTERM', closeWebServer);
|
||||
const closeWebServer = () => { webServer.close() }
|
||||
process.on('SIGINT', closeWebServer)
|
||||
process.on('SIGTERM', closeWebServer)
|
||||
|
||||
return eventToPromise(webServer, 'close');
|
||||
});
|
||||
return eventToPromise(webServer, 'close')
|
||||
})
|
||||
|
||||
exports = module.exports = main;
|
||||
exports = module.exports = main
|
||||
|
54
src/model.js
54
src/model.js
@ -1,70 +1,70 @@
|
||||
import assign from 'lodash.assign';
|
||||
import forEach from 'lodash.foreach';
|
||||
import isEmpty from 'lodash.isempty';
|
||||
import {EventEmitter} from 'events';
|
||||
import assign from 'lodash.assign'
|
||||
import forEach from 'lodash.foreach'
|
||||
import isEmpty from 'lodash.isempty'
|
||||
import {EventEmitter} from 'events'
|
||||
|
||||
//====================================================================
|
||||
// ===================================================================
|
||||
|
||||
export default class Model extends EventEmitter {
|
||||
constructor(properties) {
|
||||
super();
|
||||
constructor (properties) {
|
||||
super()
|
||||
|
||||
this.properties = assign({}, this.default);
|
||||
this.properties = assign({}, this.default)
|
||||
|
||||
if (properties) {
|
||||
this.set(properties);
|
||||
this.set(properties)
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the model after construction.
|
||||
initialize() {}
|
||||
initialize () {}
|
||||
|
||||
// Validate the defined properties.
|
||||
//
|
||||
// Returns the error if any.
|
||||
validate(properties) {}
|
||||
validate (properties) {}
|
||||
|
||||
// Get a property.
|
||||
get(name, def) {
|
||||
let value = this.properties[name];
|
||||
return value !== undefined ? value : def;
|
||||
get (name, def) {
|
||||
const value = this.properties[name]
|
||||
return value !== undefined ? value : def
|
||||
}
|
||||
|
||||
// Check whether a property exists.
|
||||
has(name) {
|
||||
return (this.properties[name] !== undefined);
|
||||
has (name) {
|
||||
return (this.properties[name] !== undefined)
|
||||
}
|
||||
|
||||
// Set properties.
|
||||
set(properties, value) {
|
||||
set (properties, value) {
|
||||
// This method can also be used with two arguments to set a single
|
||||
// property.
|
||||
if (value !== undefined) {
|
||||
properties = { [properties]: value };
|
||||
properties = { [properties]: value }
|
||||
}
|
||||
|
||||
let previous = {};
|
||||
const previous = {}
|
||||
|
||||
forEach(properties, (value, name) => {
|
||||
let prev = this.properties[name];
|
||||
const prev = this.properties[name]
|
||||
|
||||
if (value !== prev) {
|
||||
previous[name] = prev;
|
||||
previous[name] = prev
|
||||
|
||||
if (value === undefined) {
|
||||
delete this.properties[name];
|
||||
delete this.properties[name]
|
||||
} else {
|
||||
this.properties[name] = value;
|
||||
this.properties[name] = value
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
if (!isEmpty(previous)) {
|
||||
this.emit('change', previous);
|
||||
this.emit('change', previous)
|
||||
|
||||
forEach(previous, (value, name) => {
|
||||
this.emit('change:' + name, value);
|
||||
});
|
||||
this.emit('change:' + name, value)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
147
src/utils.js
147
src/utils.js
@ -1,38 +1,38 @@
|
||||
import base64url from 'base64url';
|
||||
import forEach from 'lodash.foreach';
|
||||
import has from 'lodash.has';
|
||||
import humanFormat from 'human-format';
|
||||
import isArray from 'lodash.isarray';
|
||||
import multiKeyHash from 'multikey-hash';
|
||||
import xml2js from 'xml2js';
|
||||
import {promisify, method} from 'bluebird';
|
||||
import {randomBytes} from 'crypto';
|
||||
import base64url from 'base64url'
|
||||
import forEach from 'lodash.foreach'
|
||||
import has from 'lodash.has'
|
||||
import humanFormat from 'human-format'
|
||||
import isArray from 'lodash.isarray'
|
||||
import multiKeyHashInt from 'multikey-hash'
|
||||
import xml2js from 'xml2js'
|
||||
import {promisify, method} from 'bluebird'
|
||||
import {randomBytes} from 'crypto'
|
||||
|
||||
randomBytes = promisify(randomBytes);
|
||||
randomBytes = promisify(randomBytes)
|
||||
|
||||
//====================================================================
|
||||
/* eslint no-lone-blocks: 0 */
|
||||
|
||||
// ===================================================================
|
||||
|
||||
// Ensure the value is an array, wrap it if necessary.
|
||||
let ensureArray = (value) => {
|
||||
export const ensureArray = (value) => {
|
||||
if (value === undefined) {
|
||||
return [];
|
||||
return []
|
||||
}
|
||||
|
||||
return isArray(value) ? value : [value];
|
||||
};
|
||||
export {ensureArray};
|
||||
return isArray(value) ? value : [value]
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// Generate a secure random Base64 string.
|
||||
let generateToken = (n = 32) => randomBytes(n).then(base64url);
|
||||
export {generateToken};
|
||||
export const generateToken = (n = 32) => randomBytes(n).then(base64url)
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
let formatXml;
|
||||
export let formatXml
|
||||
{
|
||||
let builder = new xml2js.Builder({
|
||||
const builder = new xml2js.Builder({
|
||||
xmldec: {
|
||||
// Do not include an XML header.
|
||||
//
|
||||
@ -42,109 +42,98 @@ let formatXml;
|
||||
// TODO: Find a better alternative.
|
||||
headless: true
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
formatXml = (...args) => builder.buildObject(...args);
|
||||
formatXml = (...args) => builder.buildObject(...args)
|
||||
}
|
||||
export {formatXml};
|
||||
|
||||
let parseXml;
|
||||
export let parseXml
|
||||
{
|
||||
let opts = {
|
||||
const opts = {
|
||||
mergeAttrs: true,
|
||||
explicitArray: false,
|
||||
};
|
||||
explicitArray: false
|
||||
}
|
||||
|
||||
parseXml = (xml) => {
|
||||
let result;
|
||||
let result
|
||||
|
||||
// xml2js.parseString() use a callback for synchronous code.
|
||||
xml2js.parseString(xml, opts, (error, result_) => {
|
||||
if (error) {
|
||||
throw error;
|
||||
throw error
|
||||
}
|
||||
|
||||
result = result_;
|
||||
});
|
||||
result = result_
|
||||
})
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
export {parseXml};
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
function parseSize(size) {
|
||||
let bytes = humanFormat.parse.raw(size, { scale: 'binary' });
|
||||
if (bytes.unit && bytes.unit !== 'B') {
|
||||
bytes = humanFormat.parse.raw(size);
|
||||
|
||||
if (bytes.unit && bytes.unit !== 'B') {
|
||||
throw new Error('invalid size: ' + size);
|
||||
return result
|
||||
}
|
||||
}
|
||||
return Math.floor(bytes.value * bytes.factor);
|
||||
}
|
||||
|
||||
export {parseSize};
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
export function parseSize (size) {
|
||||
let bytes = humanFormat.parse.raw(size, { scale: 'binary' })
|
||||
if (bytes.unit && bytes.unit !== 'B') {
|
||||
bytes = humanFormat.parse.raw(size)
|
||||
|
||||
if (bytes.unit && bytes.unit !== 'B') {
|
||||
throw new Error('invalid size: ' + size)
|
||||
}
|
||||
}
|
||||
return Math.floor(bytes.value * bytes.factor)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// Special value which can be returned to stop an iteration in map()
|
||||
// and mapInPlace().
|
||||
let done = {};
|
||||
export {done};
|
||||
export const done = {}
|
||||
|
||||
// Similar to `lodash.map()` for array and `lodash.mapValues()` for
|
||||
// objects.
|
||||
//
|
||||
// Note: can be interrupted by returning the special value `done`
|
||||
// provided as the forth argument.
|
||||
function map(col, iterator, thisArg = this) {
|
||||
let result = has(col, 'length') ? [] : {};
|
||||
export function map (col, iterator, thisArg = this) {
|
||||
const result = has(col, 'length') ? [] : {}
|
||||
forEach(col, (item, i) => {
|
||||
let value = iterator.call(thisArg, item, i, done);
|
||||
const value = iterator.call(thisArg, item, i, done)
|
||||
if (value === done) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
result[i] = value;
|
||||
});
|
||||
return result;
|
||||
result[i] = value
|
||||
})
|
||||
return result
|
||||
}
|
||||
export {map};
|
||||
|
||||
// Create a hash from multiple values.
|
||||
multiKeyHash = (function (multiKeyHash) {
|
||||
return method((...args) => {
|
||||
let hash = multiKeyHash(...args);
|
||||
export const multiKeyHash = method((...args) => {
|
||||
const hash = multiKeyHashInt(...args)
|
||||
|
||||
let buf = new Buffer(4);
|
||||
buf.writeUInt32LE(hash, 0);
|
||||
const buf = new Buffer(4)
|
||||
buf.writeUInt32LE(hash, 0)
|
||||
|
||||
return base64url(buf);
|
||||
});
|
||||
})(multiKeyHash);
|
||||
export {multiKeyHash};
|
||||
return base64url(buf)
|
||||
})
|
||||
|
||||
// Similar to `map()` but change the current collection.
|
||||
//
|
||||
// Note: can be interrupted by returning the special value `done`
|
||||
// provided as the forth argument.
|
||||
function mapInPlace(col, iterator, thisArg = this) {
|
||||
export function mapInPlace (col, iterator, thisArg = this) {
|
||||
forEach(col, (item, i) => {
|
||||
let value = iterator.call(thisArg, item, i, done);
|
||||
const value = iterator.call(thisArg, item, i, done)
|
||||
if (value === done) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
col[i] = value;
|
||||
});
|
||||
col[i] = value
|
||||
})
|
||||
|
||||
return col;
|
||||
return col
|
||||
}
|
||||
export {mapInPlace};
|
||||
|
||||
// Wrap a value in a function.
|
||||
let wrap = (value) => () => value;
|
||||
export {wrap};
|
||||
export const wrap = (value) => () => value
|
||||
|
@ -1,51 +1,51 @@
|
||||
import assign from 'lodash.assign';
|
||||
import debug from 'debug';
|
||||
import WebSocket from 'ws';
|
||||
import assign from 'lodash.assign'
|
||||
import debug from 'debug'
|
||||
import WebSocket from 'ws'
|
||||
|
||||
debug = debug('xo:wsProxy');
|
||||
debug = debug('xo:wsProxy')
|
||||
|
||||
let defaults = {
|
||||
const defaults = {
|
||||
// Automatically close the client connection when the remote close.
|
||||
autoClose: true,
|
||||
|
||||
// Reject secure connections to unauthorized remotes (bad CA).
|
||||
rejectUnauthorized: false,
|
||||
};
|
||||
rejectUnauthorized: false
|
||||
}
|
||||
|
||||
// Proxy a WebSocket `client` to a remote server which has `url` as
|
||||
// address.
|
||||
export default function wsProxy(client, url, opts) {
|
||||
opts = assign({}, defaults, opts);
|
||||
export default function wsProxy (client, url, opts) {
|
||||
opts = assign({}, defaults, opts)
|
||||
|
||||
let remote = new WebSocket(url, {
|
||||
const remote = new WebSocket(url, {
|
||||
protocol: opts.protocol || client.protocol,
|
||||
rejectUnauthorized: opts.rejectUnauthorized,
|
||||
rejectUnauthorized: opts.rejectUnauthorized
|
||||
}).once('open', function () {
|
||||
debug('connected to', url);
|
||||
debug('connected to', url)
|
||||
}).once('close', function () {
|
||||
debug('remote closed');
|
||||
debug('remote closed')
|
||||
|
||||
if (opts.autoClose) {
|
||||
client.close();
|
||||
client.close()
|
||||
}
|
||||
}).once('error', function (error) {
|
||||
debug('remote error', error);
|
||||
debug('remote error', error)
|
||||
}).on('message', function (message) {
|
||||
client.send(message, function (error) {
|
||||
if (error) {
|
||||
debug('client send error', error);
|
||||
debug('client send error', error)
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
client.once('close', function () {
|
||||
debug('client closed');
|
||||
remote.close();
|
||||
debug('client closed')
|
||||
remote.close()
|
||||
}).on('message', function (message) {
|
||||
remote.send(message, function (error) {
|
||||
if (error) {
|
||||
debug('remote send error', error);
|
||||
debug('remote send error', error)
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user