vsphere-infra
Version:
159 lines (147 loc) • 5.34 kB
text/typescript
/*
* 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;
}
}