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
text/typescript
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`);
}