Skip to content

Commit e6ae872

Browse files
authored
51 24 attachment in body and bigger files (#60)
1 parent 5dc2f98 commit e6ae872

17 files changed

+6844
-1787
lines changed

.circleci/config.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ workflows:
118118
jobs:
119119
- build:
120120
name: "Build and publish docker image"
121+
context:
122+
- componentspusher
121123
filters:
122124
branches:
123125
ignore: /.*/

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
node_modules
22
coverage
33
.idea
4+
.vscode

.grype-ignore.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
ignore:
2+
- vulnerability: CVE-2023-2650
3+
package:
4+
name: libssl3
5+
version: 3.1.0-r4
6+
7+
- vulnerability: CVE-2023-2650
8+
package:
9+
name: libcrypto3
10+
version: 3.1.0-r4

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 1.4.0 (June 09, 2023)
2+
* Implemented support `attachments` inside message body for `XML Attachment to JSON` action
3+
* Updated Sailor version to 2.7.1
4+
* Removed old dependencies
5+
16
## 1.3.7 (September 12, 2022)
27

38
* Deleted buildType from component.json to fix component build

README.md

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,12 @@ is equivalent to
3838
```
3939

4040
#### Environment variables
41-
* `MAX_FILE_SIZE`: *optional* - Controls the maximum size of an attachment to be written in MB.
42-
Defaults to 10 MB where 1 MB = 1024 * 1024 bytes.
41+
* `MAX_FILE_SIZE`: *optional* - Controls the maximum size of an attachment to be read or written in MB.
42+
43+
Defaults to 10 MB where 1 MB = 1024 * 1024 bytes.
44+
* `EIO_REQUIRED_RAM_MB`: *optional* - You can increase memory usage limit for component if you going to work with big files
45+
46+
Defaults to 256 MB where 1 MB = 1024 * 1024 bytes.
4347

4448
## Actions
4549

@@ -74,9 +78,41 @@ will be converted into:
7478
```
7579

7680
### XML Attachment to JSON
77-
Looks at the JSON array of attachments passed in to component and converts all XML that it finds to generic JSON objects
78-
and produces one outbound message per matching attachment. As input, the user can enter a patter pattern for filtering
79-
files by name or leave this field empty for processing all incoming *.xml files.
81+
#### Configuration Fields
82+
83+
* **Pattern** - (string, optional): RegEx for filtering files by name provided via old attachment mechanism (outside message body)
84+
* **Upload single file** - (checkbox, optional): Use this option if you want to upload a single file
85+
86+
#### Input Metadata
87+
If `Upload single file` checked, there will be 2 fields:
88+
* **URL** - (string, required): link to file on Internet or platform
89+
90+
If `Upload single file` unchecked:
91+
* **Attachments** - (array, required): Collection of files to upload, each record contains object with two keys:
92+
* **URL** - (string, required): link to file on Internet or platform
93+
94+
If you going to use this option with static data, you need to switch to Developer mode
95+
<details><summary>Sample</summary>
96+
<p>
97+
98+
```json
99+
{
100+
"attachments": [
101+
{
102+
"url": "https://example.com/files/file1.xml"
103+
},
104+
{
105+
"url": "https://example.com/files/file2.xml"
106+
}
107+
]
108+
}
109+
```
110+
</p>
111+
</details>
112+
113+
#### Output Metadata
114+
115+
Resulting JSON object
80116

81117
### JSON to XML
82118
Provides an input where a user provides a JSONata expression that should evaluate to an object to convert to JSON.
@@ -104,12 +140,10 @@ The incoming message should have a single field `input`. When using integrator m
104140
```
105141

106142
## Known limitations
107-
- The maximum size of incoming file for processing is 5 MiB. If the size of incoming file will be more than 5 MiB,
108-
action will throw error `Attachment *.xml is to large to be processed by XML component. File limit is: 5242880 byte,
109-
file given was: * byte.`.
110143
- All actions involving attachments are not supported on local agents due to current platform limitations.
111144
- When creating XML files with invalid XML tags, the name of the potentially invalid tag will not be reported.
112-
145+
- When you try to retrieve sample in `XML Attachment to JSON` action and it's size is more then 500Kb, there will be generated new smaller sample with same structure as original
146+
113147
## Additional Info
114148
Icon made by Freepik from www.flaticon.com
115149

component.json

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"title": "XML",
3-
"version": "1.3.7",
3+
"version": "1.4.0",
44
"description": "Component to convert between XML and JSON data",
55
"actions": {
66
"xmlToJson": {
@@ -97,12 +97,18 @@
9797
"required": false,
9898
"viewClass": "TextFieldView",
9999
"placeholder": "Pattern"
100+
},
101+
"uploadSingleFile": {
102+
"label": "Upload single file",
103+
"viewClass": "CheckBoxView",
104+
"required": false,
105+
"order": 70,
106+
"help": {
107+
"description": "Use this option if you want to upload a single file"
108+
}
100109
}
101110
},
102-
"metadata": {
103-
"in": {},
104-
"out": "./lib/schemas/attachmentToJson.out.json"
105-
}
111+
"dynamicMetadata": true
106112
}
107113
}
108114
}

lib/actions/attachmentToJson.js

Lines changed: 69 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
1-
/* eslint-disable no-await-in-loop */
2-
const sizeof = require('object-sizeof');
1+
/* eslint-disable no-await-in-loop, no-restricted-syntax, max-len, no-unused-vars, no-loop-func */
32
const { AttachmentProcessor } = require('@elastic.io/component-commons-library');
4-
const { messages } = require('elasticio-node');
5-
const { getUserAgent } = require('../utils');
6-
3+
const { createSchema } = require('genson-js');
4+
const jsf = require('json-schema-faker');
5+
const sizeof = require('object-sizeof');
6+
const { newMessageWithBody } = require('elasticio-node/lib/messages');
7+
const { readFile, writeFile, stat } = require('fs/promises');
8+
const {
9+
getUserAgent,
10+
MAX_FILE_SIZE,
11+
MAX_FILE_SIZE_FOR_SAMPLE,
12+
memUsage,
13+
} = require('../utils');
14+
const attachmentToJsonIn = require('../schemas/attachmentToJson.in.json');
715
const xml2Json = require('../xml2Json');
816

9-
const MAX_FILE_SIZE = 5242880; // 5 MiB
17+
const isDebugFlow = process.env.ELASTICIO_FLOW_TYPE === 'debug';
18+
const tempFile = '/tmp/data.json';
1019

1120
function checkFileName(self, fileName, pattern) {
1221
if (fileName === undefined) {
@@ -26,61 +35,66 @@ function checkFileName(self, fileName, pattern) {
2635
return true;
2736
}
2837

38+
const tooLargeErrMsg = (fileName, fileSize) => `Attachment ${fileName} is too large to be processed by XML component. `
39+
+ `File limit is: ${MAX_FILE_SIZE} byte, file given was: ${fileSize} byte.`;
40+
2941
module.exports.process = async function processAction(msg, cfg) {
3042
const self = this;
31-
const { attachments } = msg;
32-
const pattern = new RegExp(cfg !== undefined ? cfg.pattern || '(.xml)' : '(.xml)');
33-
let foundXML = false;
43+
const { attachments, body = {} } = msg;
44+
const { pattern = '(.xml)', uploadSingleFile } = cfg || {};
45+
const files = [];
46+
if (uploadSingleFile) {
47+
files.push(msg.body);
48+
} else if (body.attachments && body.attachments.length > 0) {
49+
files.push(...(body.attachments || []));
50+
} else if (Object.keys(attachments || {}).length > 0) {
51+
const filteredFiles = Object.keys(attachments)
52+
.map((key) => ({ fileName: key, ...attachments[key] }))
53+
.filter((file) => checkFileName(self, file.fileName, new RegExp(pattern)));
54+
const tooLarge = filteredFiles.find((file) => file.size && file.size > MAX_FILE_SIZE);
55+
if (tooLarge) throw new Error(tooLargeErrMsg(tooLarge.fileName, tooLarge.size));
56+
files.push(...filteredFiles);
57+
}
3458

35-
self.logger.info('Attachment to XML started');
36-
self.logger.info('Found %s attachments', Object.keys(attachments || {}).length);
59+
self.logger.info(`Attachment to XML started\nFound ${files.length} attachments`);
3760

3861
const attachmentProcessor = new AttachmentProcessor(getUserAgent(), msg.id);
39-
// eslint-disable-next-line no-restricted-syntax
40-
for (const key of Object.keys(attachments)) {
41-
const attachment = attachments[key];
42-
const fileName = key;
43-
// get file size based attachment object may not be define or be accurate
44-
let fileSize = attachment.size;
62+
for (const file of files) {
4563
self.logger.info('Processing attachment');
46-
47-
if (checkFileName(self, fileName, pattern)) {
48-
if (fileSize === undefined || fileSize < MAX_FILE_SIZE) {
49-
// eslint-disable-next-line no-await-in-loop
50-
const response = await attachmentProcessor.getAttachment(attachment.url, 'arraybuffer');
51-
52-
this.logger.debug(`For provided filename response status: ${response.status}`);
53-
54-
if (response.status >= 400) {
55-
throw new Error(`Error in making request to ${attachment.url}
56-
Status code: ${response.status},
57-
Body: ${Buffer.from(response.data, 'binary').toString('base64')}`);
58-
}
59-
60-
const responseBodyString = Buffer.from(response.data, 'binary').toString('utf-8');
61-
62-
if (!responseBodyString) {
63-
throw new Error(`Empty attachment received for file ${fileName}`);
64-
}
65-
66-
fileSize = sizeof(responseBodyString);
67-
68-
if (fileSize < MAX_FILE_SIZE) {
69-
const returnMsg = await xml2Json.process(this, responseBodyString);
70-
this.logger.debug('Attachment to XML finished');
71-
foundXML = true;
72-
await self.emit('data', messages.newMessageWithBody(returnMsg.body));
73-
} else {
74-
throw new Error(`Attachment ${key} is too large to be processed my XML component.`
75-
+ ` File limit is: ${MAX_FILE_SIZE} byte, file given was: ${fileSize} byte.`);
76-
}
77-
} else {
78-
throw new Error(`Attachment ${key} is too large to be processed my XML component.`
79-
+ ` File limit is: ${MAX_FILE_SIZE} byte, file given was: ${fileSize} byte.`);
80-
}
64+
let response = await attachmentProcessor.getAttachment(file.url, 'text');
65+
this.logger.debug(`For provided filename response status: ${response.status}`);
66+
let responseBodyString = response.data;
67+
if (response.status >= 400) {
68+
throw new Error(`Error in making request to ${file.url} Status code: ${response.status}, Body: ${responseBodyString}`);
8169
}
82-
}
83-
if (!foundXML) {
84-
self.logger.info('No XML files that match the pattern found within attachments');
70+
if (!responseBodyString) {
71+
throw new Error(`Empty attachment received for file ${file.fileName || ''}`);
72+
}
73+
const fileSize = response.headers['content-length'];
74+
response = null;
75+
if (Number(fileSize) > MAX_FILE_SIZE) throw new Error(tooLargeErrMsg(file.fileName || '', fileSize));
76+
let { body: json } = await xml2Json.process(this, responseBodyString);
77+
responseBodyString = null;
78+
await writeFile(tempFile, JSON.stringify(json));
79+
if ((await stat(tempFile)).size > MAX_FILE_SIZE_FOR_SAMPLE && isDebugFlow) {
80+
this.logger.warn('The message size exceeded the sample size limit. To match the limitation we will generate a smaller sample using the structure/schema from the original file.');
81+
const schema = createSchema(json);
82+
jsf.option({
83+
alwaysFakeOptionals: true,
84+
fillProperties: false,
85+
});
86+
json = jsf.generate(schema);
87+
}
88+
this.logger.debug(`Attachment to XML finished, emitting message. ${memUsage()}`);
89+
await self.emit('data', newMessageWithBody(json));
8590
}
8691
};
92+
93+
async function getMetaModel(cfg) {
94+
return {
95+
in: cfg.uploadSingleFile ? attachmentToJsonIn.properties.attachments.items : attachmentToJsonIn,
96+
out: {},
97+
};
98+
}
99+
100+
module.exports.getMetaModel = getMetaModel;

lib/actions/jsonToXml.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1+
/* eslint-disable no-param-reassign */
12
const { AttachmentProcessor } = require('@elastic.io/component-commons-library');
23
const { messages } = require('elasticio-node');
34
const xml2js = require('xml2js');
45
const _ = require('lodash');
5-
const { getUserAgent } = require('../utils');
6-
7-
const MB_TO_BYTES = 1024 * 1024;
8-
const MAX_FILE_SIZE = process.env.MAX_FILE_SIZE * MB_TO_BYTES || 10 * MB_TO_BYTES;
6+
const { Readable } = require('stream');
7+
const { getUserAgent, MAX_FILE_SIZE } = require('../utils');
98

109
module.exports.process = async function process(msg, cfg) {
1110
const { input } = msg.body;
11+
const msgid = msg.id;
1212
const { uploadToAttachment, excludeXmlHeader, headerStandalone } = cfg;
1313

1414
this.logger.info('Message received.');
@@ -34,8 +34,8 @@ module.exports.process = async function process(msg, cfg) {
3434
throw new Error('Input must be an object with exactly one key.');
3535
}
3636

37-
const xml2String = () => builder.buildObject(input);
38-
const xmlString = xml2String();
37+
const xmlString = builder.buildObject(input);
38+
msg = null;
3939

4040
if (!uploadToAttachment) {
4141
this.logger.info('Sending XML data in message.');
@@ -50,9 +50,10 @@ module.exports.process = async function process(msg, cfg) {
5050
throw new Error(`XML data is ${attachmentSize} bytes, and is too large to upload as an attachment. Max attachment size is ${MAX_FILE_SIZE} bytes`);
5151
}
5252
this.logger.info(`Will create XML attachment of size ${attachmentSize} byte(s)`);
53+
const getAttachment = async () => Readable.from([xmlString]);
5354

54-
const attachmentProcessor = new AttachmentProcessor(getUserAgent(), msg.id);
55-
const createdAttachmentId = await attachmentProcessor.uploadAttachment(xml2String);
55+
const attachmentProcessor = new AttachmentProcessor(getUserAgent(), msgid);
56+
const createdAttachmentId = await attachmentProcessor.uploadAttachment(getAttachment);
5657
const attachmentUrl = attachmentProcessor.getMaesterAttachmentUrlById(createdAttachmentId);
5758
this.logger.info('Attachment created successfully');
5859

lib/actions/parse.js

Lines changed: 0 additions & 67 deletions
This file was deleted.

0 commit comments

Comments
 (0)