refactor(core): Generalize binary data manager interface (no-changelog) (#7164)

Depends on: #7092 | Story:
[PAY-768](https://linear.app/n8n/issue/PAY-768)

This PR: 
- Generalizes the `IBinaryDataManager` interface.
- Adjusts `Filesystem.ts` to satisfy the interface.
- Sets up an S3 client stub to be filled in in the next PR.
- Turns `BinaryDataManager` into an injectable service.
- Adjusts the config schema and adds new validators.

Note that the PR looks large but all the main changes are in
`packages/core/src/binaryData`.

Out of scope:
- `BinaryDataManager` (now `BinaryDataService`) and `Filesystem.ts` (now
`fs.client.ts`) were slightly refactored for maintainability, but fully
overhauling them is **not** the focus of this PR, which is meant to
clear the way for the S3 implementation. Future improvements for these
two should include setting up a backwards-compatible dir structure that
makes it easier to locate binary data files to delete, removing
duplication, simplifying cloning methods, using integers for binary data
size instead of `prettyBytes()`, writing tests for existing binary data
logic, etc.

---------

Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
Iván Ovejero
2023-09-22 17:22:12 +02:00
committed by GitHub
parent 4614e1e1c9
commit 6d6e2488c6
46 changed files with 602 additions and 552 deletions

View File

@@ -1,5 +1,5 @@
import nock from 'nock';
import { getWorkflowFilenames, initBinaryDataManager, testWorkflows } from '@test/nodes/Helpers';
import { getWorkflowFilenames, initBinaryDataService, testWorkflows } from '@test/nodes/Helpers';
const workflows = getWorkflowFilenames(__dirname);
@@ -11,7 +11,7 @@ describe('Test S3 V1 Node', () => {
beforeAll(async () => {
jest.useFakeTimers({ doNotFake: ['nextTick'], now });
await initBinaryDataManager();
await initBinaryDataService();
nock.disableNetConnect();
mock = nock('https://bucket.s3.eu-central-1.amazonaws.com');

View File

@@ -1,5 +1,5 @@
import nock from 'nock';
import { getWorkflowFilenames, initBinaryDataManager, testWorkflows } from '@test/nodes/Helpers';
import { getWorkflowFilenames, initBinaryDataService, testWorkflows } from '@test/nodes/Helpers';
const workflows = getWorkflowFilenames(__dirname);
@@ -11,7 +11,7 @@ describe('Test S3 V2 Node', () => {
beforeAll(async () => {
jest.useFakeTimers({ doNotFake: ['nextTick'], now });
await initBinaryDataManager();
await initBinaryDataService();
nock.disableNetConnect();
mock = nock('https://bucket.s3.eu-central-1.amazonaws.com');

View File

@@ -3,7 +3,7 @@ import { NodeVM } from '@n8n/vm2';
import type { IExecuteFunctions, IWorkflowDataProxyData } from 'n8n-workflow';
import { NodeHelpers } from 'n8n-workflow';
import { normalizeItems } from 'n8n-core';
import { testWorkflows, getWorkflowFilenames, initBinaryDataManager } from '@test/nodes/Helpers';
import { testWorkflows, getWorkflowFilenames, initBinaryDataService } from '@test/nodes/Helpers';
import { Code } from '../Code.node';
import { ValidationError } from '../ValidationError';
@@ -11,7 +11,7 @@ describe('Test Code Node', () => {
const workflows = getWorkflowFilenames(__dirname);
beforeAll(async () => {
await initBinaryDataManager();
await initBinaryDataService();
});
testWorkflows(workflows);

View File

@@ -5,7 +5,7 @@ import type { IDataObject } from 'n8n-workflow';
import {
getResultNodeData,
setup,
initBinaryDataManager,
initBinaryDataService,
readJsonFileSync,
} from '@test/nodes/Helpers';
import { executeWorkflow } from '@test/nodes/ExecuteWorkflow';
@@ -16,7 +16,7 @@ import os from 'node:os';
if (os.platform() !== 'win32') {
describe('Execute Compression Node', () => {
beforeEach(async () => {
await initBinaryDataManager();
await initBinaryDataService();
});
const workflowData = readJsonFileSync('nodes/Compression/test/node/workflow.compression.json');

View File

@@ -1,7 +1,7 @@
import fs from 'fs';
import fsPromises from 'fs/promises';
import { Readable } from 'stream';
import { testWorkflows, getWorkflowFilenames, initBinaryDataManager } from '@test/nodes/Helpers';
import { testWorkflows, getWorkflowFilenames, initBinaryDataService } from '@test/nodes/Helpers';
const workflows = getWorkflowFilenames(__dirname);
@@ -13,7 +13,7 @@ describe('Test Crypto Node', () => {
fs.createReadStream = () => Readable.from(Buffer.from('test')) as fs.ReadStream;
beforeEach(async () => {
await initBinaryDataManager();
await initBinaryDataService();
});
testWorkflows(workflows);

View File

@@ -4,7 +4,7 @@ import {
equalityTest,
workflowToTests,
getWorkflowFilenames,
initBinaryDataManager,
initBinaryDataService,
} from '@test/nodes/Helpers';
describe('Test Binary Data Download', () => {
@@ -14,7 +14,7 @@ describe('Test Binary Data Download', () => {
const baseUrl = 'https://dummy.domain';
beforeAll(async () => {
await initBinaryDataManager();
await initBinaryDataService();
nock.disableNetConnect();

View File

@@ -1,4 +1,10 @@
import { setup, equalityTest, workflowToTests, getWorkflowFilenames } from '@test/nodes/Helpers';
import {
initBinaryDataService,
setup,
equalityTest,
workflowToTests,
getWorkflowFilenames,
} from '@test/nodes/Helpers';
import nock from 'nock';
@@ -8,7 +14,8 @@ describe('Test HTTP Request Node', () => {
const baseUrl = 'https://dummyjson.com';
beforeAll(() => {
beforeAll(async () => {
await initBinaryDataService();
nock.disableNetConnect();
//GET

View File

@@ -5,13 +5,13 @@ import {
getResultNodeData,
setup,
readJsonFileSync,
initBinaryDataManager,
initBinaryDataService,
} from '@test/nodes/Helpers';
import { executeWorkflow } from '@test/nodes/ExecuteWorkflow';
describe('Execute iCalendar Node', () => {
beforeEach(async () => {
await initBinaryDataManager();
await initBinaryDataService();
});
const workflowData = readJsonFileSync('nodes/ICalendar/test/node/workflow.iCalendar.json');

View File

@@ -6,7 +6,7 @@ import path from 'path';
describe('Test Move Binary Data Node', () => {
beforeEach(async () => {
await Helpers.initBinaryDataManager();
await Helpers.initBinaryDataService();
});
const workflow = Helpers.readJsonFileSync(

View File

@@ -6,7 +6,7 @@ import nock from 'nock';
describe('Test QuickChart Node', () => {
beforeEach(async () => {
await Helpers.initBinaryDataManager();
await Helpers.initBinaryDataService();
nock.disableNetConnect();
nock('https://quickchart.io')
.persist()

View File

@@ -6,7 +6,7 @@ import path from 'path';
describe('Test Read Binary File Node', () => {
beforeEach(async () => {
await Helpers.initBinaryDataManager();
await Helpers.initBinaryDataService();
});
const workflow = Helpers.readJsonFileSync(

View File

@@ -6,7 +6,7 @@ import path from 'path';
describe('Test Read Binary Files Node', () => {
beforeEach(async () => {
await Helpers.initBinaryDataManager();
await Helpers.initBinaryDataService();
});
const workflow = Helpers.readJsonFileSync(

View File

@@ -1,10 +1,10 @@
import { getWorkflowFilenames, initBinaryDataManager, testWorkflows } from '@test/nodes/Helpers';
import { getWorkflowFilenames, initBinaryDataService, testWorkflows } from '@test/nodes/Helpers';
describe('Test Read PDF Node', () => {
const workflows = getWorkflowFilenames(__dirname);
beforeAll(async () => {
await initBinaryDataManager();
await initBinaryDataService();
});
testWorkflows(workflows);

View File

@@ -6,7 +6,7 @@ import path from 'path';
describe('Execute Spreadsheet File Node', () => {
beforeEach(async () => {
await Helpers.initBinaryDataManager();
await Helpers.initBinaryDataService();
});
// replace workflow json 'Read Binary File' node's filePath to local file

View File

@@ -6,7 +6,7 @@ import path from 'path';
describe('Test Write Binary File Node', () => {
beforeEach(async () => {
await Helpers.initBinaryDataManager();
await Helpers.initBinaryDataService();
});
const temporaryDir = Helpers.createTemporaryDir();

View File

@@ -872,6 +872,7 @@
"snowflake-sdk": "^1.8.0",
"ssh2-sftp-client": "^7.0.0",
"tmp-promise": "^3.0.2",
"typedi": "^0.10.0",
"uuid": "^8.3.2",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.19.3/xlsx-0.19.3.tgz",
"xml2js": "^0.5.0"

View File

@@ -3,7 +3,8 @@ import path from 'path';
import { tmpdir } from 'os';
import { isEmpty } from 'lodash';
import { get } from 'lodash';
import { BinaryDataManager, Credentials, constructExecutionMetaData } from 'n8n-core';
import { BinaryDataService, Credentials, constructExecutionMetaData } from 'n8n-core';
import { Container } from 'typedi';
import type {
CredentialLoadingDetails,
ICredentialDataDecryptedObject,
@@ -216,14 +217,10 @@ export function createTemporaryDir(prefix = 'n8n') {
return mkdtempSync(path.join(tmpdir(), prefix));
}
export async function initBinaryDataManager(mode: 'default' | 'filesystem' = 'default') {
const temporaryDir = createTemporaryDir();
await BinaryDataManager.init({
mode,
availableModes: mode,
localStoragePath: temporaryDir,
});
return temporaryDir;
export async function initBinaryDataService(mode: 'default' | 'filesystem' = 'default') {
const binaryDataService = new BinaryDataService();
await binaryDataService.init({ mode: 'default', availableModes: [mode] });
Container.set(BinaryDataService, binaryDataService);
}
const credentialTypes = new CredentialType();