Merge branch 'master' into nr-fix-S3-upload

This commit is contained in:
Nicolas Raynaud
2021-05-21 11:43:50 +02:00
197 changed files with 861 additions and 810 deletions

View File

@@ -13,7 +13,7 @@ module.exports = {
overrides: [
{
files: ['cli.js', '*-cli.js', '**/*cli*/**/*.js'],
files: ['cli.{,c,m}js', '*-cli.{,c,m}js', '**/*cli*/**/*.{,c,m}js'],
rules: {
'no-console': 'off',
},

6
.gitignore vendored
View File

@@ -19,9 +19,9 @@
/packages/xen-api/plot.dat
/packages/xo-server/.xo-server.*
/packages/xo-server/src/api/index.js
/packages/xo-server/src/xapi/mixins/index.js
/packages/xo-server/src/xo-mixins/index.js
/packages/xo-server/src/api/index.mjs
/packages/xo-server/src/xapi/mixins/index.mjs
/packages/xo-server/src/xo-mixins/index.mjs
/packages/xo-server-auth-ldap/ldap.cache.conf

View File

@@ -1,8 +1,8 @@
const { asyncMap, asyncMapSettled } = require('@xen-orchestra/async-map')
const Disposable = require('promise-toolbox/Disposable.js')
const ignoreErrors = require('promise-toolbox/ignoreErrors.js')
const limitConcurrency = require('limit-concurrency-decorator').default
const { compileTemplate } = require('@xen-orchestra/template')
const { limitConcurrency } = require('limit-concurrency-decorator')
const { extractIdsFromSimplePattern } = require('./_extractIdsFromSimplePattern.js')
const { PoolMetadataBackup } = require('./_PoolMetadataBackup.js')

View File

@@ -1,10 +1,10 @@
const assert = require('assert')
const limitConcurrency = require('limit-concurrency-decorator').default
const { asyncMap } = require('@xen-orchestra/async-map')
const { default: Vhd, mergeVhd } = require('vhd-lib')
const { dirname, resolve } = require('path')
const { DISK_TYPE_DIFFERENCING } = require('vhd-lib/dist/_constants.js')
const { isMetadataFile, isVhdFile, isXvaFile, isXvaSumFile } = require('./_backupType.js')
const { limitConcurrency } = require('limit-concurrency-decorator')
// chain is an array of VHDs from child to parent
//

View File

@@ -28,7 +28,7 @@
"end-of-stream": "^1.4.4",
"fs-extra": "^9.0.0",
"golike-defer": "^0.5.1",
"limit-concurrency-decorator": "^0.4.0",
"limit-concurrency-decorator": "^0.5.0",
"lodash": "^4.17.20",
"node-zone": "^0.4.0",
"parse-pairs": "^1.1.0",

View File

@@ -1 +0,0 @@
module.exports = require('../../@xen-orchestra/babel-config')(require('./package.json'))

View File

@@ -1 +0,0 @@
../../scripts/babel-eslintrc.js

View File

@@ -11,7 +11,7 @@
// process.env.http_proxy
// ])
// ```
export default function defined() {
function defined() {
let args = arguments
let n = args.length
if (n === 1) {
@@ -29,6 +29,7 @@ export default function defined() {
}
}
}
module.exports = exports = defined
// Usage:
//
@@ -39,7 +40,7 @@ export default function defined() {
// const getFriendName = _ => _.friends[0].name
// const friendName = get(getFriendName, props.user)
// ```
export const get = (accessor, arg) => {
function get(accessor, arg) {
try {
return accessor(arg)
} catch (error) {
@@ -49,6 +50,7 @@ export const get = (accessor, arg) => {
}
}
}
exports.get = get
// Usage:
//
@@ -58,4 +60,6 @@ export const get = (accessor, arg) => {
// _ => new ProxyAgent(_)
// )
// ```
export const ifDef = (value, thenFn) => (value !== undefined ? thenFn(value) : value)
exports.ifDef = function ifDef(value, thenFn) {
return value !== undefined ? thenFn(value) : value
}

View File

@@ -16,27 +16,13 @@
"url": "https://vates.fr"
},
"preferGlobal": false,
"main": "dist/",
"browserslist": [
">2%"
],
"engines": {
"node": ">=6"
},
"devDependencies": {
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"cross-env": "^7.0.2",
"rimraf": "^3.0.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"clean": "rimraf dist/",
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -1 +0,0 @@
module.exports = require('../../@xen-orchestra/babel-config')(require('./package.json'))

View File

@@ -1 +0,0 @@
../../scripts/babel-eslintrc.js

View File

@@ -1,4 +1,4 @@
export default function emitAsync(event) {
module.exports = function emitAsync(event) {
let opts
let i = 1

View File

@@ -16,27 +16,13 @@
"url": "https://vates.fr"
},
"preferGlobal": false,
"main": "dist/",
"browserslist": [
">2%"
],
"engines": {
"node": ">=6"
},
"devDependencies": {
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"cross-env": "^7.0.2",
"rimraf": "^3.0.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"clean": "rimraf dist/",
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -27,7 +27,7 @@
"execa": "^5.0.0",
"fs-extra": "^9.0.0",
"get-stream": "^6.0.0",
"limit-concurrency-decorator": "^0.4.0",
"limit-concurrency-decorator": "^0.5.0",
"lodash": "^4.17.4",
"promise-toolbox": "^0.19.2",
"proper-lockfile": "^4.1.2",

View File

@@ -1,10 +1,10 @@
import asyncMapSettled from '@xen-orchestra/async-map/legacy'
import getStream from 'get-stream'
import limit from 'limit-concurrency-decorator'
import path, { basename } from 'path'
import synchronized from 'decorator-synchronized'
import { coalesceCalls } from '@vates/coalesce-calls'
import { fromCallback, fromEvent, ignoreErrors, timeout } from 'promise-toolbox'
import { limitConcurrency } from 'limit-concurrency-decorator'
import { parse } from 'xo-remote-parser'
import { pipeline } from 'stream'
import { randomBytes } from 'crypto'
@@ -74,7 +74,7 @@ export default class RemoteHandlerAbstract {
}
;({ highWaterMark: this._highWaterMark, timeout: this._timeout = DEFAULT_TIMEOUT } = options)
const sharedLimit = limit(options.maxParallelOperations ?? DEFAULT_MAX_PARALLEL_OPERATIONS)
const sharedLimit = limitConcurrency(options.maxParallelOperations ?? DEFAULT_MAX_PARALLEL_OPERATIONS)
this.closeFile = sharedLimit(this.closeFile)
this.getInfo = sharedLimit(this.getInfo)
this.getSize = sharedLimit(this.getSize)

View File

@@ -1 +0,0 @@
module.exports = require('../../@xen-orchestra/babel-config')(require('./package.json'))

View File

@@ -1 +0,0 @@
../../scripts/babel-eslintrc.js

View File

@@ -1 +1,107 @@
module.exports = require('./dist/configure')
const createConsoleTransport = require('./transports/console')
const { LEVELS, resolve } = require('./levels')
const { compileGlobPattern } = require('./utils')
// ===================================================================
const createTransport = config => {
if (typeof config === 'function') {
return config
}
if (Array.isArray(config)) {
const transports = config.map(createTransport)
const { length } = transports
return function () {
for (let i = 0; i < length; ++i) {
transports[i].apply(this, arguments)
}
}
}
let { filter } = config
let transport = createTransport(config.transport)
const level = resolve(config.level)
if (filter !== undefined) {
if (typeof filter === 'string') {
const re = compileGlobPattern(filter)
filter = log => re.test(log.namespace)
}
const orig = transport
transport = function (log) {
if ((level !== undefined && log.level >= level) || filter(log)) {
return orig.apply(this, arguments)
}
}
} else if (level !== undefined) {
const orig = transport
transport = function (log) {
if (log.level >= level) {
return orig.apply(this, arguments)
}
}
}
return transport
}
const symbol = typeof Symbol !== 'undefined' ? Symbol.for('@xen-orchestra/log') : '@@@xen-orchestra/log'
const { env } = process
global[symbol] = createTransport({
// display warnings or above, and all that are enabled via DEBUG or
// NODE_DEBUG env
filter: [env.DEBUG, env.NODE_DEBUG].filter(Boolean).join(','),
level: resolve(env.LOG_LEVEL, LEVELS.INFO),
transport: createConsoleTransport(),
})
const configure = config => {
global[symbol] = createTransport(config)
}
exports.configure = configure
// -------------------------------------------------------------------
const catchGlobalErrors = logger => {
// patch process
const onUncaughtException = error => {
logger.error('uncaught exception', { error })
}
const onUnhandledRejection = error => {
logger.warn('possibly unhandled rejection', { error })
}
const onWarning = error => {
logger.warn('Node warning', { error })
}
process.on('uncaughtException', onUncaughtException)
process.on('unhandledRejection', onUnhandledRejection)
process.on('warning', onWarning)
// patch EventEmitter
const EventEmitter = require('events')
const { prototype } = EventEmitter
const { emit } = prototype
function patchedEmit(event, error) {
if (event === 'error' && this.listenerCount(event) === 0) {
logger.error('unhandled error event', { error })
return false
}
return emit.apply(this, arguments)
}
prototype.emit = patchedEmit
return () => {
process.removeListener('uncaughtException', onUncaughtException)
process.removeListener('unhandledRejection', onUnhandledRejection)
process.removeListener('warning', onWarning)
if (prototype.emit === patchedEmit) {
prototype.emit = emit
}
}
}
exports.catchGlobalErrors = catchGlobalErrors

View File

@@ -1,5 +1,5 @@
import createTransport from './transports/console'
import LEVELS, { resolve } from './levels'
const createTransport = require('./transports/console')
const { LEVELS, resolve } = require('./levels')
const symbol = typeof Symbol !== 'undefined' ? Symbol.for('@xen-orchestra/log') : '@@@xen-orchestra/log'
if (!(symbol in global)) {
@@ -68,5 +68,7 @@ prototype.wrap = function (message, fn) {
}
}
export const createLogger = namespace => new Logger(namespace)
export { createLogger as default }
const createLogger = namespace => new Logger(namespace)
module.exports = exports = createLogger
exports.createLogger = createLogger

View File

@@ -1,5 +1,5 @@
const LEVELS = Object.create(null)
export { LEVELS as default }
exports.LEVELS = LEVELS
// https://github.com/trentm/node-bunyan#levels
LEVELS.FATAL = 60 // service/app is going down
@@ -8,7 +8,8 @@ LEVELS.WARN = 40 // something went wrong but it's not fatal
LEVELS.INFO = 30 // detail on unusual but normal operation
LEVELS.DEBUG = 20
export const NAMES = Object.create(null)
const NAMES = Object.create(null)
exports.NAMES = NAMES
for (const name in LEVELS) {
NAMES[LEVELS[name]] = name
}
@@ -16,7 +17,7 @@ for (const name in LEVELS) {
// resolves to the number representation of a level
//
// returns `defaultLevel` if invalid
export const resolve = (level, defaultLevel) => {
const resolve = (level, defaultLevel) => {
const type = typeof level
if (type === 'number') {
if (level in NAMES) {
@@ -30,6 +31,7 @@ export const resolve = (level, defaultLevel) => {
}
return defaultLevel
}
exports.resolve = resolve
Object.freeze(LEVELS)
Object.freeze(NAMES)

View File

@@ -1,8 +1,8 @@
/* eslint-env jest */
import { forEach, isInteger } from 'lodash'
const { forEach, isInteger } = require('lodash')
import LEVELS, { NAMES, resolve } from './levels'
const { LEVELS, NAMES, resolve } = require('./levels')
describe('LEVELS', () => {
it('maps level names to their integer values', () => {

View File

@@ -16,7 +16,6 @@
"url": "https://vates.fr"
},
"preferGlobal": false,
"main": "dist/",
"browserslist": [
">2%"
],
@@ -27,21 +26,7 @@
"lodash": "^4.17.4",
"promise-toolbox": "^0.19.2"
},
"devDependencies": {
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^7.0.2",
"rimraf": "^3.0.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"clean": "rimraf dist/",
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "yarn run clean",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build",
"postversion": "npm publish"
}
}

View File

@@ -1,105 +0,0 @@
import createConsoleTransport from './transports/console'
import LEVELS, { resolve } from './levels'
import { compileGlobPattern } from './utils'
// ===================================================================
const createTransport = config => {
if (typeof config === 'function') {
return config
}
if (Array.isArray(config)) {
const transports = config.map(createTransport)
const { length } = transports
return function () {
for (let i = 0; i < length; ++i) {
transports[i].apply(this, arguments)
}
}
}
let { filter } = config
let transport = createTransport(config.transport)
const level = resolve(config.level)
if (filter !== undefined) {
if (typeof filter === 'string') {
const re = compileGlobPattern(filter)
filter = log => re.test(log.namespace)
}
const orig = transport
transport = function (log) {
if ((level !== undefined && log.level >= level) || filter(log)) {
return orig.apply(this, arguments)
}
}
} else if (level !== undefined) {
const orig = transport
transport = function (log) {
if (log.level >= level) {
return orig.apply(this, arguments)
}
}
}
return transport
}
const symbol = typeof Symbol !== 'undefined' ? Symbol.for('@xen-orchestra/log') : '@@@xen-orchestra/log'
const { env } = process
global[symbol] = createTransport({
// display warnings or above, and all that are enabled via DEBUG or
// NODE_DEBUG env
filter: [env.DEBUG, env.NODE_DEBUG].filter(Boolean).join(','),
level: resolve(env.LOG_LEVEL, LEVELS.INFO),
transport: createConsoleTransport(),
})
export const configure = config => {
global[symbol] = createTransport(config)
}
// -------------------------------------------------------------------
export const catchGlobalErrors = logger => {
// patch process
const onUncaughtException = error => {
logger.error('uncaught exception', { error })
}
const onUnhandledRejection = error => {
logger.warn('possibly unhandled rejection', { error })
}
const onWarning = error => {
logger.warn('Node warning', { error })
}
process.on('uncaughtException', onUncaughtException)
process.on('unhandledRejection', onUnhandledRejection)
process.on('warning', onWarning)
// patch EventEmitter
const EventEmitter = require('events')
const { prototype } = EventEmitter
const { emit } = prototype
function patchedEmit(event, error) {
if (event === 'error' && this.listenerCount(event) === 0) {
logger.error('unhandled error event', { error })
return false
}
return emit.apply(this, arguments)
}
prototype.emit = patchedEmit
return () => {
process.removeListener('uncaughtException', onUncaughtException)
process.removeListener('unhandledRejection', onUnhandledRejection)
process.removeListener('warning', onWarning)
if (prototype.emit === patchedEmit) {
prototype.emit = emit
}
}
}

View File

@@ -1,79 +0,0 @@
import LEVELS, { NAMES } from '../levels'
const { DEBUG, ERROR, FATAL, INFO, WARN } = LEVELS
let formatLevel, formatNamespace
if (process.stdout !== undefined && process.stdout.isTTY && process.stderr !== undefined && process.stderr.isTTY) {
const ansi = (style, str) => `\x1b[${style}m${str}\x1b[0m`
const LEVEL_STYLES = {
[DEBUG]: '2',
[ERROR]: '1;31',
[FATAL]: '1;31',
[INFO]: '1',
[WARN]: '1;33',
}
formatLevel = level => {
const style = LEVEL_STYLES[level]
const name = NAMES[level]
return style === undefined ? name : ansi(style, name)
}
const NAMESPACE_COLORS = [
196,
202,
208,
214,
220,
226,
190,
154,
118,
82,
46,
47,
48,
49,
50,
51,
45,
39,
33,
27,
21,
57,
93,
129,
165,
201,
200,
199,
198,
197,
]
formatNamespace = namespace => {
// https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
let hash = 0
for (let i = 0, n = namespace.length; i < n; ++i) {
hash = ((hash << 5) - hash + namespace.charCodeAt(i)) | 0
}
return ansi(`1;38;5;${NAMESPACE_COLORS[Math.abs(hash) % NAMESPACE_COLORS.length]}`, namespace)
}
} else {
formatLevel = str => NAMES[str]
formatNamespace = str => str
}
const consoleTransport = ({ data, level, namespace, message, time }) => {
const fn =
/* eslint-disable no-console */
level < INFO ? console.log : level < WARN ? console.info : level < ERROR ? console.warn : console.error
/* eslint-enable no-console */
const args = [time.toISOString(), formatNamespace(namespace), formatLevel(level), message]
if (data != null) {
args.push(data)
}
fn.apply(console, args)
}
export default () => consoleTransport

View File

@@ -1,64 +0,0 @@
import fromCallback from 'promise-toolbox/fromCallback'
import prettyFormat from 'pretty-format' // eslint-disable-line node/no-extraneous-import
import { createTransport } from 'nodemailer' // eslint-disable-line node/no-extraneous-import
import { evalTemplate, required } from '../utils'
import { NAMES } from '../levels'
export default ({
// transport options (https://nodemailer.com/smtp/)
auth,
authMethod,
host,
ignoreTLS,
port,
proxy,
requireTLS,
secure,
service,
tls,
// message options (https://nodemailer.com/message/)
bcc,
cc,
from = required('from'),
to = required('to'),
subject = '[{{level}} - {{namespace}}] {{time}} {{message}}',
}) => {
const transporter = createTransport(
{
auth,
authMethod,
host,
ignoreTLS,
port,
proxy,
requireTLS,
secure,
service,
tls,
disableFileAccess: true,
disableUrlAccess: true,
},
{
bcc,
cc,
from,
to,
}
)
return log =>
fromCallback(cb =>
transporter.sendMail(
{
subject: evalTemplate(subject, key =>
key === 'level' ? NAMES[log.level] : key === 'time' ? log.time.toISOString() : log[key]
),
text: prettyFormat(log.data),
},
cb
)
)
}

View File

@@ -1,7 +0,0 @@
export default () => {
const memoryLogger = log => {
logs.push(log)
}
const logs = (memoryLogger.logs = [])
return memoryLogger
}

View File

@@ -1,41 +0,0 @@
import fromCallback from 'promise-toolbox/fromCallback'
import splitHost from 'split-host'
import { createClient, Facility, Severity, Transport } from 'syslog-client'
import LEVELS from '../levels'
// https://github.com/paulgrove/node-syslog-client#syslogseverity
const LEVEL_TO_SEVERITY = {
[LEVELS.FATAL]: Severity.Critical,
[LEVELS.ERROR]: Severity.Error,
[LEVELS.WARN]: Severity.Warning,
[LEVELS.INFO]: Severity.Informational,
[LEVELS.DEBUG]: Severity.Debug,
}
const facility = Facility.User
export default target => {
const opts = {}
if (target !== undefined) {
if (target.startsWith('tcp://')) {
target = target.slice(6)
opts.transport = Transport.Tcp
} else if (target.startsWith('udp://')) {
target = target.slice(6)
opts.transport = Transport.Udp
}
;({ host: target, port: opts.port } = splitHost(target))
}
const client = createClient(target, opts)
return log =>
fromCallback(cb =>
client.log(log.message, {
facility,
severity: LEVEL_TO_SEVERITY[log.level],
})
)
}

View File

@@ -1 +1,82 @@
module.exports = require('../dist/transports/console.js')
const { LEVELS, NAMES } = require('../levels')
const { DEBUG, ERROR, FATAL, INFO, WARN } = LEVELS
let formatLevel, formatNamespace
if (process.stdout !== undefined && process.stdout.isTTY && process.stderr !== undefined && process.stderr.isTTY) {
const ansi = (style, str) => `\x1b[${style}m${str}\x1b[0m`
const LEVEL_STYLES = {
[DEBUG]: '2',
[ERROR]: '1;31',
[FATAL]: '1;31',
[INFO]: '1',
[WARN]: '1;33',
}
formatLevel = level => {
const style = LEVEL_STYLES[level]
const name = NAMES[level]
return style === undefined ? name : ansi(style, name)
}
const NAMESPACE_COLORS = [
196,
202,
208,
214,
220,
226,
190,
154,
118,
82,
46,
47,
48,
49,
50,
51,
45,
39,
33,
27,
21,
57,
93,
129,
165,
201,
200,
199,
198,
197,
]
formatNamespace = namespace => {
// https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
let hash = 0
for (let i = 0, n = namespace.length; i < n; ++i) {
hash = ((hash << 5) - hash + namespace.charCodeAt(i)) | 0
}
return ansi(`1;38;5;${NAMESPACE_COLORS[Math.abs(hash) % NAMESPACE_COLORS.length]}`, namespace)
}
} else {
formatLevel = str => NAMES[str]
formatNamespace = str => str
}
const consoleTransport = ({ data, level, namespace, message, time }) => {
const fn =
/* eslint-disable no-console */
level < INFO ? console.log : level < WARN ? console.info : level < ERROR ? console.warn : console.error
/* eslint-enable no-console */
const args = [time.toISOString(), formatNamespace(namespace), formatLevel(level), message]
if (data != null) {
args.push(data)
}
fn.apply(console, args)
}
const createTransport = () => consoleTransport
module.exports = exports = createTransport

View File

@@ -1 +1,66 @@
module.exports = require('../dist/transports/email.js')
const fromCallback = require('promise-toolbox/fromCallback')
const nodemailer = require('nodemailer') // eslint-disable-line node/no-extraneous-import
const prettyFormat = require('pretty-format') // eslint-disable-line node/no-extraneous-import
const { evalTemplate, required } = require('../utils')
const { NAMES } = require('../levels')
function createTransport({
// transport options (https://nodemailer.com/smtp/)
auth,
authMethod,
host,
ignoreTLS,
port,
proxy,
requireTLS,
secure,
service,
tls,
// message options (https://nodemailer.com/message/)
bcc,
cc,
from = required('from'),
to = required('to'),
subject = '[{{level}} - {{namespace}}] {{time}} {{message}}',
}) {
const transporter = nodemailer.createTransport(
{
auth,
authMethod,
host,
ignoreTLS,
port,
proxy,
requireTLS,
secure,
service,
tls,
disableFileAccess: true,
disableUrlAccess: true,
},
{
bcc,
cc,
from,
to,
}
)
return log =>
fromCallback(cb =>
transporter.sendMail(
{
subject: evalTemplate(subject, key =>
key === 'level' ? NAMES[log.level] : key === 'time' ? log.time.toISOString() : log[key]
),
text: prettyFormat(log.data),
},
cb
)
)
}
module.exports = exports = createTransport

View File

@@ -1 +1,9 @@
module.exports = require('../dist/transports/memory.js')
function createTransport() {
const memoryLogger = log => {
logs.push(log)
}
const logs = (memoryLogger.logs = [])
return memoryLogger
}
module.exports = exports = createTransport

View File

@@ -1 +1,43 @@
module.exports = require('../dist/transports/syslog.js')
const fromCallback = require('promise-toolbox/fromCallback')
const splitHost = require('split-host')
const { createClient, Facility, Severity, Transport } = require('syslog-client')
const LEVELS = require('../levels')
// https://github.com/paulgrove/node-syslog-client#syslogseverity
const LEVEL_TO_SEVERITY = {
[LEVELS.FATAL]: Severity.Critical,
[LEVELS.ERROR]: Severity.Error,
[LEVELS.WARN]: Severity.Warning,
[LEVELS.INFO]: Severity.Informational,
[LEVELS.DEBUG]: Severity.Debug,
}
const facility = Facility.User
function createTransport(target) {
const opts = {}
if (target !== undefined) {
if (target.startsWith('tcp://')) {
target = target.slice(6)
opts.transport = Transport.Tcp
} else if (target.startsWith('udp://')) {
target = target.slice(6)
opts.transport = Transport.Udp
}
;({ host: target, port: opts.port } = splitHost(target))
}
const client = createClient(target, opts)
return log =>
fromCallback(cb =>
client.log(log.message, {
facility,
severity: LEVEL_TO_SEVERITY[log.level],
})
)
}
module.exports = exports = createTransport

View File

@@ -1,19 +1,20 @@
import escapeRegExp from 'lodash/escapeRegExp'
const escapeRegExp = require('lodash/escapeRegExp')
// ===================================================================
const TPL_RE = /\{\{(.+?)\}\}/g
export const evalTemplate = (tpl, data) => {
const evalTemplate = (tpl, data) => {
const getData = typeof data === 'function' ? (_, key) => data(key) : (_, key) => data[key]
return tpl.replace(TPL_RE, getData)
}
exports.evalTemplate = evalTemplate
// -------------------------------------------------------------------
const compileGlobPatternFragment = pattern => pattern.split('*').map(escapeRegExp).join('.*')
export const compileGlobPattern = pattern => {
const compileGlobPattern = pattern => {
const no = []
const yes = []
pattern.split(/[\s,]+/).forEach(pattern => {
@@ -40,19 +41,22 @@ export const compileGlobPattern = pattern => {
return new RegExp(raw.join(''))
}
exports.compileGlobPattern = compileGlobPattern
// -------------------------------------------------------------------
export const required = name => {
const required = name => {
throw new Error(`missing required arg ${name}`)
}
exports.required = required
// -------------------------------------------------------------------
export const serializeError = error => ({
const serializeError = error => ({
...error, // Copy enumerable properties.
code: error.code,
message: error.message,
name: error.name,
stack: error.stack,
})
exports.serializeError = serializeError

View File

@@ -1,6 +1,6 @@
/* eslint-env jest */
import { compileGlobPattern } from './utils'
const { compileGlobPattern } = require('./utils')
describe('compileGlobPattern()', () => {
it('works', () => {

View File

@@ -1,5 +1,5 @@
const assert = require('assert')
const emitAsync = require('@xen-orchestra/emit-async').default
const emitAsync = require('@xen-orchestra/emit-async')
const EventEmitter = require('events')
const { createLogger } = require('@xen-orchestra/log')

View File

@@ -48,7 +48,7 @@
"golike-defer": "^0.5.1",
"http-server-plus": "^0.11.0",
"json-rpc-protocol": "^0.13.1",
"jsonrpc-websocket-client": "^0.5.0",
"jsonrpc-websocket-client": "^0.6.0",
"koa": "^2.5.1",
"koa-compress": "^5.0.1",
"koa-helmet": "^5.1.0",
@@ -73,8 +73,7 @@
"@vates/toggle-scripts": "^1.0.0",
"babel-plugin-transform-dev": "^2.0.1",
"cross-env": "^7.0.2",
"index-modules": "^0.4.0",
"rimraf": "^3.0.0"
"index-modules": "^0.4.0"
},
"scripts": {
"_build": "index-modules --index-file index.mjs src/app/mixins && babel --delete-dir-on-start --keep-file-extension --source-maps --out-dir=dist/ src/",

View File

@@ -2,18 +2,18 @@ import Disposable from 'promise-toolbox/Disposable.js'
import fromCallback from 'promise-toolbox/fromCallback.js'
import fromEvent from 'promise-toolbox/fromEvent.js'
import fse from 'fs-extra'
import JsonRpcWebsocketClient from 'jsonrpc-websocket-client'
import parsePairs from 'parse-pairs'
import { createLogger } from '@xen-orchestra/log'
import { deduped } from '@vates/disposable/deduped.js'
import { execFile, spawn } from 'child_process'
import { JsonRpcWebSocketClient } from 'jsonrpc-websocket-client'
const TUNNEL_SERVICE = 'xoa-support-tunnel.service'
const { debug, warn } = createLogger('xo:proxy:appliance')
const getUpdater = deduped(async function () {
const updater = new JsonRpcWebsocketClient('ws://localhost:9001')
const updater = new JsonRpcWebSocketClient('ws://localhost:9001')
await updater.open()
return new Disposable(() => updater.close(), updater)
})
@@ -153,6 +153,10 @@ export default class Appliance {
// A proxy can be bound to a unique license
getSelfLicense() {
return Disposable.use(getUpdater(), _ => _.call('getSelfLicenses').then(licenses => licenses[0]))
return Disposable.use(getUpdater(), async updater => {
const licenses = await updater.call('getSelfLicenses')
const now = Date.now()
return licenses.find(({ expires }) => expires === undefined || expires > now)
})
}
}

View File

@@ -107,8 +107,8 @@ export default class Backups {
async function () {
if (!__DEV__) {
const license = await app.appliance.getSelfLicense()
if (license === undefined || license.expires < Date.now()) {
throw new Error('the proxy license is not valid')
if (license === undefined) {
throw new Error('no valid proxy license')
}
}
return run.apply(this, arguments)

View File

@@ -8,6 +8,7 @@
> Users must be able to say: “Nice enhancement, I'm eager to test it”
- [Metadata Backup] Add a warning on restoring a metadata backup (PR [#5769](https://github.com/vatesfr/xen-orchestra/pull/5769))
- [SAML] Compatible with users created with other authentication providers (PR [#5781](https://github.com/vatesfr/xen-orchestra/pull/5781))
### Bug fixes
@@ -32,6 +33,12 @@
>
> In case of conflict, the highest (lowest in previous list) `$version` wins.
- @xen-orchestra/emit-async minor
- @xen-orchestra/defined patch
- xo-collection minor
- @xen-orchestra/log patch
- xen-api minor
- xo-server-auth-saml minor
- xo-server-backup-reports patch
- xo-web minor
- xo-server patch

View File

@@ -113,7 +113,7 @@ Then restart Xen Orchestra if it was running.
```
yarn global add forever
# Run the below as the user owning XO
forever start bin/xo-server
forever start dist/cli.mjs
```
- Or you can use [forever-service](https://github.com/zapty/forever-service) to install XO as a system service, so it starts automatically at boot. Run the following as root:

View File

@@ -190,7 +190,7 @@ Then restart Xen Orchestra if it was running.
```
yarn global add forever
# Run the below as the user owning XO
forever start bin/xo-server
forever start dist/cli.mjs
```
- Or you can use [forever-service](https://github.com/zapty/forever-service) to install XO as a system service, so it starts automatically at boot. Run the following as root:

View File

@@ -21,7 +21,7 @@
"async-iterator-to-stream": "^1.0.2",
"core-js": "^3.0.0",
"fs-extra": "^9.0.0",
"limit-concurrency-decorator": "^0.4.0",
"limit-concurrency-decorator": "^0.5.0",
"lodash": "^4.17.4",
"promise-toolbox": "^0.19.2",
"struct-fu": "^1.2.0",

View File

@@ -1,9 +1,9 @@
// TODO: remove once completely merged in vhd.js
import assert from 'assert'
import concurrency from 'limit-concurrency-decorator'
import noop from './_noop'
import { createLogger } from '@xen-orchestra/log'
import { limitConcurrency } from 'limit-concurrency-decorator'
import Vhd from './vhd'
import { basename, dirname } from 'path'
@@ -14,7 +14,7 @@ const { warn } = createLogger('vhd-lib:merge')
// Merge vhd child into vhd parent.
//
// TODO: rename the VHD file during the merge
export default concurrency(2)(async function merge(
export default limitConcurrency(2)(async function merge(
parentHandler,
parentPath,
childHandler,

View File

@@ -42,6 +42,7 @@ const usage = 'Usage: xen-api <url> [<user> [<password>]]'
async function main(createClient) {
const opts = minimist(process.argv.slice(2), {
string: ['session-id'],
boolean: ['allow-unauthorized', 'help', 'read-only', 'verbose'],
alias: {
@@ -68,6 +69,8 @@ async function main(createClient) {
if (opts._.length > 1) {
const [, user, password = await askPassword()] = opts._
auth = { user, password }
} else if (opts['session-id'] !== undefined) {
auth = { sessionId: opts['session-id'] }
}
{

View File

@@ -1,9 +1,9 @@
import assert from 'assert'
import Collection from 'xo-collection'
import dns from 'dns'
import kindOf from 'kindof'
import ms from 'ms'
import httpRequest from 'http-request-plus'
import { Collection } from 'xo-collection'
import { EventEmitter } from 'events'
import { map, noop, omit } from 'lodash'
import { cancelable, defer, fromCallback, fromEvents, ignoreErrors, pDelay, pRetry, pTimeout } from 'promise-toolbox'
@@ -700,25 +700,34 @@ export class Xapi extends EventEmitter {
_sessionCallRetryOptions = {
tries: 2,
when: error => this._status !== DISCONNECTED && error?.code === 'SESSION_INVALID',
when: error =>
this._status !== DISCONNECTED && error?.code === 'SESSION_INVALID' && this._auth.password !== undefined,
onRetry: () => this._sessionOpen(),
}
_sessionCall(method, args, timeout) {
async _sessionCall(method, args, timeout) {
if (method.startsWith('session.')) {
return Promise.reject(new Error('session.*() methods are disabled from this interface'))
}
return pRetry(() => {
const sessionId = this._sessionId
assert.notStrictEqual(sessionId, undefined)
try {
return await pRetry(() => {
const sessionId = this._sessionId
assert.notStrictEqual(sessionId, undefined)
const newArgs = [sessionId]
if (args !== undefined) {
newArgs.push.apply(newArgs, args)
const newArgs = [sessionId]
if (args !== undefined) {
newArgs.push.apply(newArgs, args)
}
return this._call(method, newArgs, timeout)
}, this._sessionCallRetryOptions)
} catch (error) {
if (error?.code === 'SESSION_INVALID') {
await ignoreErrors.call(this.disconnect())
}
return this._call(method, newArgs, timeout)
}, this._sessionCallRetryOptions)
throw error
}
}
// FIXME: (probably rare) race condition leading to unnecessary login when:
@@ -728,29 +737,40 @@ export class Xapi extends EventEmitter {
// 3. the session is renewed
// 4. the second call fails with SESSION_INVALID which leads to a new
// unnecessary renewal
_sessionOpenRetryOptions = {
tries: 2,
when: { code: 'HOST_IS_SLAVE' },
onRetry: error => {
this._setUrl({ ...this._url, hostname: error.params[0] })
},
}
_sessionOpen = coalesceCalls(this._sessionOpen)
async _sessionOpen() {
const { user, password } = this._auth
const params = [user, password]
this._sessionId = await pRetry(
() => this._interruptOnDisconnect(this._call('session.login_with_password', params)),
{
tries: 2,
when: { code: 'HOST_IS_SLAVE' },
onRetry: error => {
this._setUrl({ ...this._url, hostname: error.params[0] })
},
}
)
const { user, password, sessionId } = this._auth
this._sessionId = sessionId
if (sessionId === undefined) {
const params = [user, password]
this._sessionId = await pRetry(
() => this._interruptOnDisconnect(this._call('session.login_with_password', params)),
this._sessionOpenRetryOptions
)
}
const oldPoolRef = this._pool?.$ref
// Similar to `(await this.getAllRecords('pool'))[0]` but prevents a
// deadlock in case of error due to a pRetry calling _sessionOpen again
const pools = await this._call('pool.get_all_records', [this._sessionId])
const pools = await pRetry(
() => this._call('pool.get_all_records', [this._sessionId]),
this._sessionOpenRetryOptions
)
const poolRef = Object.keys(pools)[0]
this._pool = this._wrapRecord('pool', poolRef, pools[poolRef])
this.emit('sessionId', this._sessionId)
// if the pool ref has changed, it means that the XAPI has been restarted or
// it's not the same XAPI, we need to refetch the available types and reset
// the event loop in that case
@@ -781,7 +801,7 @@ export class Xapi extends EventEmitter {
}
_setUrl(url) {
this._humanId = `${this._auth.user}@${url.hostname}`
this._humanId = `${this._auth.user ?? 'unknown'}@${url.hostname}`
this._transport = autoTransport({
secureOptions: {
minVersion: 'TLSv1',

View File

@@ -17,7 +17,7 @@ Installation of the [npm package](https://npmjs.org/package/xo-collection):
## Usage
```javascript
var Collection = require('xo-collection')
var { Collection } = require('xo-collection')
```
### Creation
@@ -218,7 +218,7 @@ for (const value of col.values()) {
### Views
```javascript
const View = require('xo-collection/view')
const { View } = require('xo-collection/view')
```
> A view is a read-only collection which contains only the items of a

View File

@@ -1,5 +1,5 @@
```javascript
var Collection = require('xo-collection')
var { Collection } = require('xo-collection')
```
### Creation
@@ -200,7 +200,7 @@ for (const value of col.values()) {
### Views
```javascript
const View = require('xo-collection/view')
const { View } = require('xo-collection/view')
```
> A view is a read-only collection which contains only the items of a

View File

@@ -30,7 +30,7 @@
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"cross-env": "^7.0.2",
"event-to-promise": "^0.8.0",
"promise-toolbox": "^0.19.2",
"rimraf": "^3.0.0"
},
"scripts": {

View File

@@ -74,7 +74,7 @@ const isValidKey = key => typeof key === 'number' || typeof key === 'string'
// -------------------------------------------------------------------
export default class Collection extends EventEmitter {
export class Collection extends EventEmitter {
constructor() {
super()

View File

@@ -1,9 +1,9 @@
/* eslint-env jest */
import eventToPromise from 'event-to-promise'
import fromEvent from 'promise-toolbox/fromEvent'
import { forEach } from 'lodash'
import Collection, { DuplicateItem, NoSuchItem } from './collection'
import { Collection, DuplicateItem, NoSuchItem } from './collection'
// ===================================================================
@@ -69,7 +69,7 @@ describe('Collection', function () {
expect(spy).not.toHaveBeenCalled()
// Async event.
return eventToPromise(col, 'add').then(function (added) {
return fromEvent(col, 'add').then(function (added) {
expect(Object.keys(added)).toEqual(['foo'])
expect(added.foo).toBe(true)
})
@@ -102,7 +102,7 @@ describe('Collection', function () {
expect(spy).not.toHaveBeenCalled()
// Async event.
return eventToPromise(col, 'update').then(function (updated) {
return fromEvent(col, 'update').then(function (updated) {
expect(Object.keys(updated)).toEqual(['bar'])
expect(updated.bar).toBe(2)
})
@@ -134,7 +134,7 @@ describe('Collection', function () {
expect(spy).not.toHaveBeenCalled()
// Async event.
return eventToPromise(col, 'remove').then(function (removed) {
return fromEvent(col, 'remove').then(function (removed) {
expect(Object.keys(removed)).toEqual(['bar'])
expect(removed.bar).toBeUndefined()
})
@@ -166,7 +166,7 @@ describe('Collection', function () {
expect(spy).not.toHaveBeenCalled()
// Async events.
return eventToPromise(col, 'add').then(function (added) {
return fromEvent(col, 'add').then(function (added) {
expect(Object.keys(added)).toEqual(['foo'])
expect(added.foo).toBe(true)
})
@@ -184,7 +184,7 @@ describe('Collection', function () {
expect(spy).not.toHaveBeenCalled()
// Async events.
return eventToPromise(col, 'update').then(function (updated) {
return fromEvent(col, 'update').then(function (updated) {
expect(Object.keys(updated)).toEqual(['bar'])
expect(updated.bar).toBe(1)
})
@@ -205,7 +205,7 @@ describe('Collection', function () {
expect(col.has('bar')).toBe(false)
return eventToPromise(col, 'remove').then(function (removed) {
return fromEvent(col, 'remove').then(function (removed) {
expect(Object.keys(removed)).toEqual(['bar'])
expect(removed.bar).toBeUndefined()
})
@@ -220,7 +220,7 @@ describe('Collection', function () {
expect(col.has('bar')).toBe(false)
return eventToPromise(col, 'remove').then(function (removed) {
return fromEvent(col, 'remove').then(function (removed) {
expect(Object.keys(removed)).toEqual(['bar'])
expect(removed.bar).toBeUndefined()
})
@@ -235,7 +235,7 @@ describe('Collection', function () {
return waitTicks().then(() => {
col.touch(foo)
return eventToPromise(col, 'update', items => {
return fromEvent(col, 'update', items => {
expect(Object.keys(items)).toEqual(['foo'])
expect(items.foo).toBe(foo)
})
@@ -249,7 +249,7 @@ describe('Collection', function () {
expect(col.size).toBe(0)
return eventToPromise(col, 'remove').then(items => {
return fromEvent(col, 'remove').then(items => {
expect(Object.keys(items)).toEqual(['bar'])
expect(items.bar).toBeUndefined()
})

View File

@@ -7,7 +7,7 @@ import { ACTION_ADD, ACTION_UPDATE, ACTION_REMOVE } from './collection'
// ===================================================================
export default class Index {
export class Index {
constructor(computeHash) {
if (computeHash) {
this.computeHash = iteratee(computeHash)

View File

@@ -1,10 +1,10 @@
/* eslint-env jest */
import eventToPromise from 'event-to-promise'
import fromEvent from 'promise-toolbox/fromEvent'
import { forEach } from 'lodash'
import Collection from './collection'
import Index from './index'
import { Collection } from './collection'
import { Index } from './index'
// ===================================================================
@@ -144,7 +144,7 @@ describe('Index', function () {
col.update(item1bis)
return eventToPromise(col, 'finish').then(() => {
return fromEvent(col, 'finish').then(() => {
expect(col.indexes).toEqual({
byGroup: {
foo: {

View File

@@ -6,7 +6,7 @@ import { ACTION_ADD, ACTION_UPDATE, ACTION_REMOVE } from './collection'
// ===================================================================
export default class UniqueIndex {
export class UniqueIndex {
constructor(computeHash) {
if (computeHash) {
this.computeHash = iteratee(computeHash)

View File

@@ -1,10 +1,10 @@
/* eslint-env jest */
import eventToPromise from 'event-to-promise'
import fromEvent from 'promise-toolbox/fromEvent'
import { forEach } from 'lodash'
import Collection from './collection'
import Index from './unique-index'
import { Collection } from './collection'
import { UniqueIndex } from './unique-index'
// ===================================================================
@@ -45,7 +45,7 @@ describe('UniqueIndex', function () {
col.add(item)
})
byKey = new Index('key')
byKey = new UniqueIndex('key')
col.createIndex('byKey', byKey)
@@ -119,7 +119,7 @@ describe('UniqueIndex', function () {
col.update(item1bis)
return eventToPromise(col, 'finish').then(() => {
return fromEvent(col, 'finish').then(() => {
expect(col.indexes).toEqual({
byKey: {
[item1.key]: item1bis,

View File

@@ -1,7 +1,8 @@
/* eslint-disable no-console */
import { forEach } from 'lodash'
import Collection from './collection'
import View from './view'
import { Collection } from './collection'
import { View } from './view'
// ===================================================================

View File

@@ -5,7 +5,7 @@ import Collection, { ACTION_ADD, ACTION_UPDATE, ACTION_REMOVE } from './collecti
// ===================================================================
export default class View extends Collection {
export class View extends Collection {
constructor(collection, predicate) {
super()

View File

@@ -29,7 +29,7 @@
"node": ">=6"
},
"dependencies": {
"jsonrpc-websocket-client": "^0.5.0",
"jsonrpc-websocket-client": "^0.6.0",
"lodash": "^4.17.2",
"make-error": "^1.0.4"
},

View File

@@ -1,6 +1,6 @@
import JsonRpcWebSocketClient, { OPEN, CLOSED } from 'jsonrpc-websocket-client'
import trimEnd from 'lodash/trimEnd'
import { BaseError } from 'make-error'
import { JsonRpcWebSocketClient, OPEN, CLOSED } from 'jsonrpc-websocket-client'
// ===================================================================

View File

@@ -85,7 +85,7 @@ class AuthSamlXoPlugin {
}
try {
done(null, await xo.registerUser('saml', name))
done(null, await xo.registerUser2('saml', { user: { id: name, name } }))
} catch (error) {
done(error.message)
}

View File

@@ -1,6 +1,6 @@
/* eslint-env jest */
import Xo from 'xo-lib'
import XoCollection from 'xo-collection'
import { Collection as XoCollection } from 'xo-collection'
import { decorateWith } from '@vates/decorate-with'
import { defaultsDeep, find, forOwn, iteratee, pick } from 'lodash'
import { defer } from 'golike-defer'

View File

@@ -0,0 +1,5 @@
module.exports = require('../../@xen-orchestra/babel-config')(require('./package.json'), {
'@babel/preset-env': {
modules: false,
},
})

View File

@@ -1 +0,0 @@
module.exports = require('../../@xen-orchestra/babel-config')(require('./package.json'))

View File

@@ -1,11 +0,0 @@
#!/usr/bin/env node
'use strict'
global.Promise = require('bluebird')
process.on('unhandledRejection', function (reason) {
console.warn('[Warn] Possibly unhandled rejection:', (reason && reason.stack) || reason)
})
require('exec-promise')(require('../dist/vhd-test').default)

View File

@@ -1,14 +0,0 @@
#!/usr/bin/env node
'use strict'
// ===================================================================
// Better stack traces if possible.
try {
require('source-map-support').install({
handleUncaughtExceptions: false,
})
} catch (_) {}
require('exec-promise')(require('../dist/logs-cli').default)

View File

@@ -1,3 +0,0 @@
#!/usr/bin/env node
require('exec-promise')(require('../dist/recover-account-cli').default)

View File

@@ -1,11 +0,0 @@
'use strict'
// ===================================================================
// Enable xo logs by default.
if (process.env.DEBUG === undefined) {
process.env.DEBUG = 'app-conf,xo:*,-xo:api'
}
// Import the real main module.
module.exports = require('./dist').default

View File

@@ -18,11 +18,13 @@
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"preferGlobal": true,
"directories": {
"bin": "bin"
"bin": {
"xo-server": "dist/cli.mjs",
"xo-server-logs": "dist/logs-cli.mjs",
"xo-server-recover-account": "dist/recover-account-cli.mjs"
},
"engines": {
"node": ">=11"
"node": ">=14.13"
},
"dependencies": {
"@iarna/toml": "^2.2.1",
@@ -58,7 +60,7 @@
"cookie": "^0.4.0",
"cookie-parser": "^1.4.3",
"d3-time-format": "^3.0.0",
"decorator-synchronized": "^0.5.0",
"decorator-synchronized": "^0.6.0",
"deptree": "^1.0.0",
"exec-promise": "^0.7.0",
"execa": "^5.0.0",
@@ -83,7 +85,7 @@
"json5": "^2.0.1",
"kindof": "^2.0.0",
"level-party": "^5.0.0",
"limit-concurrency-decorator": "^0.4.0",
"limit-concurrency-decorator": "^0.5.0",
"lodash": "^4.17.4",
"make-error": "^1",
"memorystore": "^1.6.2",
@@ -142,20 +144,16 @@
"@babel/plugin-proposal-pipeline-operator": "^7.0.0",
"@babel/plugin-proposal-throw-expressions": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-plugin-lodash": "^3.3.2",
"babel-plugin-transform-dev": "^2.0.1",
"cross-env": "^7.0.2",
"index-modules": "^0.3.0",
"rimraf": "^3.0.0"
"index-modules": "^0.4.2"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"clean": "rimraf dist/",
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "index-modules src/api src/xapi/mixins src/xo-mixins && yarn run clean",
"predev": "yarn run prebuild",
"_build": "index-modules --index-file index.mjs src/api src/xapi/mixins src/xo-mixins && babel --delete-dir-on-start --keep-file-extension --source-maps --out-dir=dist/ src/",
"build": "cross-env NODE_ENV=production yarn run _build",
"dev": "cross-env NODE_ENV=development yarn run _build --watch",
"prepublishOnly": "yarn run build",
"start": "node bin/xo-server"
"start": "node dist/cli.mjs"
},
"author": {
"name": "Vates SAS",

View File

@@ -12,7 +12,7 @@ function* values(object) {
*
* @param {(Array|Object)} collection
*/
module.exports = asyncIteratorToStream(function* (collection) {
export default asyncIteratorToStream(function* (collection) {
for (const value of Array.isArray(collection) ? collection : values(collection)) {
yield JSON.stringify(value)
yield '\n'

View File

@@ -1,6 +1,6 @@
/* eslint-env jest */
import ensureArray from './_ensureArray.js'
import ensureArray from './_ensureArray.mjs'
describe('ensureArray()', function () {
it('wrap the value in an array', function () {

View File

@@ -1,6 +1,6 @@
import { MultiKeyMap } from '@vates/multi-key-map'
import ensureArray from './_ensureArray.js'
import ensureArray from './_ensureArray.mjs'
function removeCacheEntry(cache, keys) {
cache.delete(keys)

View File

@@ -1,6 +1,6 @@
/* eslint-env jest */
import { debounceWithKey, REMOVE_CACHE_ENTRY } from './_pDebounceWithKey.js'
import { debounceWithKey, REMOVE_CACHE_ENTRY } from './_pDebounceWithKey.mjs'
describe('REMOVE_CACHE_ENTRY', () => {
it('clears the cache', async () => {

View File

@@ -2,9 +2,9 @@ import { basename } from 'path'
import { fromCallback } from 'promise-toolbox'
import { pipeline } from 'readable-stream'
import createNdJsonStream from '../_createNdJsonStream.js'
import { REMOVE_CACHE_ENTRY } from '../_pDebounceWithKey.js'
import { safeDateFormat } from '../utils.js'
import createNdJsonStream from '../_createNdJsonStream.mjs'
import { REMOVE_CACHE_ENTRY } from '../_pDebounceWithKey.mjs'
import { safeDateFormat } from '../utils.mjs'
export function createJob({ schedules, ...job }) {
job.userId = this.user.id

View File

@@ -8,7 +8,7 @@ import { noSuchObject } from 'xo-common/api-errors.js'
import { peekFooterFromVhdStream } from 'vhd-lib'
import { vmdkToVhd } from 'xo-vmdk-to-vhd'
import { VDI_FORMAT_VHD } from '../xapi/index.js'
import { VDI_FORMAT_VHD } from '../xapi/index.mjs'
const log = createLogger('xo:disk')

View File

@@ -288,7 +288,7 @@ isHostServerTimeConsistent.params = {
}
isHostServerTimeConsistent.resolve = {
host: ['host', 'host', 'administrate'],
host: ['host', 'host', 'view'],
}
// -------------------------------------------------------------------

View File

@@ -1,4 +1,4 @@
import xapiObjectToXo from '../xapi-object-to-xo.js'
import xapiObjectToXo from '../xapi-object-to-xo.mjs'
export function getBondModes() {
return ['balance-slb', 'active-backup', 'lacp']

View File

@@ -1,8 +1,9 @@
// TODO: too low level, move into host.
import { filter, find } from 'lodash'
import filter from 'lodash/filter.js'
import find from 'lodash/find.js'
import { IPV4_CONFIG_MODES, IPV6_CONFIG_MODES } from '../xapi/index.js'
import { IPV4_CONFIG_MODES, IPV6_CONFIG_MODES } from '../xapi/index.mjs'
export function getIpv4ConfigurationModes() {
return IPV4_CONFIG_MODES

Some files were not shown because too many files have changed in this diff Show More