chore(node-vsphere-soap): convert to ESM
BREAKING CHANGE
This commit is contained in:
parent
de217eabd9
commit
f0c94496bf
@ -1,231 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
/*
|
|
||||||
|
|
||||||
node-vsphere-soap
|
|
||||||
|
|
||||||
client.js
|
|
||||||
|
|
||||||
This file creates the Client class
|
|
||||||
|
|
||||||
- when the class is instantiated, a connection will be made to the ESXi/vCenter server to verify that the creds are good
|
|
||||||
- upon a bad login, the connnection will be terminated
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
const EventEmitter = require('events').EventEmitter
|
|
||||||
const axios = require('axios')
|
|
||||||
const https = require('node:https')
|
|
||||||
const util = require('util')
|
|
||||||
const soap = require('soap')
|
|
||||||
const Cookie = require('soap-cookie') // required for session persistence
|
|
||||||
// Client class
|
|
||||||
// inherits from EventEmitter
|
|
||||||
// possible events: connect, error, ready
|
|
||||||
|
|
||||||
function Client(vCenterHostname, username, password, sslVerify) {
|
|
||||||
this.status = 'disconnected'
|
|
||||||
this.reconnectCount = 0
|
|
||||||
|
|
||||||
sslVerify = typeof sslVerify !== 'undefined' ? sslVerify : false
|
|
||||||
|
|
||||||
EventEmitter.call(this)
|
|
||||||
|
|
||||||
// sslVerify argument handling
|
|
||||||
if (sslVerify) {
|
|
||||||
this.clientopts = {}
|
|
||||||
} else {
|
|
||||||
this.clientopts = {
|
|
||||||
request: axios.create({
|
|
||||||
httpsAgent: new https.Agent({
|
|
||||||
rejectUnauthorized: false,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.connectionInfo = {
|
|
||||||
host: vCenterHostname,
|
|
||||||
user: username,
|
|
||||||
password,
|
|
||||||
sslVerify,
|
|
||||||
}
|
|
||||||
|
|
||||||
this._loginArgs = {
|
|
||||||
userName: this.connectionInfo.user,
|
|
||||||
password: this.connectionInfo.password,
|
|
||||||
}
|
|
||||||
|
|
||||||
this._vcUrl = 'https://' + this.connectionInfo.host + '/sdk/vimService.wsdl'
|
|
||||||
|
|
||||||
// connect to the vCenter / ESXi host
|
|
||||||
this.on('connect', this._connect)
|
|
||||||
this.emit('connect')
|
|
||||||
|
|
||||||
// close session
|
|
||||||
this.on('close', this._close)
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
util.inherits(Client, EventEmitter)
|
|
||||||
|
|
||||||
Client.prototype.runCommand = function (command, args) {
|
|
||||||
const self = this
|
|
||||||
let cmdargs
|
|
||||||
if (!args || args === null) {
|
|
||||||
cmdargs = {}
|
|
||||||
} else {
|
|
||||||
cmdargs = args
|
|
||||||
}
|
|
||||||
|
|
||||||
const emitter = new EventEmitter()
|
|
||||||
|
|
||||||
// check if client has successfully connected
|
|
||||||
if (self.status === 'ready' || self.status === 'connecting') {
|
|
||||||
self.client.VimService.VimPort[command](cmdargs, function (err, result, raw, soapHeader) {
|
|
||||||
if (err) {
|
|
||||||
_soapErrorHandler(self, emitter, command, cmdargs, err)
|
|
||||||
}
|
|
||||||
if (command === 'Logout') {
|
|
||||||
self.status = 'disconnected'
|
|
||||||
process.removeAllListeners('beforeExit')
|
|
||||||
}
|
|
||||||
emitter.emit('result', result, raw, soapHeader)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// if connection not ready or connecting, reconnect to instance
|
|
||||||
if (self.status === 'disconnected') {
|
|
||||||
self.emit('connect')
|
|
||||||
}
|
|
||||||
self.once('ready', function () {
|
|
||||||
self.client.VimService.VimPort[command](cmdargs, function (err, result, raw, soapHeader) {
|
|
||||||
if (err) {
|
|
||||||
_soapErrorHandler(self, emitter, command, cmdargs, err)
|
|
||||||
}
|
|
||||||
if (command === 'Logout') {
|
|
||||||
self.status = 'disconnected'
|
|
||||||
process.removeAllListeners('beforeExit')
|
|
||||||
}
|
|
||||||
emitter.emit('result', result, raw, soapHeader)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return emitter
|
|
||||||
}
|
|
||||||
|
|
||||||
Client.prototype.close = function () {
|
|
||||||
const self = this
|
|
||||||
|
|
||||||
self.emit('close')
|
|
||||||
}
|
|
||||||
|
|
||||||
Client.prototype._connect = function () {
|
|
||||||
const self = this
|
|
||||||
|
|
||||||
if (self.status !== 'disconnected') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.status = 'connecting'
|
|
||||||
|
|
||||||
soap.createClient(
|
|
||||||
self._vcUrl,
|
|
||||||
self.clientopts,
|
|
||||||
function (err, client) {
|
|
||||||
if (err) {
|
|
||||||
self.emit('error', err)
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
|
|
||||||
self.client = client // save client for later use
|
|
||||||
|
|
||||||
self
|
|
||||||
.runCommand('RetrieveServiceContent', { _this: 'ServiceInstance' })
|
|
||||||
.once('result', function (result, raw, soapHeader) {
|
|
||||||
if (!result.returnval) {
|
|
||||||
self.status = 'disconnected'
|
|
||||||
self.emit('error', raw)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.serviceContent = result.returnval
|
|
||||||
self.sessionManager = result.returnval.sessionManager
|
|
||||||
const loginArgs = { _this: self.sessionManager, ...self._loginArgs }
|
|
||||||
|
|
||||||
self
|
|
||||||
.runCommand('Login', loginArgs)
|
|
||||||
.once('result', function (result, raw, soapHeader) {
|
|
||||||
self.authCookie = new Cookie(client.lastResponseHeaders)
|
|
||||||
self.client.setSecurity(self.authCookie) // needed since vSphere SOAP WS uses cookies
|
|
||||||
|
|
||||||
self.userName = result.returnval.userName
|
|
||||||
self.fullName = result.returnval.fullName
|
|
||||||
self.reconnectCount = 0
|
|
||||||
|
|
||||||
self.status = 'ready'
|
|
||||||
self.emit('ready')
|
|
||||||
process.once('beforeExit', self._close)
|
|
||||||
})
|
|
||||||
.once('error', function (err) {
|
|
||||||
self.status = 'disconnected'
|
|
||||||
self.emit('error', err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.once('error', function (err) {
|
|
||||||
self.status = 'disconnected'
|
|
||||||
self.emit('error', err)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
self._vcUrl
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Client.prototype._close = function () {
|
|
||||||
const self = this
|
|
||||||
|
|
||||||
if (self.status === 'ready') {
|
|
||||||
self
|
|
||||||
.runCommand('Logout', { _this: self.sessionManager })
|
|
||||||
.once('result', function () {
|
|
||||||
self.status = 'disconnected'
|
|
||||||
})
|
|
||||||
.once('error', function () {
|
|
||||||
/* don't care of error during disconnection */
|
|
||||||
self.status = 'disconnected'
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
self.status = 'disconnected'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _soapErrorHandler(self, emitter, command, args, err) {
|
|
||||||
err = err || { body: 'general error' }
|
|
||||||
|
|
||||||
if (err.body.match(/session is not authenticated/)) {
|
|
||||||
self.status = 'disconnected'
|
|
||||||
process.removeAllListeners('beforeExit')
|
|
||||||
|
|
||||||
if (self.reconnectCount < 10) {
|
|
||||||
self.reconnectCount += 1
|
|
||||||
self
|
|
||||||
.runCommand(command, args)
|
|
||||||
.once('result', function (result, raw, soapHeader) {
|
|
||||||
emitter.emit('result', result, raw, soapHeader)
|
|
||||||
})
|
|
||||||
.once('error', function (err) {
|
|
||||||
emitter.emit('error', err.body)
|
|
||||||
throw err
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
emitter.emit('error', err.body)
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
emitter.emit('error', err.body)
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// end
|
|
||||||
exports.Client = Client
|
|
@ -2,7 +2,7 @@
|
|||||||
"name": "@vates/node-vsphere-soap",
|
"name": "@vates/node-vsphere-soap",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "interface to vSphere SOAP/WSDL from node for interfacing with vCenter or ESXi, forked from node-vsphere-soap",
|
"description": "interface to vSphere SOAP/WSDL from node for interfacing with vCenter or ESXi, forked from node-vsphere-soap",
|
||||||
"main": "lib/client.js",
|
"main": "lib/client.mjs",
|
||||||
"author": "reedog117",
|
"author": "reedog117",
|
||||||
"repository": {
|
"repository": {
|
||||||
"directory": "@vates/node-vsphere-soap",
|
"directory": "@vates/node-vsphere-soap",
|
||||||
@ -30,7 +30,7 @@
|
|||||||
"private": false,
|
"private": false,
|
||||||
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/node-vsphere-soap",
|
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/node-vsphere-soap",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.10"
|
"node": ">=14"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postversion": "npm publish --access public"
|
"postversion": "npm publish --access public"
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
// place your own credentials here for a vCenter or ESXi server
|
|
||||||
// this information will be used for connecting to a vCenter instance
|
|
||||||
// for module testing
|
|
||||||
// name the file config-test.js
|
|
||||||
|
|
||||||
const vCenterTestCreds = {
|
|
||||||
vCenterIP: 'vcsa',
|
|
||||||
vCenterUser: 'vcuser',
|
|
||||||
vCenterPassword: 'vcpw',
|
|
||||||
vCenter: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.vCenterTestCreds = vCenterTestCreds
|
|
@ -1,140 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
/*
|
|
||||||
vsphere-soap.test.js
|
|
||||||
|
|
||||||
tests for the vCenterConnectionInstance class
|
|
||||||
*/
|
|
||||||
|
|
||||||
const assert = require('assert')
|
|
||||||
const { describe, it } = require('test')
|
|
||||||
|
|
||||||
const vc = require('../lib/client')
|
|
||||||
|
|
||||||
// eslint-disable-next-line n/no-missing-require
|
|
||||||
const TestCreds = require('../config-test.js').vCenterTestCreds
|
|
||||||
|
|
||||||
const VItest = new vc.Client(TestCreds.vCenterIP, TestCreds.vCenterUser, TestCreds.vCenterPassword, false)
|
|
||||||
|
|
||||||
describe('Client object initialization:', function () {
|
|
||||||
it('provides a successful login', { timeout: 5000 }, function (t, done) {
|
|
||||||
VItest.once('ready', function () {
|
|
||||||
assert.notEqual(VItest.userName, null)
|
|
||||||
assert.notEqual(VItest.fullName, null)
|
|
||||||
assert.notEqual(VItest.serviceContent, null)
|
|
||||||
done()
|
|
||||||
}).once('error', function (err) {
|
|
||||||
console.error(err)
|
|
||||||
// this should fail if there's a problem
|
|
||||||
assert.notEqual(VItest.userName, null)
|
|
||||||
assert.notEqual(VItest.fullName, null)
|
|
||||||
assert.notEqual(VItest.serviceContent, null)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Client reconnection test:', function () {
|
|
||||||
it('can successfully reconnect', { timeout: 5000 }, function (t, done) {
|
|
||||||
VItest.runCommand('Logout', { _this: VItest.serviceContent.sessionManager })
|
|
||||||
.once('result', function (result) {
|
|
||||||
// now we're logged out, so let's try running a command to test automatic re-login
|
|
||||||
VItest.runCommand('CurrentTime', { _this: 'ServiceInstance' })
|
|
||||||
.once('result', function (result) {
|
|
||||||
assert(result.returnval instanceof Date)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
.once('error', function (err) {
|
|
||||||
console.error(err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.once('error', function (err) {
|
|
||||||
console.error(err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// these tests don't work yet
|
|
||||||
describe('Client tests - query commands:', function () {
|
|
||||||
it('retrieves current time', { timeout: 5000 }, function (t, done) {
|
|
||||||
VItest.runCommand('CurrentTime', { _this: 'ServiceInstance' }).once('result', function (result) {
|
|
||||||
assert(result.returnval instanceof Date)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('retrieves current time 2 (check for event clobbering)', { timeout: 5000 }, function (t, done) {
|
|
||||||
VItest.runCommand('CurrentTime', { _this: 'ServiceInstance' }).once('result', function (result) {
|
|
||||||
assert(result.returnval instanceof Date)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('can obtain the names of all Virtual Machines in the inventory', { timeout: 20000 }, function (t, done) {
|
|
||||||
// get property collector
|
|
||||||
const propertyCollector = VItest.serviceContent.propertyCollector
|
|
||||||
// get view manager
|
|
||||||
const viewManager = VItest.serviceContent.viewManager
|
|
||||||
// get root folder
|
|
||||||
const rootFolder = VItest.serviceContent.rootFolder
|
|
||||||
|
|
||||||
let containerView, objectSpec, traversalSpec, propertySpec, propertyFilterSpec
|
|
||||||
// this is the equivalent to
|
|
||||||
VItest.runCommand('CreateContainerView', {
|
|
||||||
_this: viewManager,
|
|
||||||
container: rootFolder,
|
|
||||||
type: ['VirtualMachine'],
|
|
||||||
recursive: true,
|
|
||||||
}).once('result', function (result) {
|
|
||||||
// build all the data structures needed to query all the vm names
|
|
||||||
containerView = result.returnval
|
|
||||||
|
|
||||||
objectSpec = {
|
|
||||||
attributes: { 'xsi:type': 'ObjectSpec' }, // setting attributes xsi:type is important or else the server may mis-recognize types!
|
|
||||||
obj: containerView,
|
|
||||||
skip: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
traversalSpec = {
|
|
||||||
attributes: { 'xsi:type': 'TraversalSpec' },
|
|
||||||
name: 'traverseEntities',
|
|
||||||
type: 'ContainerView',
|
|
||||||
path: 'view',
|
|
||||||
skip: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
objectSpec = { ...objectSpec, selectSet: [traversalSpec] }
|
|
||||||
|
|
||||||
propertySpec = {
|
|
||||||
attributes: { 'xsi:type': 'PropertySpec' },
|
|
||||||
type: 'VirtualMachine',
|
|
||||||
pathSet: ['name'],
|
|
||||||
}
|
|
||||||
|
|
||||||
propertyFilterSpec = {
|
|
||||||
attributes: { 'xsi:type': 'PropertyFilterSpec' },
|
|
||||||
propSet: [propertySpec],
|
|
||||||
objectSet: [objectSpec],
|
|
||||||
}
|
|
||||||
// TODO: research why it fails if propSet is declared after objectSet
|
|
||||||
|
|
||||||
VItest.runCommand('RetrievePropertiesEx', {
|
|
||||||
_this: propertyCollector,
|
|
||||||
specSet: [propertyFilterSpec],
|
|
||||||
options: { attributes: { type: 'RetrieveOptions' } },
|
|
||||||
})
|
|
||||||
.once('result', function (result, raw) {
|
|
||||||
assert.notEqual(result.returnval.objects, null)
|
|
||||||
if (Array.isArray(result.returnval.objects)) {
|
|
||||||
assert.strictEqual(result.returnval.objects[0].obj.attributes.type, 'VirtualMachine')
|
|
||||||
} else {
|
|
||||||
assert.strictEqual(result.returnval.objects.obj.attributes.type, 'VirtualMachine')
|
|
||||||
}
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
.once('error', function (err) {
|
|
||||||
console.error('\n\nlast request : ' + VItest.client.lastRequest, err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
@ -36,6 +36,7 @@
|
|||||||
|
|
||||||
- @vates/fuse-vhd major
|
- @vates/fuse-vhd major
|
||||||
- @vates/nbd-client major
|
- @vates/nbd-client major
|
||||||
|
- @vates/node-vsphere-soap major
|
||||||
- @xen-orchestra/backups minor
|
- @xen-orchestra/backups minor
|
||||||
- @xen-orchestra/xapi major
|
- @xen-orchestra/xapi major
|
||||||
- complex-matcher patch
|
- complex-matcher patch
|
||||||
|
Loading…
Reference in New Issue
Block a user