UNPKG

vsphere-infra

Version:

159 lines (147 loc) 5.34 kB
/* * Copyright 2014-2021 Firestack, all rights reserved. */ import { IncomingHttpHeaders } from 'http'; import * as https from 'https'; import * as cookie from 'cookie'; import * as _ from 'lodash'; import * as soap from 'soap'; import * as uuid from 'uuid'; import { VsphereCaller } from './vsphere.caller'; import { ix } from 'ts-basis'; import { CustomHttpClient } from './soap.request'; import { VsphereDatacenter } from './vsphere'; type SoapMethodCallback<Params, Result> = (params: Params, callback: ( err: Error | null, result: { returnval: Result; } | null, rawResponse: string, soapHeaders: Record<string, string>, rawRequest: string ) => void) => void; export class VsphereConnection extends ix.Entity { readonly connId = uuid.v4(); api: VsphereCaller; client: soap.Client; traffic = { up: 0, down: 0, baseOverhead: 200 }; constructor( public $dc: VsphereDatacenter, public readonly url: string, public readonly options: soap.IOptions, private readonly connectionAllowed?: boolean ) { super('vcenter-conn'); $dc.lifecycle.manage(this); } /** * Executes a SOAP command * @returns result and headers */ async exec<Params, Result>(command: string, params: Params): Promise<{ result: Result; headers: IncomingHttpHeaders; }> { // remove "connection" members to avoid circular references const paramsClone = this.sanitizeParams(_.cloneDeep(params)); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const callback = this.client.VimService.VimPort[command] as SoapMethodCallback<Params, Result>; const result = await new Promise<Result>((resolve, reject) => { callback(paramsClone as unknown as Params, (err, res) => { if (err) { return reject(err); } if (res === null) { return reject(new Error('No result returned from vSphere')); } resolve(res.returnval); }); }); return { result, headers: this.client.lastResponseHeaders ?? {} }; } async connect(): Promise<boolean> { this.$dc.debugOutput(`connecting... (connId=${this.connId})`); if (!this.connectionAllowed) { throw new Error(`Cannot connect to ${this.url}, connection not granted.`); } let url = this.url; if (!url.startsWith('https://') && !url.startsWith('http://')) { url = 'https://' + url; } if (!url.endsWith('/sdk/vimService.wsdl')) { if (url.endsWith('/')) { url += 'sdk/vimService.wsdl'; } else { url += '/sdk/vimService.wsdl'; } } return new Promise<boolean>(async resolve => { soap.createClientAsync(url, { endpoint: url, httpClient: new CustomHttpClient({ sdkCache: this.$dc.vinfra.behavior.useSdkCache, }) as any, }, url).then(client => { this.client = client; this.client.setSecurity(new soap.ClientSSLSecurity( null, // key null, // cert null, // ca { strictSSL: true, forever: true, ca: https.globalAgent.options.ca } )); this.$dc.debugOutput(`client created (clientId=${this.connId})`); // tslint:disable-next-line: no-console this.client.on('request', (xml, eid) => { this.traffic.up += xml.length + this.traffic.baseOverhead; }); this.client.on('response', (body, eid) => { if (body) { this.traffic.down += body.length + this.traffic.baseOverhead; } // console.log('res', body, eid); }); this.client.on('soapError', (error, eid) => { if (error.body) { this.traffic.down += error.body.length + this.traffic.baseOverhead; } // console.log('soapE', eid); }); this.api = new VsphereCaller(this.$dc, this.client); resolve(true); }).catch(e => { this.$dc.debugOutput(e); return resolve(false); }); }); } async login(username: string, password: string): Promise<boolean> { try { const res = await this.exec('Login', { _this: { $value: 'SessionManager' }, userName: username, password }); // make sure we use VMWare's session cookie const header = res.headers['set-cookie']; if (header && header.length > 0) { const sessionId = cookie.parse(header[0]).vmware_soap_session; this.client.addHttpHeader('Cookie', cookie.serialize('vmware_soap_session', sessionId)); return true; } else { return false; } } catch (err) { if (err instanceof Error && err.message.includes('Cannot complete login due to an incorrect user name or password.')) { throw new Error(`vSphere rejected login credentials for '${username}' target '${this.url}'`); } throw err; } } /** Recursively remove "connection" properties from an object */ private sanitizeParams<Params>(params: Params): Params { (Object.keys(params) as (keyof Params)[]).forEach(key => { if (key === 'connection') { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete params[key]; } else if (typeof params[key] === 'object') { this.sanitizeParams(params[key]); } }); return params; } }