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
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 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)}`,
);
}