UNPKG

n8n-nodes-proxmox

Version:

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

240 lines (197 loc) 7.2 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 executeStorageOperation( this: IExecuteFunctions, operation: string, i: number, ): Promise<IDataObject | IDataObject[]> { try { logger.operation('storage', operation, 'start', { operation }); let response: IDataObject | IDataObject[]; switch (operation) { case 'getAll': response = await getAllStorage.call(this, i); break; case 'get': response = await getStorage.call(this, i); break; case 'getContent': response = await getStorageContent.call(this, i); break; case 'getStatus': response = await getStorageStatus.call(this, i); break; case 'upload': response = await uploadToStorage.call(this, i); break; case 'deleteFile': response = await deleteFileFromStorage.call(this, i); break; default: throw new Error(`Unknown storage operation: ${operation}`); } logger.operation('storage', operation, 'success', { operation }); return response; } catch (error) { logger.operation('storage', operation, 'error', { operation, error: error.message }); return handleProxMoxError.call(this, error, operation, 'storage'); } } async function getAllStorage(this: IExecuteFunctions, i: number): Promise<IDataObject[]> { const returnAll = this.getNodeParameter('returnAll', i) as boolean; const additionalFields = this.getNodeParameter('additionalFields', i, {}) as IDataObject; let qs: IDataObject = {}; // Add filters if (additionalFields.enabled !== undefined) { qs.enabled = additionalFields.enabled ? 1 : 0; } logger.debug('storage', 'Getting all storage configurations', { additionalFields }); if (returnAll) { return await apiRequestAllItems.call(this, 'GET', '/storage', {}, qs); } else { const limit = this.getNodeParameter('limit', i) as number; // ProxMox API doesn't support limit parameter, we'll slice the results client-side const response = await apiRequest.call(this, 'GET', '/storage', {}, qs); return Array.isArray(response) ? response.slice(0, limit) : [response]; } } async function getStorage(this: IExecuteFunctions, i: number): Promise<IDataObject> { const storageId = this.getNodeParameter('storageId', i) as string; validateParameters.call(this, { storageId }, ['storageId'], 'get storage'); logger.debug('storage', 'Getting storage configuration', { storageId }); return await apiRequest.call(this, 'GET', `/storage/${storageId}`); } async function getStorageContent(this: IExecuteFunctions, i: number): Promise<IDataObject[]> { const nodeName = this.getNodeParameter('nodeName', i) as string; const storageId = this.getNodeParameter('storageId', i) as string; const returnAll = this.getNodeParameter('returnAll', i) as boolean; const contentType = this.getNodeParameter('contentType', i, '') as string; const additionalFields = this.getNodeParameter('additionalFields', i, {}) as IDataObject; validateParameters.call( this, { nodeName, storageId }, ['nodeName', 'storageId'], 'get storage content', ); let qs: IDataObject = {}; if (contentType) { qs.content = contentType; } // Add additional filters if (additionalFields.vmid) { qs.vmid = additionalFields.vmid; } if (additionalFields.format) { qs.format = additionalFields.format; } logger.debug('storage', 'Getting storage content', { nodeName, storageId, contentType, additionalFields, }); if (returnAll) { return await apiRequestAllItems.call( this, 'GET', `/nodes/${nodeName}/storage/${storageId}/content`, {}, qs, ); } else { const limit = this.getNodeParameter('limit', i) as number; // ProxMox API doesn't support limit parameter, we'll slice the results client-side const response = await apiRequest.call( this, 'GET', `/nodes/${nodeName}/storage/${storageId}/content`, {}, qs, ); return Array.isArray(response) ? response.slice(0, limit) : [response]; } } async function getStorageStatus(this: IExecuteFunctions, i: number): Promise<IDataObject> { const nodeName = this.getNodeParameter('nodeName', i) as string; const storageId = this.getNodeParameter('storageId', i) as string; validateParameters.call( this, { nodeName, storageId }, ['nodeName', 'storageId'], 'get storage status', ); logger.debug('storage', 'Getting storage status', { nodeName, storageId }); return await apiRequest.call(this, 'GET', `/nodes/${nodeName}/storage/${storageId}/status`); } async function uploadToStorage(this: IExecuteFunctions, i: number): Promise<IDataObject> { const nodeName = this.getNodeParameter('nodeName', i) as string; const storageId = this.getNodeParameter('storageId', i) as string; const fileName = this.getNodeParameter('fileName', i) as string; const fileContent = this.getNodeParameter('fileContent', i) as string; const uploadOptions = this.getNodeParameter('uploadOptions', i, {}) as IDataObject; validateParameters.call( this, { nodeName, storageId, fileName, fileContent }, ['nodeName', 'storageId', 'fileName', 'fileContent'], 'upload to storage', ); // Decode base64 content let decodedContent: Buffer; try { decodedContent = Buffer.from(fileContent, 'base64'); } catch (error) { throw new Error(`Invalid base64 file content: ${error.message}`); } const body: IDataObject = { filename: fileName, content: uploadOptions.content || 'iso', ...uploadOptions, }; // Handle checksum validation if provided if (uploadOptions.checksum) { const [algorithm, hash] = (uploadOptions.checksum as string).split(':'); if (algorithm && hash) { body.checksum = hash; body['checksum-algorithm'] = algorithm; } } logger.debug('storage', 'Uploading file to storage', { nodeName, storageId, fileName, size: decodedContent.length, }); // Note: This is a simplified implementation. In a real scenario, you would need to handle // multipart form data upload with the actual file content. The ProxMox API expects // a multipart/form-data request with the file as a binary attachment. return await apiRequest.call(this, 'POST', `/nodes/${nodeName}/storage/${storageId}/upload`, { ...body, content: decodedContent.toString('base64'), }); } async function deleteFileFromStorage(this: IExecuteFunctions, i: number): Promise<IDataObject> { const nodeName = this.getNodeParameter('nodeName', i) as string; const storageId = this.getNodeParameter('storageId', i) as string; const filePath = this.getNodeParameter('filePath', i) as string; validateParameters.call( this, { nodeName, storageId, filePath }, ['nodeName', 'storageId', 'filePath'], 'delete file from storage', ); // The volume ID format is typically storage:path const volumeId = `${storageId}:${filePath}`; logger.debug('storage', 'Deleting file from storage', { nodeName, storageId, filePath, volumeId, }); return await apiRequest.call( this, 'DELETE', `/nodes/${nodeName}/storage/${storageId}/content/${encodeURIComponent(volumeId)}`, ); }