Compare commits
7 Commits
xen-api-un
...
xen-api-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50dc3e10e4 | ||
|
|
a9fbcf3962 | ||
|
|
887b49ebbf | ||
|
|
858ecbc217 | ||
|
|
ffd523679d | ||
|
|
bd9db437f1 | ||
|
|
0365bacfbb |
@@ -84,6 +84,13 @@ export class ImportVmBackup {
|
||||
vmRef,
|
||||
`${metadata.vm.name_label} (${formatFilenameDate(metadata.timestamp)})`
|
||||
),
|
||||
xapi.call(
|
||||
'VM.set_name_description',
|
||||
vmRef,
|
||||
`Restored on ${formatFilenameDate(+new Date())} from ${adapter._handler._remote.name} -
|
||||
${metadata.vm.name_description}
|
||||
`
|
||||
),
|
||||
])
|
||||
|
||||
return {
|
||||
|
||||
@@ -256,7 +256,9 @@ export const importIncrementalVm = defer(async function importIncrementalVm(
|
||||
if (stream.length === undefined) {
|
||||
stream = await createVhdStreamWithLength(stream)
|
||||
}
|
||||
await xapi.setField('VDI', vdi.$ref, 'name_label', `[Importing] ${vdiRecords[id].name_label}`)
|
||||
await vdi.$importContent(stream, { cancelToken, format: 'vhd' })
|
||||
await xapi.setField('VDI', vdi.$ref, 'name_label', vdiRecords[id].name_label)
|
||||
}
|
||||
}),
|
||||
|
||||
|
||||
@@ -137,14 +137,16 @@ class Vdi {
|
||||
|
||||
const vdi = await this.getRecord('VDI', ref)
|
||||
const sr = await this.getRecord('SR', vdi.SR)
|
||||
|
||||
try {
|
||||
const taskRef = await this.task_create(`Importing content into VDI ${vdi.name_label} on SR ${sr.name_label}`)
|
||||
const uuid = await this.getField('task', taskRef, 'uuid')
|
||||
await vdi.update_other_config({ 'xo:import:task': uuid, 'xo:import:length': stream.length.toString() })
|
||||
await this.putResource(cancelToken, stream, '/import_raw_vdi/', {
|
||||
query: {
|
||||
format,
|
||||
vdi: ref,
|
||||
},
|
||||
task: await this.task_create(`Importing content into VDI ${vdi.name_label} on SR ${sr.name_label}`),
|
||||
task: taskRef,
|
||||
})
|
||||
} catch (error) {
|
||||
// augment the error with as much relevant info as possible
|
||||
@@ -153,6 +155,8 @@ class Vdi {
|
||||
error.SR = sr
|
||||
error.VDI = vdi
|
||||
throw error
|
||||
} finally {
|
||||
vdi.update_other_config({ 'xo:import:task': null, 'xo:import:length': null }).catch(warn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
|
||||
- [Netbox] Ability to synchronize XO users as Netbox tenants (PR [#7158](https://github.com/vatesfr/xen-orchestra/pull/7158))
|
||||
- [VM/Console] Add a message to indicate that the console view has been [disabled](https://support.citrix.com/article/CTX217766/how-to-disable-the-console-for-the-vm-in-xencenter) for this VM [#6319](https://github.com/vatesfr/xen-orchestra/issues/6319) (PR [#7161](https://github.com/vatesfr/xen-orchestra/pull/7161))
|
||||
- [Restore] Show source remote and restoration time on a restored VM (PR [#7186](https://github.com/vatesfr/xen-orchestra/pull/7186))
|
||||
- [Backup/Import] Show disk import status during Incremental Replication or restoration of Incremental Backup (PR [#7171](https://github.com/vatesfr/xen-orchestra/pull/7171))
|
||||
- [VM Creation] Added ISO option in new VM form when creating from template with a disk [#3464](https://github.com/vatesfr/xen-orchestra/issues/3464) (PR [#7166](https://github.com/vatesfr/xen-orchestra/pull/7166))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
@@ -36,7 +39,7 @@
|
||||
<!--packages-start-->
|
||||
|
||||
- @vates/nbd-client patch
|
||||
- @xen-orchestra/backups patch
|
||||
- @xen-orchestra/backups minor
|
||||
- @xen-orchestra/cr-seed-cli major
|
||||
- @xen-orchestra/vmware-explorer patch
|
||||
- xen-api major
|
||||
|
||||
@@ -112,7 +112,7 @@ apt-get install build-essential redis-server libpng-dev git python3-minimal libv
|
||||
On Fedora/CentOS like:
|
||||
|
||||
```sh
|
||||
dnf install redis libpng-devel git libvhdi-utils lvm2 cifs-utils make automake gcc gcc-c++
|
||||
dnf install redis libpng-devel git libvhdi-tools lvm2 cifs-utils make automake gcc gcc-c++
|
||||
```
|
||||
|
||||
### Make sure Redis is running
|
||||
|
||||
111
packages/xen-api/.USAGE2.md
Normal file
111
packages/xen-api/.USAGE2.md
Normal file
@@ -0,0 +1,111 @@
|
||||
```js
|
||||
import { Xapi } from 'xen-api'
|
||||
|
||||
// bare-bones XAPI client
|
||||
const xapi = new Xapi({
|
||||
// URL to a host belonging to the XCP-ng/XenServer pool we want to connect to
|
||||
url: 'https://xen1.company.net',
|
||||
|
||||
// credentials used to connect to this XAPI
|
||||
auth: {
|
||||
user: 'root',
|
||||
password: 'important secret password',
|
||||
},
|
||||
|
||||
// if true, only side-effects free calls will be allowed
|
||||
readOnly: false,
|
||||
})
|
||||
|
||||
// ensure that the connection is working
|
||||
await xapi.checkConnection()
|
||||
|
||||
// call a XAPI method
|
||||
//
|
||||
// see available methods there: https://xapi-project.github.io/xen-api/
|
||||
const result = await xapi.call(
|
||||
// name of the method
|
||||
'VM.snapshot',
|
||||
|
||||
// list of params
|
||||
[vm.$ref, 'My new snapshot'],
|
||||
|
||||
// options
|
||||
{
|
||||
// AbortSignal that can be used to stop the call
|
||||
//
|
||||
// Note: this will not stop/rollback the side-effects of the call
|
||||
signal,
|
||||
}
|
||||
)
|
||||
|
||||
// after a call (or checkConnection) has succeed, the following properties are available
|
||||
|
||||
// list of classes available on this XAPI
|
||||
xapi.classes
|
||||
|
||||
// timestamp of the last reply from XAPI
|
||||
xapi.lastReply
|
||||
|
||||
// pool record of this XAPI
|
||||
xapi.pool
|
||||
|
||||
// secret identifier of the current session
|
||||
//
|
||||
// it might become obsolete, in that case, it will be automatically renewed by the next call
|
||||
xapi.sessionId
|
||||
|
||||
// invalidate the session identifier
|
||||
await xapi.logOut()
|
||||
```
|
||||
|
||||
```js
|
||||
import { Proxy } from 'xen-api/proxy'
|
||||
|
||||
const proxy = new Proxy(xapi)
|
||||
|
||||
await proxy.VM.snapshot()
|
||||
```
|
||||
|
||||
```js
|
||||
import { Events } from 'xen-api/events'
|
||||
|
||||
const events = new Events(xapi)
|
||||
|
||||
// ensure that all events until now have been received and processed
|
||||
await events.barrier()
|
||||
|
||||
// watch events on tasks and wait for a task to finish
|
||||
const task = await events.waitTask(taskRef, { signal })
|
||||
|
||||
// for long running actions, it's better to use an async call which will are based on tasks
|
||||
const result = await events.asyncCall(method)
|
||||
|
||||
const stop = events.watch(
|
||||
// class that we are interested in
|
||||
//
|
||||
// use `*` for all classes
|
||||
'pool',
|
||||
|
||||
// called each time a new event for this class has been received
|
||||
//
|
||||
// https://xapi-project.github.io/xen-api/classes/event.html
|
||||
event => {
|
||||
stop()
|
||||
}
|
||||
)
|
||||
|
||||
// when wanting to really stop watching all events, simply remove all watchers
|
||||
events.clear()
|
||||
```
|
||||
|
||||
```js
|
||||
import { Cache } from 'xen-api/events'
|
||||
|
||||
const cache = new Cache(watcher)
|
||||
|
||||
const host = await cache.get('host', 'OpaqueRef:1c3f19c8-f80a-464d-9c48-a2c19d4e4fc3')
|
||||
|
||||
const vm = await cache.getByUuid('VM', '355ee47d-ff4c-4924-3db2-fd86ae629676')
|
||||
|
||||
cache.clear()
|
||||
```
|
||||
@@ -1,32 +0,0 @@
|
||||
const EMPTY = 'OpaqueRef:NULL'
|
||||
const PREFIX = 'OpaqueRef:'
|
||||
|
||||
export default {
|
||||
// Reference to use to indicate it's not pointing to an object
|
||||
EMPTY,
|
||||
|
||||
// Whether this value is a reference (probably) pointing to an object
|
||||
isNotEmpty(val) {
|
||||
return val !== EMPTY && typeof val === 'string' && val.startsWith(PREFIX)
|
||||
},
|
||||
|
||||
// Whether this value looks like a reference
|
||||
is(val) {
|
||||
return (
|
||||
typeof val === 'string' &&
|
||||
(val.startsWith(PREFIX) ||
|
||||
// 2019-02-07 - JFT: even if `value` should not be an empty string for
|
||||
// a ref property, an user had the case on XenServer 7.0 on the CD VBD
|
||||
// of a VM created by XenCenter
|
||||
val === '' ||
|
||||
// 2021-03-08 - JFT: there is an bug in XCP-ng/XenServer which leads to
|
||||
// some refs to be `Ref:*` instead of being rewritten
|
||||
//
|
||||
// We'll consider them as empty refs in this lib to avoid issues with
|
||||
// _wrapRecord.
|
||||
//
|
||||
// See https://github.com/xapi-project/xen-api/issues/4338
|
||||
val.startsWith('Ref:'))
|
||||
)
|
||||
},
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { BaseError } from 'make-error'
|
||||
|
||||
export default class XapiError extends BaseError {
|
||||
static wrap(error) {
|
||||
let code, params
|
||||
if (Array.isArray(error)) {
|
||||
// < XenServer 7.3
|
||||
;[code, ...params] = error
|
||||
} else {
|
||||
code = error.message
|
||||
params = error.data
|
||||
if (!Array.isArray(params)) {
|
||||
params = []
|
||||
}
|
||||
}
|
||||
return new XapiError(code, params)
|
||||
}
|
||||
|
||||
constructor(code, params) {
|
||||
super(`${code}(${params.join(', ')})`)
|
||||
|
||||
this.code = code
|
||||
this.params = params
|
||||
|
||||
// slots than can be assigned later
|
||||
this.call = undefined
|
||||
this.url = undefined
|
||||
this.task = undefined
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import debug from 'debug'
|
||||
|
||||
export default debug('xen-api')
|
||||
@@ -1,22 +0,0 @@
|
||||
import { Cancel } from 'promise-toolbox'
|
||||
|
||||
import XapiError from './_XapiError.mjs'
|
||||
|
||||
export default task => {
|
||||
const { status } = task
|
||||
if (status === 'cancelled') {
|
||||
return Promise.reject(new Cancel('task canceled'))
|
||||
}
|
||||
if (status === 'failure') {
|
||||
const error = XapiError.wrap(task.error_info)
|
||||
error.task = task
|
||||
return Promise.reject(error)
|
||||
}
|
||||
if (status === 'success') {
|
||||
// the result might be:
|
||||
// - empty string
|
||||
// - an opaque reference
|
||||
// - an XML-RPC value
|
||||
return Promise.resolve(task.result)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
const SUFFIX = '.get_all_records'
|
||||
|
||||
export default method => method.endsWith(SUFFIX)
|
||||
@@ -1,6 +0,0 @@
|
||||
const RE = /^[^.]+\.get_/
|
||||
|
||||
export default function isReadOnlyCall(method, args) {
|
||||
const n = args.length
|
||||
return (n === 0 || (n === 1 && typeof args[0] === 'string')) && RE.test(method)
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
export default (setting, defaultValue) =>
|
||||
setting === undefined
|
||||
? () => defaultValue
|
||||
: typeof setting === 'function'
|
||||
? setting
|
||||
: typeof setting === 'object'
|
||||
? method => setting[method] ?? setting['*'] ?? defaultValue
|
||||
: () => setting
|
||||
@@ -1,26 +0,0 @@
|
||||
const URL_RE = /^(?:(https?:)\/*)?(?:(([^:]*)(?::([^@]*))?)@)?(\[[^\]]+\]|[^:/]+)(?::([0-9]+))?(\/[^?#]*)?$/
|
||||
|
||||
export default url => {
|
||||
const matches = URL_RE.exec(url)
|
||||
if (matches === null) {
|
||||
throw new Error('invalid URL: ' + url)
|
||||
}
|
||||
|
||||
const [, protocol = 'https:', auth, username = '', password = '', hostname, port, pathname = '/'] = matches
|
||||
const parsedUrl = {
|
||||
protocol,
|
||||
hostname,
|
||||
port,
|
||||
pathname,
|
||||
|
||||
// compat with url.parse
|
||||
auth,
|
||||
}
|
||||
if (username !== '') {
|
||||
parsedUrl.username = decodeURIComponent(username)
|
||||
}
|
||||
if (password !== '') {
|
||||
parsedUrl.password = decodeURIComponent(password)
|
||||
}
|
||||
return parsedUrl
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import t from 'tap'
|
||||
|
||||
import parseUrl from './_parseUrl.mjs'
|
||||
|
||||
const data = {
|
||||
'xcp.company.lan': {
|
||||
hostname: 'xcp.company.lan',
|
||||
pathname: '/',
|
||||
protocol: 'https:',
|
||||
},
|
||||
'[::1]': {
|
||||
hostname: '[::1]',
|
||||
pathname: '/',
|
||||
protocol: 'https:',
|
||||
},
|
||||
'http://username:password@xcp.company.lan': {
|
||||
auth: 'username:password',
|
||||
hostname: 'xcp.company.lan',
|
||||
password: 'password',
|
||||
pathname: '/',
|
||||
protocol: 'http:',
|
||||
username: 'username',
|
||||
},
|
||||
'https://username@xcp.company.lan': {
|
||||
auth: 'username',
|
||||
hostname: 'xcp.company.lan',
|
||||
pathname: '/',
|
||||
protocol: 'https:',
|
||||
username: 'username',
|
||||
},
|
||||
}
|
||||
|
||||
t.test('invalid url', function (t) {
|
||||
t.throws(() => parseUrl(''))
|
||||
t.end()
|
||||
})
|
||||
|
||||
for (const url of Object.keys(data)) {
|
||||
t.test(url, function (t) {
|
||||
const parsed = parseUrl(url)
|
||||
for (const key of Object.keys(parsed)) {
|
||||
if (parsed[key] === undefined) {
|
||||
delete parsed[key]
|
||||
}
|
||||
}
|
||||
|
||||
t.same(parsed, data[url])
|
||||
t.end()
|
||||
})
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import mapValues from 'lodash/mapValues.js'
|
||||
|
||||
export default function replaceSensitiveValues(value, replacement) {
|
||||
function helper(value, name) {
|
||||
if (name === 'password' && typeof value === 'string') {
|
||||
return replacement
|
||||
}
|
||||
|
||||
if (typeof value !== 'object' || value === null) {
|
||||
return value
|
||||
}
|
||||
|
||||
return Array.isArray(value) ? value.map(helper) : mapValues(value, helper)
|
||||
}
|
||||
|
||||
return helper(value)
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
import blocked from 'blocked'
|
||||
import createDebug from 'debug'
|
||||
import filter from 'lodash/filter.js'
|
||||
import find from 'lodash/find.js'
|
||||
import L from 'lodash'
|
||||
import minimist from 'minimist'
|
||||
import pw from 'pw'
|
||||
import { asCallback, fromCallback, fromEvent } from 'promise-toolbox'
|
||||
import { diff } from 'jest-diff'
|
||||
import { getBoundPropertyDescriptor } from 'bind-property-descriptor'
|
||||
import { start as createRepl } from 'repl'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
function askPassword(prompt = 'Password: ') {
|
||||
if (prompt) {
|
||||
process.stdout.write(prompt)
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
pw(resolve)
|
||||
})
|
||||
}
|
||||
|
||||
const { getPrototypeOf, ownKeys } = Reflect
|
||||
function getAllBoundDescriptors(object) {
|
||||
const descriptors = { __proto__: null }
|
||||
let current = object
|
||||
do {
|
||||
ownKeys(current).forEach(key => {
|
||||
if (!(key in descriptors)) {
|
||||
descriptors[key] = getBoundPropertyDescriptor(current, key, object)
|
||||
}
|
||||
})
|
||||
} while ((current = getPrototypeOf(current)) !== null)
|
||||
return descriptors
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const usage = 'Usage: xen-api <url> [<user> [<password>]]'
|
||||
|
||||
export async function main(createClient) {
|
||||
const opts = minimist(process.argv.slice(2), {
|
||||
string: ['proxy', 'session-id', 'transport'],
|
||||
boolean: ['allow-unauthorized', 'help', 'read-only', 'verbose'],
|
||||
|
||||
alias: {
|
||||
'allow-unauthorized': 'au',
|
||||
debounce: 'd',
|
||||
help: 'h',
|
||||
proxy: 'p',
|
||||
'read-only': 'ro',
|
||||
verbose: 'v',
|
||||
transport: 't',
|
||||
},
|
||||
})
|
||||
|
||||
if (opts.help) {
|
||||
return usage
|
||||
}
|
||||
|
||||
if (opts.verbose) {
|
||||
// Does not work perfectly.
|
||||
//
|
||||
// https://github.com/visionmedia/debug/pull/156
|
||||
createDebug.enable('xen-api,xen-api:*')
|
||||
}
|
||||
|
||||
let auth
|
||||
if (opts._.length > 1) {
|
||||
const [, user, password = await askPassword()] = opts._
|
||||
auth = { user, password }
|
||||
} else if (opts['session-id'] !== undefined) {
|
||||
auth = { sessionId: opts['session-id'] }
|
||||
}
|
||||
|
||||
{
|
||||
const debug = createDebug('xen-api:perf')
|
||||
blocked(ms => {
|
||||
debug('blocked for %sms', ms | 0)
|
||||
})
|
||||
}
|
||||
|
||||
const xapi = createClient({
|
||||
url: opts._[0],
|
||||
allowUnauthorized: opts.au,
|
||||
auth,
|
||||
debounce: opts.debounce != null ? +opts.debounce : null,
|
||||
httpProxy: opts.proxy,
|
||||
readOnly: opts.ro,
|
||||
syncStackTraces: true,
|
||||
transport: opts.transport || undefined,
|
||||
})
|
||||
await xapi.connect()
|
||||
|
||||
const repl = createRepl({
|
||||
prompt: `${xapi._humanId}> `,
|
||||
})
|
||||
|
||||
{
|
||||
const ctx = repl.context
|
||||
ctx.xapi = xapi
|
||||
|
||||
ctx.diff = (a, b) => console.log('%s', diff(a, b))
|
||||
ctx.find = predicate => find(xapi.objects.all, predicate)
|
||||
ctx.findAll = predicate => filter(xapi.objects.all, predicate)
|
||||
ctx.L = L
|
||||
|
||||
Object.defineProperties(ctx, getAllBoundDescriptors(xapi))
|
||||
}
|
||||
|
||||
// Make the REPL waits for promise completion.
|
||||
repl.eval = (evaluate => (cmd, context, filename, cb) => {
|
||||
asCallback.call(
|
||||
fromCallback(cb => {
|
||||
evaluate.call(repl, cmd, context, filename, cb)
|
||||
}).then(value => (Array.isArray(value) ? Promise.all(value) : value)),
|
||||
cb
|
||||
)
|
||||
})(repl.eval)
|
||||
|
||||
await fromEvent(repl, 'exit')
|
||||
|
||||
try {
|
||||
await xapi.disconnect()
|
||||
} catch (error) {}
|
||||
}
|
||||
/* eslint-enable no-console */
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { createClient } from './index.mjs'
|
||||
import { main } from './cli-lib.mjs'
|
||||
|
||||
main(createClient).catch(console.error.bind(console, 'FATAL'))
|
||||
115
packages/xen-api/events.mjs
Normal file
115
packages/xen-api/events.mjs
Normal file
@@ -0,0 +1,115 @@
|
||||
const EVENT_TIMEOUT = 60e3
|
||||
|
||||
export class Watcher {
|
||||
#abortController
|
||||
#typeWatchers = new Map()
|
||||
classes = new Map()
|
||||
xapi
|
||||
|
||||
constructor(xapi) {
|
||||
this.xapi = xapi
|
||||
}
|
||||
|
||||
async asyncCall(method, params, { signal }) {
|
||||
const taskRef = await this.xapi.call('Async.' + method, params, { signal })
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const stop = this.watch(
|
||||
'task',
|
||||
taskRef,
|
||||
task => {
|
||||
const { status } = task
|
||||
if (status === 'success') {
|
||||
stop()
|
||||
resolve(task.status)
|
||||
} else if (status === 'cancelled' || status === 'failure') {
|
||||
stop()
|
||||
reject(task.error_info)
|
||||
}
|
||||
},
|
||||
{ signal }
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
async #start() {
|
||||
const { xapi } = this
|
||||
const { signal } = this.#abortController
|
||||
const watchers = this.#typeWatchers
|
||||
|
||||
let token = await xapi.call('event.inject', 'pool', xapi.pool.$ref)
|
||||
|
||||
while (true) {
|
||||
signal.throwIfRequested()
|
||||
|
||||
const result = await xapi.call({ signal }, 'event.from', this.classes, token, EVENT_TIMEOUT)
|
||||
|
||||
for (const event of result.events) {
|
||||
}
|
||||
}
|
||||
this.#abortController = undefined
|
||||
}
|
||||
|
||||
start() {
|
||||
if (this.#abortController !== undefined) {
|
||||
throw new Error('already started')
|
||||
}
|
||||
|
||||
this.#abortController = new AbortController()
|
||||
this.#start()
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.#abortController === undefined) {
|
||||
throw new Error('already stopped')
|
||||
}
|
||||
|
||||
this.#abortController.abort()
|
||||
}
|
||||
}
|
||||
|
||||
export class Cache {
|
||||
// contains records indexed by type + ref
|
||||
//
|
||||
// plain records when retrieved by events
|
||||
//
|
||||
// promises to record when retrieved by a get_record call (might be a rejection if the record does not exist)
|
||||
#recordCache = new Map()
|
||||
#watcher
|
||||
|
||||
constructor(watcher) {
|
||||
this.#watcher = watcher
|
||||
}
|
||||
|
||||
async #get(type, ref) {
|
||||
let record
|
||||
try {
|
||||
record = await this.#watcher.xapi.call(`${type}.get_record`, ref)
|
||||
} catch (error) {
|
||||
if (error.code !== 'HANDLE_INVALID') {
|
||||
throw error
|
||||
}
|
||||
record = Promise.reject(error)
|
||||
}
|
||||
this.#recordCache.set(type, Promise.resolve(record))
|
||||
return record
|
||||
}
|
||||
|
||||
async get(type, ref) {
|
||||
const cache = this.#recordCache
|
||||
const key = type + ref
|
||||
|
||||
let record = cache.get(key)
|
||||
if (record === undefined) {
|
||||
record = this.#get(type, ref)
|
||||
cache.set(key, record)
|
||||
}
|
||||
|
||||
return record
|
||||
}
|
||||
|
||||
async getByUuid(type, uuid) {
|
||||
return this.get(type, await this.#watcher.xapi.call(`${type}.get_by_uuid`, uuid))
|
||||
}
|
||||
}
|
||||
exports.Cache = Cache
|
||||
@@ -1,5 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
ignorePatterns: ['*'],
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
if (process.env.DEBUG === undefined) {
|
||||
process.env.DEBUG = 'xen-api'
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import './env.mjs'
|
||||
|
||||
import createProgress from 'progress-stream'
|
||||
import createTop from 'process-top'
|
||||
import getopts from 'getopts'
|
||||
import { defer } from 'golike-defer'
|
||||
import { CancelToken } from 'promise-toolbox'
|
||||
|
||||
import { createClient } from '../index.mjs'
|
||||
|
||||
import { createOutputStream, formatProgress, pipeline, resolveRecord, throttle } from './utils.mjs'
|
||||
|
||||
defer(async ($defer, rawArgs) => {
|
||||
const {
|
||||
raw,
|
||||
throttle: bps,
|
||||
_: args,
|
||||
} = getopts(rawArgs, {
|
||||
boolean: 'raw',
|
||||
alias: {
|
||||
raw: 'r',
|
||||
throttle: 't',
|
||||
},
|
||||
})
|
||||
|
||||
if (args.length < 2) {
|
||||
return console.log('Usage: export-vdi [--raw] <XS URL> <VDI identifier> [<VHD file>]')
|
||||
}
|
||||
|
||||
const xapi = createClient({
|
||||
allowUnauthorized: true,
|
||||
url: args[0],
|
||||
watchEvents: false,
|
||||
})
|
||||
|
||||
await xapi.connect()
|
||||
$defer(() => xapi.disconnect())
|
||||
|
||||
const { cancel, token } = CancelToken.source()
|
||||
process.on('SIGINT', cancel)
|
||||
|
||||
const vdi = await resolveRecord(xapi, 'VDI', args[1])
|
||||
|
||||
// https://xapi-project.github.io/xen-api/snapshots.html#downloading-a-disk-or-snapshot
|
||||
const exportStream = await xapi.getResource(token, '/export_raw_vdi/', {
|
||||
query: {
|
||||
format: raw ? 'raw' : 'vhd',
|
||||
vdi: vdi.$ref,
|
||||
},
|
||||
})
|
||||
|
||||
console.warn('Export task:', exportStream.headers['task-id'])
|
||||
|
||||
const top = createTop()
|
||||
const progressStream = createProgress()
|
||||
|
||||
$defer(
|
||||
clearInterval,
|
||||
setInterval(() => {
|
||||
console.warn('\r %s | %s', top.toString(), formatProgress(progressStream.progress()))
|
||||
}, 1e3)
|
||||
)
|
||||
|
||||
await pipeline(exportStream.body, progressStream, throttle(bps), createOutputStream(args[2]))
|
||||
})(process.argv.slice(2)).catch(console.error.bind(console, 'error'))
|
||||
@@ -1,54 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import './env.mjs'
|
||||
|
||||
import createProgress from 'progress-stream'
|
||||
import getopts from 'getopts'
|
||||
import { defer } from 'golike-defer'
|
||||
import { CancelToken } from 'promise-toolbox'
|
||||
|
||||
import { createClient } from '../index.mjs'
|
||||
|
||||
import { createOutputStream, formatProgress, pipeline, resolveRecord } from './utils.mjs'
|
||||
|
||||
defer(async ($defer, rawArgs) => {
|
||||
const {
|
||||
gzip,
|
||||
zstd,
|
||||
_: args,
|
||||
} = getopts(rawArgs, {
|
||||
boolean: ['gzip', 'zstd'],
|
||||
})
|
||||
|
||||
if (args.length < 2) {
|
||||
return console.log('Usage: export-vm <XS URL> <VM identifier> [<XVA file>]')
|
||||
}
|
||||
|
||||
const xapi = createClient({
|
||||
allowUnauthorized: true,
|
||||
url: args[0],
|
||||
watchEvents: false,
|
||||
})
|
||||
|
||||
await xapi.connect()
|
||||
$defer(() => xapi.disconnect())
|
||||
|
||||
const { cancel, token } = CancelToken.source()
|
||||
process.on('SIGINT', cancel)
|
||||
|
||||
// https://xapi-project.github.io/xen-api/importexport.html
|
||||
const exportStream = await xapi.getResource(token, '/export/', {
|
||||
query: {
|
||||
ref: (await resolveRecord(xapi, 'VM', args[1])).$ref,
|
||||
use_compression: zstd ? 'zstd' : gzip ? 'true' : 'false',
|
||||
},
|
||||
})
|
||||
|
||||
console.warn('Export task:', exportStream.headers['task-id'])
|
||||
|
||||
await pipeline(
|
||||
exportStream.body,
|
||||
createProgress({ time: 1e3 }, p => console.warn(formatProgress(p))),
|
||||
createOutputStream(args[2])
|
||||
)
|
||||
})(process.argv.slice(2)).catch(console.error.bind(console, 'error'))
|
||||
@@ -1,88 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import './env.mjs'
|
||||
|
||||
import getopts from 'getopts'
|
||||
import { defer } from 'golike-defer'
|
||||
import { CancelToken } from 'promise-toolbox'
|
||||
import { createVhdStreamWithLength } from 'vhd-lib'
|
||||
|
||||
import { createClient } from '../index.mjs'
|
||||
|
||||
import { createInputStream, resolveRef } from './utils.mjs'
|
||||
|
||||
defer(async ($defer, argv) => {
|
||||
const opts = getopts(argv, { boolean: ['events', 'raw', 'remove-length'], string: ['sr', 'vdi'] })
|
||||
|
||||
const url = opts._[0]
|
||||
|
||||
if (url === undefined) {
|
||||
return console.log(
|
||||
'Usage: import-vdi [--events] [--raw] [--sr <SR identifier>] [--vdi <VDI identifier>] <XS URL> [<VHD file>]'
|
||||
)
|
||||
}
|
||||
|
||||
const { raw, sr, vdi } = opts
|
||||
|
||||
const createVdi = vdi === ''
|
||||
if (createVdi) {
|
||||
if (sr === '') {
|
||||
throw 'requires either --vdi or --sr'
|
||||
}
|
||||
if (!raw) {
|
||||
throw 'creating a VDI requires --raw'
|
||||
}
|
||||
} else if (sr !== '') {
|
||||
throw '--vdi and --sr are mutually exclusive'
|
||||
}
|
||||
|
||||
const xapi = createClient({
|
||||
allowUnauthorized: true,
|
||||
url,
|
||||
watchEvents: opts.events && ['task'],
|
||||
})
|
||||
|
||||
await xapi.connect()
|
||||
$defer(() => xapi.disconnect())
|
||||
|
||||
const { cancel, token } = CancelToken.source()
|
||||
process.on('SIGINT', cancel)
|
||||
|
||||
let input = createInputStream(opts._[1])
|
||||
$defer.onFailure(() => input.destroy())
|
||||
|
||||
let vdiRef
|
||||
if (createVdi) {
|
||||
vdiRef = await xapi.call('VDI.create', {
|
||||
name_label: 'xen-api/import-vdi',
|
||||
other_config: {},
|
||||
read_only: false,
|
||||
sharable: false,
|
||||
SR: await resolveRef(xapi, 'SR', sr),
|
||||
type: 'user',
|
||||
virtual_size: input.length,
|
||||
})
|
||||
$defer.onFailure(() => xapi.call('VDI.destroy', vdiRef))
|
||||
} else {
|
||||
vdiRef = await resolveRef(xapi, 'VDI', vdi)
|
||||
}
|
||||
|
||||
if (opts['remove-length']) {
|
||||
delete input.length
|
||||
console.log('length removed')
|
||||
} else if (!raw && input.length === undefined) {
|
||||
input = await createVhdStreamWithLength(input)
|
||||
}
|
||||
|
||||
// https://xapi-project.github.io/xen-api/snapshots.html#uploading-a-disk-or-snapshot
|
||||
const result = await xapi.putResource(token, input, '/import_raw_vdi/', {
|
||||
query: {
|
||||
format: raw ? 'raw' : 'vhd',
|
||||
vdi: vdiRef,
|
||||
},
|
||||
})
|
||||
|
||||
if (result !== undefined) {
|
||||
console.log(result)
|
||||
}
|
||||
})(process.argv.slice(2)).catch(console.error.bind(console, 'Fatal:'))
|
||||
@@ -1,33 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import './env.mjs'
|
||||
|
||||
import { defer } from 'golike-defer'
|
||||
import { CancelToken } from 'promise-toolbox'
|
||||
|
||||
import { createClient } from '../index.mjs'
|
||||
|
||||
import { createInputStream, resolveRef } from './utils.mjs'
|
||||
|
||||
defer(async ($defer, args) => {
|
||||
if (args.length < 1) {
|
||||
return console.log('Usage: import-vm <XS URL> [<XVA file>] [<SR identifier>]')
|
||||
}
|
||||
|
||||
const xapi = createClient({
|
||||
allowUnauthorized: true,
|
||||
url: args[0],
|
||||
watchEvents: false,
|
||||
})
|
||||
|
||||
await xapi.connect()
|
||||
$defer(() => xapi.disconnect())
|
||||
|
||||
const { cancel, token } = CancelToken.source()
|
||||
process.on('SIGINT', cancel)
|
||||
|
||||
// https://xapi-project.github.io/xen-api/importexport.html
|
||||
await xapi.putResource(token, createInputStream(args[1]), '/import/', {
|
||||
query: args[2] && { sr_id: await resolveRef(xapi, 'SR', args[2]) },
|
||||
})
|
||||
})(process.argv.slice(2)).catch(console.error.bind(console, 'error'))
|
||||
@@ -1,59 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import 'source-map-support/register.js'
|
||||
|
||||
import forEach from 'lodash/forEach.js'
|
||||
import size from 'lodash/size.js'
|
||||
|
||||
import { createClient } from '../index.mjs'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
if (process.argv.length < 3) {
|
||||
throw new Error('Usage: log-events <XS URL>')
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// Creation
|
||||
|
||||
const xapi = createClient({
|
||||
allowUnauthorized: true,
|
||||
url: process.argv[2],
|
||||
})
|
||||
|
||||
// ===================================================================
|
||||
// Method call
|
||||
|
||||
xapi.connect().then(() => {
|
||||
xapi
|
||||
.call('VM.get_all_records')
|
||||
.then(function (vms) {
|
||||
console.log('%s VMs fetched', size(vms))
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.error(error)
|
||||
})
|
||||
})
|
||||
|
||||
// ===================================================================
|
||||
// Objects
|
||||
|
||||
const objects = xapi.objects
|
||||
|
||||
objects.on('add', objects => {
|
||||
forEach(objects, object => {
|
||||
console.log('+ %s: %s', object.$type, object.$id)
|
||||
})
|
||||
})
|
||||
|
||||
objects.on('update', objects => {
|
||||
forEach(objects, object => {
|
||||
console.log('± %s: %s', object.$type, object.$id)
|
||||
})
|
||||
})
|
||||
|
||||
objects.on('remove', objects => {
|
||||
forEach(objects, (value, id) => {
|
||||
console.log('- %s', id)
|
||||
})
|
||||
})
|
||||
2647
packages/xen-api/examples/package-lock.json
generated
2647
packages/xen-api/examples/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"getopts": "^2.2.3",
|
||||
"golike-defer": "^0.5.1",
|
||||
"human-format": "^0.11.0",
|
||||
"lodash": "^4.17.21",
|
||||
"process-top": "^1.2.0",
|
||||
"progress-stream": "^2.0.0",
|
||||
"promise-toolbox": "^0.19.2",
|
||||
"readable-stream": "^4.4.2",
|
||||
"source-map-support": "^0.5.21",
|
||||
"throttle": "^1.0.3",
|
||||
"vhd-lib": "^4.6.1"
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
import { createReadStream, createWriteStream, statSync } from 'fs'
|
||||
import { fromCallback } from 'promise-toolbox'
|
||||
import { PassThrough, pipeline as Pipeline } from 'readable-stream'
|
||||
import humanFormat from 'human-format'
|
||||
import Throttle from 'throttle'
|
||||
|
||||
import Ref from '../_Ref.mjs'
|
||||
|
||||
export const createInputStream = path => {
|
||||
if (path === undefined || path === '-') {
|
||||
return process.stdin
|
||||
}
|
||||
|
||||
const { size } = statSync(path)
|
||||
|
||||
const stream = createReadStream(path)
|
||||
stream.length = size
|
||||
return stream
|
||||
}
|
||||
|
||||
export const createOutputStream = path => {
|
||||
if (path !== undefined && path !== '-') {
|
||||
return createWriteStream(path)
|
||||
}
|
||||
|
||||
// introduce a through stream because stdout is not a normal stream!
|
||||
const stream = new PassThrough()
|
||||
stream.pipe(process.stdout)
|
||||
return stream
|
||||
}
|
||||
|
||||
const formatSizeOpts = { scale: 'binary', unit: 'B' }
|
||||
const formatSize = bytes => humanFormat(bytes, formatSizeOpts)
|
||||
|
||||
export const formatProgress = p => {
|
||||
return [
|
||||
formatSize(p.transferred),
|
||||
' / ',
|
||||
formatSize(p.length),
|
||||
' | ',
|
||||
p.runtime,
|
||||
's / ',
|
||||
p.eta,
|
||||
's | ',
|
||||
formatSize(p.speed),
|
||||
'/s',
|
||||
].join('')
|
||||
}
|
||||
|
||||
export const pipeline = (...streams) => {
|
||||
return fromCallback(cb => {
|
||||
streams = streams.filter(_ => _ != null)
|
||||
streams.push(cb)
|
||||
Pipeline.apply(undefined, streams)
|
||||
})
|
||||
}
|
||||
|
||||
const resolveRef = (xapi, type, refOrUuidOrNameLabel) =>
|
||||
Ref.is(refOrUuidOrNameLabel)
|
||||
? refOrUuidOrNameLabel
|
||||
: xapi.call(`${type}.get_by_uuid`, refOrUuidOrNameLabel).catch(() =>
|
||||
xapi.call(`${type}.get_by_name_label`, refOrUuidOrNameLabel).then(refs => {
|
||||
if (refs.length === 1) {
|
||||
return refs[0]
|
||||
}
|
||||
throw new Error(`no single match for ${type} with name label ${refOrUuidOrNameLabel}`)
|
||||
})
|
||||
)
|
||||
|
||||
export const resolveRecord = async (xapi, type, refOrUuidOrNameLabel) =>
|
||||
xapi.getRecord(type, await resolveRef(xapi, type, refOrUuidOrNameLabel))
|
||||
|
||||
export { resolveRef }
|
||||
|
||||
export const throttle = opts => (opts != null ? new Throttle(opts) : undefined)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -48,8 +48,7 @@
|
||||
"promise-toolbox": "^0.21.0",
|
||||
"proxy-agent": "^5.0.0",
|
||||
"pw": "0.0.4",
|
||||
"undici": "^5.27.2",
|
||||
"xmlrpc-parser": "^1.0.3",
|
||||
"xmlrpc": "^1.3.2",
|
||||
"xo-collection": "^0.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import makeError from 'make-error'
|
||||
|
||||
export default makeError('UnsupportedTransport')
|
||||
@@ -1,25 +0,0 @@
|
||||
// Prepare values before passing them to the XenAPI:
|
||||
//
|
||||
// - cast integers to strings
|
||||
export default function prepare(param) {
|
||||
if (Number.isInteger(param)) {
|
||||
return String(param)
|
||||
}
|
||||
|
||||
if (typeof param !== 'object' || param === null) {
|
||||
return param
|
||||
}
|
||||
|
||||
if (Array.isArray(param)) {
|
||||
return param.map(prepare)
|
||||
}
|
||||
|
||||
const values = {}
|
||||
Object.keys(param).forEach(key => {
|
||||
const value = param[key]
|
||||
if (value !== undefined) {
|
||||
values[key] = prepare(value)
|
||||
}
|
||||
})
|
||||
return values
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import jsonRpc from './json-rpc.mjs'
|
||||
import UnsupportedTransport from './_UnsupportedTransport.mjs'
|
||||
import xmlRpc from './xml-rpc.mjs'
|
||||
|
||||
const factories = [jsonRpc, xmlRpc]
|
||||
const { length } = factories
|
||||
|
||||
export default opts => {
|
||||
let i = 0
|
||||
|
||||
let call
|
||||
function create() {
|
||||
const current = factories[i++](opts)
|
||||
if (i < length) {
|
||||
const currentI = i
|
||||
call = (method, args) =>
|
||||
current(method, args).catch(error => {
|
||||
if (error instanceof UnsupportedTransport) {
|
||||
if (currentI === i) {
|
||||
// not changed yet
|
||||
create()
|
||||
}
|
||||
return call(method, args)
|
||||
}
|
||||
|
||||
throw error
|
||||
})
|
||||
} else {
|
||||
call = current
|
||||
}
|
||||
}
|
||||
create()
|
||||
|
||||
return (method, args) => call(method, args)
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import auto from './auto.mjs'
|
||||
import jsonRpc from './json-rpc.mjs'
|
||||
import xmlRpc from './xml-rpc.mjs'
|
||||
|
||||
export default {
|
||||
__proto__: null,
|
||||
|
||||
auto,
|
||||
'json-rpc': jsonRpc,
|
||||
'xml-rpc': xmlRpc,
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import { format, parse } from 'json-rpc-protocol'
|
||||
|
||||
import XapiError from '../_XapiError.mjs'
|
||||
|
||||
import UnsupportedTransport from './_UnsupportedTransport.mjs'
|
||||
|
||||
// https://github.com/xenserver/xenadmin/blob/0df39a9d83cd82713f32d24704852a0fd57b8a64/XenModel/XenAPI/Session.cs#L403-L433
|
||||
export default ({ agent, client, url }) => {
|
||||
url = new URL('./jsonrpc', Object.assign(new URL('http://localhost'), url))
|
||||
const path = url.pathname + url.search
|
||||
|
||||
return async function (method, args) {
|
||||
const res = await client.request({
|
||||
body: format.request(0, method, args),
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'POST',
|
||||
path,
|
||||
agent,
|
||||
})
|
||||
|
||||
// content-type is `text/xml` on old hosts where JSON-RPC is unsupported
|
||||
if (res.headers['content-type'] !== 'application/json') {
|
||||
throw new UnsupportedTransport()
|
||||
}
|
||||
|
||||
const response = parse(await res.body.text())
|
||||
|
||||
if (response.type === 'response') {
|
||||
return response.result
|
||||
}
|
||||
|
||||
throw XapiError.wrap(response.error)
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import { XmlRpcMessage, XmlRpcResponse } from 'xmlrpc-parser'
|
||||
|
||||
import prepareXmlRpcParams from './_prepareXmlRpcParams.mjs'
|
||||
import XapiError from '../_XapiError.mjs'
|
||||
import UnsupportedTransport from './_UnsupportedTransport.mjs'
|
||||
|
||||
const parseResult = result => {
|
||||
const status = result.Status
|
||||
|
||||
// Return the plain result if it does not have a valid XAPI
|
||||
// format.
|
||||
if (status === undefined) {
|
||||
return result
|
||||
}
|
||||
|
||||
if (status !== 'Success') {
|
||||
throw XapiError.wrap(result.ErrorDescription)
|
||||
}
|
||||
|
||||
return result.Value
|
||||
}
|
||||
|
||||
export default ({ agent, client, url }) => {
|
||||
url = new URL('./xmlrpc', Object.assign(new URL('http://localhost'), url))
|
||||
const path = url.pathname + url.search
|
||||
|
||||
return async function (method, args) {
|
||||
const message = new XmlRpcMessage(method, prepareXmlRpcParams(args))
|
||||
|
||||
const res = await client.request({
|
||||
body: message.xml(),
|
||||
headers: {
|
||||
Accept: 'text/xml',
|
||||
'Content-Type': 'text/xml',
|
||||
},
|
||||
method: 'POST',
|
||||
path,
|
||||
agent,
|
||||
})
|
||||
|
||||
if (res.headers['content-type'] !== 'text/xml' && res.headers['content-type'] !== 'application/xml') {
|
||||
throw new UnsupportedTransport()
|
||||
}
|
||||
|
||||
const xml = await res.body.text()
|
||||
const response = await new XmlRpcResponse().parse(xml)
|
||||
|
||||
return parseResult(response.params[0])
|
||||
}
|
||||
}
|
||||
@@ -594,6 +594,7 @@ const TRANSFORMS = {
|
||||
usage: +obj.physical_utilisation,
|
||||
VDI_type: obj.type,
|
||||
current_operations: obj.current_operations,
|
||||
other_config: obj.other_config,
|
||||
|
||||
$SR: link(obj, 'SR'),
|
||||
$VBDs: link(obj, 'VBDs'),
|
||||
|
||||
@@ -1223,40 +1223,6 @@ export default class NewVm extends BaseComponent {
|
||||
</SectionContent>
|
||||
) : (
|
||||
<SectionContent>
|
||||
<Item>
|
||||
<span className={styles.item}>
|
||||
<input
|
||||
checked={installMethod === 'ISO'}
|
||||
name='installMethod'
|
||||
onChange={this._linkState('installMethod')}
|
||||
type='radio'
|
||||
value='ISO'
|
||||
/>
|
||||
|
||||
<span>{_('newVmIsoDvdLabel')}</span>
|
||||
|
||||
<span className={styles.inlineSelect}>
|
||||
{this.props.pool ? (
|
||||
<SelectVdi
|
||||
disabled={installMethod !== 'ISO'}
|
||||
onChange={this._linkState('installIso')}
|
||||
predicate={isVdiPresent}
|
||||
srPredicate={this._getIsoPredicate()}
|
||||
value={installIso}
|
||||
/>
|
||||
) : (
|
||||
<SelectResourceSetsVdi
|
||||
disabled={installMethod !== 'ISO'}
|
||||
onChange={this._linkState('installIso')}
|
||||
predicate={isVdiPresent}
|
||||
resourceSet={this._getResolvedResourceSet()}
|
||||
srPredicate={this._getIsoPredicate()}
|
||||
value={installIso}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
</Item>
|
||||
{template.virtualizationMode === 'pv' ? (
|
||||
<span>
|
||||
<Item>
|
||||
@@ -1295,6 +1261,40 @@ export default class NewVm extends BaseComponent {
|
||||
)}
|
||||
</SectionContent>
|
||||
)}
|
||||
<SectionContent>
|
||||
<span className={styles.item}>
|
||||
<input
|
||||
checked={installMethod === 'ISO'}
|
||||
name='installMethod'
|
||||
onChange={this._linkState('installMethod')}
|
||||
type='radio'
|
||||
value='ISO'
|
||||
/>
|
||||
|
||||
<span>{_('newVmIsoDvdLabel')}</span>
|
||||
|
||||
<span className={styles.inlineSelect}>
|
||||
{this.props.pool ? (
|
||||
<SelectVdi
|
||||
disabled={installMethod !== 'ISO'}
|
||||
onChange={this._linkState('installIso')}
|
||||
predicate={isVdiPresent}
|
||||
srPredicate={this._getIsoPredicate()}
|
||||
value={installIso}
|
||||
/>
|
||||
) : (
|
||||
<SelectResourceSetsVdi
|
||||
disabled={installMethod !== 'ISO'}
|
||||
onChange={this._linkState('installIso')}
|
||||
predicate={isVdiPresent}
|
||||
resourceSet={this._getResolvedResourceSet()}
|
||||
srPredicate={this._getIsoPredicate()}
|
||||
value={installIso}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
</SectionContent>
|
||||
{this._isCoreOs() && (
|
||||
<div>
|
||||
<label>{_('newVmCloudConfig')}</label>{' '}
|
||||
|
||||
47
yarn.lock
47
yarn.lock
@@ -2106,11 +2106,6 @@
|
||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.54.0.tgz#4fab9a2ff7860082c304f750e94acd644cf984cf"
|
||||
integrity sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==
|
||||
|
||||
"@fastify/busboy@^2.0.0":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.0.tgz#0709e9f4cb252351c609c6e6d8d6779a8d25edff"
|
||||
integrity sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==
|
||||
|
||||
"@fontsource/poppins@^5.0.8":
|
||||
version "5.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@fontsource/poppins/-/poppins-5.0.8.tgz#a1c5540aedb3719a36eba5c7c5dfaa3aed3c9f80"
|
||||
@@ -18787,21 +18782,16 @@ sass@^1.38.1:
|
||||
immutable "^4.0.0"
|
||||
source-map-js ">=0.6.2 <2.0.0"
|
||||
|
||||
sax-parser@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/sax-parser/-/sax-parser-2.0.2.tgz#7b3b4a25fc69bf4e729ad5f0f98430205d461689"
|
||||
integrity sha512-EjLxlFjZdmv/cpOwV+klYEeOYjR2Dc9C495d2Ruk+N6xknrOnIfjSum2a63hfi9Vox2fCsjYc3NuDVo0YkGpjg==
|
||||
sax@1.2.x, sax@~1.2.4:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
||||
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
|
||||
|
||||
sax@>=0.6, sax@>=0.6.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0"
|
||||
integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==
|
||||
|
||||
sax@~1.2.4:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
||||
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
|
||||
|
||||
scheduler@^0.20.2:
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
|
||||
@@ -20819,13 +20809,6 @@ undici-types@~5.26.4:
|
||||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
|
||||
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
|
||||
|
||||
undici@^5.27.2:
|
||||
version "5.27.2"
|
||||
resolved "https://registry.yarnpkg.com/undici/-/undici-5.27.2.tgz#a270c563aea5b46cc0df2550523638c95c5d4411"
|
||||
integrity sha512-iS857PdOEy/y3wlM3yRp+6SNQQ6xU0mmZcwRSriqk+et/cwWAtwmIGf6WkoDN2EK/AMdCO/dfXzIwi+rFMrjjQ==
|
||||
dependencies:
|
||||
"@fastify/busboy" "^2.0.0"
|
||||
|
||||
unicode-canonical-property-names-ecmascript@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc"
|
||||
@@ -21050,11 +21033,6 @@ use@^3.1.0:
|
||||
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
|
||||
integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
|
||||
|
||||
utf8-base64@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/utf8-base64/-/utf8-base64-0.1.2.tgz#555806c458f9ba3f089c3ebe0c5f6198348bb57b"
|
||||
integrity sha512-DNeEx/I7HruiVsfk/DbEl4bpdRR/mv5p6FGDFZVyA8wqdMOqYp0CeCgW4/DzsPIW/skOq5Bxv49/eYfvAYJTWg==
|
||||
|
||||
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
@@ -22003,6 +21981,11 @@ xml2js@^0.4.19, xml2js@^0.4.23:
|
||||
sax ">=0.6.0"
|
||||
xmlbuilder "~11.0.0"
|
||||
|
||||
xmlbuilder@8.2.x:
|
||||
version "8.2.2"
|
||||
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-8.2.2.tgz#69248673410b4ba42e1a6136551d2922335aa773"
|
||||
integrity sha512-eKRAFz04jghooy8muekqzo8uCSVNeyRedbuJrp0fovbLIi7wlsYtdUn3vBAAPq2Y3/0xMz2WMEUQ8yhVVO9Stw==
|
||||
|
||||
xmlbuilder@^15.1.1:
|
||||
version "15.1.1"
|
||||
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5"
|
||||
@@ -22013,13 +21996,13 @@ xmlbuilder@~11.0.0:
|
||||
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
|
||||
integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
|
||||
|
||||
xmlrpc-parser@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/xmlrpc-parser/-/xmlrpc-parser-1.0.3.tgz#94f21bb74daaa2290a51471635c73f8b6dc1f3d9"
|
||||
integrity sha512-0197DF6MrKFoiaccl2GuB5mcc3F0jSebPLHIqsahpau4yyztg34bVZDhc6HGzs5hji801prnytCKTo4Kdpa7Rw==
|
||||
xmlrpc@^1.3.2:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/xmlrpc/-/xmlrpc-1.3.2.tgz#26b2ea347848d028aac7e7514b5351976de3e83d"
|
||||
integrity sha512-jQf5gbrP6wvzN71fgkcPPkF4bF/Wyovd7Xdff8d6/ihxYmgETQYSuTc+Hl+tsh/jmgPLro/Aro48LMFlIyEKKQ==
|
||||
dependencies:
|
||||
sax-parser "^2.0.2"
|
||||
utf8-base64 "^0.1.2"
|
||||
sax "1.2.x"
|
||||
xmlbuilder "8.2.x"
|
||||
|
||||
xok@^1.0.0:
|
||||
version "1.0.0"
|
||||
|
||||
Reference in New Issue
Block a user