first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.awsS3",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Development", "Data & Storage"],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/credentials/aws/"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.awss3/"
|
||||
}
|
||||
],
|
||||
"generic": [
|
||||
{
|
||||
"label": "Why business process automation with n8n can change your daily life",
|
||||
"icon": "🧬",
|
||||
"url": "https://n8n.io/blog/why-business-process-automation-with-n8n-can-change-your-daily-life/"
|
||||
},
|
||||
{
|
||||
"label": "7 no-code workflow automations for Amazon Web Services",
|
||||
"url": "https://n8n.io/blog/aws-workflow-automation/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { INodeTypeBaseDescription, IVersionedNodeType } from 'n8n-workflow';
|
||||
import { VersionedNodeType } from 'n8n-workflow';
|
||||
|
||||
import { AwsS3V1 } from './V1/AwsS3V1.node';
|
||||
import { AwsS3V2 } from './V2/AwsS3V2.node';
|
||||
|
||||
export class AwsS3 extends VersionedNodeType {
|
||||
constructor() {
|
||||
const baseDescription: INodeTypeBaseDescription = {
|
||||
displayName: 'AwsS3',
|
||||
name: 'awsS3',
|
||||
icon: 'file:s3.svg',
|
||||
group: ['output'],
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Sends data to AWS S3',
|
||||
defaultVersion: 2,
|
||||
schemaPath: 'Aws/S3',
|
||||
};
|
||||
|
||||
const nodeVersions: IVersionedNodeType['nodeVersions'] = {
|
||||
1: new AwsS3V1(baseDescription),
|
||||
2: new AwsS3V2(baseDescription),
|
||||
};
|
||||
|
||||
super(nodeVersions, baseDescription);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,908 @@
|
||||
import { paramCase, snakeCase } from 'change-case';
|
||||
import { createHash } from 'crypto';
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeBaseDescription,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
|
||||
import { Builder } from 'xml2js';
|
||||
|
||||
import { bucketFields, bucketOperations } from './BucketDescription';
|
||||
import { fileFields, fileOperations } from './FileDescription';
|
||||
import { folderFields, folderOperations } from './FolderDescription';
|
||||
import {
|
||||
awsApiRequestREST,
|
||||
awsApiRequestSOAP,
|
||||
awsApiRequestSOAPAllItems,
|
||||
} from './GenericFunctions';
|
||||
|
||||
export class AwsS3V1 implements INodeType {
|
||||
description: INodeTypeDescription;
|
||||
|
||||
constructor(baseDescription: INodeTypeBaseDescription) {
|
||||
this.description = {
|
||||
...baseDescription,
|
||||
displayName: 'AWS S3',
|
||||
name: 'awsS3',
|
||||
icon: 'file:s3.svg',
|
||||
group: ['output'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Sends data to AWS S3',
|
||||
defaults: {
|
||||
name: 'AWS S3',
|
||||
},
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
credentials: [
|
||||
{
|
||||
name: 'aws',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Bucket',
|
||||
value: 'bucket',
|
||||
},
|
||||
{
|
||||
name: 'File',
|
||||
value: 'file',
|
||||
},
|
||||
{
|
||||
name: 'Folder',
|
||||
value: 'folder',
|
||||
},
|
||||
],
|
||||
default: 'file',
|
||||
},
|
||||
// BUCKET
|
||||
...bucketOperations,
|
||||
...bucketFields,
|
||||
// FOLDER
|
||||
...folderOperations,
|
||||
...folderFields,
|
||||
// UPLOAD
|
||||
...fileOperations,
|
||||
...fileFields,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
const qs: IDataObject = {};
|
||||
let responseData;
|
||||
const resource = this.getNodeParameter('resource', 0);
|
||||
const operation = this.getNodeParameter('operation', 0);
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const headers: IDataObject = {};
|
||||
try {
|
||||
if (resource === 'bucket') {
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateBucket.html
|
||||
if (operation === 'create') {
|
||||
const credentials = await this.getCredentials('aws');
|
||||
const name = this.getNodeParameter('name', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i);
|
||||
if (additionalFields.acl) {
|
||||
headers['x-amz-acl'] = paramCase(additionalFields.acl as string);
|
||||
}
|
||||
if (additionalFields.bucketObjectLockEnabled) {
|
||||
headers['x-amz-bucket-object-lock-enabled'] =
|
||||
additionalFields.bucketObjectLockEnabled as boolean;
|
||||
}
|
||||
if (additionalFields.grantFullControl) {
|
||||
headers['x-amz-grant-full-control'] = '';
|
||||
}
|
||||
if (additionalFields.grantRead) {
|
||||
headers['x-amz-grant-read'] = '';
|
||||
}
|
||||
if (additionalFields.grantReadAcp) {
|
||||
headers['x-amz-grant-read-acp'] = '';
|
||||
}
|
||||
if (additionalFields.grantWrite) {
|
||||
headers['x-amz-grant-write'] = '';
|
||||
}
|
||||
if (additionalFields.grantWriteAcp) {
|
||||
headers['x-amz-grant-write-acp'] = '';
|
||||
}
|
||||
let region = credentials.region as string;
|
||||
|
||||
if (additionalFields.region) {
|
||||
region = additionalFields.region as string;
|
||||
}
|
||||
|
||||
const body: IDataObject = {
|
||||
CreateBucketConfiguration: {
|
||||
$: {
|
||||
xmlns: 'http://s3.amazonaws.com/doc/2006-03-01/',
|
||||
},
|
||||
},
|
||||
};
|
||||
let data = '';
|
||||
// if credentials has the S3 defaul region (us-east-1) the body (XML) does not have to be sent.
|
||||
if (region !== 'us-east-1') {
|
||||
// @ts-ignore
|
||||
body.CreateBucketConfiguration.LocationConstraint = [region];
|
||||
const builder = new Builder();
|
||||
data = builder.buildObject(body);
|
||||
}
|
||||
responseData = await awsApiRequestSOAP.call(
|
||||
this,
|
||||
`${name}.s3`,
|
||||
'PUT',
|
||||
'',
|
||||
data,
|
||||
qs,
|
||||
headers,
|
||||
);
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ success: true }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucket.html
|
||||
if (operation === 'delete') {
|
||||
const name = this.getNodeParameter('name', i) as string;
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(
|
||||
this,
|
||||
`${name}.s3`,
|
||||
'DELETE',
|
||||
'',
|
||||
'',
|
||||
{},
|
||||
headers,
|
||||
);
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ success: true }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListBuckets.html
|
||||
if (operation === 'getAll') {
|
||||
const returnAll = this.getNodeParameter('returnAll', 0);
|
||||
if (returnAll) {
|
||||
responseData = await awsApiRequestSOAPAllItems.call(
|
||||
this,
|
||||
'ListAllMyBucketsResult.Buckets.Bucket',
|
||||
's3',
|
||||
'GET',
|
||||
'',
|
||||
);
|
||||
} else {
|
||||
qs.limit = this.getNodeParameter('limit', 0);
|
||||
responseData = await awsApiRequestSOAPAllItems.call(
|
||||
this,
|
||||
'ListAllMyBucketsResult.Buckets.Bucket',
|
||||
's3',
|
||||
'GET',
|
||||
'',
|
||||
'',
|
||||
qs,
|
||||
);
|
||||
responseData = responseData.slice(0, qs.limit);
|
||||
}
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject[]),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html
|
||||
if (operation === 'search') {
|
||||
const bucketName = this.getNodeParameter('bucketName', i) as string;
|
||||
const returnAll = this.getNodeParameter('returnAll', 0);
|
||||
const additionalFields = this.getNodeParameter('additionalFields', 0);
|
||||
|
||||
if (additionalFields.prefix) {
|
||||
qs.prefix = additionalFields.prefix as string;
|
||||
}
|
||||
|
||||
if (additionalFields.encodingType) {
|
||||
qs['encoding-type'] = additionalFields.encodingType as string;
|
||||
}
|
||||
|
||||
if (additionalFields.delimiter) {
|
||||
qs.delimiter = additionalFields.delimiter as string;
|
||||
}
|
||||
|
||||
if (additionalFields.fetchOwner) {
|
||||
qs['fetch-owner'] = additionalFields.fetchOwner as string;
|
||||
}
|
||||
|
||||
if (additionalFields.startAfter) {
|
||||
qs['start-after'] = additionalFields.startAfter as string;
|
||||
}
|
||||
|
||||
if (additionalFields.requesterPays) {
|
||||
qs['x-amz-request-payer'] = 'requester';
|
||||
}
|
||||
|
||||
qs['list-type'] = 2;
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'GET', '', '', {
|
||||
location: '',
|
||||
});
|
||||
|
||||
const region = responseData.LocationConstraint._ as string;
|
||||
|
||||
if (returnAll) {
|
||||
responseData = await awsApiRequestSOAPAllItems.call(
|
||||
this,
|
||||
'ListBucketResult.Contents',
|
||||
`${bucketName}.s3`,
|
||||
'GET',
|
||||
'',
|
||||
'',
|
||||
qs,
|
||||
{},
|
||||
{},
|
||||
region,
|
||||
);
|
||||
} else {
|
||||
qs['max-keys'] = this.getNodeParameter('limit', 0);
|
||||
responseData = await awsApiRequestSOAP.call(
|
||||
this,
|
||||
`${bucketName}.s3`,
|
||||
'GET',
|
||||
'',
|
||||
'',
|
||||
qs,
|
||||
{},
|
||||
{},
|
||||
region,
|
||||
);
|
||||
responseData = responseData.ListBucketResult.Contents;
|
||||
}
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject[]),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
}
|
||||
if (resource === 'folder') {
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html
|
||||
if (operation === 'create') {
|
||||
const bucketName = this.getNodeParameter('bucketName', i) as string;
|
||||
const folderName = this.getNodeParameter('folderName', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i);
|
||||
let path = `/${folderName}/`;
|
||||
|
||||
if (additionalFields.requesterPays) {
|
||||
headers['x-amz-request-payer'] = 'requester';
|
||||
}
|
||||
if (additionalFields.parentFolderKey) {
|
||||
path = `/${additionalFields.parentFolderKey}${folderName}/`;
|
||||
}
|
||||
if (additionalFields.storageClass) {
|
||||
headers['x-amz-storage-class'] = snakeCase(
|
||||
additionalFields.storageClass as string,
|
||||
).toUpperCase();
|
||||
}
|
||||
responseData = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'GET', '', '', {
|
||||
location: '',
|
||||
});
|
||||
|
||||
const region = responseData.LocationConstraint._;
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(
|
||||
this,
|
||||
`${bucketName}.s3`,
|
||||
'PUT',
|
||||
path,
|
||||
'',
|
||||
qs,
|
||||
headers,
|
||||
{},
|
||||
region as string,
|
||||
);
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ success: true }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjects.html
|
||||
if (operation === 'delete') {
|
||||
const bucketName = this.getNodeParameter('bucketName', i) as string;
|
||||
const folderKey = this.getNodeParameter('folderKey', i) as string;
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'GET', '', '', {
|
||||
location: '',
|
||||
});
|
||||
|
||||
const region = responseData.LocationConstraint._;
|
||||
|
||||
responseData = await awsApiRequestSOAPAllItems.call(
|
||||
this,
|
||||
'ListBucketResult.Contents',
|
||||
`${bucketName}.s3`,
|
||||
'GET',
|
||||
'/',
|
||||
'',
|
||||
{ 'list-type': 2, prefix: folderKey },
|
||||
{},
|
||||
{},
|
||||
region as string,
|
||||
);
|
||||
|
||||
// folder empty then just delete it
|
||||
if (responseData.length === 0) {
|
||||
responseData = await awsApiRequestSOAP.call(
|
||||
this,
|
||||
`${bucketName}.s3`,
|
||||
'DELETE',
|
||||
`/${folderKey}`,
|
||||
'',
|
||||
qs,
|
||||
{},
|
||||
{},
|
||||
region as string,
|
||||
);
|
||||
|
||||
responseData = { deleted: [{ Key: folderKey }] };
|
||||
} else {
|
||||
// delete everything inside the folder
|
||||
const body: IDataObject = {
|
||||
Delete: {
|
||||
$: {
|
||||
xmlns: 'http://s3.amazonaws.com/doc/2006-03-01/',
|
||||
},
|
||||
Object: [],
|
||||
},
|
||||
};
|
||||
|
||||
for (const childObject of responseData) {
|
||||
//@ts-ignore
|
||||
(body.Delete.Object as IDataObject[]).push({
|
||||
Key: childObject.Key as string,
|
||||
});
|
||||
}
|
||||
|
||||
const builder = new Builder();
|
||||
const data = builder.buildObject(body);
|
||||
|
||||
headers['Content-MD5'] = createHash('md5').update(data).digest('base64');
|
||||
|
||||
headers['Content-Type'] = 'application/xml';
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(
|
||||
this,
|
||||
`${bucketName}.s3`,
|
||||
'POST',
|
||||
'/',
|
||||
data,
|
||||
{ delete: '' },
|
||||
headers,
|
||||
{},
|
||||
region as string,
|
||||
);
|
||||
|
||||
responseData = { deleted: responseData.DeleteResult.Deleted };
|
||||
}
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html
|
||||
if (operation === 'getAll') {
|
||||
const bucketName = this.getNodeParameter('bucketName', i) as string;
|
||||
const returnAll = this.getNodeParameter('returnAll', 0);
|
||||
const options = this.getNodeParameter('options', 0);
|
||||
|
||||
if (options.folderKey) {
|
||||
qs.prefix = options.folderKey as string;
|
||||
}
|
||||
|
||||
if (options.fetchOwner) {
|
||||
qs['fetch-owner'] = options.fetchOwner as string;
|
||||
}
|
||||
|
||||
qs['list-type'] = 2;
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'GET', '', '', {
|
||||
location: '',
|
||||
});
|
||||
|
||||
const region = responseData.LocationConstraint._;
|
||||
|
||||
if (returnAll) {
|
||||
responseData = await awsApiRequestSOAPAllItems.call(
|
||||
this,
|
||||
'ListBucketResult.Contents',
|
||||
`${bucketName}.s3`,
|
||||
'GET',
|
||||
'',
|
||||
'',
|
||||
qs,
|
||||
{},
|
||||
{},
|
||||
region as string,
|
||||
);
|
||||
} else {
|
||||
qs.limit = this.getNodeParameter('limit', 0);
|
||||
responseData = await awsApiRequestSOAPAllItems.call(
|
||||
this,
|
||||
'ListBucketResult.Contents',
|
||||
`${bucketName}.s3`,
|
||||
'GET',
|
||||
'',
|
||||
'',
|
||||
qs,
|
||||
{},
|
||||
{},
|
||||
region as string,
|
||||
);
|
||||
}
|
||||
if (Array.isArray(responseData)) {
|
||||
responseData = responseData.filter(
|
||||
(e: IDataObject) =>
|
||||
(e.Key as string).endsWith('/') && e.Size === '0' && e.Key !== options.folderKey,
|
||||
);
|
||||
if (qs.limit) {
|
||||
responseData = responseData.splice(0, qs.limit as number);
|
||||
}
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resource === 'file') {
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyObject.html
|
||||
if (operation === 'copy') {
|
||||
const sourcePath = this.getNodeParameter('sourcePath', i) as string;
|
||||
const destinationPath = this.getNodeParameter('destinationPath', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i);
|
||||
|
||||
headers['x-amz-copy-source'] = sourcePath;
|
||||
|
||||
if (additionalFields.requesterPays) {
|
||||
headers['x-amz-request-payer'] = 'requester';
|
||||
}
|
||||
if (additionalFields.storageClass) {
|
||||
headers['x-amz-storage-class'] = snakeCase(
|
||||
additionalFields.storageClass as string,
|
||||
).toUpperCase();
|
||||
}
|
||||
if (additionalFields.acl) {
|
||||
headers['x-amz-acl'] = paramCase(additionalFields.acl as string);
|
||||
}
|
||||
if (additionalFields.grantFullControl) {
|
||||
headers['x-amz-grant-full-control'] = '';
|
||||
}
|
||||
if (additionalFields.grantRead) {
|
||||
headers['x-amz-grant-read'] = '';
|
||||
}
|
||||
if (additionalFields.grantReadAcp) {
|
||||
headers['x-amz-grant-read-acp'] = '';
|
||||
}
|
||||
if (additionalFields.grantWriteAcp) {
|
||||
headers['x-amz-grant-write-acp'] = '';
|
||||
}
|
||||
if (additionalFields.lockLegalHold) {
|
||||
headers['x-amz-object-lock-legal-hold'] = (additionalFields.lockLegalHold as boolean)
|
||||
? 'ON'
|
||||
: 'OFF';
|
||||
}
|
||||
if (additionalFields.lockMode) {
|
||||
headers['x-amz-object-lock-mode'] = (
|
||||
additionalFields.lockMode as string
|
||||
).toUpperCase();
|
||||
}
|
||||
if (additionalFields.lockRetainUntilDate) {
|
||||
headers['x-amz-object-lock-retain-until-date'] =
|
||||
additionalFields.lockRetainUntilDate as string;
|
||||
}
|
||||
if (additionalFields.serverSideEncryption) {
|
||||
headers['x-amz-server-side-encryption'] =
|
||||
additionalFields.serverSideEncryption as string;
|
||||
}
|
||||
if (additionalFields.encryptionAwsKmsKeyId) {
|
||||
headers['x-amz-server-side-encryption-aws-kms-key-id'] =
|
||||
additionalFields.encryptionAwsKmsKeyId as string;
|
||||
}
|
||||
if (additionalFields.serverSideEncryptionContext) {
|
||||
headers['x-amz-server-side-encryption-context'] =
|
||||
additionalFields.serverSideEncryptionContext as string;
|
||||
}
|
||||
if (additionalFields.serversideEncryptionCustomerAlgorithm) {
|
||||
headers['x-amz-server-side-encryption-customer-algorithm'] =
|
||||
additionalFields.serversideEncryptionCustomerAlgorithm as string;
|
||||
}
|
||||
if (additionalFields.serversideEncryptionCustomerKey) {
|
||||
headers['x-amz-server-side-encryption-customer-key'] =
|
||||
additionalFields.serversideEncryptionCustomerKey as string;
|
||||
}
|
||||
if (additionalFields.serversideEncryptionCustomerKeyMD5) {
|
||||
headers['x-amz-server-side-encryption-customer-key-MD5'] =
|
||||
additionalFields.serversideEncryptionCustomerKeyMD5 as string;
|
||||
}
|
||||
if (additionalFields.taggingDirective) {
|
||||
headers['x-amz-tagging-directive'] = (
|
||||
additionalFields.taggingDirective as string
|
||||
).toUpperCase();
|
||||
}
|
||||
if (additionalFields.metadataDirective) {
|
||||
headers['x-amz-metadata-directive'] = (
|
||||
additionalFields.metadataDirective as string
|
||||
).toUpperCase();
|
||||
}
|
||||
|
||||
const destinationParts = destinationPath.split('/');
|
||||
|
||||
const bucketName = destinationParts[1];
|
||||
|
||||
const destination = `/${destinationParts.slice(2, destinationParts.length).join('/')}`;
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'GET', '', '', {
|
||||
location: '',
|
||||
});
|
||||
|
||||
const region = responseData.LocationConstraint._;
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(
|
||||
this,
|
||||
`${bucketName}.s3`,
|
||||
'PUT',
|
||||
destination,
|
||||
'',
|
||||
qs,
|
||||
headers,
|
||||
{},
|
||||
region as string,
|
||||
);
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData.CopyObjectResult as IDataObject[]),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html
|
||||
if (operation === 'download') {
|
||||
const bucketName = this.getNodeParameter('bucketName', i) as string;
|
||||
|
||||
const fileKey = this.getNodeParameter('fileKey', i) as string;
|
||||
|
||||
const fileName = fileKey.split('/')[fileKey.split('/').length - 1];
|
||||
|
||||
if (fileKey.substring(fileKey.length - 1) === '/') {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
'Downloading a whole directory is not yet supported, please provide a file key',
|
||||
);
|
||||
}
|
||||
|
||||
let region = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'GET', '', '', {
|
||||
location: '',
|
||||
});
|
||||
|
||||
region = region.LocationConstraint._;
|
||||
|
||||
const response = await awsApiRequestREST.call(
|
||||
this,
|
||||
`${bucketName}.s3`,
|
||||
'GET',
|
||||
`/${fileKey}`,
|
||||
'',
|
||||
qs,
|
||||
{},
|
||||
{ encoding: null, resolveWithFullResponse: true },
|
||||
region as string,
|
||||
);
|
||||
|
||||
let mimeType: string | undefined;
|
||||
if (response.headers['content-type']) {
|
||||
mimeType = response.headers['content-type'];
|
||||
}
|
||||
|
||||
const newItem: INodeExecutionData = {
|
||||
json: items[i].json,
|
||||
binary: {},
|
||||
};
|
||||
|
||||
if (items[i].binary !== undefined && newItem.binary) {
|
||||
// Create a shallow copy of the binary data so that the old
|
||||
// data references which do not get changed still stay behind
|
||||
// but the incoming data does not get changed.
|
||||
Object.assign(newItem.binary, items[i].binary);
|
||||
}
|
||||
|
||||
items[i] = newItem;
|
||||
|
||||
const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i);
|
||||
|
||||
const data = Buffer.from(response.body as string, 'utf8');
|
||||
|
||||
items[i].binary![dataPropertyNameDownload] = await this.helpers.prepareBinaryData(
|
||||
data as unknown as Buffer,
|
||||
fileName,
|
||||
mimeType,
|
||||
);
|
||||
}
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObject.html
|
||||
if (operation === 'delete') {
|
||||
const bucketName = this.getNodeParameter('bucketName', i) as string;
|
||||
|
||||
const fileKey = this.getNodeParameter('fileKey', i) as string;
|
||||
|
||||
const options = this.getNodeParameter('options', i);
|
||||
|
||||
if (options.versionId) {
|
||||
qs.versionId = options.versionId as string;
|
||||
}
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'GET', '', '', {
|
||||
location: '',
|
||||
});
|
||||
|
||||
const region = responseData.LocationConstraint._;
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(
|
||||
this,
|
||||
`${bucketName}.s3`,
|
||||
'DELETE',
|
||||
`/${fileKey}`,
|
||||
'',
|
||||
qs,
|
||||
{},
|
||||
{},
|
||||
region as string,
|
||||
);
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ success: true }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html
|
||||
if (operation === 'getAll') {
|
||||
const bucketName = this.getNodeParameter('bucketName', i) as string;
|
||||
const returnAll = this.getNodeParameter('returnAll', 0);
|
||||
const options = this.getNodeParameter('options', 0);
|
||||
|
||||
if (options.folderKey) {
|
||||
qs.prefix = options.folderKey as string;
|
||||
}
|
||||
|
||||
if (options.fetchOwner) {
|
||||
qs['fetch-owner'] = options.fetchOwner as string;
|
||||
}
|
||||
|
||||
qs.delimiter = '/';
|
||||
|
||||
qs['list-type'] = 2;
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'GET', '', '', {
|
||||
location: '',
|
||||
});
|
||||
|
||||
const region = responseData.LocationConstraint._;
|
||||
|
||||
if (returnAll) {
|
||||
responseData = await awsApiRequestSOAPAllItems.call(
|
||||
this,
|
||||
'ListBucketResult.Contents',
|
||||
`${bucketName}.s3`,
|
||||
'GET',
|
||||
'',
|
||||
'',
|
||||
qs,
|
||||
{},
|
||||
{},
|
||||
region as string,
|
||||
);
|
||||
} else {
|
||||
qs.limit = this.getNodeParameter('limit', 0);
|
||||
responseData = await awsApiRequestSOAPAllItems.call(
|
||||
this,
|
||||
'ListBucketResult.Contents',
|
||||
`${bucketName}.s3`,
|
||||
'GET',
|
||||
'',
|
||||
'',
|
||||
qs,
|
||||
{},
|
||||
{},
|
||||
region as string,
|
||||
);
|
||||
responseData = responseData.splice(0, qs.limit);
|
||||
}
|
||||
if (Array.isArray(responseData)) {
|
||||
responseData = responseData.filter(
|
||||
(e: IDataObject) => !(e.Key as string).endsWith('/') && e.Size !== '0',
|
||||
);
|
||||
if (qs.limit) {
|
||||
responseData = responseData.splice(0, qs.limit as number);
|
||||
}
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
}
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html
|
||||
if (operation === 'upload') {
|
||||
const bucketName = this.getNodeParameter('bucketName', i) as string;
|
||||
const fileName = this.getNodeParameter('fileName', i) as string;
|
||||
const isBinaryData = this.getNodeParameter('binaryData', i);
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i);
|
||||
const tagsValues = (this.getNodeParameter('tagsUi', i) as IDataObject)
|
||||
.tagsValues as IDataObject[];
|
||||
let path = '/';
|
||||
let body;
|
||||
|
||||
if (additionalFields.requesterPays) {
|
||||
headers['x-amz-request-payer'] = 'requester';
|
||||
}
|
||||
if (additionalFields.parentFolderKey) {
|
||||
path = `/${additionalFields.parentFolderKey}/`;
|
||||
}
|
||||
if (additionalFields.storageClass) {
|
||||
headers['x-amz-storage-class'] = snakeCase(
|
||||
additionalFields.storageClass as string,
|
||||
).toUpperCase();
|
||||
}
|
||||
if (additionalFields.acl) {
|
||||
headers['x-amz-acl'] = paramCase(additionalFields.acl as string);
|
||||
}
|
||||
if (additionalFields.grantFullControl) {
|
||||
headers['x-amz-grant-full-control'] = '';
|
||||
}
|
||||
if (additionalFields.grantRead) {
|
||||
headers['x-amz-grant-read'] = '';
|
||||
}
|
||||
if (additionalFields.grantReadAcp) {
|
||||
headers['x-amz-grant-read-acp'] = '';
|
||||
}
|
||||
if (additionalFields.grantWriteAcp) {
|
||||
headers['x-amz-grant-write-acp'] = '';
|
||||
}
|
||||
if (additionalFields.lockLegalHold) {
|
||||
headers['x-amz-object-lock-legal-hold'] = (additionalFields.lockLegalHold as boolean)
|
||||
? 'ON'
|
||||
: 'OFF';
|
||||
}
|
||||
if (additionalFields.lockMode) {
|
||||
headers['x-amz-object-lock-mode'] = (
|
||||
additionalFields.lockMode as string
|
||||
).toUpperCase();
|
||||
}
|
||||
if (additionalFields.lockRetainUntilDate) {
|
||||
headers['x-amz-object-lock-retain-until-date'] =
|
||||
additionalFields.lockRetainUntilDate as string;
|
||||
}
|
||||
if (additionalFields.serverSideEncryption) {
|
||||
headers['x-amz-server-side-encryption'] =
|
||||
additionalFields.serverSideEncryption as string;
|
||||
}
|
||||
if (additionalFields.encryptionAwsKmsKeyId) {
|
||||
headers['x-amz-server-side-encryption-aws-kms-key-id'] =
|
||||
additionalFields.encryptionAwsKmsKeyId as string;
|
||||
}
|
||||
if (additionalFields.serverSideEncryptionContext) {
|
||||
headers['x-amz-server-side-encryption-context'] =
|
||||
additionalFields.serverSideEncryptionContext as string;
|
||||
}
|
||||
if (additionalFields.serversideEncryptionCustomerAlgorithm) {
|
||||
headers['x-amz-server-side-encryption-customer-algorithm'] =
|
||||
additionalFields.serversideEncryptionCustomerAlgorithm as string;
|
||||
}
|
||||
if (additionalFields.serversideEncryptionCustomerKey) {
|
||||
headers['x-amz-server-side-encryption-customer-key'] =
|
||||
additionalFields.serversideEncryptionCustomerKey as string;
|
||||
}
|
||||
if (additionalFields.serversideEncryptionCustomerKeyMD5) {
|
||||
headers['x-amz-server-side-encryption-customer-key-MD5'] =
|
||||
additionalFields.serversideEncryptionCustomerKeyMD5 as string;
|
||||
}
|
||||
if (tagsValues) {
|
||||
const tags: string[] = [];
|
||||
tagsValues.forEach((o: IDataObject) => {
|
||||
tags.push(`${o.key}=${o.value}`);
|
||||
});
|
||||
headers['x-amz-tagging'] = tags.join('&');
|
||||
}
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'GET', '', '', {
|
||||
location: '',
|
||||
});
|
||||
|
||||
const region = responseData.LocationConstraint._;
|
||||
|
||||
if (isBinaryData) {
|
||||
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i);
|
||||
const binaryPropertyData = this.helpers.assertBinaryData(i, binaryPropertyName);
|
||||
const binaryDataBuffer = await this.helpers.getBinaryDataBuffer(
|
||||
i,
|
||||
binaryPropertyName,
|
||||
);
|
||||
|
||||
body = binaryDataBuffer;
|
||||
|
||||
headers['Content-Type'] = binaryPropertyData.mimeType;
|
||||
|
||||
headers['Content-MD5'] = createHash('md5').update(body).digest('base64');
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(
|
||||
this,
|
||||
`${bucketName}.s3`,
|
||||
'PUT',
|
||||
`${path}${fileName || binaryPropertyData.fileName}`,
|
||||
body,
|
||||
qs,
|
||||
headers,
|
||||
{},
|
||||
region as string,
|
||||
);
|
||||
} else {
|
||||
const fileContent = this.getNodeParameter('fileContent', i) as string;
|
||||
|
||||
body = Buffer.from(fileContent, 'utf8');
|
||||
|
||||
headers['Content-Type'] = 'text/html';
|
||||
|
||||
headers['Content-MD5'] = createHash('md5').update(fileContent).digest('base64');
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(
|
||||
this,
|
||||
`${bucketName}.s3`,
|
||||
'PUT',
|
||||
`${path}${fileName}`,
|
||||
body,
|
||||
qs,
|
||||
headers,
|
||||
{},
|
||||
region as string,
|
||||
);
|
||||
}
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ success: true }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionData);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
if (resource === 'file' && operation === 'download') {
|
||||
// For file downloads the files get attached to the existing items
|
||||
return [items];
|
||||
} else {
|
||||
return [returnData];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const bucketOperations: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['bucket'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a bucket',
|
||||
action: 'Create a bucket',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a bucket',
|
||||
action: 'Delete a bucket',
|
||||
},
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
description: 'Get many buckets',
|
||||
action: 'Get many buckets',
|
||||
},
|
||||
{
|
||||
name: 'Search',
|
||||
value: 'search',
|
||||
description: 'Search within a bucket',
|
||||
action: 'Search a bucket',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
},
|
||||
];
|
||||
|
||||
export const bucketFields: INodeProperties[] = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* bucket:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['bucket'],
|
||||
operation: ['create'],
|
||||
},
|
||||
},
|
||||
description: 'A succinct description of the nature, symptoms, cause, or effect of the bucket',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['bucket'],
|
||||
operation: ['create'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'ACL',
|
||||
name: 'acl',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Authenticated Read',
|
||||
value: 'authenticatedRead',
|
||||
},
|
||||
{
|
||||
name: 'Private',
|
||||
value: 'Private',
|
||||
},
|
||||
{
|
||||
name: 'Public Read',
|
||||
value: 'publicRead',
|
||||
},
|
||||
{
|
||||
name: 'Public Read Write',
|
||||
value: 'publicReadWrite',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'The canned ACL to apply to the bucket',
|
||||
},
|
||||
{
|
||||
displayName: 'Bucket Object Lock Enabled',
|
||||
name: 'bucketObjectLockEnabled',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether you want S3 Object Lock to be enabled for the new bucket',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Full Control',
|
||||
name: 'grantFullControl',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether to allow grantee the read, write, read ACP, and write ACP permissions on the bucket',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Read',
|
||||
name: 'grantRead',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to allow grantee to list the objects in the bucket',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Read ACP',
|
||||
name: 'grantReadAcp',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to allow grantee to read the bucket ACL',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Write',
|
||||
name: 'grantWrite',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether to allow grantee to create, overwrite, and delete any object in the bucket',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Write ACP',
|
||||
name: 'grantWriteAcp',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to allow grantee to write the ACL for the applicable bucket',
|
||||
},
|
||||
{
|
||||
displayName: 'Region',
|
||||
name: 'region',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description:
|
||||
'Region you want to create the bucket in, by default the buckets are created on the region defined on the credentials',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* bucket:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['bucket'],
|
||||
operation: ['delete'],
|
||||
},
|
||||
},
|
||||
description: 'Name of the AWS S3 bucket to delete',
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* bucket:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getAll'],
|
||||
resource: ['bucket'],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getAll'],
|
||||
resource: ['bucket'],
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'Max number of results to return',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* bucket:search */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Bucket Name',
|
||||
name: 'bucketName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['bucket'],
|
||||
operation: ['search'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['search'],
|
||||
resource: ['bucket'],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['search'],
|
||||
resource: ['bucket'],
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'Max number of results to return',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['bucket'],
|
||||
operation: ['search'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Delimiter',
|
||||
name: 'delimiter',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'A delimiter is a character you use to group keys',
|
||||
},
|
||||
{
|
||||
displayName: 'Encoding Type',
|
||||
name: 'encodingType',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'URL',
|
||||
value: 'url',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'Encoding type used by Amazon S3 to encode object keys in the response',
|
||||
},
|
||||
{
|
||||
displayName: 'Fetch Owner',
|
||||
name: 'fetchOwner',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
|
||||
description:
|
||||
'The owner field is not present in listV2 by default, if you want to return owner field with each key in the result then set the fetch owner field to true',
|
||||
},
|
||||
{
|
||||
displayName: 'Prefix',
|
||||
name: 'prefix',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Limits the response to keys that begin with the specified prefix',
|
||||
},
|
||||
{
|
||||
displayName: 'Requester Pays',
|
||||
name: 'requesterPays',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether the requester will pay for requests and data transfer. While Requester Pays is enabled, anonymous access to this bucket is disabled.',
|
||||
},
|
||||
{
|
||||
displayName: 'Start After',
|
||||
name: 'startAfter',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description:
|
||||
'StartAfter is where you want Amazon S3 to start listing from. Amazon S3 starts listing after this specified key.',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,841 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const fileOperations: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Copy',
|
||||
value: 'copy',
|
||||
description: 'Copy a file',
|
||||
action: 'Copy a file',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a file',
|
||||
action: 'Delete a file',
|
||||
},
|
||||
{
|
||||
name: 'Download',
|
||||
value: 'download',
|
||||
description: 'Download a file',
|
||||
action: 'Download a file',
|
||||
},
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
description: 'Get many files',
|
||||
action: 'Get many files',
|
||||
},
|
||||
{
|
||||
name: 'Upload',
|
||||
value: 'upload',
|
||||
description: 'Upload a file',
|
||||
action: 'Upload a file',
|
||||
},
|
||||
],
|
||||
default: 'download',
|
||||
},
|
||||
];
|
||||
|
||||
export const fileFields: INodeProperties[] = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* file:copy */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Source Path',
|
||||
name: 'sourcePath',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
placeholder: '/bucket/my-image.jpg',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['copy'],
|
||||
},
|
||||
},
|
||||
description:
|
||||
'The name of the source bucket should start with (/) and key name of the source object, separated by a slash (/)',
|
||||
},
|
||||
{
|
||||
displayName: 'Destination Path',
|
||||
name: 'destinationPath',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
placeholder: '/bucket/my-second-image.jpg',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['copy'],
|
||||
},
|
||||
},
|
||||
description:
|
||||
'The name of the destination bucket and key name of the destination object, separated by a slash (/)',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['copy'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'ACL',
|
||||
name: 'acl',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Authenticated Read',
|
||||
value: 'authenticatedRead',
|
||||
},
|
||||
{
|
||||
name: 'AWS Exec Read',
|
||||
value: 'awsExecRead',
|
||||
},
|
||||
{
|
||||
name: 'Bucket Owner Full Control',
|
||||
value: 'bucketOwnerFullControl',
|
||||
},
|
||||
{
|
||||
name: 'Bucket Owner Read',
|
||||
value: 'bucketOwnerRead',
|
||||
},
|
||||
{
|
||||
name: 'Private',
|
||||
value: 'private',
|
||||
},
|
||||
{
|
||||
name: 'Public Read',
|
||||
value: 'publicRead',
|
||||
},
|
||||
{
|
||||
name: 'Public Read Write',
|
||||
value: 'publicReadWrite',
|
||||
},
|
||||
],
|
||||
default: 'private',
|
||||
description: 'The canned ACL to apply to the object',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Full Control',
|
||||
name: 'grantFullControl',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether to give the grantee READ, READ_ACP, and WRITE_ACP permissions on the object',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Read',
|
||||
name: 'grantRead',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to allow grantee to read the object data and its metadata',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Read ACP',
|
||||
name: 'grantReadAcp',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to allow grantee to read the object ACL',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Write ACP',
|
||||
name: 'grantWriteAcp',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to allow grantee to write the ACL for the applicable object',
|
||||
},
|
||||
{
|
||||
displayName: 'Lock Legal Hold',
|
||||
name: 'lockLegalHold',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether a legal hold will be applied to this object',
|
||||
},
|
||||
{
|
||||
displayName: 'Lock Mode',
|
||||
name: 'lockMode',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Governance',
|
||||
value: 'governance',
|
||||
},
|
||||
{
|
||||
name: 'Compliance',
|
||||
value: 'compliance',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'The Object Lock mode that you want to apply to this object',
|
||||
},
|
||||
{
|
||||
displayName: 'Lock Retain Until Date',
|
||||
name: 'lockRetainUntilDate',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: "The date and time when you want this object's Object Lock to expire",
|
||||
},
|
||||
{
|
||||
displayName: 'Metadata Directive',
|
||||
name: 'metadataDirective',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Copy',
|
||||
value: 'copy',
|
||||
},
|
||||
{
|
||||
name: 'Replace',
|
||||
value: 'replace',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description:
|
||||
'Specifies whether the metadata is copied from the source object or replaced with metadata provided in the request',
|
||||
},
|
||||
{
|
||||
displayName: 'Requester Pays',
|
||||
name: 'requesterPays',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether the requester will pay for requests and data transfer. While Requester Pays is enabled, anonymous access to this bucket is disabled.',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption',
|
||||
name: 'serverSideEncryption',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'AES256',
|
||||
value: 'AES256',
|
||||
},
|
||||
{
|
||||
name: 'AWS:KMS',
|
||||
value: 'aws:kms',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description:
|
||||
'The server-side encryption algorithm used when storing this object in Amazon S3',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption Context',
|
||||
name: 'serverSideEncryptionContext',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Specifies the AWS KMS Encryption Context to use for object encryption',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption AWS KMS Key ID',
|
||||
name: 'encryptionAwsKmsKeyId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'If x-amz-server-side-encryption is present and has the value of aws:kms',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption Customer Algorithm',
|
||||
name: 'serversideEncryptionCustomerAlgorithm',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description:
|
||||
'Specifies the algorithm to use to when encrypting the object (for example, AES256)',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption Customer Key',
|
||||
name: 'serversideEncryptionCustomerKey',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description:
|
||||
'Specifies the customer-provided encryption key for Amazon S3 to use in encrypting data',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption Customer Key MD5',
|
||||
name: 'serversideEncryptionCustomerKeyMD5',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Specifies the 128-bit MD5 digest of the encryption key according to RFC 1321',
|
||||
},
|
||||
{
|
||||
displayName: 'Storage Class',
|
||||
name: 'storageClass',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Deep Archive',
|
||||
value: 'deepArchive',
|
||||
},
|
||||
{
|
||||
name: 'Glacier',
|
||||
value: 'glacier',
|
||||
},
|
||||
{
|
||||
name: 'Intelligent Tiering',
|
||||
value: 'intelligentTiering',
|
||||
},
|
||||
{
|
||||
name: 'One Zone IA',
|
||||
value: 'onezoneIA',
|
||||
},
|
||||
{
|
||||
name: 'Standard',
|
||||
value: 'standard',
|
||||
},
|
||||
{
|
||||
name: 'Standard IA',
|
||||
value: 'standardIA',
|
||||
},
|
||||
],
|
||||
default: 'standard',
|
||||
description: 'Amazon S3 storage classes',
|
||||
},
|
||||
{
|
||||
displayName: 'Tagging Directive',
|
||||
name: 'taggingDirective',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Copy',
|
||||
value: 'copy',
|
||||
},
|
||||
{
|
||||
name: 'Replace',
|
||||
value: 'replace',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description:
|
||||
'Specifies whether the metadata is copied from the source object or replaced with metadata provided in the request',
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* file:upload */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Bucket Name',
|
||||
name: 'bucketName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['upload'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'File Name',
|
||||
name: 'fileName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'hello.txt',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['upload'],
|
||||
binaryData: [false],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'File Name',
|
||||
name: 'fileName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['upload'],
|
||||
binaryData: [true],
|
||||
},
|
||||
},
|
||||
description: 'If not set the binary data filename will be used',
|
||||
},
|
||||
{
|
||||
displayName: 'Binary File',
|
||||
name: 'binaryData',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['upload'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
description: 'Whether the data to upload should be taken from binary field',
|
||||
},
|
||||
{
|
||||
displayName: 'File Content',
|
||||
name: 'fileContent',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['upload'],
|
||||
resource: ['file'],
|
||||
binaryData: [false],
|
||||
},
|
||||
},
|
||||
placeholder: '',
|
||||
description: 'The text content of the file to upload',
|
||||
},
|
||||
{
|
||||
displayName: 'Input Binary Field',
|
||||
name: 'binaryPropertyName',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['upload'],
|
||||
resource: ['file'],
|
||||
binaryData: [true],
|
||||
},
|
||||
},
|
||||
placeholder: '',
|
||||
hint: 'The name of the input binary field containing the file to be uploaded',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['upload'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'ACL',
|
||||
name: 'acl',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Authenticated Read',
|
||||
value: 'authenticatedRead',
|
||||
},
|
||||
{
|
||||
name: 'AWS Exec Read',
|
||||
value: 'awsExecRead',
|
||||
},
|
||||
{
|
||||
name: 'Bucket Owner Full Control',
|
||||
value: 'bucketOwnerFullControl',
|
||||
},
|
||||
{
|
||||
name: 'Bucket Owner Read',
|
||||
value: 'bucketOwnerRead',
|
||||
},
|
||||
{
|
||||
name: 'Private',
|
||||
value: 'private',
|
||||
},
|
||||
{
|
||||
name: 'Public Read',
|
||||
value: 'publicRead',
|
||||
},
|
||||
{
|
||||
name: 'Public Read Write',
|
||||
value: 'publicReadWrite',
|
||||
},
|
||||
],
|
||||
default: 'private',
|
||||
description: 'The canned ACL to apply to the object',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Full Control',
|
||||
name: 'grantFullControl',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether to give the grantee READ, READ_ACP, and WRITE_ACP permissions on the object',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Read',
|
||||
name: 'grantRead',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to allow grantee to read the object data and its metadata',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Read ACP',
|
||||
name: 'grantReadAcp',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to allow grantee to read the object ACL',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Write ACP',
|
||||
name: 'grantWriteAcp',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to allow grantee to write the ACL for the applicable object',
|
||||
},
|
||||
{
|
||||
displayName: 'Lock Legal Hold',
|
||||
name: 'lockLegalHold',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether a legal hold will be applied to this object',
|
||||
},
|
||||
{
|
||||
displayName: 'Lock Mode',
|
||||
name: 'lockMode',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Governance',
|
||||
value: 'governance',
|
||||
},
|
||||
{
|
||||
name: 'Compliance',
|
||||
value: 'compliance',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'The Object Lock mode that you want to apply to this object',
|
||||
},
|
||||
{
|
||||
displayName: 'Lock Retain Until Date',
|
||||
name: 'lockRetainUntilDate',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: "The date and time when you want this object's Object Lock to expire",
|
||||
},
|
||||
{
|
||||
displayName: 'Parent Folder Key',
|
||||
name: 'parentFolderKey',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Parent folder you want to create the file in',
|
||||
},
|
||||
{
|
||||
displayName: 'Requester Pays',
|
||||
name: 'requesterPays',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether the requester will pay for requests and data transfer. While Requester Pays is enabled, anonymous access to this bucket is disabled.',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption',
|
||||
name: 'serverSideEncryption',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'AES256',
|
||||
value: 'AES256',
|
||||
},
|
||||
{
|
||||
name: 'AWS:KMS',
|
||||
value: 'aws:kms',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description:
|
||||
'The server-side encryption algorithm used when storing this object in Amazon S3',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption Context',
|
||||
name: 'serverSideEncryptionContext',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Specifies the AWS KMS Encryption Context to use for object encryption',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption AWS KMS Key ID',
|
||||
name: 'encryptionAwsKmsKeyId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'If x-amz-server-side-encryption is present and has the value of aws:kms',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption Customer Algorithm',
|
||||
name: 'serversideEncryptionCustomerAlgorithm',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description:
|
||||
'Specifies the algorithm to use to when encrypting the object (for example, AES256)',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption Customer Key',
|
||||
name: 'serversideEncryptionCustomerKey',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description:
|
||||
'Specifies the customer-provided encryption key for Amazon S3 to use in encrypting data',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption Customer Key MD5',
|
||||
name: 'serversideEncryptionCustomerKeyMD5',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Specifies the 128-bit MD5 digest of the encryption key according to RFC 1321',
|
||||
},
|
||||
{
|
||||
displayName: 'Storage Class',
|
||||
name: 'storageClass',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Deep Archive',
|
||||
value: 'deepArchive',
|
||||
},
|
||||
{
|
||||
name: 'Glacier',
|
||||
value: 'glacier',
|
||||
},
|
||||
{
|
||||
name: 'Intelligent Tiering',
|
||||
value: 'intelligentTiering',
|
||||
},
|
||||
{
|
||||
name: 'One Zone IA',
|
||||
value: 'onezoneIA',
|
||||
},
|
||||
{
|
||||
name: 'Standard',
|
||||
value: 'standard',
|
||||
},
|
||||
{
|
||||
name: 'Standard IA',
|
||||
value: 'standardIA',
|
||||
},
|
||||
],
|
||||
default: 'standard',
|
||||
description: 'Amazon S3 storage classes',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Tags',
|
||||
name: 'tagsUi',
|
||||
placeholder: 'Add Tag',
|
||||
type: 'fixedCollection',
|
||||
default: {},
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['upload'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'tagsValues',
|
||||
displayName: 'Tag',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Key',
|
||||
name: 'key',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
description: 'Optional extra headers to add to the message (most headers are allowed)',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* file:download */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Bucket Name',
|
||||
name: 'bucketName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['download'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'File Key',
|
||||
name: 'fileKey',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['download'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Put Output File in Field',
|
||||
name: 'binaryPropertyName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: 'data',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['download'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
hint: 'The name of the output binary field to put the file in',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* file:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Bucket Name',
|
||||
name: 'bucketName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['delete'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'File Key',
|
||||
name: 'fileKey',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['delete'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['delete'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Version ID',
|
||||
name: 'versionId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* file:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Bucket Name',
|
||||
name: 'bucketName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getAll'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getAll'],
|
||||
resource: ['file'],
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'Max number of results to return',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Fetch Owner',
|
||||
name: 'fetchOwner',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
|
||||
description:
|
||||
'The owner field is not present in listV2 by default, if you want to return owner field with each key in the result then set the fetch owner field to true',
|
||||
},
|
||||
{
|
||||
displayName: 'Folder Key',
|
||||
name: 'folderKey',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,241 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const folderOperations: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['folder'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a folder',
|
||||
action: 'Create a folder',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a folder',
|
||||
action: 'Delete a folder',
|
||||
},
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
description: 'Get many folders',
|
||||
action: 'Get many folders',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
},
|
||||
];
|
||||
|
||||
export const folderFields: INodeProperties[] = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Bucket Name',
|
||||
name: 'bucketName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['folder'],
|
||||
operation: ['create'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Folder Name',
|
||||
name: 'folderName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['folder'],
|
||||
operation: ['create'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['folder'],
|
||||
operation: ['create'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Parent Folder Key',
|
||||
name: 'parentFolderKey',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Parent folder you want to create the folder in',
|
||||
},
|
||||
{
|
||||
displayName: 'Requester Pays',
|
||||
name: 'requesterPays',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether the requester will pay for requests and data transfer. While Requester Pays is enabled, anonymous access to this bucket is disabled.',
|
||||
},
|
||||
{
|
||||
displayName: 'Storage Class',
|
||||
name: 'storageClass',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Deep Archive',
|
||||
value: 'deepArchive',
|
||||
},
|
||||
{
|
||||
name: 'Glacier',
|
||||
value: 'glacier',
|
||||
},
|
||||
{
|
||||
name: 'Intelligent Tiering',
|
||||
value: 'intelligentTiering',
|
||||
},
|
||||
{
|
||||
name: 'One Zone IA',
|
||||
value: 'onezoneIA',
|
||||
},
|
||||
{
|
||||
name: 'Reduced Redundancy',
|
||||
value: 'RecudedRedundancy',
|
||||
},
|
||||
{
|
||||
name: 'Standard',
|
||||
value: 'standard',
|
||||
},
|
||||
{
|
||||
name: 'Standard IA',
|
||||
value: 'standardIA',
|
||||
},
|
||||
],
|
||||
default: 'standard',
|
||||
description: 'Amazon S3 storage classes',
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Bucket Name',
|
||||
name: 'bucketName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['folder'],
|
||||
operation: ['delete'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Folder Key',
|
||||
name: 'folderKey',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['folder'],
|
||||
operation: ['delete'],
|
||||
},
|
||||
},
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Bucket Name',
|
||||
name: 'bucketName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['folder'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getAll'],
|
||||
resource: ['folder'],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getAll'],
|
||||
resource: ['folder'],
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'Max number of results to return',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['folder'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Fetch Owner',
|
||||
name: 'fetchOwner',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
|
||||
description:
|
||||
'The owner field is not present in listV2 by default, if you want to return owner field with each key in the result then set the fetch owner field to true',
|
||||
},
|
||||
{
|
||||
displayName: 'Folder Key',
|
||||
name: 'folderKey',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,162 @@
|
||||
import get from 'lodash/get';
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
IHookFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
IWebhookFunctions,
|
||||
IHttpRequestOptions,
|
||||
IHttpRequestMethods,
|
||||
} from 'n8n-workflow';
|
||||
import { parseString } from 'xml2js';
|
||||
|
||||
export async function awsApiRequest(
|
||||
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions,
|
||||
service: string,
|
||||
method: IHttpRequestMethods,
|
||||
path: string,
|
||||
body?: string | Buffer,
|
||||
query: IDataObject = {},
|
||||
headers?: object,
|
||||
option: IDataObject = {},
|
||||
_region?: string,
|
||||
): Promise<any> {
|
||||
const requestOptions = {
|
||||
qs: {
|
||||
...query,
|
||||
service,
|
||||
path,
|
||||
query,
|
||||
},
|
||||
method,
|
||||
body,
|
||||
url: '',
|
||||
headers,
|
||||
//region: credentials?.region as string,
|
||||
} as IHttpRequestOptions;
|
||||
|
||||
if (Object.keys(option).length !== 0) {
|
||||
Object.assign(requestOptions, option);
|
||||
}
|
||||
return await this.helpers.requestWithAuthentication.call(this, 'aws', requestOptions);
|
||||
}
|
||||
|
||||
export async function awsApiRequestREST(
|
||||
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,
|
||||
service: string,
|
||||
method: IHttpRequestMethods,
|
||||
path: string,
|
||||
body?: string,
|
||||
query: IDataObject = {},
|
||||
headers?: object,
|
||||
options: IDataObject = {},
|
||||
region?: string,
|
||||
): Promise<any> {
|
||||
const response = await awsApiRequest.call(
|
||||
this,
|
||||
service,
|
||||
method,
|
||||
path,
|
||||
body,
|
||||
query,
|
||||
headers,
|
||||
options,
|
||||
region,
|
||||
);
|
||||
try {
|
||||
return JSON.parse(response as string);
|
||||
} catch (error) {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
export async function awsApiRequestSOAP(
|
||||
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions,
|
||||
service: string,
|
||||
method: IHttpRequestMethods,
|
||||
path: string,
|
||||
body?: string | Buffer,
|
||||
query: IDataObject = {},
|
||||
headers?: object,
|
||||
option: IDataObject = {},
|
||||
region?: string,
|
||||
): Promise<any> {
|
||||
const response = await awsApiRequest.call(
|
||||
this,
|
||||
service,
|
||||
method,
|
||||
path,
|
||||
body,
|
||||
query,
|
||||
headers,
|
||||
option,
|
||||
region,
|
||||
);
|
||||
try {
|
||||
return await new Promise((resolve, reject) => {
|
||||
parseString(response as string, { explicitArray: false }, (err, data) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function awsApiRequestSOAPAllItems(
|
||||
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions,
|
||||
propertyName: string,
|
||||
service: string,
|
||||
method: IHttpRequestMethods,
|
||||
path: string,
|
||||
body?: string,
|
||||
query: IDataObject = {},
|
||||
headers: IDataObject = {},
|
||||
option: IDataObject = {},
|
||||
region?: string,
|
||||
): Promise<any> {
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
let responseData;
|
||||
|
||||
do {
|
||||
responseData = await awsApiRequestSOAP.call(
|
||||
this,
|
||||
service,
|
||||
method,
|
||||
path,
|
||||
body,
|
||||
query,
|
||||
headers,
|
||||
option,
|
||||
region,
|
||||
);
|
||||
|
||||
//https://forums.aws.amazon.com/thread.jspa?threadID=55746
|
||||
if (get(responseData, [propertyName.split('.')[0], 'NextContinuationToken'])) {
|
||||
query['continuation-token'] = get(responseData, [
|
||||
propertyName.split('.')[0],
|
||||
'NextContinuationToken',
|
||||
]);
|
||||
}
|
||||
if (get(responseData, propertyName)) {
|
||||
if (Array.isArray(get(responseData, propertyName))) {
|
||||
returnData.push.apply(returnData, get(responseData, propertyName) as IDataObject[]);
|
||||
} else {
|
||||
returnData.push(get(responseData, propertyName) as IDataObject);
|
||||
}
|
||||
}
|
||||
const limit = query.limit as number | undefined;
|
||||
if (limit && limit <= returnData.length) {
|
||||
return returnData;
|
||||
}
|
||||
} while (
|
||||
get(responseData, [propertyName.split('.')[0], 'IsTruncated']) !== undefined &&
|
||||
get(responseData, [propertyName.split('.')[0], 'IsTruncated']) !== 'false'
|
||||
);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,321 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const bucketOperations: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['bucket'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a bucket',
|
||||
action: 'Create a bucket',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a bucket',
|
||||
action: 'Delete a bucket',
|
||||
},
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
description: 'Get many buckets',
|
||||
action: 'Get many buckets',
|
||||
},
|
||||
{
|
||||
name: 'Search',
|
||||
value: 'search',
|
||||
description: 'Search within a bucket',
|
||||
action: 'Search a bucket',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
},
|
||||
];
|
||||
|
||||
export const bucketFields: INodeProperties[] = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* bucket:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['bucket'],
|
||||
operation: ['create'],
|
||||
},
|
||||
},
|
||||
description: 'A succinct description of the nature, symptoms, cause, or effect of the bucket',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['bucket'],
|
||||
operation: ['create'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'ACL',
|
||||
name: 'acl',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Authenticated Read',
|
||||
value: 'authenticatedRead',
|
||||
},
|
||||
{
|
||||
name: 'Private',
|
||||
value: 'Private',
|
||||
},
|
||||
{
|
||||
name: 'Public Read',
|
||||
value: 'publicRead',
|
||||
},
|
||||
{
|
||||
name: 'Public Read Write',
|
||||
value: 'publicReadWrite',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'The canned ACL to apply to the bucket',
|
||||
},
|
||||
{
|
||||
displayName: 'Bucket Object Lock Enabled',
|
||||
name: 'bucketObjectLockEnabled',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether you want S3 Object Lock to be enabled for the new bucket',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Full Control',
|
||||
name: 'grantFullControl',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether to allow grantee the read, write, read ACP, and write ACP permissions on the bucket',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Read',
|
||||
name: 'grantRead',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to allow grantee to list the objects in the bucket',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Read ACP',
|
||||
name: 'grantReadAcp',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to allow grantee to read the bucket ACL',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Write',
|
||||
name: 'grantWrite',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether to allow grantee to create, overwrite, and delete any object in the bucket',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Write ACP',
|
||||
name: 'grantWriteAcp',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to allow grantee to write the ACL for the applicable bucket',
|
||||
},
|
||||
{
|
||||
displayName: 'Region',
|
||||
name: 'region',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description:
|
||||
'Region you want to create the bucket in, by default the buckets are created on the region defined on the credentials',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* bucket:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['bucket'],
|
||||
operation: ['delete'],
|
||||
},
|
||||
},
|
||||
description: 'Name of the AWS S3 bucket to delete',
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* bucket:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getAll'],
|
||||
resource: ['bucket'],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getAll'],
|
||||
resource: ['bucket'],
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'Max number of results to return',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* bucket:search */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Bucket Name',
|
||||
name: 'bucketName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['bucket'],
|
||||
operation: ['search'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['search'],
|
||||
resource: ['bucket'],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['search'],
|
||||
resource: ['bucket'],
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'Max number of results to return',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['bucket'],
|
||||
operation: ['search'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Delimiter',
|
||||
name: 'delimiter',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'A delimiter is a character you use to group keys',
|
||||
},
|
||||
{
|
||||
displayName: 'Encoding Type',
|
||||
name: 'encodingType',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'URL',
|
||||
value: 'url',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'Encoding type used by Amazon S3 to encode object keys in the response',
|
||||
},
|
||||
{
|
||||
displayName: 'Fetch Owner',
|
||||
name: 'fetchOwner',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
|
||||
description:
|
||||
'The owner field is not present in listV2 by default, if you want to return owner field with each key in the result then set the fetch owner field to true',
|
||||
},
|
||||
{
|
||||
displayName: 'Prefix',
|
||||
name: 'prefix',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Limits the response to keys that begin with the specified prefix',
|
||||
},
|
||||
{
|
||||
displayName: 'Requester Pays',
|
||||
name: 'requesterPays',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether the requester will pay for requests and data transfer. While Requester Pays is enabled, anonymous access to this bucket is disabled.',
|
||||
},
|
||||
{
|
||||
displayName: 'Start After',
|
||||
name: 'startAfter',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description:
|
||||
'StartAfter is where you want Amazon S3 to start listing from. Amazon S3 starts listing after this specified key.',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,841 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const fileOperations: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Copy',
|
||||
value: 'copy',
|
||||
description: 'Copy a file',
|
||||
action: 'Copy a file',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a file',
|
||||
action: 'Delete a file',
|
||||
},
|
||||
{
|
||||
name: 'Download',
|
||||
value: 'download',
|
||||
description: 'Download a file',
|
||||
action: 'Download a file',
|
||||
},
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
description: 'Get many files',
|
||||
action: 'Get many files',
|
||||
},
|
||||
{
|
||||
name: 'Upload',
|
||||
value: 'upload',
|
||||
description: 'Upload a file',
|
||||
action: 'Upload a file',
|
||||
},
|
||||
],
|
||||
default: 'download',
|
||||
},
|
||||
];
|
||||
|
||||
export const fileFields: INodeProperties[] = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* file:copy */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Source Path',
|
||||
name: 'sourcePath',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
placeholder: '/bucket/my-image.jpg',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['copy'],
|
||||
},
|
||||
},
|
||||
description:
|
||||
'The name of the source bucket should start with (/) and key name of the source object, separated by a slash (/)',
|
||||
},
|
||||
{
|
||||
displayName: 'Destination Path',
|
||||
name: 'destinationPath',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
placeholder: '/bucket/my-second-image.jpg',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['copy'],
|
||||
},
|
||||
},
|
||||
description:
|
||||
'The name of the destination bucket and key name of the destination object, separated by a slash (/)',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['copy'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'ACL',
|
||||
name: 'acl',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Authenticated Read',
|
||||
value: 'authenticatedRead',
|
||||
},
|
||||
{
|
||||
name: 'AWS Exec Read',
|
||||
value: 'awsExecRead',
|
||||
},
|
||||
{
|
||||
name: 'Bucket Owner Full Control',
|
||||
value: 'bucketOwnerFullControl',
|
||||
},
|
||||
{
|
||||
name: 'Bucket Owner Read',
|
||||
value: 'bucketOwnerRead',
|
||||
},
|
||||
{
|
||||
name: 'Private',
|
||||
value: 'private',
|
||||
},
|
||||
{
|
||||
name: 'Public Read',
|
||||
value: 'publicRead',
|
||||
},
|
||||
{
|
||||
name: 'Public Read Write',
|
||||
value: 'publicReadWrite',
|
||||
},
|
||||
],
|
||||
default: 'private',
|
||||
description: 'The canned ACL to apply to the object',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Full Control',
|
||||
name: 'grantFullControl',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether to give the grantee READ, READ_ACP, and WRITE_ACP permissions on the object',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Read',
|
||||
name: 'grantRead',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to allow grantee to read the object data and its metadata',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Read ACP',
|
||||
name: 'grantReadAcp',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to allow grantee to read the object ACL',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Write ACP',
|
||||
name: 'grantWriteAcp',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to allow grantee to write the ACL for the applicable object',
|
||||
},
|
||||
{
|
||||
displayName: 'Lock Legal Hold',
|
||||
name: 'lockLegalHold',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether a legal hold will be applied to this object',
|
||||
},
|
||||
{
|
||||
displayName: 'Lock Mode',
|
||||
name: 'lockMode',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Governance',
|
||||
value: 'governance',
|
||||
},
|
||||
{
|
||||
name: 'Compliance',
|
||||
value: 'compliance',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'The Object Lock mode that you want to apply to this object',
|
||||
},
|
||||
{
|
||||
displayName: 'Lock Retain Until Date',
|
||||
name: 'lockRetainUntilDate',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: "The date and time when you want this object's Object Lock to expire",
|
||||
},
|
||||
{
|
||||
displayName: 'Metadata Directive',
|
||||
name: 'metadataDirective',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Copy',
|
||||
value: 'copy',
|
||||
},
|
||||
{
|
||||
name: 'Replace',
|
||||
value: 'replace',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description:
|
||||
'Specifies whether the metadata is copied from the source object or replaced with metadata provided in the request',
|
||||
},
|
||||
{
|
||||
displayName: 'Requester Pays',
|
||||
name: 'requesterPays',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether the requester will pay for requests and data transfer. While Requester Pays is enabled, anonymous access to this bucket is disabled.',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption',
|
||||
name: 'serverSideEncryption',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'AES256',
|
||||
value: 'AES256',
|
||||
},
|
||||
{
|
||||
name: 'AWS:KMS',
|
||||
value: 'aws:kms',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description:
|
||||
'The server-side encryption algorithm used when storing this object in Amazon S3',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption Context',
|
||||
name: 'serverSideEncryptionContext',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Specifies the AWS KMS Encryption Context to use for object encryption',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption AWS KMS Key ID',
|
||||
name: 'encryptionAwsKmsKeyId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'If x-amz-server-side-encryption is present and has the value of aws:kms',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption Customer Algorithm',
|
||||
name: 'serversideEncryptionCustomerAlgorithm',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description:
|
||||
'Specifies the algorithm to use to when encrypting the object (for example, AES256)',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption Customer Key',
|
||||
name: 'serversideEncryptionCustomerKey',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description:
|
||||
'Specifies the customer-provided encryption key for Amazon S3 to use in encrypting data',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption Customer Key MD5',
|
||||
name: 'serversideEncryptionCustomerKeyMD5',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Specifies the 128-bit MD5 digest of the encryption key according to RFC 1321',
|
||||
},
|
||||
{
|
||||
displayName: 'Storage Class',
|
||||
name: 'storageClass',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Deep Archive',
|
||||
value: 'deepArchive',
|
||||
},
|
||||
{
|
||||
name: 'Glacier',
|
||||
value: 'glacier',
|
||||
},
|
||||
{
|
||||
name: 'Intelligent Tiering',
|
||||
value: 'intelligentTiering',
|
||||
},
|
||||
{
|
||||
name: 'One Zone IA',
|
||||
value: 'onezoneIA',
|
||||
},
|
||||
{
|
||||
name: 'Standard',
|
||||
value: 'standard',
|
||||
},
|
||||
{
|
||||
name: 'Standard IA',
|
||||
value: 'standardIA',
|
||||
},
|
||||
],
|
||||
default: 'standard',
|
||||
description: 'Amazon S3 storage classes',
|
||||
},
|
||||
{
|
||||
displayName: 'Tagging Directive',
|
||||
name: 'taggingDirective',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Copy',
|
||||
value: 'copy',
|
||||
},
|
||||
{
|
||||
name: 'Replace',
|
||||
value: 'replace',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description:
|
||||
'Specifies whether the metadata is copied from the source object or replaced with metadata provided in the request',
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* file:upload */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Bucket Name',
|
||||
name: 'bucketName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['upload'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'File Name',
|
||||
name: 'fileName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'hello.txt',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['upload'],
|
||||
binaryData: [false],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'File Name',
|
||||
name: 'fileName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['upload'],
|
||||
binaryData: [true],
|
||||
},
|
||||
},
|
||||
description: 'If not set the binary data filename will be used',
|
||||
},
|
||||
{
|
||||
displayName: 'Binary File',
|
||||
name: 'binaryData',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['upload'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
description: 'Whether the data to upload should be taken from binary field',
|
||||
},
|
||||
{
|
||||
displayName: 'File Content',
|
||||
name: 'fileContent',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['upload'],
|
||||
resource: ['file'],
|
||||
binaryData: [false],
|
||||
},
|
||||
},
|
||||
placeholder: '',
|
||||
description: 'The text content of the file to upload',
|
||||
},
|
||||
{
|
||||
displayName: 'Input Binary Field',
|
||||
name: 'binaryPropertyName',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['upload'],
|
||||
resource: ['file'],
|
||||
binaryData: [true],
|
||||
},
|
||||
},
|
||||
placeholder: '',
|
||||
hint: 'The name of the input binary field containing the file to be uploaded',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['upload'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'ACL',
|
||||
name: 'acl',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Authenticated Read',
|
||||
value: 'authenticatedRead',
|
||||
},
|
||||
{
|
||||
name: 'AWS Exec Read',
|
||||
value: 'awsExecRead',
|
||||
},
|
||||
{
|
||||
name: 'Bucket Owner Full Control',
|
||||
value: 'bucketOwnerFullControl',
|
||||
},
|
||||
{
|
||||
name: 'Bucket Owner Read',
|
||||
value: 'bucketOwnerRead',
|
||||
},
|
||||
{
|
||||
name: 'Private',
|
||||
value: 'private',
|
||||
},
|
||||
{
|
||||
name: 'Public Read',
|
||||
value: 'publicRead',
|
||||
},
|
||||
{
|
||||
name: 'Public Read Write',
|
||||
value: 'publicReadWrite',
|
||||
},
|
||||
],
|
||||
default: 'private',
|
||||
description: 'The canned ACL to apply to the object',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Full Control',
|
||||
name: 'grantFullControl',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether to give the grantee READ, READ_ACP, and WRITE_ACP permissions on the object',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Read',
|
||||
name: 'grantRead',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to allow grantee to read the object data and its metadata',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Read ACP',
|
||||
name: 'grantReadAcp',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to allow grantee to read the object ACL',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Write ACP',
|
||||
name: 'grantWriteAcp',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to allow grantee to write the ACL for the applicable object',
|
||||
},
|
||||
{
|
||||
displayName: 'Lock Legal Hold',
|
||||
name: 'lockLegalHold',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether a legal hold will be applied to this object',
|
||||
},
|
||||
{
|
||||
displayName: 'Lock Mode',
|
||||
name: 'lockMode',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Governance',
|
||||
value: 'governance',
|
||||
},
|
||||
{
|
||||
name: 'Compliance',
|
||||
value: 'compliance',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'The Object Lock mode that you want to apply to this object',
|
||||
},
|
||||
{
|
||||
displayName: 'Lock Retain Until Date',
|
||||
name: 'lockRetainUntilDate',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: "The date and time when you want this object's Object Lock to expire",
|
||||
},
|
||||
{
|
||||
displayName: 'Parent Folder Key',
|
||||
name: 'parentFolderKey',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Parent folder you want to create the file in',
|
||||
},
|
||||
{
|
||||
displayName: 'Requester Pays',
|
||||
name: 'requesterPays',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether the requester will pay for requests and data transfer. While Requester Pays is enabled, anonymous access to this bucket is disabled.',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption',
|
||||
name: 'serverSideEncryption',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'AES256',
|
||||
value: 'AES256',
|
||||
},
|
||||
{
|
||||
name: 'AWS:KMS',
|
||||
value: 'aws:kms',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description:
|
||||
'The server-side encryption algorithm used when storing this object in Amazon S3',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption Context',
|
||||
name: 'serverSideEncryptionContext',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Specifies the AWS KMS Encryption Context to use for object encryption',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption AWS KMS Key ID',
|
||||
name: 'encryptionAwsKmsKeyId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'If x-amz-server-side-encryption is present and has the value of aws:kms',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption Customer Algorithm',
|
||||
name: 'serversideEncryptionCustomerAlgorithm',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description:
|
||||
'Specifies the algorithm to use to when encrypting the object (for example, AES256)',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption Customer Key',
|
||||
name: 'serversideEncryptionCustomerKey',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description:
|
||||
'Specifies the customer-provided encryption key for Amazon S3 to use in encrypting data',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption Customer Key MD5',
|
||||
name: 'serversideEncryptionCustomerKeyMD5',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Specifies the 128-bit MD5 digest of the encryption key according to RFC 1321',
|
||||
},
|
||||
{
|
||||
displayName: 'Storage Class',
|
||||
name: 'storageClass',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Deep Archive',
|
||||
value: 'deepArchive',
|
||||
},
|
||||
{
|
||||
name: 'Glacier',
|
||||
value: 'glacier',
|
||||
},
|
||||
{
|
||||
name: 'Intelligent Tiering',
|
||||
value: 'intelligentTiering',
|
||||
},
|
||||
{
|
||||
name: 'One Zone IA',
|
||||
value: 'onezoneIA',
|
||||
},
|
||||
{
|
||||
name: 'Standard',
|
||||
value: 'standard',
|
||||
},
|
||||
{
|
||||
name: 'Standard IA',
|
||||
value: 'standardIA',
|
||||
},
|
||||
],
|
||||
default: 'standard',
|
||||
description: 'Amazon S3 storage classes',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Tags',
|
||||
name: 'tagsUi',
|
||||
placeholder: 'Add Tag',
|
||||
type: 'fixedCollection',
|
||||
default: {},
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['upload'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'tagsValues',
|
||||
displayName: 'Tag',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Key',
|
||||
name: 'key',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
description: 'Optional extra headers to add to the message (most headers are allowed)',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* file:download */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Bucket Name',
|
||||
name: 'bucketName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['download'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'File Key',
|
||||
name: 'fileKey',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['download'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Put Output File in Field',
|
||||
name: 'binaryPropertyName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: 'data',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['download'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
hint: 'The name of the output binary field to put the file in',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* file:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Bucket Name',
|
||||
name: 'bucketName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['delete'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'File Key',
|
||||
name: 'fileKey',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['delete'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['delete'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Version ID',
|
||||
name: 'versionId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* file:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Bucket Name',
|
||||
name: 'bucketName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getAll'],
|
||||
resource: ['file'],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getAll'],
|
||||
resource: ['file'],
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'Max number of results to return',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['file'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Fetch Owner',
|
||||
name: 'fetchOwner',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
|
||||
description:
|
||||
'The owner field is not present in listV2 by default, if you want to return owner field with each key in the result then set the fetch owner field to true',
|
||||
},
|
||||
{
|
||||
displayName: 'Folder Key',
|
||||
name: 'folderKey',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,241 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const folderOperations: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['folder'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a folder',
|
||||
action: 'Create a folder',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a folder',
|
||||
action: 'Delete a folder',
|
||||
},
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
description: 'Get many folders',
|
||||
action: 'Get many folders',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
},
|
||||
];
|
||||
|
||||
export const folderFields: INodeProperties[] = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Bucket Name',
|
||||
name: 'bucketName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['folder'],
|
||||
operation: ['create'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Folder Name',
|
||||
name: 'folderName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['folder'],
|
||||
operation: ['create'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['folder'],
|
||||
operation: ['create'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Parent Folder Key',
|
||||
name: 'parentFolderKey',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Parent folder you want to create the folder in',
|
||||
},
|
||||
{
|
||||
displayName: 'Requester Pays',
|
||||
name: 'requesterPays',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether the requester will pay for requests and data transfer. While Requester Pays is enabled, anonymous access to this bucket is disabled.',
|
||||
},
|
||||
{
|
||||
displayName: 'Storage Class',
|
||||
name: 'storageClass',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Deep Archive',
|
||||
value: 'deepArchive',
|
||||
},
|
||||
{
|
||||
name: 'Glacier',
|
||||
value: 'glacier',
|
||||
},
|
||||
{
|
||||
name: 'Intelligent Tiering',
|
||||
value: 'intelligentTiering',
|
||||
},
|
||||
{
|
||||
name: 'One Zone IA',
|
||||
value: 'onezoneIA',
|
||||
},
|
||||
{
|
||||
name: 'Reduced Redundancy',
|
||||
value: 'RecudedRedundancy',
|
||||
},
|
||||
{
|
||||
name: 'Standard',
|
||||
value: 'standard',
|
||||
},
|
||||
{
|
||||
name: 'Standard IA',
|
||||
value: 'standardIA',
|
||||
},
|
||||
],
|
||||
default: 'standard',
|
||||
description: 'Amazon S3 storage classes',
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Bucket Name',
|
||||
name: 'bucketName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['folder'],
|
||||
operation: ['delete'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Folder Key',
|
||||
name: 'folderKey',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['folder'],
|
||||
operation: ['delete'],
|
||||
},
|
||||
},
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Bucket Name',
|
||||
name: 'bucketName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['folder'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getAll'],
|
||||
resource: ['folder'],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getAll'],
|
||||
resource: ['folder'],
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'Max number of results to return',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['folder'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Fetch Owner',
|
||||
name: 'fetchOwner',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
|
||||
description:
|
||||
'The owner field is not present in listV2 by default, if you want to return owner field with each key in the result then set the fetch owner field to true',
|
||||
},
|
||||
{
|
||||
displayName: 'Folder Key',
|
||||
name: 'folderKey',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,136 @@
|
||||
import get from 'lodash/get';
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
IHookFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
IWebhookFunctions,
|
||||
IHttpRequestOptions,
|
||||
IHttpRequestMethods,
|
||||
} from 'n8n-workflow';
|
||||
import { parseString } from 'xml2js';
|
||||
import { getAwsCredentials } from '../../GenericFunctions';
|
||||
|
||||
export async function awsApiRequest(
|
||||
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions,
|
||||
service: string,
|
||||
method: IHttpRequestMethods,
|
||||
path: string,
|
||||
body?: string | Buffer | any,
|
||||
query: IDataObject = {},
|
||||
headers?: object,
|
||||
option: IDataObject = {},
|
||||
_region?: string,
|
||||
): Promise<any> {
|
||||
const requestOptions = {
|
||||
qs: {
|
||||
...query,
|
||||
service,
|
||||
path,
|
||||
query,
|
||||
_region,
|
||||
},
|
||||
method,
|
||||
body,
|
||||
url: '',
|
||||
headers,
|
||||
} as IHttpRequestOptions;
|
||||
|
||||
if (Object.keys(option).length !== 0) {
|
||||
Object.assign(requestOptions, option);
|
||||
}
|
||||
const { credentialsType } = await getAwsCredentials(this);
|
||||
|
||||
return await this.helpers.requestWithAuthentication.call(this, credentialsType, requestOptions);
|
||||
}
|
||||
|
||||
export async function awsApiRequestREST(
|
||||
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,
|
||||
service: string,
|
||||
method: IHttpRequestMethods,
|
||||
path: string,
|
||||
body?: string | Buffer | any,
|
||||
query: IDataObject = {},
|
||||
headers?: object,
|
||||
options: IDataObject = {},
|
||||
region?: string,
|
||||
): Promise<any> {
|
||||
const response = await awsApiRequest.call(
|
||||
this,
|
||||
service,
|
||||
method,
|
||||
path,
|
||||
body,
|
||||
query,
|
||||
headers,
|
||||
options,
|
||||
region,
|
||||
);
|
||||
try {
|
||||
if (response.includes('<?xml version="1.0" encoding="UTF-8"?>')) {
|
||||
return await new Promise((resolve, reject) => {
|
||||
parseString(response as string, { explicitArray: false }, (err, data) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
return JSON.parse(response as string);
|
||||
} catch (error) {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
export async function awsApiRequestRESTAllItems(
|
||||
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,
|
||||
propertyName: string,
|
||||
service: string,
|
||||
method: IHttpRequestMethods,
|
||||
path: string,
|
||||
body?: string,
|
||||
query: IDataObject = {},
|
||||
headers?: object,
|
||||
option: IDataObject = {},
|
||||
region?: string,
|
||||
): Promise<any> {
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
let responseData;
|
||||
do {
|
||||
responseData = await awsApiRequestREST.call(
|
||||
this,
|
||||
service,
|
||||
method,
|
||||
path,
|
||||
body,
|
||||
query,
|
||||
headers,
|
||||
option,
|
||||
region,
|
||||
);
|
||||
//https://forums.aws.amazon.com/thread.jspa?threadID=55746
|
||||
if (get(responseData, [propertyName.split('.')[0], 'NextContinuationToken'])) {
|
||||
query['continuation-token'] = get(responseData, [
|
||||
propertyName.split('.')[0],
|
||||
'NextContinuationToken',
|
||||
]);
|
||||
}
|
||||
if (get(responseData, propertyName)) {
|
||||
if (Array.isArray(get(responseData, propertyName))) {
|
||||
returnData.push.apply(returnData, get(responseData, propertyName) as IDataObject[]);
|
||||
} else {
|
||||
returnData.push(get(responseData, propertyName) as IDataObject);
|
||||
}
|
||||
}
|
||||
const limit = query.limit as number | undefined;
|
||||
if (limit && limit <= returnData.length) {
|
||||
return returnData;
|
||||
}
|
||||
} while (
|
||||
get(responseData, [propertyName.split('.')[0], 'IsTruncated']) !== undefined &&
|
||||
get(responseData, [propertyName.split('.')[0], 'IsTruncated']) !== 'false'
|
||||
);
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"BucketArn": {
|
||||
"type": "string"
|
||||
},
|
||||
"CreationDate": {
|
||||
"type": "string"
|
||||
},
|
||||
"Name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 2
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ChecksumAlgorithm": {
|
||||
"type": "string"
|
||||
},
|
||||
"ChecksumType": {
|
||||
"type": "string"
|
||||
},
|
||||
"ETag": {
|
||||
"type": "string"
|
||||
},
|
||||
"Key": {
|
||||
"type": "string"
|
||||
},
|
||||
"LastModified": {
|
||||
"type": "string"
|
||||
},
|
||||
"Size": {
|
||||
"type": "string"
|
||||
},
|
||||
"StorageClass": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"$": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"xmlns": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ChecksumCRC64NVME": {
|
||||
"type": "string"
|
||||
},
|
||||
"ETag": {
|
||||
"type": "string"
|
||||
},
|
||||
"LastModified": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ETag": {
|
||||
"type": "string"
|
||||
},
|
||||
"Key": {
|
||||
"type": "string"
|
||||
},
|
||||
"LastModified": {
|
||||
"type": "string"
|
||||
},
|
||||
"Size": {
|
||||
"type": "string"
|
||||
},
|
||||
"StorageClass": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ChecksumAlgorithm": {
|
||||
"type": "string"
|
||||
},
|
||||
"ChecksumType": {
|
||||
"type": "string"
|
||||
},
|
||||
"ETag": {
|
||||
"type": "string"
|
||||
},
|
||||
"Key": {
|
||||
"type": "string"
|
||||
},
|
||||
"LastModified": {
|
||||
"type": "string"
|
||||
},
|
||||
"Size": {
|
||||
"type": "string"
|
||||
},
|
||||
"StorageClass": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 3
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"$": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"xmlns": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Bucket": {
|
||||
"type": "string"
|
||||
},
|
||||
"ChecksumCRC64NVME": {
|
||||
"type": "string"
|
||||
},
|
||||
"ChecksumType": {
|
||||
"type": "string"
|
||||
},
|
||||
"ETag": {
|
||||
"type": "string"
|
||||
},
|
||||
"Key": {
|
||||
"type": "string"
|
||||
},
|
||||
"Location": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ChecksumAlgorithm": {
|
||||
"type": "string"
|
||||
},
|
||||
"ChecksumType": {
|
||||
"type": "string"
|
||||
},
|
||||
"ETag": {
|
||||
"type": "string"
|
||||
},
|
||||
"Key": {
|
||||
"type": "string"
|
||||
},
|
||||
"LastModified": {
|
||||
"type": "string"
|
||||
},
|
||||
"Size": {
|
||||
"type": "string"
|
||||
},
|
||||
"StorageClass": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 2
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="2065" height="2500" preserveAspectRatio="xMidYMid" viewBox="0 0 256 310"><path fill="#8C3123" d="M20.624 53.686 0 64v181.02l20.624 10.254.124-.149V53.828z"/><path fill="#E05243" d="M131 229 20.624 255.274V53.686L131 79.387z"/><path fill="#8C3123" d="m81.178 187.866 46.818 5.96.294-.678.263-76.77-.557-.6-46.818 5.874z"/><path fill="#8C3123" d="m127.996 229.295 107.371 26.035.169-.269-.003-201.195-.17-.18-107.367 25.996z"/><path fill="#E05243" d="m174.827 187.866-46.831 5.96v-78.048l46.831 5.874z"/><path fill="#5E1F18" d="m174.827 89.631-46.831 8.535-46.818-8.535 46.759-12.256z"/><path fill="#F2B0A9" d="m174.827 219.801-46.831-8.591-46.818 8.591 46.761 13.053z"/><path fill="#8C3123" d="m81.178 89.631 46.818-11.586.379-.117V.313L127.996 0 81.178 23.413z"/><path fill="#E05243" d="m174.827 89.631-46.831-11.586V0l46.831 23.413z"/><path fill="#8C3123" d="m127.996 309.428-46.823-23.405v-66.217l46.823 11.582.689.783-.187 75.906z"/><path fill="#E05243" d="m127.996 309.428 46.827-23.405v-66.217l-46.827 11.582zM235.367 53.686 256 64v181.02l-20.633 10.31z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"name": "Test S3 upload",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "8f35d24b-1493-43a4-846f-bacb577bfcb2",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [540, 340]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "jsonToBinary",
|
||||
"options": {}
|
||||
},
|
||||
"id": "eae2946a-1a1e-47e9-9fd6-e32119b13ec0",
|
||||
"name": "Move Binary Data",
|
||||
"type": "n8n-nodes-base.moveBinaryData",
|
||||
"typeVersion": 1,
|
||||
"position": [900, 340]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "upload",
|
||||
"bucketName": "bucket",
|
||||
"fileName": "binary.json",
|
||||
"additionalFields": {}
|
||||
},
|
||||
"id": "6f21fa3f-ede1-44b1-8182-a2c07152f666",
|
||||
"name": "AWS S3",
|
||||
"type": "n8n-nodes-base.awsS3",
|
||||
"typeVersion": 1,
|
||||
"position": [1080, 340],
|
||||
"credentials": {
|
||||
"aws": {
|
||||
"id": "1",
|
||||
"name": "AWS account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"data": [
|
||||
{
|
||||
"key": "value"
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": "e12f1876-cfd1-47a4-a21b-d478452683bc",
|
||||
"name": "Code",
|
||||
"type": "n8n-nodes-testing.testData",
|
||||
"typeVersion": 1,
|
||||
"position": [720, 340]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Move Binary Data": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "AWS S3",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Code": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Move Binary Data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"pinData": {
|
||||
"AWS S3": [
|
||||
{
|
||||
"json": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
import { credentials } from '../../../__tests__/credentials';
|
||||
|
||||
describe('Test S3 V1 Node', () => {
|
||||
describe('File Upload', () => {
|
||||
let mock: nock.Scope;
|
||||
const now = 1683028800000;
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.useFakeTimers({ doNotFake: ['nextTick'], now });
|
||||
|
||||
mock = nock('https://bucket.s3.eu-central-1.amazonaws.com');
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
mock.get('/?location').reply(
|
||||
200,
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<LocationConstraint>
|
||||
<LocationConstraint>eu-central-1</LocationConstraint>
|
||||
</LocationConstraint>`,
|
||||
{
|
||||
'content-type': 'application/xml',
|
||||
},
|
||||
);
|
||||
|
||||
mock
|
||||
.put('/binary.json')
|
||||
.matchHeader(
|
||||
'X-Amz-Content-Sha256',
|
||||
'e43abcf3375244839c012f9633f95862d232a95b00d5bc7348b3098b9fed7f32',
|
||||
)
|
||||
.once()
|
||||
.reply(200, { success: true });
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({ credentials });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,339 @@
|
||||
import { mockDeep } from 'jest-mock-extended';
|
||||
import type { IExecuteFunctions, ILoadOptionsFunctions } from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
awsApiRequest,
|
||||
awsApiRequestREST,
|
||||
awsApiRequestSOAP,
|
||||
awsApiRequestSOAPAllItems,
|
||||
} from '../../V1/GenericFunctions';
|
||||
|
||||
describe('AWS S3 V1 GenericFunctions', () => {
|
||||
describe('awsApiRequest', () => {
|
||||
let mockExecuteFunctions: jest.Mocked<IExecuteFunctions>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockExecuteFunctions = mockDeep<IExecuteFunctions>();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should make AWS API request with basic parameters', async () => {
|
||||
const mockResponse = { success: true };
|
||||
const mockRequestWithAuth = mockExecuteFunctions.helpers
|
||||
.requestWithAuthentication as jest.Mock;
|
||||
mockRequestWithAuth.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await awsApiRequest.call(
|
||||
mockExecuteFunctions,
|
||||
's3',
|
||||
'GET',
|
||||
'/bucket',
|
||||
'',
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
'us-east-1',
|
||||
);
|
||||
|
||||
expect(result).toEqual(mockResponse);
|
||||
expect(mockRequestWithAuth).toHaveBeenCalledWith(
|
||||
'aws',
|
||||
expect.objectContaining({
|
||||
qs: expect.objectContaining({
|
||||
service: 's3',
|
||||
path: '/bucket',
|
||||
}),
|
||||
method: 'GET',
|
||||
body: '',
|
||||
url: '',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle query parameters correctly', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
const queryParams = { 'list-type': '2', 'max-keys': '10' };
|
||||
const mockRequestWithAuth = mockExecuteFunctions.helpers
|
||||
.requestWithAuthentication as jest.Mock;
|
||||
mockRequestWithAuth.mockResolvedValue(mockResponse);
|
||||
|
||||
await awsApiRequest.call(
|
||||
mockExecuteFunctions,
|
||||
's3',
|
||||
'GET',
|
||||
'/bucket',
|
||||
'',
|
||||
queryParams,
|
||||
{},
|
||||
{},
|
||||
);
|
||||
|
||||
expect(mockRequestWithAuth).toHaveBeenCalledWith(
|
||||
'aws',
|
||||
expect.objectContaining({
|
||||
qs: expect.objectContaining({
|
||||
...queryParams,
|
||||
service: 's3',
|
||||
path: '/bucket',
|
||||
query: queryParams,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('awsApiRequestREST', () => {
|
||||
let mockExecuteFunctions: jest.Mocked<IExecuteFunctions>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockExecuteFunctions = mockDeep<IExecuteFunctions>();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should parse valid JSON response', async () => {
|
||||
const jsonString = JSON.stringify({ id: '123', name: 'test' });
|
||||
const mockRequestWithAuth = mockExecuteFunctions.helpers
|
||||
.requestWithAuthentication as jest.Mock;
|
||||
mockRequestWithAuth.mockResolvedValue(jsonString);
|
||||
|
||||
const result = await awsApiRequestREST.call(mockExecuteFunctions, 's3', 'GET', '/bucket');
|
||||
|
||||
expect(result).toEqual({ id: '123', name: 'test' });
|
||||
});
|
||||
|
||||
it('should return raw response when JSON parsing fails', async () => {
|
||||
const rawResponse = 'not valid json';
|
||||
const mockRequestWithAuth = mockExecuteFunctions.helpers
|
||||
.requestWithAuthentication as jest.Mock;
|
||||
mockRequestWithAuth.mockResolvedValue(rawResponse);
|
||||
|
||||
const result = await awsApiRequestREST.call(mockExecuteFunctions, 's3', 'GET', '/bucket');
|
||||
|
||||
expect(result).toBe(rawResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('awsApiRequestSOAP', () => {
|
||||
let mockExecuteFunctions: jest.Mocked<IExecuteFunctions>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockExecuteFunctions = mockDeep<IExecuteFunctions>();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should parse valid XML response', async () => {
|
||||
const xmlResponse =
|
||||
'<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Name>test-bucket</Name></ListBucketResult>';
|
||||
|
||||
const mockRequestWithAuth = mockExecuteFunctions.helpers
|
||||
.requestWithAuthentication as jest.Mock;
|
||||
mockRequestWithAuth.mockResolvedValue(xmlResponse);
|
||||
|
||||
const result = await awsApiRequestSOAP.call(mockExecuteFunctions, 's3', 'GET', '/bucket');
|
||||
|
||||
expect(result).toHaveProperty('ListBucketResult.Name', 'test-bucket');
|
||||
});
|
||||
|
||||
it('should return error when XML parsing fails', async () => {
|
||||
const invalidXml = 'not valid xml';
|
||||
|
||||
const mockRequestWithAuth = mockExecuteFunctions.helpers
|
||||
.requestWithAuthentication as jest.Mock;
|
||||
mockRequestWithAuth.mockResolvedValue(invalidXml);
|
||||
|
||||
const result = await awsApiRequestSOAP.call(mockExecuteFunctions, 's3', 'GET', '/bucket');
|
||||
|
||||
expect(result).toBeInstanceOf(Error);
|
||||
});
|
||||
});
|
||||
|
||||
describe('awsApiRequestSOAPAllItems', () => {
|
||||
let mockExecuteFunctions: jest.Mocked<IExecuteFunctions>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockExecuteFunctions = mockDeep<IExecuteFunctions>();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should collect all items from single page response', async () => {
|
||||
const xmlResponse =
|
||||
'<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Contents><Key>file1.txt</Key><Size>1024</Size></Contents><Contents><Key>file2.txt</Key><Size>2048</Size></Contents><IsTruncated>false</IsTruncated></ListBucketResult>';
|
||||
|
||||
const mockRequestWithAuth = mockExecuteFunctions.helpers
|
||||
.requestWithAuthentication as jest.Mock;
|
||||
mockRequestWithAuth.mockResolvedValue(xmlResponse);
|
||||
|
||||
const result = await awsApiRequestSOAPAllItems.call(
|
||||
mockExecuteFunctions,
|
||||
'ListBucketResult.Contents',
|
||||
's3',
|
||||
'GET',
|
||||
'/bucket',
|
||||
);
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toEqual([
|
||||
{ Key: 'file1.txt', Size: '1024' },
|
||||
{ Key: 'file2.txt', Size: '2048' },
|
||||
]);
|
||||
expect(mockRequestWithAuth).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle empty response', async () => {
|
||||
const xmlResponse =
|
||||
'<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><IsTruncated>false</IsTruncated></ListBucketResult>';
|
||||
|
||||
const mockRequestWithAuth = mockExecuteFunctions.helpers
|
||||
.requestWithAuthentication as jest.Mock;
|
||||
mockRequestWithAuth.mockResolvedValue(xmlResponse);
|
||||
|
||||
const result = await awsApiRequestSOAPAllItems.call(
|
||||
mockExecuteFunctions,
|
||||
'ListBucketResult.Contents',
|
||||
's3',
|
||||
'GET',
|
||||
'/bucket',
|
||||
);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
expect(mockRequestWithAuth).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle pagination with NextContinuationToken', async () => {
|
||||
const firstPageResponse =
|
||||
'<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Contents><Key>file1.txt</Key><Size>1024</Size></Contents><IsTruncated>true</IsTruncated><NextContinuationToken>token123</NextContinuationToken></ListBucketResult>';
|
||||
|
||||
const secondPageResponse =
|
||||
'<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Contents><Key>file2.txt</Key><Size>2048</Size></Contents><IsTruncated>false</IsTruncated></ListBucketResult>';
|
||||
|
||||
const mockRequestWithAuth = mockExecuteFunctions.helpers
|
||||
.requestWithAuthentication as jest.Mock;
|
||||
mockRequestWithAuth
|
||||
.mockResolvedValueOnce(firstPageResponse)
|
||||
.mockResolvedValueOnce(secondPageResponse);
|
||||
|
||||
const result = await awsApiRequestSOAPAllItems.call(
|
||||
mockExecuteFunctions,
|
||||
'ListBucketResult.Contents',
|
||||
's3',
|
||||
'GET',
|
||||
'/bucket',
|
||||
'',
|
||||
{},
|
||||
);
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toEqual([
|
||||
{ Key: 'file1.txt', Size: '1024' },
|
||||
{ Key: 'file2.txt', Size: '2048' },
|
||||
]);
|
||||
expect(mockRequestWithAuth).toHaveBeenCalledTimes(2);
|
||||
|
||||
// Verify that continuation token was passed in the second call
|
||||
expect(mockRequestWithAuth).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'aws',
|
||||
expect.objectContaining({
|
||||
qs: expect.objectContaining({
|
||||
'continuation-token': 'token123',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should respect limit parameter and stop early', async () => {
|
||||
const firstPageResponse =
|
||||
'<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Contents><Key>file1.txt</Key><Size>1024</Size></Contents><Contents><Key>file2.txt</Key><Size>2048</Size></Contents><IsTruncated>true</IsTruncated><NextContinuationToken>token123</NextContinuationToken></ListBucketResult>';
|
||||
|
||||
const secondPageResponse =
|
||||
'<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Contents><Key>file3.txt</Key><Size>3072</Size></Contents><IsTruncated>false</IsTruncated></ListBucketResult>';
|
||||
|
||||
const mockRequestWithAuth = mockExecuteFunctions.helpers
|
||||
.requestWithAuthentication as jest.Mock;
|
||||
mockRequestWithAuth
|
||||
.mockResolvedValueOnce(firstPageResponse)
|
||||
.mockResolvedValueOnce(secondPageResponse);
|
||||
|
||||
const result = await awsApiRequestSOAPAllItems.call(
|
||||
mockExecuteFunctions,
|
||||
'ListBucketResult.Contents',
|
||||
's3',
|
||||
'GET',
|
||||
'/bucket',
|
||||
'',
|
||||
{ limit: 2 },
|
||||
);
|
||||
|
||||
// Should stop after collecting 2 items, even though there are more pages
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result).toEqual([
|
||||
{ Key: 'file1.txt', Size: '1024' },
|
||||
{ Key: 'file2.txt', Size: '2048' },
|
||||
]);
|
||||
expect(mockRequestWithAuth).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle single item response (not an array)', async () => {
|
||||
const xmlResponse =
|
||||
'<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Contents><Key>single-file.txt</Key><Size>512</Size></Contents><IsTruncated>false</IsTruncated></ListBucketResult>';
|
||||
|
||||
const mockRequestWithAuth = mockExecuteFunctions.helpers
|
||||
.requestWithAuthentication as jest.Mock;
|
||||
mockRequestWithAuth.mockResolvedValue(xmlResponse);
|
||||
|
||||
const result = await awsApiRequestSOAPAllItems.call(
|
||||
mockExecuteFunctions,
|
||||
'ListBucketResult.Contents',
|
||||
's3',
|
||||
'GET',
|
||||
'/bucket',
|
||||
);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result).toEqual([{ Key: 'single-file.txt', Size: '512' }]);
|
||||
expect(mockRequestWithAuth).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle error responses from SOAP parsing', async () => {
|
||||
const invalidXml = 'invalid xml response';
|
||||
|
||||
const mockRequestWithAuth = mockExecuteFunctions.helpers
|
||||
.requestWithAuthentication as jest.Mock;
|
||||
mockRequestWithAuth.mockResolvedValue(invalidXml);
|
||||
|
||||
const result = await awsApiRequestSOAPAllItems.call(
|
||||
mockExecuteFunctions,
|
||||
'ListBucketResult.Contents',
|
||||
's3',
|
||||
'GET',
|
||||
'/bucket',
|
||||
);
|
||||
|
||||
// When XML parsing fails, awsApiRequestSOAP returns an Error object
|
||||
// and awsApiRequestSOAPAllItems should handle this gracefully
|
||||
expect(result).toEqual([]);
|
||||
expect(mockRequestWithAuth).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should work with different function contexts', async () => {
|
||||
const mockLoadOptionsFunctions = mockDeep<ILoadOptionsFunctions>();
|
||||
const xmlResponse =
|
||||
'<?xml version="1.0" encoding="UTF-8"?><ListBucketResult><Contents><Key>file1.txt</Key></Contents><IsTruncated>false</IsTruncated></ListBucketResult>';
|
||||
|
||||
const mockRequestWithAuth = mockLoadOptionsFunctions.helpers
|
||||
.requestWithAuthentication as jest.Mock;
|
||||
mockRequestWithAuth.mockResolvedValue(xmlResponse);
|
||||
|
||||
const result = await awsApiRequestSOAPAllItems.call(
|
||||
mockLoadOptionsFunctions,
|
||||
'ListBucketResult.Contents',
|
||||
's3',
|
||||
'GET',
|
||||
'/bucket',
|
||||
);
|
||||
|
||||
expect(result).toEqual([{ Key: 'file1.txt' }]);
|
||||
expect(mockRequestWithAuth).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"name": "Test S3 upload",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "8f35d24b-1493-43a4-846f-bacb577bfcb2",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [540, 340]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "jsonToBinary",
|
||||
"options": {}
|
||||
},
|
||||
"id": "eae2946a-1a1e-47e9-9fd6-e32119b13ec0",
|
||||
"name": "Move Binary Data",
|
||||
"type": "n8n-nodes-base.moveBinaryData",
|
||||
"typeVersion": 1,
|
||||
"position": [900, 340]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "upload",
|
||||
"bucketName": "buc.ket",
|
||||
"fileName": "binary.json",
|
||||
"additionalFields": {}
|
||||
},
|
||||
"id": "6f21fa3f-ede1-44b1-8182-a2c07152f666",
|
||||
"name": "AWS S3",
|
||||
"type": "n8n-nodes-base.awsS3",
|
||||
"typeVersion": 2,
|
||||
"position": [1080, 340],
|
||||
"credentials": {
|
||||
"aws": {
|
||||
"id": "1",
|
||||
"name": "AWS account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"data": [
|
||||
{
|
||||
"key": "value"
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": "e12f1876-cfd1-47a4-a21b-d478452683bc",
|
||||
"name": "Code",
|
||||
"type": "n8n-nodes-testing.testData",
|
||||
"typeVersion": 1,
|
||||
"position": [720, 340]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Move Binary Data": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "AWS S3",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Code": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Move Binary Data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"pinData": {
|
||||
"AWS S3": [
|
||||
{
|
||||
"json": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
import { mockDeep } from 'jest-mock-extended';
|
||||
import type { IExecuteFunctions, INode } from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import { AwsS3V2 } from '../../V2/AwsS3V2.node';
|
||||
import * as GenericFunctions from '../../V2/GenericFunctions';
|
||||
|
||||
const mockLocationResponse = {
|
||||
LocationConstraint: {
|
||||
_: 'eu-central-1',
|
||||
},
|
||||
};
|
||||
|
||||
const mockFileResponse = {
|
||||
body: Buffer.from('test file content'),
|
||||
headers: {
|
||||
'content-type': 'text/plain',
|
||||
},
|
||||
};
|
||||
|
||||
describe('AWS S3 V2 Node - File Download', () => {
|
||||
const executeFunctionsMock = mockDeep<IExecuteFunctions>();
|
||||
const awsApiRequestRESTSpy = jest.spyOn(GenericFunctions, 'awsApiRequestREST');
|
||||
let node: AwsS3V2;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
node = new AwsS3V2({
|
||||
displayName: 'AWS S3',
|
||||
name: 'awsS3',
|
||||
icon: 'file:s3.svg',
|
||||
group: ['output'],
|
||||
description: 'Sends data to AWS S3',
|
||||
});
|
||||
|
||||
executeFunctionsMock.getCredentials.mockResolvedValue({
|
||||
accessKeyId: 'test-key',
|
||||
secretAccessKey: 'test-secret',
|
||||
region: 'eu-central-1',
|
||||
});
|
||||
|
||||
executeFunctionsMock.getNode.mockReturnValue({
|
||||
typeVersion: 2,
|
||||
} as INode);
|
||||
|
||||
executeFunctionsMock.getInputData.mockReturnValue([{ json: { test: 'data' } }]);
|
||||
executeFunctionsMock.continueOnFail.mockReturnValue(false);
|
||||
|
||||
executeFunctionsMock.helpers.returnJsonArray.mockImplementation((data) =>
|
||||
Array.isArray(data) ? data.map((item) => ({ json: item })) : [{ json: data }],
|
||||
);
|
||||
|
||||
executeFunctionsMock.helpers.constructExecutionMetaData.mockImplementation(
|
||||
(data) => data as any,
|
||||
);
|
||||
|
||||
executeFunctionsMock.helpers.prepareBinaryData.mockResolvedValue({
|
||||
data: 'mock-binary-data-id',
|
||||
mimeType: 'text/plain',
|
||||
fileName: 'test.txt',
|
||||
});
|
||||
});
|
||||
|
||||
describe('successful file download', () => {
|
||||
beforeEach(() => {
|
||||
executeFunctionsMock.getNodeParameter.mockImplementation((paramName) => {
|
||||
switch (paramName) {
|
||||
case 'resource':
|
||||
return 'file';
|
||||
case 'operation':
|
||||
return 'download';
|
||||
case 'bucketName':
|
||||
return 'test-bucket';
|
||||
case 'fileKey':
|
||||
return 'path/to/test.txt';
|
||||
case 'binaryPropertyName':
|
||||
return 'data';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
||||
awsApiRequestRESTSpy
|
||||
.mockResolvedValueOnce(mockLocationResponse)
|
||||
.mockResolvedValueOnce(mockFileResponse);
|
||||
});
|
||||
|
||||
it('should successfully download a file and return binary data', async () => {
|
||||
const result = await node.execute.call(executeFunctionsMock);
|
||||
|
||||
expect(awsApiRequestRESTSpy).toHaveBeenCalledTimes(2);
|
||||
|
||||
expect(awsApiRequestRESTSpy).toHaveBeenNthCalledWith(1, 'test-bucket.s3', 'GET', '', '', {
|
||||
location: '',
|
||||
});
|
||||
|
||||
expect(awsApiRequestRESTSpy).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'test-bucket.s3',
|
||||
'GET',
|
||||
'/path/to/test.txt',
|
||||
'',
|
||||
{},
|
||||
{},
|
||||
{ encoding: null, resolveWithFullResponse: true },
|
||||
'eu-central-1',
|
||||
);
|
||||
|
||||
expect(executeFunctionsMock.helpers.prepareBinaryData).toHaveBeenCalledWith(
|
||||
expect.any(Buffer),
|
||||
'test.txt',
|
||||
'text/plain',
|
||||
);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toHaveLength(1);
|
||||
expect(result[0][0]).toHaveProperty('json');
|
||||
expect(result[0][0]).toHaveProperty('binary');
|
||||
});
|
||||
|
||||
it('should handle bucket names with dots correctly', async () => {
|
||||
executeFunctionsMock.getNodeParameter.mockImplementation((paramName) => {
|
||||
switch (paramName) {
|
||||
case 'resource':
|
||||
return 'file';
|
||||
case 'operation':
|
||||
return 'download';
|
||||
case 'bucketName':
|
||||
return 'test.bucket.com';
|
||||
case 'fileKey':
|
||||
return 'path/to/test.txt';
|
||||
case 'binaryPropertyName':
|
||||
return 'data';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
||||
await node.execute.call(executeFunctionsMock);
|
||||
|
||||
expect(awsApiRequestRESTSpy).toHaveBeenNthCalledWith(1, 's3', 'GET', '/test.bucket.com', '', {
|
||||
location: '',
|
||||
});
|
||||
|
||||
expect(awsApiRequestRESTSpy).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
's3',
|
||||
'GET',
|
||||
'/test.bucket.com/path/to/test.txt',
|
||||
'',
|
||||
{},
|
||||
{},
|
||||
{ encoding: null, resolveWithFullResponse: true },
|
||||
'eu-central-1',
|
||||
);
|
||||
});
|
||||
|
||||
it('should extract filename correctly from different file key formats', async () => {
|
||||
const testCases = [
|
||||
{ fileKey: 'simple.txt', expectedFileName: 'simple.txt' },
|
||||
{ fileKey: 'path/to/file.pdf', expectedFileName: 'file.pdf' },
|
||||
{ fileKey: 'deep/nested/path/document.docx', expectedFileName: 'document.docx' },
|
||||
];
|
||||
|
||||
for (const testCase of testCases) {
|
||||
jest.clearAllMocks();
|
||||
awsApiRequestRESTSpy
|
||||
.mockResolvedValueOnce(mockLocationResponse)
|
||||
.mockResolvedValueOnce(mockFileResponse);
|
||||
|
||||
executeFunctionsMock.getNodeParameter.mockImplementation((paramName) => {
|
||||
switch (paramName) {
|
||||
case 'resource':
|
||||
return 'file';
|
||||
case 'operation':
|
||||
return 'download';
|
||||
case 'bucketName':
|
||||
return 'test-bucket';
|
||||
case 'fileKey':
|
||||
return testCase.fileKey;
|
||||
case 'binaryPropertyName':
|
||||
return 'data';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
||||
await node.execute.call(executeFunctionsMock);
|
||||
|
||||
expect(executeFunctionsMock.helpers.prepareBinaryData).toHaveBeenCalledWith(
|
||||
expect.any(Buffer),
|
||||
testCase.expectedFileName,
|
||||
'text/plain',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
beforeEach(() => {
|
||||
executeFunctionsMock.getNodeParameter.mockImplementation((paramName) => {
|
||||
switch (paramName) {
|
||||
case 'resource':
|
||||
return 'file';
|
||||
case 'operation':
|
||||
return 'download';
|
||||
case 'bucketName':
|
||||
return 'test-bucket';
|
||||
case 'fileKey':
|
||||
return 'path/to/directory/';
|
||||
case 'binaryPropertyName':
|
||||
return 'data';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw error when trying to download a directory', async () => {
|
||||
await expect(node.execute.call(executeFunctionsMock)).rejects.toThrow(NodeOperationError);
|
||||
await expect(node.execute.call(executeFunctionsMock)).rejects.toThrow(
|
||||
'Downloading a whole directory is not yet supported, please provide a file key',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('continueOnFail logic', () => {
|
||||
beforeEach(() => {
|
||||
executeFunctionsMock.getNodeParameter.mockImplementation((paramName) => {
|
||||
switch (paramName) {
|
||||
case 'resource':
|
||||
return 'file';
|
||||
case 'operation':
|
||||
return 'download';
|
||||
case 'bucketName':
|
||||
return 'test-bucket';
|
||||
case 'fileKey':
|
||||
return 'path/to/test.txt';
|
||||
case 'binaryPropertyName':
|
||||
return 'data';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should continue execution and return error data when continueOnFail is true', async () => {
|
||||
const testError = new Error('AWS API Error');
|
||||
executeFunctionsMock.continueOnFail.mockReturnValue(true);
|
||||
awsApiRequestRESTSpy.mockRejectedValue(testError);
|
||||
|
||||
const result = await node.execute.call(executeFunctionsMock);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toHaveLength(1);
|
||||
expect(result[0][0].json).toEqual({ error: 'AWS API Error' });
|
||||
|
||||
expect(executeFunctionsMock.helpers.constructExecutionMetaData).toHaveBeenCalledWith(
|
||||
[{ json: { error: 'AWS API Error' } }],
|
||||
{ itemData: { item: 0 } },
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error when continueOnFail is false', async () => {
|
||||
const testError = new Error('AWS API Error');
|
||||
executeFunctionsMock.continueOnFail.mockReturnValue(false);
|
||||
awsApiRequestRESTSpy.mockRejectedValue(testError);
|
||||
|
||||
await expect(node.execute.call(executeFunctionsMock)).rejects.toThrow('AWS API Error');
|
||||
});
|
||||
|
||||
it('should handle multiple items with mixed success/failure when continueOnFail is true', async () => {
|
||||
executeFunctionsMock.getInputData.mockReturnValue([
|
||||
{ json: { test: 'data1' } },
|
||||
{ json: { test: 'data2' } },
|
||||
{ json: { test: 'data3' } },
|
||||
]);
|
||||
|
||||
executeFunctionsMock.continueOnFail.mockReturnValue(true);
|
||||
|
||||
awsApiRequestRESTSpy
|
||||
.mockResolvedValueOnce(mockLocationResponse)
|
||||
.mockResolvedValueOnce(mockFileResponse)
|
||||
.mockResolvedValueOnce(mockLocationResponse)
|
||||
.mockRejectedValueOnce(new Error('File not found'))
|
||||
.mockResolvedValueOnce(mockLocationResponse)
|
||||
.mockResolvedValueOnce(mockFileResponse);
|
||||
|
||||
const result = await node.execute.call(executeFunctionsMock);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toHaveLength(3);
|
||||
|
||||
expect(result[0][0]).toHaveProperty('binary');
|
||||
expect(result[0][1].json).toEqual({ error: 'File not found' });
|
||||
expect(result[0][2]).toHaveProperty('binary');
|
||||
});
|
||||
});
|
||||
|
||||
describe('binary data handling', () => {
|
||||
beforeEach(() => {
|
||||
executeFunctionsMock.getNodeParameter.mockImplementation((paramName) => {
|
||||
switch (paramName) {
|
||||
case 'resource':
|
||||
return 'file';
|
||||
case 'operation':
|
||||
return 'download';
|
||||
case 'bucketName':
|
||||
return 'test-bucket';
|
||||
case 'fileKey':
|
||||
return 'path/to/test.txt';
|
||||
case 'binaryPropertyName':
|
||||
return 'customData';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
||||
awsApiRequestRESTSpy
|
||||
.mockResolvedValueOnce(mockLocationResponse)
|
||||
.mockResolvedValueOnce(mockFileResponse);
|
||||
});
|
||||
|
||||
it('should handle custom binary property name', async () => {
|
||||
await node.execute.call(executeFunctionsMock);
|
||||
|
||||
expect(executeFunctionsMock.helpers.prepareBinaryData).toHaveBeenCalledWith(
|
||||
expect.any(Buffer),
|
||||
'test.txt',
|
||||
'text/plain',
|
||||
);
|
||||
});
|
||||
|
||||
it('should preserve existing binary data when adding new binary data', async () => {
|
||||
executeFunctionsMock.getInputData.mockReturnValue([
|
||||
{
|
||||
json: { test: 'data' },
|
||||
binary: {
|
||||
existingFile: {
|
||||
data: 'existing-data',
|
||||
mimeType: 'image/png',
|
||||
fileName: 'existing.png',
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const result = await node.execute.call(executeFunctionsMock);
|
||||
|
||||
expect(result[0][0].binary).toHaveProperty('existingFile');
|
||||
expect(result[0][0].binary).toHaveProperty('customData');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
import { credentials } from '../../../__tests__/credentials';
|
||||
|
||||
describe('Test S3 V2 Node', () => {
|
||||
describe('File Upload', () => {
|
||||
let mock: nock.Scope;
|
||||
const now = 1683028800000;
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.useFakeTimers({ doNotFake: ['nextTick'], now });
|
||||
|
||||
mock = nock('https://s3.eu-central-1.amazonaws.com/buc.ket');
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
mock.get('?location').reply(
|
||||
200,
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<LocationConstraint>
|
||||
<LocationConstraint>eu-central-1</LocationConstraint>
|
||||
</LocationConstraint>`,
|
||||
{
|
||||
'content-type': 'application/xml',
|
||||
},
|
||||
);
|
||||
|
||||
mock
|
||||
.put('/binary.json')
|
||||
.matchHeader(
|
||||
'X-Amz-Content-Sha256',
|
||||
'e43abcf3375244839c012f9633f95862d232a95b00d5bc7348b3098b9fed7f32',
|
||||
)
|
||||
.once()
|
||||
.reply(200, { success: true });
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({ credentials });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user