n8n-nodes-proxmox
Version:
n8n community node for Proxmox Virtual Environment (VE) API integration with VM, container, storage, and cluster management capabilities
191 lines (164 loc) • 5.26 kB
text/typescript
import { IExecuteFunctions, NodeOperationError, NodeApiError } from 'n8n-workflow';
/**
* Handle ProxMox API errors with context-specific messages
*/
export function handleProxMoxError(
this: IExecuteFunctions,
error: any,
operation: string,
resource: string,
): never {
let message = `Failed to ${operation} ${resource}`;
if (error instanceof NodeApiError || error instanceof NodeOperationError) {
throw error;
}
// Handle specific ProxMox error cases
if (error.response) {
const statusCode = error.statusCode || error.response.status;
const errorBody = error.error || error.response.body || error.response.data;
switch (statusCode) {
case 400:
message = `Bad request: Invalid parameters for ${operation} ${resource}`;
if (errorBody?.errors) {
const errors = Array.isArray(errorBody.errors)
? errorBody.errors.join(', ')
: JSON.stringify(errorBody.errors);
message += ` - ${errors}`;
}
break;
case 401:
message = 'Authentication failed. Please check your ProxMox credentials.';
break;
case 403:
message = `Permission denied. User lacks privileges to ${operation} ${resource}.`;
break;
case 404:
message = `${resource} not found. Please check the resource ID or name.`;
break;
case 409:
message = `Conflict: ${resource} already exists or is in an incompatible state.`;
break;
case 422:
message = `Validation error for ${operation} ${resource}`;
if (errorBody?.errors) {
const errors = Array.isArray(errorBody.errors)
? errorBody.errors.join(', ')
: JSON.stringify(errorBody.errors);
message += ` - ${errors}`;
}
break;
case 500:
message = `ProxMox server error during ${operation} ${resource}. Please try again later.`;
break;
case 503:
message = `ProxMox service unavailable. The node may be busy or under maintenance.`;
break;
default:
if (errorBody) {
if (typeof errorBody === 'object') {
if (errorBody.message) {
message = errorBody.message;
} else if (errorBody.reason) {
message = errorBody.reason;
} else if (errorBody.errors) {
const errors = Array.isArray(errorBody.errors)
? errorBody.errors.join(', ')
: JSON.stringify(errorBody.errors);
message = `Error ${operation} ${resource}: ${errors}`;
}
} else if (typeof errorBody === 'string') {
message = errorBody;
}
}
}
} else if (error.message) {
message = error.message;
}
throw new NodeOperationError(this.getNode(), message);
}
/**
* Validate required parameters for ProxMox operations
*/
export function validateParameters(
this: IExecuteFunctions,
parameters: { [key: string]: any },
required: string[],
operation: string,
): void {
const missing = required.filter((param) => !parameters[param]);
if (missing.length > 0) {
throw new NodeOperationError(
this.getNode(),
`Missing required parameters for ${operation}: ${missing.join(', ')}`,
);
}
}
/**
* Validate ProxMox node name format
*/
export function validateNodeName(nodeName: string): boolean {
// ProxMox node names should be valid hostnames
const nodeNameRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/;
return nodeNameRegex.test(nodeName) && nodeName.length <= 63;
}
/**
* Validate ProxMox VM/Container ID format
*/
export function validateVmId(vmId: string | number): boolean {
const id = typeof vmId === 'string' ? parseInt(vmId, 10) : vmId;
return !isNaN(id) && id >= 100 && id <= 999999999;
}
/**
* Validate ProxMox storage name format
*/
export function validateStorageName(storageName: string): boolean {
// Storage names in ProxMox follow specific naming conventions
const storageNameRegex = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
return storageNameRegex.test(storageName) && storageName.length <= 64;
}
/**
* Parse and validate ProxMox resource ID from various formats
*/
export function parseResourceId(resourceId: string): {
node: string;
type?: string;
vmid?: number;
storage?: string;
} {
// Handle different ProxMox resource ID formats:
// - node/pve (node reference)
// - node/qemu/vmid (VM reference)
// - node/lxc/vmid (Container reference)
// - node/storage/storagename (Storage reference)
const parts = resourceId.split('/');
const result: { node: string; type?: string; vmid?: number; storage?: string } = {
node: parts[0],
};
if (parts.length >= 2) {
result.type = parts[1];
if (parts.length >= 3) {
if (parts[1] === 'qemu' || parts[1] === 'lxc') {
const vmid = parseInt(parts[2], 10);
if (isNaN(vmid)) {
throw new Error(`Invalid VM/Container ID: ${parts[2]}`);
}
result.vmid = vmid;
} else if (parts[1] === 'storage') {
result.storage = parts[2];
}
}
}
// Validate node name
if (!validateNodeName(result.node)) {
throw new Error(`Invalid node name: ${result.node}`);
}
// Validate VM ID if present
if (result.vmid !== undefined && !validateVmId(result.vmid)) {
throw new Error(`Invalid VM/Container ID: ${result.vmid}`);
}
// Validate storage name if present
if (result.storage !== undefined && !validateStorageName(result.storage)) {
throw new Error(`Invalid storage name: ${result.storage}`);
}
return result;
}