'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