This commit is contained in:
2026-06-25 14:43:13 -05:00
parent 6f77fac58e
commit aaa6a9ee26
25 changed files with 1542 additions and 67 deletions

View File

@@ -15,6 +15,7 @@ const API_PROFILES = {
sessionPath: '/api/session',
vmListPath: '/api/vcenter/vm',
vmNameParam: 'names',
vmDetailPath: (vmId) => `/api/vcenter/vm/${encodeURIComponent(vmId)}`,
guestIdentityPath: (vmId) => `/api/vcenter/vm/${encodeURIComponent(vmId)}/guest/identity`,
guestNetworkingPath: (vmId) => `/api/vcenter/vm/${encodeURIComponent(vmId)}/guest/networking/interfaces`
},
@@ -24,6 +25,7 @@ const API_PROFILES = {
sessionPath: '/rest/com/vmware/cis/session',
vmListPath: '/rest/vcenter/vm',
vmNameParam: 'filter.names',
vmDetailPath: (vmId) => `/rest/vcenter/vm/${encodeURIComponent(vmId)}`,
guestIdentityPath: (vmId) => `/rest/vcenter/vm/${encodeURIComponent(vmId)}/guest/identity`,
guestNetworkingPath: (vmId) => `/rest/vcenter/vm/${encodeURIComponent(vmId)}/guest/networking/interfaces`
}
@@ -52,6 +54,20 @@ export function getVCenterSettings() {
};
}
export function settingsFromVCenterConnection(connection) {
if (!connection) return getVCenterSettings();
const apiMode = String(connection.api_mode || connection.apiMode || 'auto').toLowerCase();
return {
enabled: Boolean(connection.enabled ?? true),
baseUrl: normalizeBaseUrl(connection.base_url || connection.baseUrl),
username: connection.username || '',
password: connection.password || '',
apiMode: ['auto', 'api', 'rest'].includes(apiMode) ? apiMode : 'auto',
requestTimeoutMs: Number(connection.request_timeout_ms || connection.requestTimeoutMs || 20000),
allowUntrustedTls: Boolean(connection.allow_untrusted_tls ?? connection.allowUntrustedTls)
};
}
export function normalizeBaseUrl(value) {
return String(value || '').trim().replace(/\/+$/, '');
}
@@ -69,6 +85,21 @@ export function vcenterStatus() {
};
}
export function vcenterConnectionStatus(connection) {
const settings = settingsFromVCenterConnection(connection);
return {
id: connection?.id || '',
name: connection?.name || 'Configured default',
enabled: settings.enabled,
configured: Boolean(settings.baseUrl && settings.username && settings.password),
baseUrl: settings.baseUrl,
username: settings.username,
apiMode: settings.apiMode,
requestTimeoutMs: settings.requestTimeoutMs,
allowUntrustedTls: settings.allowUntrustedTls
};
}
function assertConfigured(settings) {
if (!settings.enabled) throw new Error('VMware vCenter integration is disabled in Configuration.');
if (!settings.baseUrl) throw new Error('VMware vCenter base URL is missing. Set vcenter_base_url in Configuration.');
@@ -361,13 +392,16 @@ export function buildHostPayloadFromVm(candidate, options = {}) {
candidate.ipAddresses?.length ? `IP addresses: ${candidate.ipAddresses.join(', ')}.` : 'IP address unavailable from VMware Tools at import time.'
].filter(Boolean).join(' '),
visibility: options.visibility || 'shared',
groupId: options.visibility === 'group' ? options.groupId || null : null
groupId: options.visibility === 'group' ? options.groupId || null : null,
sourceType: 'vmware',
sourceConnectionId: options.connectionId || null,
sourceRef: candidate.id || ''
};
}
export async function previewVCenterHosts(options = {}) {
const trace = [];
const settings = getVCenterSettings();
const settings = settingsFromVCenterConnection(options.connection);
try {
assertConfigured(settings);
} catch (err) {
@@ -405,7 +439,7 @@ export async function previewVCenterHosts(options = {}) {
});
return {
status: vcenterStatus(),
status: options.connection ? vcenterConnectionStatus(options.connection) : vcenterStatus(),
filter: options.filter || {},
count: candidates.length,
diagnostics,
@@ -414,6 +448,50 @@ export async function previewVCenterHosts(options = {}) {
};
}
export async function testVCenterConnection(connection) {
const trace = [];
const settings = settingsFromVCenterConnection(connection);
assertConfigured(settings);
const { profile, vmPayload } = await connectAndListVms(settings, {}, trace);
return {
status: vcenterConnectionStatus(connection),
apiProfile: profile.label,
count: unwrapList(vmPayload).length,
trace,
message: `Connected to ${settings.baseUrl} with ${profile.label}; ${unwrapList(vmPayload).length} VM summary row(s) returned.`
};
}
export async function getVCenterVmPowerState(connection, vmId, trace = null) {
if (!connection || !vmId) return { checked: false, reason: 'Missing vCenter connection or VM reference.' };
const settings = settingsFromVCenterConnection(connection);
assertConfigured(settings);
const modes = settings.apiMode === 'auto' ? ['api', 'rest'] : [settings.apiMode];
let lastError = null;
for (const mode of modes) {
const profile = API_PROFILES[mode];
try {
const sessionId = await createSession(settings, profile, trace);
const payload = await vcenterFetch(settings, profile.vmDetailPath(vmId), { sessionId, trace });
const value = payload?.value || payload || {};
return {
checked: true,
state: value.power_state || value.powerState || '',
apiProfile: profile.label
};
} catch (err) {
lastError = err;
if (settings.apiMode !== 'auto' || mode === modes.at(-1) || !shouldTryFallback(err)) throw err;
pushTraceInfo(trace, `Falling back after ${profile.label} power-state lookup failed`, {
mode,
error: err.message,
statusCode: err.statusCode
});
}
}
throw lastError || new VCenterTraceError('No vCenter API profile could be attempted for power-state lookup.', trace);
}
async function enrichVmsForPreview({ vms, settings, profile, sessionId, trace, filter, limit, diagnostics, candidates }) {
let index = 0;
async function worker() {