UNPKG

n8n-nodes-proxmox

Version:

n8n community node for Proxmox Virtual Environment (VE) API integration with VM, container, storage, and cluster management capabilities

532 lines (416 loc) 16.7 kB
import { IExecuteFunctions, IDataObject } from 'n8n-workflow'; import { apiRequest, apiRequestAllItems } from '../helpers/apiRequest'; import { handleProxMoxError, validateParameters } from '../helpers/errorHandler'; import { logger } from '../helpers/logger'; export async function executeVmOperation( this: IExecuteFunctions, operation: string, i: number, ): Promise<IDataObject | IDataObject[]> { try { console.log(`=== VM Operation Debug: ${operation} ===`); logger.operation('vm', operation, 'start', { operation }); let response: IDataObject | IDataObject[]; switch (operation) { case 'create': response = await createVm.call(this, i); break; case 'delete': response = await deleteVm.call(this, i); break; case 'get': response = await getVm.call(this, i); break; case 'getAll': response = await getAllVms.call(this, i); break; case 'start': response = await startVm.call(this, i); break; case 'stop': response = await stopVm.call(this, i); break; case 'shutdown': response = await shutdownVm.call(this, i); break; case 'reboot': response = await rebootVm.call(this, i); break; case 'suspend': response = await suspendVm.call(this, i); break; case 'resume': response = await resumeVm.call(this, i); break; case 'clone': response = await cloneVm.call(this, i); break; case 'migrate': response = await migrateVm.call(this, i); break; case 'snapshot': response = await snapshotVm.call(this, i); break; case 'getStatus': response = await getVmStatus.call(this, i); break; default: throw new Error(`Unknown VM operation: ${operation}`); } logger.operation('vm', operation, 'success', { operation }); return response; } catch (error) { logger.operation('vm', operation, 'error', { operation, error: error.message }); return handleProxMoxError.call(this, error, operation, 'VM'); } } async function waitForTask( this: IExecuteFunctions, nodeName: string, taskId: string, timeout: number = 300, ): Promise<IDataObject> { const startTime = Date.now(); const timeoutMs = timeout * 1000; logger.debug('task', `Waiting for task ${taskId} on node ${nodeName}`, { taskId, timeout }); while (Date.now() - startTime < timeoutMs) { try { const taskStatus = await apiRequest.call( this, 'GET', `/nodes/${nodeName}/tasks/${taskId}/status`, ); if (taskStatus.status === 'stopped') { if (taskStatus.exitstatus === 'OK') { logger.debug('task', `Task ${taskId} completed successfully`); return taskStatus; } else { throw new Error(`Task failed: ${taskStatus.exitstatus}`); } } // Wait 2 seconds before checking again await new Promise((resolve) => setTimeout(resolve, 2000)); } catch (error) { logger.error('task', `Error checking task status: ${error.message}`); throw error; } } throw new Error(`Task ${taskId} timed out after ${timeout} seconds`); } async function createVm(this: IExecuteFunctions, i: number): Promise<IDataObject> { const nodeName = this.getNodeParameter('nodeName', i) as string; const newVmId = this.getNodeParameter('newVmId', i) as number; const vmName = this.getNodeParameter('vmName', i) as string; const waitForTask = this.getNodeParameter('waitForTask', i, true) as boolean; const taskTimeout = this.getNodeParameter('taskTimeout', i, 300) as number; const additionalOptions = this.getNodeParameter('additionalOptions', i, {}) as IDataObject; validateParameters.call( this, { nodeName, newVmId, vmName }, ['nodeName', 'newVmId', 'vmName'], 'create VM', ); const body: IDataObject = { vmid: newVmId, name: vmName, ...additionalOptions, }; // Set defaults for VM creation if (!body.memory) body.memory = 1024; if (!body.cores) body.cores = 1; if (!body.sockets) body.sockets = 1; if (!body.storage) body.storage = 'local-lvm'; if (!body.bridge) body.bridge = 'vmbr0'; logger.debug('vm', 'Creating VM', { nodeName, vmId: newVmId, vmName }); const response = await apiRequest.call(this, 'POST', `/nodes/${nodeName}/qemu`, body); if (waitForTask && response.data) { const taskResult = await waitForTask.call(this, nodeName, response.data, taskTimeout); // Start VM if requested if (additionalOptions.startAfterCreate) { logger.debug('vm', 'Starting VM after creation'); await apiRequest.call(this, 'POST', `/nodes/${nodeName}/qemu/${newVmId}/status/start`); } return { ...response, taskResult }; } return response; } async function deleteVm(this: IExecuteFunctions, i: number): Promise<IDataObject> { const nodeName = this.getNodeParameter('nodeName', i) as string; const vmId = this.getNodeParameter('vmId', i) as number; const waitForTask = this.getNodeParameter('waitForTask', i, true) as boolean; const taskTimeout = this.getNodeParameter('taskTimeout', i, 300) as number; validateParameters.call(this, { nodeName, vmId }, ['nodeName', 'vmId'], 'delete VM'); logger.debug('vm', 'Deleting VM', { nodeName, vmId }); const response = await apiRequest.call(this, 'DELETE', `/nodes/${nodeName}/qemu/${vmId}`); if (waitForTask && response.data) { const taskResult = await waitForTask.call(this, nodeName, response.data, taskTimeout); return { ...response, taskResult }; } return response; } async function getVm(this: IExecuteFunctions, i: number): Promise<IDataObject> { const nodeName = this.getNodeParameter('nodeName', i) as string; const vmId = this.getNodeParameter('vmId', i) as number; validateParameters.call(this, { nodeName, vmId }, ['nodeName', 'vmId'], 'get VM'); logger.debug('vm', 'Getting VM details', { nodeName, vmId }); return await apiRequest.call(this, 'GET', `/nodes/${nodeName}/qemu/${vmId}/config`); } async function getAllVms(this: IExecuteFunctions, i: number): Promise<IDataObject[]> { console.log('=== getAllVms function start ==='); console.log('Attempting to get returnAll parameter...'); try { const returnAll = this.getNodeParameter('returnAll', i) as boolean; console.log('returnAll parameter retrieved:', returnAll); logger.debug('vm', 'Getting all VMs'); if (returnAll) { console.log('returnAll is true, making API request for all items...'); return await apiRequestAllItems.call(this, 'GET', '/cluster/resources', {}, { type: 'vm' }); } else { console.log('returnAll is false, getting limit parameter...'); const limit = this.getNodeParameter('limit', i) as number; console.log('limit parameter retrieved:', limit); console.log('Making limited API request...'); // ProxMox API doesn't support limit parameter, we'll slice the results client-side const response = await apiRequest.call(this, 'GET', '/cluster/resources', {}, { type: 'vm' }); return Array.isArray(response) ? response.slice(0, limit) : [response]; } } catch (error) { console.log('=== ERROR in getAllVms ==='); console.log('Error:', error); console.log('Error message:', error.message); console.log('Error stack:', error.stack); throw error; } } async function startVm(this: IExecuteFunctions, i: number): Promise<IDataObject> { const nodeName = this.getNodeParameter('nodeName', i) as string; const vmId = this.getNodeParameter('vmId', i) as number; const waitForTask = this.getNodeParameter('waitForTask', i, true) as boolean; const taskTimeout = this.getNodeParameter('taskTimeout', i, 300) as number; validateParameters.call(this, { nodeName, vmId }, ['nodeName', 'vmId'], 'start VM'); logger.debug('vm', 'Starting VM', { nodeName, vmId }); const response = await apiRequest.call( this, 'POST', `/nodes/${nodeName}/qemu/${vmId}/status/start`, ); if (waitForTask && response.data) { const taskResult = await waitForTask.call(this, nodeName, response.data, taskTimeout); return { ...response, taskResult }; } return response; } async function stopVm(this: IExecuteFunctions, i: number): Promise<IDataObject> { const nodeName = this.getNodeParameter('nodeName', i) as string; const vmId = this.getNodeParameter('vmId', i) as number; const waitForTask = this.getNodeParameter('waitForTask', i, true) as boolean; const taskTimeout = this.getNodeParameter('taskTimeout', i, 300) as number; const additionalOptions = this.getNodeParameter('additionalOptions', i, {}) as IDataObject; validateParameters.call(this, { nodeName, vmId }, ['nodeName', 'vmId'], 'stop VM'); const body: IDataObject = {}; if (additionalOptions.force) body.force = 1; if (additionalOptions.skipLock) body.skiplock = 1; logger.debug('vm', 'Stopping VM', { nodeName, vmId, options: additionalOptions }); const response = await apiRequest.call( this, 'POST', `/nodes/${nodeName}/qemu/${vmId}/status/stop`, body, ); if (waitForTask && response.data) { const taskResult = await waitForTask.call(this, nodeName, response.data, taskTimeout); return { ...response, taskResult }; } return response; } async function shutdownVm(this: IExecuteFunctions, i: number): Promise<IDataObject> { const nodeName = this.getNodeParameter('nodeName', i) as string; const vmId = this.getNodeParameter('vmId', i) as number; const waitForTask = this.getNodeParameter('waitForTask', i, true) as boolean; const taskTimeout = this.getNodeParameter('taskTimeout', i, 300) as number; const additionalOptions = this.getNodeParameter('additionalOptions', i, {}) as IDataObject; validateParameters.call(this, { nodeName, vmId }, ['nodeName', 'vmId'], 'shutdown VM'); const body: IDataObject = {}; if (additionalOptions.force) body.forceStop = 1; if (additionalOptions.skipLock) body.skiplock = 1; logger.debug('vm', 'Shutting down VM', { nodeName, vmId, options: additionalOptions }); const response = await apiRequest.call( this, 'POST', `/nodes/${nodeName}/qemu/${vmId}/status/shutdown`, body, ); if (waitForTask && response.data) { const taskResult = await waitForTask.call(this, nodeName, response.data, taskTimeout); return { ...response, taskResult }; } return response; } async function rebootVm(this: IExecuteFunctions, i: number): Promise<IDataObject> { const nodeName = this.getNodeParameter('nodeName', i) as string; const vmId = this.getNodeParameter('vmId', i) as number; const waitForTask = this.getNodeParameter('waitForTask', i, true) as boolean; const taskTimeout = this.getNodeParameter('taskTimeout', i, 300) as number; validateParameters.call(this, { nodeName, vmId }, ['nodeName', 'vmId'], 'reboot VM'); logger.debug('vm', 'Rebooting VM', { nodeName, vmId }); const response = await apiRequest.call( this, 'POST', `/nodes/${nodeName}/qemu/${vmId}/status/reboot`, ); if (waitForTask && response.data) { const taskResult = await waitForTask.call(this, nodeName, response.data, taskTimeout); return { ...response, taskResult }; } return response; } async function suspendVm(this: IExecuteFunctions, i: number): Promise<IDataObject> { const nodeName = this.getNodeParameter('nodeName', i) as string; const vmId = this.getNodeParameter('vmId', i) as number; const waitForTask = this.getNodeParameter('waitForTask', i, true) as boolean; const taskTimeout = this.getNodeParameter('taskTimeout', i, 300) as number; validateParameters.call(this, { nodeName, vmId }, ['nodeName', 'vmId'], 'suspend VM'); logger.debug('vm', 'Suspending VM', { nodeName, vmId }); const response = await apiRequest.call( this, 'POST', `/nodes/${nodeName}/qemu/${vmId}/status/suspend`, ); if (waitForTask && response.data) { const taskResult = await waitForTask.call(this, nodeName, response.data, taskTimeout); return { ...response, taskResult }; } return response; } async function resumeVm(this: IExecuteFunctions, i: number): Promise<IDataObject> { const nodeName = this.getNodeParameter('nodeName', i) as string; const vmId = this.getNodeParameter('vmId', i) as number; const waitForTask = this.getNodeParameter('waitForTask', i, true) as boolean; const taskTimeout = this.getNodeParameter('taskTimeout', i, 300) as number; validateParameters.call(this, { nodeName, vmId }, ['nodeName', 'vmId'], 'resume VM'); logger.debug('vm', 'Resuming VM', { nodeName, vmId }); const response = await apiRequest.call( this, 'POST', `/nodes/${nodeName}/qemu/${vmId}/status/resume`, ); if (waitForTask && response.data) { const taskResult = await waitForTask.call(this, nodeName, response.data, taskTimeout); return { ...response, taskResult }; } return response; } async function cloneVm(this: IExecuteFunctions, i: number): Promise<IDataObject> { const nodeName = this.getNodeParameter('nodeName', i) as string; const vmId = this.getNodeParameter('vmId', i) as number; const newVmId = this.getNodeParameter('newVmId', i) as number; const cloneName = this.getNodeParameter('cloneName', i, '') as string; const waitForTask = this.getNodeParameter('waitForTask', i, true) as boolean; const taskTimeout = this.getNodeParameter('taskTimeout', i, 300) as number; const additionalOptions = this.getNodeParameter('additionalOptions', i, {}) as IDataObject; validateParameters.call( this, { nodeName, vmId, newVmId }, ['nodeName', 'vmId', 'newVmId'], 'clone VM', ); const body: IDataObject = { newid: newVmId, ...additionalOptions, }; if (cloneName) { body.name = cloneName; } logger.debug('vm', 'Cloning VM', { nodeName, vmId, newVmId, cloneName }); const response = await apiRequest.call( this, 'POST', `/nodes/${nodeName}/qemu/${vmId}/clone`, body, ); if (waitForTask && response.data) { const taskResult = await waitForTask.call(this, nodeName, response.data, taskTimeout); return { ...response, taskResult }; } return response; } async function migrateVm(this: IExecuteFunctions, i: number): Promise<IDataObject> { const nodeName = this.getNodeParameter('nodeName', i) as string; const vmId = this.getNodeParameter('vmId', i) as number; const targetNode = this.getNodeParameter('targetNode', i) as string; const waitForTask = this.getNodeParameter('waitForTask', i, true) as boolean; const taskTimeout = this.getNodeParameter('taskTimeout', i, 300) as number; const additionalOptions = this.getNodeParameter('additionalOptions', i, {}) as IDataObject; validateParameters.call( this, { nodeName, vmId, targetNode }, ['nodeName', 'vmId', 'targetNode'], 'migrate VM', ); const body: IDataObject = { target: targetNode, ...additionalOptions, }; // Set default migration options if (additionalOptions.online !== false) { body.online = 1; } logger.debug('vm', 'Migrating VM', { nodeName, vmId, targetNode, options: additionalOptions }); const response = await apiRequest.call( this, 'POST', `/nodes/${nodeName}/qemu/${vmId}/migrate`, body, ); if (waitForTask && response.data) { const taskResult = await waitForTask.call(this, nodeName, response.data, taskTimeout); return { ...response, taskResult }; } return response; } async function snapshotVm(this: IExecuteFunctions, i: number): Promise<IDataObject> { const nodeName = this.getNodeParameter('nodeName', i) as string; const vmId = this.getNodeParameter('vmId', i) as number; const snapshotName = this.getNodeParameter('snapshotName', i) as string; const waitForTask = this.getNodeParameter('waitForTask', i, true) as boolean; const taskTimeout = this.getNodeParameter('taskTimeout', i, 300) as number; const additionalOptions = this.getNodeParameter('additionalOptions', i, {}) as IDataObject; validateParameters.call( this, { nodeName, vmId, snapshotName }, ['nodeName', 'vmId', 'snapshotName'], 'snapshot VM', ); const body: IDataObject = { snapname: snapshotName, ...additionalOptions, }; // Set default snapshot options if (additionalOptions.includeRam !== false) { body.vmstate = 1; } logger.debug('vm', 'Creating VM snapshot', { nodeName, vmId, snapshotName, options: additionalOptions, }); const response = await apiRequest.call( this, 'POST', `/nodes/${nodeName}/qemu/${vmId}/snapshot`, body, ); if (waitForTask && response.data) { const taskResult = await waitForTask.call(this, nodeName, response.data, taskTimeout); return { ...response, taskResult }; } return response; } async function getVmStatus(this: IExecuteFunctions, i: number): Promise<IDataObject> { const nodeName = this.getNodeParameter('nodeName', i) as string; const vmId = this.getNodeParameter('vmId', i) as number; validateParameters.call(this, { nodeName, vmId }, ['nodeName', 'vmId'], 'get VM status'); logger.debug('vm', 'Getting VM status', { nodeName, vmId }); return await apiRequest.call(this, 'GET', `/nodes/${nodeName}/qemu/${vmId}/status/current`); }