fix(FTP Node): Continue of fail looping support with paired item (#8659)

This commit is contained in:
Michael Kret 2024-02-19 18:21:32 +02:00 committed by GitHub
parent bee17dd6cc
commit 3279762221
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -18,7 +18,7 @@ import type {
INodeTypeDescription, INodeTypeDescription,
JsonObject, JsonObject,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { formatPrivateKey } from '@utils/utilities'; import { formatPrivateKey, generatePairedItemData } from '@utils/utilities';
interface ReturnFtpItem { interface ReturnFtpItem {
type: string; type: string;
@ -515,287 +515,312 @@ export class Ftp implements INodeType {
} }
let ftp: ftpClient; let ftp: ftpClient;
let sftp: sftpClient; let sftp: sftpClient;
try { try {
if (protocol === 'sftp') { try {
sftp = new sftpClient(); if (protocol === 'sftp') {
if (credentials.privateKey) { sftp = new sftpClient();
await sftp.connect({ if (credentials.privateKey) {
host: credentials.host as string, await sftp.connect({
port: credentials.port as number, host: credentials.host as string,
username: credentials.username as string, port: credentials.port as number,
password: (credentials.password as string) || undefined, username: credentials.username as string,
privateKey: formatPrivateKey(credentials.privateKey as string), password: (credentials.password as string) || undefined,
passphrase: credentials.passphrase as string | undefined, privateKey: formatPrivateKey(credentials.privateKey as string),
}); passphrase: credentials.passphrase as string | undefined,
});
} else {
await sftp.connect({
host: credentials.host as string,
port: credentials.port as number,
username: credentials.username as string,
password: credentials.password as string,
});
}
} else { } else {
await sftp.connect({ ftp = new ftpClient();
await ftp.connect({
host: credentials.host as string, host: credentials.host as string,
port: credentials.port as number, port: credentials.port as number,
username: credentials.username as string, user: credentials.username as string,
password: credentials.password as string, password: credentials.password as string,
}); });
} }
} else { } catch (error) {
ftp = new ftpClient(); if (this.continueOnFail()) {
await ftp.connect({ const pairedItem = generatePairedItemData(items.length);
host: credentials.host as string,
port: credentials.port as number, return [[{ json: { error: error.message }, pairedItem }]];
user: credentials.username as string, }
password: credentials.password as string, throw error;
});
} }
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
const newItem: INodeExecutionData = { try {
json: items[i].json, const newItem: INodeExecutionData = {
binary: {}, json: items[i].json,
pairedItem: items[i].pairedItem, binary: {},
}; pairedItem: items[i].pairedItem,
};
if (items[i].binary !== undefined && newItem.binary) { if (items[i].binary !== undefined && newItem.binary) {
// Create a shallow copy of the binary data so that the old // Create a shallow copy of the binary data so that the old
// data references which do not get changed still stay behind // data references which do not get changed still stay behind
// but the incoming data does not get changed. // but the incoming data does not get changed.
Object.assign(newItem.binary, items[i].binary); Object.assign(newItem.binary, items[i].binary);
}
items[i] = newItem;
if (protocol === 'sftp') {
if (operation === 'list') {
const path = this.getNodeParameter('path', i) as string;
const recursive = this.getNodeParameter('recursive', i) as boolean;
let responseData: sftpClient.FileInfo[];
if (recursive) {
responseData = await callRecursiveList(path, sftp!, normalizeSFtpItem);
} else {
responseData = await sftp!.list(path);
responseData.forEach((item) => normalizeSFtpItem(item, path));
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as unknown as IDataObject[]),
{ itemData: { item: i } },
);
returnItems = returnItems.concat(executionData);
} }
if (operation === 'delete') { items[i] = newItem;
const path = this.getNodeParameter('path', i) as string;
const options = this.getNodeParameter('options', i);
if (options.folder === true) { if (protocol === 'sftp') {
await sftp!.rmdir(path, !!options.recursive); if (operation === 'list') {
} else { const path = this.getNodeParameter('path', i) as string;
await sftp!.delete(path);
}
const executionData = this.helpers.constructExecutionMetaData(
[{ json: { success: true } }],
{ itemData: { item: i } },
);
returnItems = returnItems.concat(executionData);
}
if (operation === 'rename') { const recursive = this.getNodeParameter('recursive', i) as boolean;
const oldPath = this.getNodeParameter('oldPath', i) as string;
const { createDirectories = false } = this.getNodeParameter('options', i) as {
createDirectories: boolean;
};
const newPath = this.getNodeParameter('newPath', i) as string;
if (createDirectories) { let responseData: sftpClient.FileInfo[];
await recursivelyCreateSftpDirs(sftp!, newPath); if (recursive) {
} responseData = await callRecursiveList(path, sftp!, normalizeSFtpItem);
} else {
await sftp!.rename(oldPath, newPath); responseData = await sftp!.list(path);
const executionData = this.helpers.constructExecutionMetaData( responseData.forEach((item) => normalizeSFtpItem(item, path));
[{ json: { success: true } }], }
{ itemData: { item: i } },
);
returnItems = returnItems.concat(executionData);
}
if (operation === 'download') {
const path = this.getNodeParameter('path', i) as string;
const binaryFile = await tmpFile({ prefix: 'n8n-sftp-' });
try {
await sftp!.get(path, createWriteStream(binaryFile.path));
const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i);
const remoteFilePath = this.getNodeParameter('path', i) as string;
items[i].binary![dataPropertyNameDownload] = await this.nodeHelpers.copyBinaryFile(
binaryFile.path,
basename(remoteFilePath),
);
const executionData = this.helpers.constructExecutionMetaData( const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(items[i]), this.helpers.returnJsonArray(responseData as unknown as IDataObject[]),
{ itemData: { item: i } }, { itemData: { item: i } },
); );
returnItems = returnItems.concat(executionData); returnItems = returnItems.concat(executionData);
} finally {
await binaryFile.cleanup();
} }
}
if (operation === 'upload') { if (operation === 'delete') {
const remotePath = this.getNodeParameter('path', i) as string; const path = this.getNodeParameter('path', i) as string;
await recursivelyCreateSftpDirs(sftp!, remotePath); const options = this.getNodeParameter('options', i);
if (this.getNodeParameter('binaryData', i)) { if (options.folder === true) {
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i); await sftp!.rmdir(path, !!options.recursive);
const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName);
let uploadData: Buffer | Readable;
if (binaryData.id) {
uploadData = await this.helpers.getBinaryStream(binaryData.id);
} else { } else {
uploadData = Buffer.from(binaryData.data, BINARY_ENCODING); await sftp!.delete(path);
} }
await sftp!.put(uploadData, remotePath);
} else {
// Is text file
const buffer = Buffer.from(this.getNodeParameter('fileContent', i) as string, 'utf8');
await sftp!.put(buffer, remotePath);
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(items[i]),
{ itemData: { item: i } },
);
returnItems = returnItems.concat(executionData);
}
}
if (protocol === 'ftp') {
if (operation === 'list') {
const path = this.getNodeParameter('path', i) as string;
const recursive = this.getNodeParameter('recursive', i) as boolean;
let responseData;
if (recursive) {
responseData = await callRecursiveList(path, ftp!, normalizeFtpItem);
} else {
responseData = await ftp!.list(path);
responseData.forEach((item) =>
normalizeFtpItem(item as ftpClient.ListingElement, path),
);
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as unknown as IDataObject[]),
{ itemData: { item: i } },
);
returnItems = returnItems.concat(executionData);
}
if (operation === 'delete') {
const path = this.getNodeParameter('path', i) as string;
const options = this.getNodeParameter('options', i);
if (options.folder === true) {
await ftp!.rmdir(path, !!options.recursive);
} else {
await ftp!.delete(path);
}
const executionData = this.helpers.constructExecutionMetaData(
[{ json: { success: true } }],
{ itemData: { item: i } },
);
returnItems = returnItems.concat(executionData);
}
if (operation === 'download') {
const path = this.getNodeParameter('path', i) as string;
const binaryFile = await tmpFile({ prefix: 'n8n-sftp-' });
try {
const stream = await ftp!.get(path);
await pipeline(stream, createWriteStream(binaryFile.path));
const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i);
const remoteFilePath = this.getNodeParameter('path', i) as string;
items[i].binary![dataPropertyNameDownload] = await this.nodeHelpers.copyBinaryFile(
binaryFile.path,
basename(remoteFilePath),
);
const executionData = this.helpers.constructExecutionMetaData( const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(items[i]), [{ json: { success: true } }],
{ itemData: { item: i } }, { itemData: { item: i } },
); );
returnItems = returnItems.concat(executionData); returnItems = returnItems.concat(executionData);
} finally {
await binaryFile.cleanup();
} }
}
if (operation === 'rename') { if (operation === 'rename') {
const oldPath = this.getNodeParameter('oldPath', i) as string; const oldPath = this.getNodeParameter('oldPath', i) as string;
const { createDirectories = false } = this.getNodeParameter('options', i) as {
createDirectories: boolean;
};
const newPath = this.getNodeParameter('newPath', i) as string;
const newPath = this.getNodeParameter('newPath', i) as string; if (createDirectories) {
await recursivelyCreateSftpDirs(sftp!, newPath);
await ftp!.rename(oldPath, newPath);
const executionData = this.helpers.constructExecutionMetaData(
[{ json: { success: true } }],
{ itemData: { item: i } },
);
returnItems = returnItems.concat(executionData);
}
if (operation === 'upload') {
const remotePath = this.getNodeParameter('path', i) as string;
const fileName = basename(remotePath);
const dirPath = remotePath.replace(fileName, '');
if (this.getNodeParameter('binaryData', i)) {
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i);
const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName);
let uploadData: Buffer | Readable;
if (binaryData.id) {
uploadData = await this.helpers.getBinaryStream(binaryData.id);
} else {
uploadData = Buffer.from(binaryData.data, BINARY_ENCODING);
} }
await sftp!.rename(oldPath, newPath);
const executionData = this.helpers.constructExecutionMetaData(
[{ json: { success: true } }],
{ itemData: { item: i } },
);
returnItems = returnItems.concat(executionData);
}
if (operation === 'download') {
const path = this.getNodeParameter('path', i) as string;
const binaryFile = await tmpFile({ prefix: 'n8n-sftp-' });
try { try {
await ftp!.put(uploadData, remotePath); await sftp!.get(path, createWriteStream(binaryFile.path));
} catch (error) {
if (error.code === 553) { const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i);
// Create directory const remoteFilePath = this.getNodeParameter('path', i) as string;
await ftp!.mkdir(dirPath, true);
items[i].binary![dataPropertyNameDownload] = await this.nodeHelpers.copyBinaryFile(
binaryFile.path,
basename(remoteFilePath),
);
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(items[i]),
{ itemData: { item: i } },
);
returnItems = returnItems.concat(executionData);
} finally {
await binaryFile.cleanup();
}
}
if (operation === 'upload') {
const remotePath = this.getNodeParameter('path', i) as string;
await recursivelyCreateSftpDirs(sftp!, remotePath);
if (this.getNodeParameter('binaryData', i)) {
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i);
const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName);
let uploadData: Buffer | Readable;
if (binaryData.id) {
uploadData = await this.helpers.getBinaryStream(binaryData.id);
} else {
uploadData = Buffer.from(binaryData.data, BINARY_ENCODING);
}
await sftp!.put(uploadData, remotePath);
} else {
// Is text file
const buffer = Buffer.from(
this.getNodeParameter('fileContent', i) as string,
'utf8',
);
await sftp!.put(buffer, remotePath);
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(items[i]),
{ itemData: { item: i } },
);
returnItems = returnItems.concat(executionData);
}
}
if (protocol === 'ftp') {
if (operation === 'list') {
const path = this.getNodeParameter('path', i) as string;
const recursive = this.getNodeParameter('recursive', i) as boolean;
let responseData;
if (recursive) {
responseData = await callRecursiveList(path, ftp!, normalizeFtpItem);
} else {
responseData = await ftp!.list(path);
responseData.forEach((item) =>
normalizeFtpItem(item as ftpClient.ListingElement, path),
);
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as unknown as IDataObject[]),
{ itemData: { item: i } },
);
returnItems = returnItems.concat(executionData);
}
if (operation === 'delete') {
const path = this.getNodeParameter('path', i) as string;
const options = this.getNodeParameter('options', i);
if (options.folder === true) {
await ftp!.rmdir(path, !!options.recursive);
} else {
await ftp!.delete(path);
}
const executionData = this.helpers.constructExecutionMetaData(
[{ json: { success: true } }],
{ itemData: { item: i } },
);
returnItems = returnItems.concat(executionData);
}
if (operation === 'download') {
const path = this.getNodeParameter('path', i) as string;
const binaryFile = await tmpFile({ prefix: 'n8n-sftp-' });
try {
const stream = await ftp!.get(path);
await pipeline(stream, createWriteStream(binaryFile.path));
const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i);
const remoteFilePath = this.getNodeParameter('path', i) as string;
items[i].binary![dataPropertyNameDownload] = await this.nodeHelpers.copyBinaryFile(
binaryFile.path,
basename(remoteFilePath),
);
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(items[i]),
{ itemData: { item: i } },
);
returnItems = returnItems.concat(executionData);
} finally {
await binaryFile.cleanup();
}
}
if (operation === 'rename') {
const oldPath = this.getNodeParameter('oldPath', i) as string;
const newPath = this.getNodeParameter('newPath', i) as string;
await ftp!.rename(oldPath, newPath);
const executionData = this.helpers.constructExecutionMetaData(
[{ json: { success: true } }],
{ itemData: { item: i } },
);
returnItems = returnItems.concat(executionData);
}
if (operation === 'upload') {
const remotePath = this.getNodeParameter('path', i) as string;
const fileName = basename(remotePath);
const dirPath = remotePath.replace(fileName, '');
if (this.getNodeParameter('binaryData', i)) {
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i);
const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName);
let uploadData: Buffer | Readable;
if (binaryData.id) {
uploadData = await this.helpers.getBinaryStream(binaryData.id);
} else {
uploadData = Buffer.from(binaryData.data, BINARY_ENCODING);
}
try {
await ftp!.put(uploadData, remotePath); await ftp!.put(uploadData, remotePath);
} else { } catch (error) {
throw new NodeApiError(this.getNode(), error as JsonObject); if (error.code === 553) {
// Create directory
await ftp!.mkdir(dirPath, true);
await ftp!.put(uploadData, remotePath);
} else {
throw new NodeApiError(this.getNode(), error as JsonObject);
}
} }
} } else {
} else { // Is text file
// Is text file const buffer = Buffer.from(
const buffer = Buffer.from(this.getNodeParameter('fileContent', i) as string, 'utf8'); this.getNodeParameter('fileContent', i) as string,
try { 'utf8',
await ftp!.put(buffer, remotePath); );
} catch (error) { try {
if (error.code === 553) {
// Create directory
await ftp!.mkdir(dirPath, true);
await ftp!.put(buffer, remotePath); await ftp!.put(buffer, remotePath);
} else { } catch (error) {
throw new NodeApiError(this.getNode(), error as JsonObject); if (error.code === 553) {
// Create directory
await ftp!.mkdir(dirPath, true);
await ftp!.put(buffer, remotePath);
} else {
throw new NodeApiError(this.getNode(), error as JsonObject);
}
} }
} }
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(items[i]),
{ itemData: { item: i } },
);
returnItems = returnItems.concat(executionData);
} }
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(items[i]),
{ itemData: { item: i } },
);
returnItems = returnItems.concat(executionData);
} }
} catch (error) {
if (this.continueOnFail()) {
returnItems.push({ json: { error: error.message }, pairedItem: { item: i } });
continue;
}
throw error;
} }
} }
@ -810,10 +835,6 @@ export class Ftp implements INodeType {
} else { } else {
await ftp!.end(); await ftp!.end();
} }
if (this.continueOnFail()) {
return [[{ json: { error: error.message } }]];
}
throw error; throw error;
} }