mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
feat(Azure Storage Node): New node (#12536)
This commit is contained in:
parent
d550382a4a
commit
727f6f3c0e
|
@ -0,0 +1,32 @@
|
|||
import type { ICredentialType, INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export class AzureStorageOAuth2Api implements ICredentialType {
|
||||
name = 'azureStorageOAuth2Api';
|
||||
|
||||
displayName = 'Azure Storage OAuth2 API';
|
||||
|
||||
extends = ['microsoftOAuth2Api'];
|
||||
|
||||
documentationUrl = 'azurestorage';
|
||||
|
||||
properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Account',
|
||||
name: 'account',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Base URL',
|
||||
name: 'baseUrl',
|
||||
type: 'hidden',
|
||||
default: '=https://{{ $self["account"] }}.blob.core.windows.net',
|
||||
},
|
||||
{
|
||||
displayName: 'Scope',
|
||||
name: 'scope',
|
||||
type: 'hidden',
|
||||
default: 'https://storage.azure.com/.default',
|
||||
},
|
||||
];
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
import type {
|
||||
ICredentialDataDecryptedObject,
|
||||
ICredentialType,
|
||||
IHttpRequestOptions,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
import { createHmac } from 'node:crypto';
|
||||
|
||||
import {
|
||||
getCanonicalizedHeadersString,
|
||||
getCanonicalizedResourceString,
|
||||
HeaderConstants,
|
||||
} from '../nodes/Microsoft/Storage/GenericFunctions';
|
||||
|
||||
export class AzureStorageSharedKeyApi implements ICredentialType {
|
||||
name = 'azureStorageSharedKeyApi';
|
||||
|
||||
displayName = 'Azure Storage Shared Key API';
|
||||
|
||||
documentationUrl = 'azurestorage';
|
||||
|
||||
properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Account',
|
||||
name: 'account',
|
||||
description: 'Account name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Key',
|
||||
name: 'key',
|
||||
description: 'Account key',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
password: true,
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Base URL',
|
||||
name: 'baseUrl',
|
||||
type: 'hidden',
|
||||
default: '=https://{{ $self["account"] }}.blob.core.windows.net',
|
||||
},
|
||||
];
|
||||
|
||||
async authenticate(
|
||||
credentials: ICredentialDataDecryptedObject,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<IHttpRequestOptions> {
|
||||
if (requestOptions.qs) {
|
||||
for (const [key, value] of Object.entries(requestOptions.qs)) {
|
||||
if (value === undefined) {
|
||||
delete requestOptions.qs[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (requestOptions.headers) {
|
||||
for (const [key, value] of Object.entries(requestOptions.headers)) {
|
||||
if (value === undefined) {
|
||||
delete requestOptions.headers[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
requestOptions.method ??= 'GET';
|
||||
requestOptions.headers ??= {};
|
||||
|
||||
const stringToSign: string = [
|
||||
requestOptions.method.toUpperCase(),
|
||||
requestOptions.headers[HeaderConstants.CONTENT_LANGUAGE] ?? '',
|
||||
requestOptions.headers[HeaderConstants.CONTENT_ENCODING] ?? '',
|
||||
requestOptions.headers[HeaderConstants.CONTENT_LENGTH] ?? '',
|
||||
requestOptions.headers[HeaderConstants.CONTENT_MD5] ?? '',
|
||||
requestOptions.headers[HeaderConstants.CONTENT_TYPE] ?? '',
|
||||
requestOptions.headers[HeaderConstants.DATE] ?? '',
|
||||
requestOptions.headers[HeaderConstants.IF_MODIFIED_SINCE] ?? '',
|
||||
requestOptions.headers[HeaderConstants.IF_MATCH] ?? '',
|
||||
requestOptions.headers[HeaderConstants.IF_NONE_MATCH] ?? '',
|
||||
requestOptions.headers[HeaderConstants.IF_UNMODIFIED_SINCE] ?? '',
|
||||
requestOptions.headers[HeaderConstants.RANGE] ?? '',
|
||||
getCanonicalizedHeadersString(requestOptions) +
|
||||
getCanonicalizedResourceString(requestOptions, credentials),
|
||||
].join('\n');
|
||||
|
||||
const signature: string = createHmac('sha256', Buffer.from(credentials.key as string, 'base64'))
|
||||
.update(stringToSign, 'utf8')
|
||||
.digest('base64');
|
||||
|
||||
requestOptions.headers[HeaderConstants.AUTHORIZATION] =
|
||||
`SharedKey ${credentials.account as string}:${signature}`;
|
||||
|
||||
return requestOptions;
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ import type { WorkflowTestData } from '@test/nodes/types';
|
|||
|
||||
import { microsoftEntraApiResponse, microsoftEntraNodeResponse } from './mocks';
|
||||
|
||||
describe('Gong Node', () => {
|
||||
describe('Microsoft Entra Node', () => {
|
||||
const baseUrl = 'https://graph.microsoft.com/v1.0';
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -15,7 +15,7 @@ import { microsoftEntraApiResponse, microsoftEntraNodeResponse } from './mocks';
|
|||
import { FAKE_CREDENTIALS_DATA } from '../../../../test/nodes/FakeCredentialsMap';
|
||||
import { MicrosoftEntra } from '../MicrosoftEntra.node';
|
||||
|
||||
describe('Gong Node', () => {
|
||||
describe('Microsoft Entra Node', () => {
|
||||
const baseUrl = 'https://graph.microsoft.com/v1.0';
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -7,7 +7,7 @@ import type { WorkflowTestData } from '@test/nodes/types';
|
|||
|
||||
import { microsoftEntraApiResponse, microsoftEntraNodeResponse } from './mocks';
|
||||
|
||||
describe('Gong Node', () => {
|
||||
describe('Microsoft Entra Node', () => {
|
||||
const baseUrl = 'https://graph.microsoft.com/v1.0';
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"node": "n8n-nodes-base.azureStorage",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Data & Storage"],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/credentials/microsoft/"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.azurestorage/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
|
||||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
|
||||
import { blobFields, blobOperations, containerFields, containerOperations } from './descriptions';
|
||||
import { getBlobs, getContainers } from './GenericFunctions';
|
||||
|
||||
export class AzureStorage implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Azure Storage',
|
||||
name: 'azureStorage',
|
||||
icon: {
|
||||
light: 'file:azureStorage.svg',
|
||||
dark: 'file:azureStorage.dark.svg',
|
||||
},
|
||||
group: ['transform'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Interact with Azure Storage API',
|
||||
defaults: {
|
||||
name: 'Azure Storage',
|
||||
},
|
||||
inputs: [NodeConnectionType.Main],
|
||||
outputs: [NodeConnectionType.Main],
|
||||
credentials: [
|
||||
{
|
||||
name: 'azureStorageOAuth2Api',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: ['oAuth2'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'azureStorageSharedKeyApi',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: ['sharedKey'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
requestDefaults: {
|
||||
baseURL: '={{ $credentials.baseUrl }}',
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'OAuth2',
|
||||
value: 'oAuth2',
|
||||
},
|
||||
{
|
||||
name: 'Shared Key',
|
||||
value: 'sharedKey',
|
||||
},
|
||||
],
|
||||
default: 'sharedKey',
|
||||
},
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Blob',
|
||||
value: 'blob',
|
||||
},
|
||||
{
|
||||
name: 'Container',
|
||||
value: 'container',
|
||||
},
|
||||
],
|
||||
default: 'container',
|
||||
},
|
||||
|
||||
...blobOperations,
|
||||
...blobFields,
|
||||
...containerOperations,
|
||||
...containerFields,
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {},
|
||||
|
||||
listSearch: {
|
||||
getBlobs,
|
||||
getContainers,
|
||||
},
|
||||
};
|
||||
}
|
582
packages/nodes-base/nodes/Microsoft/Storage/GenericFunctions.ts
Normal file
582
packages/nodes-base/nodes/Microsoft/Storage/GenericFunctions.ts
Normal file
|
@ -0,0 +1,582 @@
|
|||
import { camelCase } from 'change-case';
|
||||
import type {
|
||||
JsonObject,
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
IExecuteSingleFunctions,
|
||||
IHttpRequestMethods,
|
||||
IHttpRequestOptions,
|
||||
ILoadOptionsFunctions,
|
||||
INodeExecutionData,
|
||||
IN8nHttpFullResponse,
|
||||
INodeListSearchResult,
|
||||
INodeListSearchItems,
|
||||
INodeParameterResourceLocator,
|
||||
ICredentialDataDecryptedObject,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeApiError } from 'n8n-workflow';
|
||||
import { Parser } from 'xml2js';
|
||||
import { firstCharLowerCase, parseBooleans, parseNumbers } from 'xml2js/lib/processors';
|
||||
|
||||
import { compareHeader } from './compare-header';
|
||||
|
||||
export const XMsVersion = '2021-12-02';
|
||||
|
||||
export const HeaderConstants = {
|
||||
AUTHORIZATION: 'authorization',
|
||||
CONTENT_ENCODING: 'content-encoding',
|
||||
CONTENT_LANGUAGE: 'content-language',
|
||||
CONTENT_LENGTH: 'content-length',
|
||||
CONTENT_MD5: 'content-md5',
|
||||
CONTENT_TYPE: 'content-Type',
|
||||
DATE: 'date',
|
||||
ETAG: 'etag',
|
||||
IF_MATCH: 'if-match',
|
||||
IF_MODIFIED_SINCE: 'if-Modified-since',
|
||||
IF_NONE_MATCH: 'if-none-match',
|
||||
IF_UNMODIFIED_SINCE: 'if-unmodified-since',
|
||||
ORIGIN: 'origin',
|
||||
RANGE: 'range',
|
||||
X_MS_COPY_SOURCE: 'x-ms-copy-source',
|
||||
X_MS_DATE: 'x-ms-date',
|
||||
X_MS_VERSION: 'x-ms-version',
|
||||
X_MS_BLOB_TYPE: 'x-ms-blob-type',
|
||||
X_MS_BLOB_CONTENT_DISPOSITION: 'x-ms-blob-content-disposition',
|
||||
X_MS_BLOB_PUBLIC_ACCESS: 'x-ms-blob-public-access',
|
||||
X_MS_HAS_IMMUTABILITY_POLICY: 'x-ms-has-immutability-policy',
|
||||
X_MS_HAS_LEGAL_HOLD: 'x-ms-has-legal-hold',
|
||||
X_MS_CONTENT_CRC64: 'x-ms-content-crc64',
|
||||
X_MS_REQUEST_SERVER_ENCRYPTED: 'x-ms-request-server-encrypted',
|
||||
X_MS_ENCRYPTION_SCOPE: 'x-ms-encryption-scope',
|
||||
X_MS_VERSION_ID: 'x-ms-version-id',
|
||||
X_MS_TAG_COUNT: 'x-ms-tag-count',
|
||||
X_MS_COPY_PROGRESS: 'x-ms-copy-progress',
|
||||
X_MS_INCREMENTAL_COPY: 'x-ms-incremental-copy',
|
||||
X_MS_BLOB_SEQUENCE_NUMBER: 'x-ms-blob-sequence-number',
|
||||
X_MS_BLOB_COMMITTED_BLOCK_COUNT: 'x-ms-blob-committed-block-count',
|
||||
X_MS_SERVER_ENCRYPTED: 'x-ms-server-encrypted',
|
||||
X_MS_ENCRYPTION_CONTEXT: 'x-ms-encryption-context',
|
||||
X_MS_BLOB_CONTENT_MD5: 'x-ms-blob-content-md5',
|
||||
X_MS_BLOB_SEALED: 'x-ms-blob-sealed',
|
||||
X_MS_IMMUTABILITY_POLICY_UNTIL_DATE: 'x-ms-immutability-policy-until-date',
|
||||
X_MS_IMMUTABILITY_POLICY_MODE: 'x-ms-immutability-policy-mode',
|
||||
X_MS_LEGAL_HOLD: 'x-ms-legal-hold',
|
||||
X_MS_DELETE_TYPE_PERMANENT: 'x-ms-delete-type-permanent',
|
||||
X_MS_ACCESS_TIER: 'x-ms-access-tier',
|
||||
X_MS_BLOB_CACHE_CONTROL: 'x-ms-blob-cache-control',
|
||||
X_MS_LEASE_ID: 'x-ms-lease-id',
|
||||
X_MS_BLOB_CONTENT_ENCODING: 'x-ms-blob-content-encoding',
|
||||
X_MS_BLOB_CONTENT_LANGUAGE: 'x-ms-blob-content-language',
|
||||
X_MS_BLOB_CONTENT_TYPE: 'x-ms-blob-content-type',
|
||||
X_MS_EXPIRY_OPTION: 'x-ms-expiry-option',
|
||||
X_MS_EXPIRY_TIME: 'x-ms-expiry-time',
|
||||
X_MS_TAGS: 'x-ms-tags',
|
||||
X_MS_UPN: 'x-ms-upn',
|
||||
PREFIX_X_MS: 'x-ms-',
|
||||
PREFIX_X_MS_META: 'x-ms-meta-',
|
||||
};
|
||||
|
||||
export async function azureStorageApiRequest(
|
||||
this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
|
||||
method: IHttpRequestMethods,
|
||||
endpoint: string,
|
||||
body: IDataObject = {},
|
||||
qs?: IDataObject,
|
||||
headers?: IDataObject,
|
||||
url?: string,
|
||||
): Promise<string> {
|
||||
const authentication = this.getNodeParameter('authentication', 0) as 'oAuth2' | 'sharedKey';
|
||||
const credentialsType =
|
||||
authentication === 'oAuth2' ? 'azureStorageOAuth2Api' : 'azureStorageSharedKeyApi';
|
||||
const credentials = await this.getCredentials<{
|
||||
baseUrl: string;
|
||||
}>(credentialsType);
|
||||
|
||||
const options: IHttpRequestOptions = {
|
||||
method,
|
||||
url: url ?? `${credentials.baseUrl}${endpoint}`,
|
||||
headers,
|
||||
body,
|
||||
qs,
|
||||
};
|
||||
|
||||
options.headers ??= {};
|
||||
options.headers[HeaderConstants.X_MS_DATE] = new Date().toUTCString();
|
||||
options.headers[HeaderConstants.X_MS_VERSION] = XMsVersion;
|
||||
|
||||
// XML response
|
||||
const response = (await this.helpers.requestWithAuthentication.call(
|
||||
this,
|
||||
credentialsType,
|
||||
options,
|
||||
)) as string;
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
export async function handleErrorPostReceive(
|
||||
this: IExecuteSingleFunctions,
|
||||
data: INodeExecutionData[],
|
||||
response: IN8nHttpFullResponse,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
if (String(response.statusCode).startsWith('4') || String(response.statusCode).startsWith('5')) {
|
||||
const resource = this.getNodeParameter('resource') as string;
|
||||
const operation = this.getNodeParameter('operation') as string;
|
||||
|
||||
const parser = new Parser({
|
||||
explicitArray: false,
|
||||
tagNameProcessors: [firstCharLowerCase],
|
||||
});
|
||||
const { error } =
|
||||
((await parser.parseStringPromise(data[0].json as unknown as string)) as {
|
||||
error: {
|
||||
code: string;
|
||||
message: string;
|
||||
headerName?: string;
|
||||
headerValue?: string;
|
||||
};
|
||||
}) ?? {};
|
||||
|
||||
if (
|
||||
error?.code === 'InvalidAuthenticationInfo' &&
|
||||
((error as IDataObject)?.authenticationErrorDetail as string) ===
|
||||
'Lifetime validation failed. The token is expired.'
|
||||
) {
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject, {
|
||||
message: 'Lifetime validation failed. The token is expired.',
|
||||
description: 'Please check your credentials and try again',
|
||||
});
|
||||
}
|
||||
|
||||
if (resource === 'blob') {
|
||||
if (error?.code === 'ContainerNotFound') {
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject, {
|
||||
message: "The required container doesn't match any existing one",
|
||||
description: "Double-check the value in the parameter 'Container Name' and try again",
|
||||
});
|
||||
}
|
||||
|
||||
if (operation === 'create') {
|
||||
if (
|
||||
this.getNodeParameter('from') === 'url' &&
|
||||
((error?.code === 'InvalidHeaderValue' &&
|
||||
error?.headerName === HeaderConstants.X_MS_COPY_SOURCE) ||
|
||||
error?.code === 'CannotVerifyCopySource')
|
||||
) {
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject, {
|
||||
message: 'The provided URL is invalid',
|
||||
description: "Double-check the value in the parameter 'URL' and try again",
|
||||
});
|
||||
}
|
||||
} else if (operation === 'delete') {
|
||||
if (error?.code === 'BlobNotFound') {
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject, {
|
||||
message: "The required blob doesn't match any existing one",
|
||||
description: "Double-check the value in the parameter 'Blob to Delete' and try again",
|
||||
});
|
||||
}
|
||||
} else if (operation === 'get') {
|
||||
if (error?.code === 'BlobNotFound') {
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject, {
|
||||
message: "The required blob doesn't match any existing one",
|
||||
description: "Double-check the value in the parameter 'Blob to Get' and try again",
|
||||
});
|
||||
}
|
||||
} else if (operation === 'getAll') {
|
||||
if (
|
||||
error?.code === 'InvalidQueryParameterValue' &&
|
||||
(this.getNodeParameter('fields', []) as string[]).includes('permissions')
|
||||
) {
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject, {
|
||||
message:
|
||||
'Permissions field is only supported for accounts with a hierarchical namespace enabled',
|
||||
description: "Exclude 'Permissions' from 'Fields' and try again",
|
||||
});
|
||||
}
|
||||
if (
|
||||
error?.code === 'UnsupportedQueryParameter' &&
|
||||
(this.getNodeParameter('fields', []) as string[]).includes('deleted') &&
|
||||
(this.getNodeParameter('filter', []) as string[]).includes('deleted')
|
||||
) {
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject, {
|
||||
message: "Including 'Deleted' field and filter is not supported",
|
||||
description: "Exclude 'Deleted' from 'Fields' or 'Filter' and try again",
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (resource === 'container') {
|
||||
if (operation === 'create') {
|
||||
if (error?.code === 'ContainerAlreadyExists') {
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject, {
|
||||
message: 'The specified container already exists',
|
||||
description: "Use a unique value for 'Container Name' and try again",
|
||||
});
|
||||
}
|
||||
if (error?.code === 'PublicAccessNotPermitted') {
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject, {
|
||||
message: 'Public access is not permitted on this storage account',
|
||||
description: "Check the 'Access Level' and try again",
|
||||
});
|
||||
}
|
||||
} else if (operation === 'delete') {
|
||||
if (error?.code === 'ContainerNotFound') {
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject, {
|
||||
message: "The required container doesn't match any existing one",
|
||||
description:
|
||||
"Double-check the value in the parameter 'Container to Delete' and try again",
|
||||
});
|
||||
}
|
||||
} else if (operation === 'get') {
|
||||
if (error?.code === 'ContainerNotFound') {
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject, {
|
||||
message: "The required container doesn't match any existing one",
|
||||
description: "Double-check the value in the parameter 'Container to Get' and try again",
|
||||
});
|
||||
}
|
||||
} else if (operation === 'getAll') {
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
throw new NodeApiError(this.getNode(), response as unknown as JsonObject, {
|
||||
message: error.code,
|
||||
description: error.message,
|
||||
});
|
||||
} else {
|
||||
throw new NodeApiError(this.getNode(), response as unknown as JsonObject, {
|
||||
parseXml: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export function getCanonicalizedHeadersString(requestOptions: IHttpRequestOptions): string {
|
||||
let headersArray: Array<{ name: string; value: string }> = [];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
for (const [name, value] of Object.entries(requestOptions.headers!) as unknown as [
|
||||
string,
|
||||
string,
|
||||
]) {
|
||||
if (name.toLowerCase().startsWith(HeaderConstants.PREFIX_X_MS)) {
|
||||
headersArray.push({ name, value });
|
||||
}
|
||||
}
|
||||
|
||||
headersArray.sort((a, b): number => {
|
||||
return compareHeader(a.name.toLowerCase(), b.name.toLowerCase());
|
||||
});
|
||||
|
||||
// Remove duplicate headers
|
||||
headersArray = headersArray.filter((value, index, array) => {
|
||||
if (index > 0 && value.name.toLowerCase() === array[index - 1].name.toLowerCase()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
let canonicalizedHeadersStringToSign: string = '';
|
||||
headersArray.forEach((header) => {
|
||||
canonicalizedHeadersStringToSign += `${header.name
|
||||
.toLowerCase()
|
||||
.trimEnd()}:${header.value.trimStart()}\n`;
|
||||
});
|
||||
|
||||
return canonicalizedHeadersStringToSign;
|
||||
}
|
||||
|
||||
export function getCanonicalizedResourceString(
|
||||
requestOptions: IHttpRequestOptions,
|
||||
credentials: ICredentialDataDecryptedObject,
|
||||
): string {
|
||||
const path: string = new URL(requestOptions.baseURL + requestOptions.url).pathname || '/';
|
||||
let canonicalizedResourceString = `/${credentials.account as string}${path}`;
|
||||
if (requestOptions.qs && Object.keys(requestOptions.qs).length > 0) {
|
||||
canonicalizedResourceString +=
|
||||
'\n' +
|
||||
Object.keys(requestOptions.qs)
|
||||
.sort()
|
||||
.map(
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
(key) => `${key.toLowerCase()}:${decodeURIComponent(requestOptions.qs![key] as string)}`,
|
||||
)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
return canonicalizedResourceString;
|
||||
}
|
||||
|
||||
export function parseHeaders(headers: IDataObject) {
|
||||
const parseBooleanHeaders = [
|
||||
HeaderConstants.X_MS_DELETE_TYPE_PERMANENT,
|
||||
HeaderConstants.X_MS_INCREMENTAL_COPY,
|
||||
HeaderConstants.X_MS_SERVER_ENCRYPTED,
|
||||
HeaderConstants.X_MS_BLOB_SEALED,
|
||||
HeaderConstants.X_MS_REQUEST_SERVER_ENCRYPTED,
|
||||
HeaderConstants.X_MS_HAS_IMMUTABILITY_POLICY,
|
||||
HeaderConstants.X_MS_HAS_LEGAL_HOLD,
|
||||
];
|
||||
const parseNumberHeaders = [
|
||||
HeaderConstants.X_MS_TAG_COUNT,
|
||||
HeaderConstants.CONTENT_LENGTH,
|
||||
HeaderConstants.X_MS_BLOB_SEQUENCE_NUMBER,
|
||||
HeaderConstants.X_MS_COPY_PROGRESS,
|
||||
HeaderConstants.X_MS_BLOB_COMMITTED_BLOCK_COUNT,
|
||||
];
|
||||
|
||||
const result: IDataObject = {};
|
||||
|
||||
const metadataKeys = Object.keys(headers).filter((x) =>
|
||||
x.startsWith(HeaderConstants.PREFIX_X_MS_META),
|
||||
);
|
||||
|
||||
for (const key in headers) {
|
||||
if (metadataKeys.includes(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let newKey = key.startsWith(HeaderConstants.PREFIX_X_MS)
|
||||
? camelCase(key.replace(HeaderConstants.PREFIX_X_MS, ''))
|
||||
: camelCase(key);
|
||||
newKey = newKey.replace('-', '');
|
||||
|
||||
const newValue = parseBooleanHeaders.includes(key)
|
||||
? parseBooleans(headers[key] as string)
|
||||
: parseNumberHeaders.includes(key)
|
||||
? parseNumbers(headers[key] as string)
|
||||
: headers[key];
|
||||
|
||||
result[newKey] = newValue;
|
||||
}
|
||||
|
||||
if (metadataKeys.length > 0) {
|
||||
result.metadata = {};
|
||||
for (const key of metadataKeys) {
|
||||
(result.metadata as IDataObject)[key.replace(HeaderConstants.PREFIX_X_MS_META, '')] =
|
||||
headers[key];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function parseBlobList(
|
||||
xml: string,
|
||||
): Promise<{ blobs: IDataObject[]; maxResults?: number; nextMarker?: string }> {
|
||||
const parser = new Parser({
|
||||
explicitArray: false,
|
||||
tagNameProcessors: [firstCharLowerCase, (name) => name.replace('-', '')],
|
||||
valueProcessors: [
|
||||
function (value, name) {
|
||||
if (
|
||||
[
|
||||
'deleted',
|
||||
'isCurrentVersion',
|
||||
'serverEncrypted',
|
||||
'incrementalCopy',
|
||||
'accessTierInferred',
|
||||
'isSealed',
|
||||
'legalHold',
|
||||
].includes(name)
|
||||
) {
|
||||
return parseBooleans(value);
|
||||
} else if (
|
||||
[
|
||||
'maxResults',
|
||||
'contentLength',
|
||||
'blobSequenceNumber',
|
||||
'remainingRetentionDays',
|
||||
'tagCount',
|
||||
'content-Length',
|
||||
].includes(name)
|
||||
) {
|
||||
return parseNumbers(value);
|
||||
}
|
||||
return value;
|
||||
},
|
||||
],
|
||||
});
|
||||
const data = (await parser.parseStringPromise(xml)) as {
|
||||
enumerationResults: {
|
||||
blobs: { blob: IDataObject | IDataObject[] };
|
||||
maxResults: number;
|
||||
nextMarker: string;
|
||||
prefix: string;
|
||||
};
|
||||
};
|
||||
|
||||
if (typeof data.enumerationResults.blobs !== 'object') {
|
||||
// No items
|
||||
return { blobs: [] };
|
||||
}
|
||||
|
||||
if (!Array.isArray(data.enumerationResults.blobs.blob)) {
|
||||
// Single item
|
||||
data.enumerationResults.blobs.blob = [data.enumerationResults.blobs.blob];
|
||||
}
|
||||
|
||||
for (const blob of data.enumerationResults.blobs.blob) {
|
||||
if (blob.tags) {
|
||||
if (!Array.isArray(((blob.tags as IDataObject).tagSet as IDataObject).tag)) {
|
||||
((blob.tags as IDataObject).tagSet as IDataObject).tag = [
|
||||
((blob.tags as IDataObject).tagSet as IDataObject).tag,
|
||||
];
|
||||
}
|
||||
blob.tags = ((blob.tags as IDataObject).tagSet as IDataObject).tag;
|
||||
}
|
||||
}
|
||||
|
||||
for (const container of data.enumerationResults.blobs.blob) {
|
||||
if (container.metadata === '') {
|
||||
delete container.metadata;
|
||||
}
|
||||
if (container.orMetadata === '') {
|
||||
delete container.orMetadata;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
blobs: data.enumerationResults.blobs.blob,
|
||||
maxResults: data.enumerationResults.maxResults,
|
||||
nextMarker: data.enumerationResults.nextMarker,
|
||||
};
|
||||
}
|
||||
|
||||
export async function parseContainerList(
|
||||
xml: string,
|
||||
): Promise<{ containers: IDataObject[]; maxResults?: number; nextMarker?: string }> {
|
||||
const parser = new Parser({
|
||||
explicitArray: false,
|
||||
tagNameProcessors: [firstCharLowerCase, (name) => name.replace('-', '')],
|
||||
valueProcessors: [
|
||||
function (value, name) {
|
||||
if (
|
||||
[
|
||||
'deleted',
|
||||
'hasImmutabilityPolicy',
|
||||
'hasLegalHold',
|
||||
'preventEncryptionScopeOverride',
|
||||
'isImmutableStorageWithVersioningEnabled',
|
||||
].includes(name)
|
||||
) {
|
||||
return parseBooleans(value);
|
||||
} else if (['maxResults', 'remainingRetentionDays'].includes(name)) {
|
||||
return parseNumbers(value);
|
||||
}
|
||||
return value;
|
||||
},
|
||||
],
|
||||
});
|
||||
const data = (await parser.parseStringPromise(xml)) as {
|
||||
enumerationResults: {
|
||||
containers: { container: IDataObject | IDataObject[] };
|
||||
maxResults: number;
|
||||
nextMarker: string;
|
||||
prefix: string;
|
||||
};
|
||||
};
|
||||
|
||||
if (typeof data.enumerationResults.containers !== 'object') {
|
||||
// No items
|
||||
return { containers: [] };
|
||||
}
|
||||
|
||||
if (!Array.isArray(data.enumerationResults.containers.container)) {
|
||||
// Single item
|
||||
data.enumerationResults.containers.container = [data.enumerationResults.containers.container];
|
||||
}
|
||||
|
||||
for (const container of data.enumerationResults.containers.container) {
|
||||
if (container.metadata === '') {
|
||||
delete container.metadata;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
containers: data.enumerationResults.containers.container,
|
||||
maxResults: data.enumerationResults.maxResults,
|
||||
nextMarker: data.enumerationResults.nextMarker,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getBlobs(
|
||||
this: ILoadOptionsFunctions,
|
||||
filter?: string,
|
||||
paginationToken?: string,
|
||||
): Promise<INodeListSearchResult> {
|
||||
const container = this.getNodeParameter('container') as INodeParameterResourceLocator;
|
||||
|
||||
let response: string;
|
||||
|
||||
const qs: IDataObject = {
|
||||
restype: 'container',
|
||||
comp: 'list',
|
||||
};
|
||||
|
||||
if (paginationToken) {
|
||||
qs.marker = paginationToken;
|
||||
response = await azureStorageApiRequest.call(this, 'GET', `/${container.value}`, {}, qs);
|
||||
} else {
|
||||
qs.maxresults = 5000;
|
||||
if (filter) {
|
||||
qs.prefix = filter;
|
||||
}
|
||||
response = await azureStorageApiRequest.call(this, 'GET', `/${container.value}`, {}, qs);
|
||||
}
|
||||
|
||||
const data = await parseBlobList(response);
|
||||
|
||||
const results: INodeListSearchItems[] = data.blobs
|
||||
.map((c) => ({
|
||||
name: c.name as string,
|
||||
value: c.name as string,
|
||||
}))
|
||||
.sort((a, b) =>
|
||||
a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' }),
|
||||
);
|
||||
|
||||
return {
|
||||
results,
|
||||
paginationToken: data.nextMarker,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getContainers(
|
||||
this: ILoadOptionsFunctions,
|
||||
filter?: string,
|
||||
paginationToken?: string,
|
||||
): Promise<INodeListSearchResult> {
|
||||
let response: string;
|
||||
|
||||
const qs: IDataObject = {
|
||||
comp: 'list',
|
||||
};
|
||||
|
||||
if (paginationToken) {
|
||||
qs.marker = paginationToken;
|
||||
response = await azureStorageApiRequest.call(this, 'GET', '/', {}, qs);
|
||||
} else {
|
||||
qs.maxresults = 5000;
|
||||
if (filter) {
|
||||
qs.prefix = filter;
|
||||
}
|
||||
response = await azureStorageApiRequest.call(this, 'GET', '/', {}, qs);
|
||||
}
|
||||
|
||||
const data = await parseContainerList(response);
|
||||
|
||||
const results: INodeListSearchItems[] = data.containers
|
||||
.map((c) => ({
|
||||
name: c.name as string,
|
||||
value: c.name as string,
|
||||
}))
|
||||
.sort((a, b) =>
|
||||
a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' }),
|
||||
);
|
||||
|
||||
return {
|
||||
results,
|
||||
paginationToken: data.nextMarker,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18" width="200" height="200" >
|
||||
<defs>
|
||||
<linearGradient id="a" x1="9" y1="15.83" x2="9" y2="5.79" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#b3b3b3"/>
|
||||
<stop offset=".26" stop-color="#c1c1c1"/>
|
||||
<stop offset="1" stop-color="#e6e6e6"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M.5 5.79h17v9.48a.57.57 0 01-.57.57H1.07a.57.57 0 01-.57-.57V5.79z" fill="url(#a)"/>
|
||||
<path d="M1.07 2.17h15.86a.57.57 0 01.57.57v3.05H.5V2.73a.57.57 0 01.57-.56z" fill="#37c2b1"/>
|
||||
<path d="M2.81 6.89h12.37a.27.27 0 01.26.27v1.4a.27.27 0 01-.26.27H2.81a.27.27 0 01-.26-.27v-1.4a.27.27 0 01.26-.27z" fill="#fff"/>
|
||||
<path d="M2.82 9.68h12.37a.27.27 0 01.26.27v1.41a.27.27 0 01-.26.27H2.82a.27.27 0 01-.26-.27V10a.27.27 0 01.26-.32z" fill="#37c2b1"/>
|
||||
<path d="M2.82 12.5h12.37a.27.27 0 01.26.27v1.41a.27.27 0 01-.26.27H2.82a.27.27 0 01-.26-.27v-1.41a.27.27 0 01.26-.27z" fill="#258277"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1,015 B |
14
packages/nodes-base/nodes/Microsoft/Storage/azureStorage.svg
Normal file
14
packages/nodes-base/nodes/Microsoft/Storage/azureStorage.svg
Normal file
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18" width="200" height="200" >
|
||||
<defs>
|
||||
<linearGradient id="a" x1="9" y1="15.83" x2="9" y2="5.79" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#b3b3b3"/>
|
||||
<stop offset=".26" stop-color="#c1c1c1"/>
|
||||
<stop offset="1" stop-color="#e6e6e6"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M.5 5.79h17v9.48a.57.57 0 01-.57.57H1.07a.57.57 0 01-.57-.57V5.79z" fill="url(#a)"/>
|
||||
<path d="M1.07 2.17h15.86a.57.57 0 01.57.57v3.05H.5V2.73a.57.57 0 01.57-.56z" fill="#37c2b1"/>
|
||||
<path d="M2.81 6.89h12.37a.27.27 0 01.26.27v1.4a.27.27 0 01-.26.27H2.81a.27.27 0 01-.26-.27v-1.4a.27.27 0 01.26-.27z" fill="#fff"/>
|
||||
<path d="M2.82 9.68h12.37a.27.27 0 01.26.27v1.41a.27.27 0 01-.26.27H2.82a.27.27 0 01-.26-.27V10a.27.27 0 01.26-.32z" fill="#37c2b1"/>
|
||||
<path d="M2.82 12.5h12.37a.27.27 0 01.26.27v1.41a.27.27 0 01-.26.27H2.82a.27.27 0 01-.26-.27v-1.41a.27.27 0 01.26-.27z" fill="#258277"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1,015 B |
|
@ -0,0 +1,72 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
/*
|
||||
* We need to imitate .Net culture-aware sorting, which is used in storage service.
|
||||
* Below tables contain sort-keys for en-US culture.
|
||||
*/
|
||||
|
||||
const table_lv0 = new Uint32Array([
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x71c, 0x0, 0x71f, 0x721,
|
||||
0x723, 0x725, 0x0, 0x0, 0x0, 0x72d, 0x803, 0x0, 0x0, 0x733, 0x0, 0xd03, 0xd1a, 0xd1c, 0xd1e,
|
||||
0xd20, 0xd22, 0xd24, 0xd26, 0xd28, 0xd2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe02, 0xe09, 0xe0a,
|
||||
0xe1a, 0xe21, 0xe23, 0xe25, 0xe2c, 0xe32, 0xe35, 0xe36, 0xe48, 0xe51, 0xe70, 0xe7c, 0xe7e, 0xe89,
|
||||
0xe8a, 0xe91, 0xe99, 0xe9f, 0xea2, 0xea4, 0xea6, 0xea7, 0xea9, 0x0, 0x0, 0x0, 0x743, 0x744, 0x748,
|
||||
0xe02, 0xe09, 0xe0a, 0xe1a, 0xe21, 0xe23, 0xe25, 0xe2c, 0xe32, 0xe35, 0xe36, 0xe48, 0xe51, 0xe70,
|
||||
0xe7c, 0xe7e, 0xe89, 0xe8a, 0xe91, 0xe99, 0xe9f, 0xea2, 0xea4, 0xea6, 0xea7, 0xea9, 0x0, 0x74c,
|
||||
0x0, 0x750, 0x0,
|
||||
]);
|
||||
const table_lv2 = new Uint32Array([
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
|
||||
0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,
|
||||
0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
]);
|
||||
const table_lv4 = new Uint32Array([
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x8012, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8212, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
]);
|
||||
|
||||
function isLessThan(lhs: string, rhs: string): boolean {
|
||||
const tables = [table_lv0, table_lv2, table_lv4];
|
||||
let curr_level = 0;
|
||||
let i = 0;
|
||||
let j = 0;
|
||||
while (curr_level < tables.length) {
|
||||
if (curr_level === tables.length - 1 && i !== j) {
|
||||
return i > j;
|
||||
}
|
||||
const weight1 = i < lhs.length ? tables[curr_level][lhs[i].charCodeAt(0)] : 0x1;
|
||||
const weight2 = j < rhs.length ? tables[curr_level][rhs[j].charCodeAt(0)] : 0x1;
|
||||
if (weight1 === 0x1 && weight2 === 0x1) {
|
||||
i = 0;
|
||||
j = 0;
|
||||
++curr_level;
|
||||
} else if (weight1 === weight2) {
|
||||
++i;
|
||||
++j;
|
||||
} else if (weight1 === 0) {
|
||||
++i;
|
||||
} else if (weight2 === 0) {
|
||||
++j;
|
||||
} else {
|
||||
return weight1 < weight2;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function compareHeader(lhs: string, rhs: string): number {
|
||||
if (isLessThan(lhs, rhs)) return -1;
|
||||
|
||||
return 1;
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,600 @@
|
|||
import type {
|
||||
DeclarativeRestApiSettings,
|
||||
IDataObject,
|
||||
IExecutePaginationFunctions,
|
||||
IExecuteSingleFunctions,
|
||||
IHttpRequestOptions,
|
||||
IN8nHttpFullResponse,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
ResourceMapperValue,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
handleErrorPostReceive,
|
||||
HeaderConstants,
|
||||
parseContainerList,
|
||||
parseHeaders,
|
||||
XMsVersion,
|
||||
} from '../GenericFunctions';
|
||||
|
||||
export const containerOperations: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['container'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a container',
|
||||
routing: {
|
||||
request: {
|
||||
ignoreHttpStatusErrors: true,
|
||||
method: 'PUT',
|
||||
qs: {
|
||||
restype: 'container',
|
||||
},
|
||||
url: '=/{{ $parameter["containerCreate"] }}',
|
||||
headers: {
|
||||
[HeaderConstants.X_MS_DATE]: '={{ new Date().toUTCString() }}',
|
||||
[HeaderConstants.X_MS_VERSION]: XMsVersion,
|
||||
},
|
||||
},
|
||||
output: {
|
||||
postReceive: [
|
||||
handleErrorPostReceive,
|
||||
async function (
|
||||
this: IExecuteSingleFunctions,
|
||||
_data: INodeExecutionData[],
|
||||
response: IN8nHttpFullResponse,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
return [
|
||||
{
|
||||
json: parseHeaders(response.headers),
|
||||
},
|
||||
];
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
action: 'Create container',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a container',
|
||||
routing: {
|
||||
request: {
|
||||
ignoreHttpStatusErrors: true,
|
||||
method: 'DELETE',
|
||||
qs: {
|
||||
restype: 'container',
|
||||
},
|
||||
url: '=/{{ $parameter["container"] }}',
|
||||
headers: {
|
||||
[HeaderConstants.X_MS_DATE]: '={{ new Date().toUTCString() }}',
|
||||
[HeaderConstants.X_MS_VERSION]: XMsVersion,
|
||||
},
|
||||
},
|
||||
output: {
|
||||
postReceive: [
|
||||
handleErrorPostReceive,
|
||||
async function (
|
||||
this: IExecuteSingleFunctions,
|
||||
_data: INodeExecutionData[],
|
||||
response: IN8nHttpFullResponse,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
return [
|
||||
{
|
||||
json: parseHeaders(response.headers),
|
||||
},
|
||||
];
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
action: 'Delete container',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Retrieve data for a specific container',
|
||||
routing: {
|
||||
request: {
|
||||
ignoreHttpStatusErrors: true,
|
||||
method: 'GET',
|
||||
qs: {
|
||||
restype: 'container',
|
||||
},
|
||||
url: '=/{{ $parameter["container"] }}',
|
||||
headers: {
|
||||
[HeaderConstants.X_MS_DATE]: '={{ new Date().toUTCString() }}',
|
||||
[HeaderConstants.X_MS_VERSION]: XMsVersion,
|
||||
},
|
||||
},
|
||||
output: {
|
||||
postReceive: [
|
||||
handleErrorPostReceive,
|
||||
async function (
|
||||
this: IExecuteSingleFunctions,
|
||||
_data: INodeExecutionData[],
|
||||
response: IN8nHttpFullResponse,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const { metadata, ...properties } = parseHeaders(response.headers);
|
||||
const simplify = this.getNodeParameter('options.simplify', true) as boolean;
|
||||
if (simplify) {
|
||||
delete properties.contentLength;
|
||||
delete properties.server;
|
||||
delete properties.requestId;
|
||||
delete properties.version;
|
||||
delete properties.date;
|
||||
delete properties.connection;
|
||||
}
|
||||
return [
|
||||
{
|
||||
json: {
|
||||
name: (this.getNodeParameter('container') as ResourceMapperValue).value,
|
||||
properties,
|
||||
...(metadata ? { metadata: metadata as IDataObject } : {}),
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
action: 'Get container',
|
||||
},
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
description: 'Retrieve a list of containers',
|
||||
routing: {
|
||||
request: {
|
||||
ignoreHttpStatusErrors: true,
|
||||
method: 'GET',
|
||||
qs: {
|
||||
comp: 'list',
|
||||
},
|
||||
url: '/',
|
||||
headers: {
|
||||
[HeaderConstants.X_MS_DATE]: '={{ new Date().toUTCString() }}',
|
||||
[HeaderConstants.X_MS_VERSION]: XMsVersion,
|
||||
},
|
||||
},
|
||||
output: {
|
||||
postReceive: [
|
||||
handleErrorPostReceive,
|
||||
async function (
|
||||
this: IExecuteSingleFunctions,
|
||||
data: INodeExecutionData[],
|
||||
_response: IN8nHttpFullResponse,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
return [
|
||||
{
|
||||
json: await parseContainerList(data[0].json as unknown as string),
|
||||
},
|
||||
];
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
action: 'Get many container',
|
||||
},
|
||||
],
|
||||
default: 'getAll',
|
||||
},
|
||||
];
|
||||
|
||||
const createFields: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Container Name',
|
||||
name: 'containerCreate',
|
||||
default: '',
|
||||
description: 'The name of the new container',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['container'],
|
||||
operation: ['create'],
|
||||
},
|
||||
},
|
||||
placeholder: 'e.g. mycontainer',
|
||||
required: true,
|
||||
routing: {
|
||||
send: {
|
||||
preSend: [
|
||||
async function (
|
||||
this: IExecuteSingleFunctions,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<IHttpRequestOptions> {
|
||||
const container = this.getNodeParameter('containerCreate') as string;
|
||||
if (container.length < 3 || container.length > 63) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
"'Container Name' must be from 3 through 63 characters long",
|
||||
);
|
||||
}
|
||||
if (/[A-Z]/.test(container)) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
"All letters in 'Container Name' must be lowercase",
|
||||
);
|
||||
}
|
||||
if (!/^[a-z0-9-]+$/.test(container)) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
"'Container Name' can contain only letters, numbers, and the hyphen/minus (-) character",
|
||||
);
|
||||
}
|
||||
if (!/^[a-z0-9].*[a-z0-9]$/.test(container)) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
"'Container Name' must start or end with a letter or number",
|
||||
);
|
||||
}
|
||||
if (/--/.test(container)) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
"Consecutive hyphens are not permitted in 'Container Name'",
|
||||
);
|
||||
}
|
||||
|
||||
return requestOptions;
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'string',
|
||||
validateType: 'string',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['container'],
|
||||
operation: ['create'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Access Level',
|
||||
name: 'accessLevel',
|
||||
default: '',
|
||||
options: [
|
||||
{
|
||||
name: 'Blob',
|
||||
value: 'blob',
|
||||
description:
|
||||
"Specifies public read access for blobs. Blob data within this container can be read via anonymous request, but container data isn't available. Clients can't enumerate blobs within the container via anonymous request.",
|
||||
},
|
||||
{
|
||||
name: 'Container',
|
||||
value: 'container',
|
||||
description:
|
||||
"Specifies full public read access for container and blob data. Clients can enumerate blobs within the container via anonymous request, but they can't enumerate containers within the storage account.",
|
||||
},
|
||||
{
|
||||
name: 'Private',
|
||||
value: '',
|
||||
description: 'Container data is private to the account owner',
|
||||
},
|
||||
],
|
||||
routing: {
|
||||
request: {
|
||||
headers: {
|
||||
[HeaderConstants.X_MS_BLOB_PUBLIC_ACCESS]: '={{ $value || undefined }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'options',
|
||||
validateType: 'options',
|
||||
},
|
||||
{
|
||||
displayName: 'Metadata',
|
||||
name: 'metadata',
|
||||
default: [],
|
||||
description: 'A name-value pair to associate with the container as metadata',
|
||||
options: [
|
||||
{
|
||||
name: 'metadataValues',
|
||||
displayName: 'Metadata',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Field Name',
|
||||
name: 'fieldName',
|
||||
default: '',
|
||||
description:
|
||||
'Names must adhere to the naming rules for <a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/">C# identifiers</a>',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
displayName: 'Field Value',
|
||||
name: 'fieldValue',
|
||||
default: '',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
placeholder: 'Add metadata',
|
||||
routing: {
|
||||
send: {
|
||||
preSend: [
|
||||
async function (
|
||||
this: IExecuteSingleFunctions,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<IHttpRequestOptions> {
|
||||
requestOptions.headers ??= {};
|
||||
const metadata = this.getNodeParameter('options.metadata') as IDataObject;
|
||||
for (const data of metadata.metadataValues as IDataObject[]) {
|
||||
requestOptions.headers[
|
||||
`${HeaderConstants.PREFIX_X_MS_META}${data.fieldName as string}`
|
||||
] = data.fieldValue as string;
|
||||
}
|
||||
return requestOptions;
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
placeholder: 'Add option',
|
||||
type: 'collection',
|
||||
},
|
||||
];
|
||||
|
||||
const deleteFields: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Container',
|
||||
name: 'container',
|
||||
default: {
|
||||
mode: 'list',
|
||||
value: '',
|
||||
},
|
||||
description: 'Select the container to delete',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['container'],
|
||||
operation: ['delete'],
|
||||
},
|
||||
},
|
||||
modes: [
|
||||
{
|
||||
displayName: 'From List',
|
||||
name: 'list',
|
||||
type: 'list',
|
||||
typeOptions: {
|
||||
searchListMethod: 'getContainers',
|
||||
searchable: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'By Name',
|
||||
name: 'id',
|
||||
placeholder: 'e.g. mycontainer',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
required: true,
|
||||
type: 'resourceLocator',
|
||||
},
|
||||
];
|
||||
|
||||
const getFields: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Container',
|
||||
name: 'container',
|
||||
default: {
|
||||
mode: 'list',
|
||||
value: '',
|
||||
},
|
||||
description: 'Select the container to get',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['container'],
|
||||
operation: ['get'],
|
||||
},
|
||||
},
|
||||
modes: [
|
||||
{
|
||||
displayName: 'From List',
|
||||
name: 'list',
|
||||
type: 'list',
|
||||
typeOptions: {
|
||||
searchListMethod: 'getContainers',
|
||||
searchable: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'By Name',
|
||||
name: 'id',
|
||||
placeholder: 'e.g. mycontainer',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
required: true,
|
||||
type: 'resourceLocator',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['container'],
|
||||
operation: ['get'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Simplify',
|
||||
name: 'simplify',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description:
|
||||
'Whether to return a simplified version of the response instead of the raw data',
|
||||
},
|
||||
],
|
||||
placeholder: 'Add option',
|
||||
type: 'collection',
|
||||
},
|
||||
];
|
||||
|
||||
const getAllFields: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['container'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
send: {
|
||||
paginate: '={{ $value }}',
|
||||
},
|
||||
operations: {
|
||||
async pagination(
|
||||
this: IExecutePaginationFunctions,
|
||||
requestOptions: DeclarativeRestApiSettings.ResultOptions,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
let executions: INodeExecutionData[] = [];
|
||||
let marker: string | undefined = undefined;
|
||||
requestOptions.options.qs ??= {};
|
||||
|
||||
do {
|
||||
requestOptions.options.qs.marker = marker;
|
||||
const responseData = await this.makeRoutingRequest(requestOptions);
|
||||
marker = responseData[0].json.nextMarker as string | undefined;
|
||||
executions = executions.concat(
|
||||
(responseData[0].json.containers as IDataObject[]).map((item) => ({ json: item })),
|
||||
);
|
||||
} while (marker);
|
||||
|
||||
return executions;
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'boolean',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
default: 50,
|
||||
description: 'Max number of results to return',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['container'],
|
||||
operation: ['getAll'],
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
send: {
|
||||
property: 'maxresults',
|
||||
type: 'query',
|
||||
value: '={{ $value }}',
|
||||
},
|
||||
output: {
|
||||
postReceive: [
|
||||
async function (
|
||||
this: IExecuteSingleFunctions,
|
||||
data: INodeExecutionData[],
|
||||
_response: IN8nHttpFullResponse,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
return (data[0].json.containers as IDataObject[]).map((item) => ({ json: item }));
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
},
|
||||
validateType: 'number',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['container'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Fields',
|
||||
name: 'fields',
|
||||
default: [],
|
||||
description: 'The fields to add to the output',
|
||||
options: [
|
||||
{
|
||||
name: 'Metadata',
|
||||
value: 'metadata',
|
||||
},
|
||||
{
|
||||
name: 'Deleted',
|
||||
value: 'deleted',
|
||||
},
|
||||
{
|
||||
name: 'System',
|
||||
value: 'system',
|
||||
},
|
||||
],
|
||||
routing: {
|
||||
send: {
|
||||
property: 'include',
|
||||
type: 'query',
|
||||
value: '={{ $value.join(",") || undefined }}',
|
||||
},
|
||||
},
|
||||
type: 'multiOptions',
|
||||
},
|
||||
{
|
||||
displayName: 'Filter',
|
||||
name: 'filter',
|
||||
default: '',
|
||||
description:
|
||||
'Filters the results to return only containers with a name that begins with the specified prefix',
|
||||
placeholder: 'e.g. mycontainer',
|
||||
routing: {
|
||||
send: {
|
||||
property: 'prefix',
|
||||
type: 'query',
|
||||
value: '={{ $value ? $value : undefined }}',
|
||||
},
|
||||
},
|
||||
type: 'string',
|
||||
validateType: 'string',
|
||||
},
|
||||
],
|
||||
placeholder: 'Add option',
|
||||
type: 'collection',
|
||||
},
|
||||
];
|
||||
|
||||
export const containerFields: INodeProperties[] = [
|
||||
...createFields,
|
||||
...deleteFields,
|
||||
...getFields,
|
||||
...getAllFields,
|
||||
];
|
|
@ -0,0 +1,2 @@
|
|||
export * from './BlobDescription';
|
||||
export * from './ContainerDescription';
|
|
@ -0,0 +1,72 @@
|
|||
import nock from 'nock';
|
||||
|
||||
import { equalityTest, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||
|
||||
describe('Azure Storage Node', () => {
|
||||
const workflows = ['nodes/Microsoft/Storage/test/workflows/blob_create.workflow.json'];
|
||||
const workflowTests = workflowToTests(workflows);
|
||||
|
||||
beforeEach(() => {
|
||||
// https://github.com/nock/nock/issues/2057#issuecomment-663665683
|
||||
if (!nock.isActive()) {
|
||||
nock.activate();
|
||||
}
|
||||
});
|
||||
|
||||
describe('should create blob', () => {
|
||||
const nodeTypes = setup(workflowTests);
|
||||
|
||||
for (const workflow of workflowTests) {
|
||||
workflow.nock = {
|
||||
baseUrl: 'https://myaccount.blob.core.windows.net',
|
||||
mocks: [
|
||||
{
|
||||
method: 'put',
|
||||
path: '/mycontainer/myblob',
|
||||
statusCode: 201,
|
||||
requestHeaders: {
|
||||
'x-ms-access-tier': 'Hot',
|
||||
'x-ms-blob-type': 'BlockBlob',
|
||||
'x-ms-blob-cache-control': 'no-cache',
|
||||
'x-ms-content-crc64': '3EDB64E77CB16A4C',
|
||||
'x-ms-blob-content-encoding': 'utf8',
|
||||
'x-ms-blob-content-language': 'en-US',
|
||||
'x-ms-blob-content-md5': 'b97f46db5f3be7709d942eefe30e5b45',
|
||||
'x-ms-blob-content-type': 'application/json',
|
||||
'x-ms-encryption-context': 'context',
|
||||
'x-ms-encryption-scope': 'encryptionScope',
|
||||
'x-ms-expiry-option': 'Absolute',
|
||||
'x-ms-blob-content-disposition': 'attachment; filename="file.json"',
|
||||
'x-ms-immutability-policy-until-date': 'Wed, 01 Jan 2025 00:00:00 -0500',
|
||||
'x-ms-immutability-policy-mode': 'unlocked',
|
||||
'x-ms-lease-id': 'leaseId123',
|
||||
'x-ms-legal-hold': 'true',
|
||||
'x-ms-meta-key1': 'value1',
|
||||
},
|
||||
responseBody: '',
|
||||
responseHeaders: {
|
||||
server: 'Azurite-Blob/3.33.0',
|
||||
etag: '"0x22769D26D3F3740"',
|
||||
'last-modified': 'Thu, 23 Jan 2025 17:53:23 GMT',
|
||||
'content-md5': 'aWQGHD8kGQd5ZtEN/S1/aw==',
|
||||
'x-ms-request-id': '75b87ee3-a7f7-468d-b7d1-e7e7b3173dab',
|
||||
'x-ms-version': '2025-01-05',
|
||||
date: 'Thu, 23 Jan 2025 17:53:23 GMT',
|
||||
'x-ms-request-server-encrypted': 'true',
|
||||
'keep-alive': 'timeout=5',
|
||||
'content-length': '0',
|
||||
'x-ms-version-id': 'Thu, 23 Jan 2025 17:53:23 GMT',
|
||||
'access-control-allow-credentials': 'access-control-allow-credentials',
|
||||
'access-control-allow-origin': 'access-control-allow-origin',
|
||||
'access-control-expose-headers': 'access-control-expose-headers',
|
||||
'x-ms-content-crc64': 'x-ms-content-crc64',
|
||||
'x-ms-encryption-key-sha256': 'x-ms-encryption-key-sha256',
|
||||
'x-ms-encryption-scope': 'x-ms-encryption-scope',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
test(workflow.description, async () => await equalityTest(workflow, nodeTypes));
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,41 @@
|
|||
import nock from 'nock';
|
||||
|
||||
import { equalityTest, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||
|
||||
describe('Azure Storage Node', () => {
|
||||
const workflows = ['nodes/Microsoft/Storage/test/workflows/blob_delete.workflow.json'];
|
||||
const workflowTests = workflowToTests(workflows);
|
||||
|
||||
beforeEach(() => {
|
||||
// https://github.com/nock/nock/issues/2057#issuecomment-663665683
|
||||
if (!nock.isActive()) {
|
||||
nock.activate();
|
||||
}
|
||||
});
|
||||
|
||||
describe('should delete blob', () => {
|
||||
const nodeTypes = setup(workflowTests);
|
||||
|
||||
for (const workflow of workflowTests) {
|
||||
workflow.nock = {
|
||||
baseUrl: 'https://myaccount.blob.core.windows.net',
|
||||
mocks: [
|
||||
{
|
||||
method: 'delete',
|
||||
path: '/mycontainer/myblob',
|
||||
statusCode: 202,
|
||||
responseBody: '',
|
||||
responseHeaders: {
|
||||
'x-ms-request-id': '75b87ee3-a7f7-468d-b7d1-e7e7b3173dab',
|
||||
'x-ms-version': '2025-01-05',
|
||||
date: 'Thu, 23 Jan 2025 17:53:23 GMT',
|
||||
'x-ms-delete-type-permanent': 'true',
|
||||
'x-ms-client-request-id': 'x-ms-client-request-id',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
test(workflow.description, async () => await equalityTest(workflow, nodeTypes));
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,93 @@
|
|||
import nock from 'nock';
|
||||
|
||||
import { equalityTest, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||
|
||||
describe('Azure Storage Node', () => {
|
||||
const workflows = ['nodes/Microsoft/Storage/test/workflows/blob_get.workflow.json'];
|
||||
const workflowTests = workflowToTests(workflows);
|
||||
|
||||
beforeEach(() => {
|
||||
// https://github.com/nock/nock/issues/2057#issuecomment-663665683
|
||||
if (!nock.isActive()) {
|
||||
nock.activate();
|
||||
}
|
||||
});
|
||||
|
||||
describe('should get blob', () => {
|
||||
const nodeTypes = setup(workflowTests);
|
||||
|
||||
for (const workflow of workflowTests) {
|
||||
workflow.nock = {
|
||||
baseUrl: 'https://myaccount.blob.core.windows.net',
|
||||
mocks: [
|
||||
{
|
||||
method: 'get',
|
||||
path: '/mycontainer/myblob',
|
||||
statusCode: 200,
|
||||
responseBody: {
|
||||
type: 'Buffer',
|
||||
data: [
|
||||
123, 10, 34, 100, 97, 116, 97, 34, 58, 123, 10, 34, 109, 121, 95, 102, 105, 101,
|
||||
108, 100, 95, 49, 34, 58, 34, 118, 97, 108, 117, 101, 34, 44, 10, 34, 109, 121, 95,
|
||||
102, 105, 101, 108, 100, 95, 50, 34, 58, 49, 10, 125, 10, 125,
|
||||
],
|
||||
},
|
||||
responseHeaders: {
|
||||
'x-ms-request-id': '75b87ee3-a7f7-468d-b7d1-e7e7b3173dab',
|
||||
'x-ms-version': '2025-01-05',
|
||||
date: 'Thu, 23 Jan 2025 17:53:23 GMT',
|
||||
'x-ms-client-request-id': 'x-ms-client-request-id',
|
||||
'last-modified': 'last-modified',
|
||||
'x-ms-creation-time': 'x-ms-creation-time',
|
||||
'x-ms-tag-count': 'x-ms-tag-count',
|
||||
'content-type': 'application/json',
|
||||
'content-range': 'content-range',
|
||||
etag: '"0x22769D26D3F3740"',
|
||||
'content-md5': 'content-md5',
|
||||
'x-ms-content-crc64': 'x-ms-content-crc64',
|
||||
'content-encoding': 'content-encoding',
|
||||
'content-language': 'content-language',
|
||||
'cache-control': 'cache-control',
|
||||
'content-disposition': 'attachment; filename="file.json"',
|
||||
'x-ms-blob-sequence-number': 'x-ms-blob-sequence-number',
|
||||
'x-ms-blob-type': 'x-ms-blob-type',
|
||||
'x-ms-copy-completion-time': 'x-ms-copy-completion-time',
|
||||
'x-ms-copy-status-description': 'x-ms-copy-status-description',
|
||||
'x-ms-copy-id': 'x-ms-copy-id',
|
||||
'x-ms-copy-progress': 'x-ms-copy-progress',
|
||||
'x-ms-copy-source': 'x-ms-copy-source',
|
||||
'x-ms-copy-status': 'x-ms-copy-status',
|
||||
'x-ms-incremental-copy': 'x-ms-incremental-copy',
|
||||
'x-ms-lease-duration': 'x-ms-lease-duration',
|
||||
'x-ms-lease-state': 'x-ms-lease-state',
|
||||
'x-ms-lease-status': 'x-ms-lease-status',
|
||||
'accept-ranges': 'accept-ranges',
|
||||
'access-control-allow-origin': 'access-control-allow-origin',
|
||||
'access-control-expose-headers': 'access-control-expose-headers',
|
||||
vary: 'vary',
|
||||
'access-control-allow-credentials': 'access-control-allow-credentials',
|
||||
'x-ms-blob-committed-block-count': 'x-ms-blob-committed-block-count',
|
||||
'x-ms-server-encrypted': 'x-ms-server-encrypted',
|
||||
'x-ms-encryption-key-sha256': 'x-ms-encryption-key-sha256',
|
||||
'x-ms-encryption-context': 'x-ms-encryption-context',
|
||||
'x-ms-encryption-scope': 'x-ms-encryption-scope',
|
||||
'x-ms-blob-content-md5': 'x-ms-blob-content-md5',
|
||||
'x-ms-last-access-time': 'x-ms-last-access-time',
|
||||
'x-ms-blob-sealed': 'x-ms-blob-sealed',
|
||||
'x-ms-immutability-policy-until-date': 'x-ms-immutability-policy-until-date',
|
||||
'x-ms-immutability-policy-mode': 'x-ms-immutability-policy-mode',
|
||||
'x-ms-legal-hold': 'x-ms-legal-hold',
|
||||
'x-ms-owner': 'x-ms-owner',
|
||||
'x-ms-group': 'x-ms-group',
|
||||
'x-ms-permissions': 'x-ms-permissions',
|
||||
'x-ms-acl': 'x-ms-acl',
|
||||
'x-ms-resource-type': 'x-ms-resource-type',
|
||||
'x-ms-meta-key1': 'value1',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
test(workflow.description, async () => await equalityTest(workflow, nodeTypes));
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
import nock from 'nock';
|
||||
|
||||
import { equalityTest, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||
|
||||
describe('Azure Storage Node', () => {
|
||||
const workflows = ['nodes/Microsoft/Storage/test/workflows/blob_getAll.workflow.json'];
|
||||
const workflowTests = workflowToTests(workflows);
|
||||
|
||||
beforeEach(() => {
|
||||
// https://github.com/nock/nock/issues/2057#issuecomment-663665683
|
||||
if (!nock.isActive()) {
|
||||
nock.activate();
|
||||
}
|
||||
});
|
||||
|
||||
describe('should get all blobs', () => {
|
||||
const nodeTypes = setup(workflowTests);
|
||||
|
||||
for (const workflow of workflowTests) {
|
||||
workflow.nock = {
|
||||
baseUrl: 'https://myaccount.blob.core.windows.net',
|
||||
mocks: [
|
||||
{
|
||||
method: 'get',
|
||||
path: '/mycontainer?restype=container&comp=list',
|
||||
statusCode: 200,
|
||||
responseBody:
|
||||
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?><EnumerationResults ServiceEndpoint="https://myaccount.blob.core.windows.net" ContainerName="item1"><Prefix/><Marker/><MaxResults>1</MaxResults><Blobs><Blob><Name>myblob1</Name><Properties><Creation-Time>Wed, 22 Jan 2025 18:53:15 GMT</Creation-Time><Last-Modified>Wed, 22 Jan 2025 18:53:15 GMT</Last-Modified><Etag>0x1F8268B228AA730</Etag><Content-Length>37</Content-Length><Content-Type>application/json</Content-Type><Content-MD5>aWQGHD8kGQd5ZtEN/S1/aw==</Content-MD5><BlobType>BlockBlob</BlobType><LeaseStatus>unlocked</LeaseStatus><LeaseState>available</LeaseState><ServerEncrypted>true</ServerEncrypted><AccessTier>Hot</AccessTier><AccessTierInferred>true</AccessTierInferred><AccessTierChangeTime>Wed, 22 Jan 2025 18:53:15 GMT</AccessTierChangeTime></Properties></Blob></Blobs><NextMarker>myblob2</NextMarker></EnumerationResults>',
|
||||
},
|
||||
{
|
||||
method: 'get',
|
||||
path: '/mycontainer?restype=container&comp=list&marker=myblob2',
|
||||
statusCode: 200,
|
||||
responseBody:
|
||||
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?><EnumerationResults ServiceEndpoint="https://myaccount.blob.core.windows.net" ContainerName="item1"><Prefix/><Marker/><MaxResults>1</MaxResults><Blobs><Blob><Name>myblob1</Name><Properties><Creation-Time>Wed, 22 Jan 2025 18:53:15 GMT</Creation-Time><Last-Modified>Wed, 22 Jan 2025 18:53:15 GMT</Last-Modified><Etag>0x1F8268B228AA730</Etag><Content-Length>37</Content-Length><Content-Type>application/json</Content-Type><Content-MD5>aWQGHD8kGQd5ZtEN/S1/aw==</Content-MD5><BlobType>BlockBlob</BlobType><LeaseStatus>unlocked</LeaseStatus><LeaseState>available</LeaseState><ServerEncrypted>true</ServerEncrypted><AccessTier>Hot</AccessTier><AccessTierInferred>true</AccessTierInferred><AccessTierChangeTime>Wed, 22 Jan 2025 18:53:15 GMT</AccessTierChangeTime></Properties></Blob></Blobs><NextMarker></NextMarker></EnumerationResults>',
|
||||
},
|
||||
],
|
||||
};
|
||||
test(workflow.description, async () => await equalityTest(workflow, nodeTypes));
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,37 @@
|
|||
import nock from 'nock';
|
||||
|
||||
import { equalityTest, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||
|
||||
describe('Azure Storage Node', () => {
|
||||
const workflows = [
|
||||
'nodes/Microsoft/Storage/test/workflows/blob_getAllLimitOptions.workflow.json',
|
||||
];
|
||||
const workflowTests = workflowToTests(workflows);
|
||||
|
||||
beforeEach(() => {
|
||||
// https://github.com/nock/nock/issues/2057#issuecomment-663665683
|
||||
if (!nock.isActive()) {
|
||||
nock.activate();
|
||||
}
|
||||
});
|
||||
|
||||
describe('should get all blobs with limit and options', () => {
|
||||
const nodeTypes = setup(workflowTests);
|
||||
|
||||
for (const workflow of workflowTests) {
|
||||
workflow.nock = {
|
||||
baseUrl: 'https://myaccount.blob.core.windows.net',
|
||||
mocks: [
|
||||
{
|
||||
method: 'get',
|
||||
path: '/mycontainer?restype=container&comp=list&maxresults=1&include=copy%2Cdeleted%2Cdeletedwithversions%2Cimmutabilitypolicy%2Cmetadata%2Clegalhold%2Cversions%2Cuncommittedblobs%2Ctags%2Csnapshots%2Cpermissions&showonly=deleted%2Cfiles%2Cdirectories',
|
||||
statusCode: 200,
|
||||
responseBody:
|
||||
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?><EnumerationResults ServiceEndpoint="https://myaccount.blob.core.windows.net" ContainerName="item1"><Prefix/><Marker/><MaxResults>1</MaxResults><Blobs><Blob><Name>myblob1</Name><Properties><Creation-Time>Wed, 22 Jan 2025 18:53:15 GMT</Creation-Time><Last-Modified>Wed, 22 Jan 2025 18:53:15 GMT</Last-Modified><Etag>0x1F8268B228AA730</Etag><Content-Length>37</Content-Length><Content-Type>application/json</Content-Type><Content-MD5>aWQGHD8kGQd5ZtEN/S1/aw==</Content-MD5><BlobType>BlockBlob</BlobType><LeaseStatus>unlocked</LeaseStatus><LeaseState>available</LeaseState><ServerEncrypted>true</ServerEncrypted><AccessTier>Hot</AccessTier><AccessTierInferred>true</AccessTierInferred><AccessTierChangeTime>Wed, 22 Jan 2025 18:53:15 GMT</AccessTierChangeTime></Properties></Blob></Blobs><NextMarker>myblob2</NextMarker></EnumerationResults>',
|
||||
},
|
||||
],
|
||||
};
|
||||
test(workflow.description, async () => await equalityTest(workflow, nodeTypes));
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,44 @@
|
|||
import nock from 'nock';
|
||||
|
||||
import { equalityTest, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||
|
||||
describe('Azure Storage Node', () => {
|
||||
const workflows = ['nodes/Microsoft/Storage/test/workflows/container_create.workflow.json'];
|
||||
const workflowTests = workflowToTests(workflows);
|
||||
|
||||
beforeEach(() => {
|
||||
// https://github.com/nock/nock/issues/2057#issuecomment-663665683
|
||||
if (!nock.isActive()) {
|
||||
nock.activate();
|
||||
}
|
||||
});
|
||||
|
||||
describe('should create container', () => {
|
||||
const nodeTypes = setup(workflowTests);
|
||||
|
||||
for (const workflow of workflowTests) {
|
||||
workflow.nock = {
|
||||
baseUrl: 'https://myaccount.blob.core.windows.net',
|
||||
mocks: [
|
||||
{
|
||||
method: 'put',
|
||||
path: '/mycontainer?restype=container',
|
||||
statusCode: 201,
|
||||
requestHeaders: { 'x-ms-blob-public-access': 'blob', 'x-ms-meta-key1': 'value1' },
|
||||
responseBody: '',
|
||||
responseHeaders: {
|
||||
etag: '"0x22769D26D3F3740"',
|
||||
'last-modified': 'Thu, 23 Jan 2025 17:53:23 GMT',
|
||||
'x-ms-request-id': '75b87ee3-a7f7-468d-b7d1-e7e7b3173dab',
|
||||
'x-ms-version': '2025-01-05',
|
||||
date: 'Thu, 23 Jan 2025 17:53:23 GMT',
|
||||
'x-ms-request-server-encrypted': 'true',
|
||||
'x-ms-client-request-id': 'client-request-id-123',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
test(workflow.description, async () => await equalityTest(workflow, nodeTypes));
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,41 @@
|
|||
import nock from 'nock';
|
||||
|
||||
import { equalityTest, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||
|
||||
describe('Azure Storage Node', () => {
|
||||
const workflows = ['nodes/Microsoft/Storage/test/workflows/container_delete.workflow.json'];
|
||||
const workflowTests = workflowToTests(workflows);
|
||||
|
||||
beforeEach(() => {
|
||||
// https://github.com/nock/nock/issues/2057#issuecomment-663665683
|
||||
if (!nock.isActive()) {
|
||||
nock.activate();
|
||||
}
|
||||
});
|
||||
|
||||
describe('should delete container', () => {
|
||||
const nodeTypes = setup(workflowTests);
|
||||
|
||||
for (const workflow of workflowTests) {
|
||||
workflow.nock = {
|
||||
baseUrl: 'https://myaccount.blob.core.windows.net',
|
||||
mocks: [
|
||||
{
|
||||
method: 'delete',
|
||||
path: '/mycontainer?restype=container',
|
||||
statusCode: 202,
|
||||
responseBody: '',
|
||||
responseHeaders: {
|
||||
'content-length': '0',
|
||||
server: 'Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0',
|
||||
'x-ms-request-id': 'ca3a8907-601e-0050-1929-723410000000',
|
||||
'x-ms-version': '2020-10-02',
|
||||
date: 'Wed, 29 Jan 2025 08:38:21 GMT',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
test(workflow.description, async () => await equalityTest(workflow, nodeTypes));
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,50 @@
|
|||
import nock from 'nock';
|
||||
|
||||
import { equalityTest, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||
|
||||
describe('Azure Storage Node', () => {
|
||||
const workflows = ['nodes/Microsoft/Storage/test/workflows/container_get.workflow.json'];
|
||||
const workflowTests = workflowToTests(workflows);
|
||||
|
||||
beforeEach(() => {
|
||||
// https://github.com/nock/nock/issues/2057#issuecomment-663665683
|
||||
if (!nock.isActive()) {
|
||||
nock.activate();
|
||||
}
|
||||
});
|
||||
|
||||
describe('should get container', () => {
|
||||
const nodeTypes = setup(workflowTests);
|
||||
|
||||
for (const workflow of workflowTests) {
|
||||
workflow.nock = {
|
||||
baseUrl: 'https://myaccount.blob.core.windows.net',
|
||||
mocks: [
|
||||
{
|
||||
method: 'get',
|
||||
path: '/mycontainer?restype=container',
|
||||
statusCode: 200,
|
||||
responseBody: '',
|
||||
responseHeaders: {
|
||||
'content-length': '0',
|
||||
'last-modified': 'Tue, 28 Jan 2025 16:40:21 GMT',
|
||||
etag: '"0x8DD3FBA74CF3620"',
|
||||
server: 'Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0',
|
||||
'x-ms-request-id': '49edb268-b01e-0053-6e29-72d574000000',
|
||||
'x-ms-version': '2020-10-02',
|
||||
'x-ms-lease-status': 'unlocked',
|
||||
'x-ms-lease-state': 'available',
|
||||
'x-ms-has-immutability-policy': 'false',
|
||||
'x-ms-has-legal-hold': 'false',
|
||||
date: 'Wed, 29 Jan 2025 08:43:08 GMT',
|
||||
'x-ms-meta-key1': 'field1',
|
||||
'x-ms-blob-public-access': 'blob',
|
||||
'x-ms-lease-duration': 'infinite',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
test(workflow.description, async () => await equalityTest(workflow, nodeTypes));
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
import nock from 'nock';
|
||||
|
||||
import { equalityTest, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||
|
||||
describe('Azure Storage Node', () => {
|
||||
const workflows = ['nodes/Microsoft/Storage/test/workflows/container_getAll.workflow.json'];
|
||||
const workflowTests = workflowToTests(workflows);
|
||||
|
||||
beforeEach(() => {
|
||||
// https://github.com/nock/nock/issues/2057#issuecomment-663665683
|
||||
if (!nock.isActive()) {
|
||||
nock.activate();
|
||||
}
|
||||
});
|
||||
|
||||
describe('should get all containers', () => {
|
||||
const nodeTypes = setup(workflowTests);
|
||||
|
||||
for (const workflow of workflowTests) {
|
||||
workflow.nock = {
|
||||
baseUrl: 'https://myaccount.blob.core.windows.net',
|
||||
mocks: [
|
||||
{
|
||||
method: 'get',
|
||||
path: '/?comp=list',
|
||||
statusCode: 200,
|
||||
responseBody:
|
||||
'<?xml version="1.0" encoding="utf-8"?><EnumerationResults ServiceEndpoint="https://myaccount.blob.core.windows.net/"><Prefix>mycontainer</Prefix><MaxResults>1</MaxResults><Containers><Container><Name>mycontainer1</Name><Deleted>true</Deleted><Version>01DB7228F6BEE6E7</Version><Properties><Last-Modified>Wed, 29 Jan 2025 08:37:00 GMT</Last-Modified><Etag>"0x8DD40401935032C"</Etag><LeaseStatus>unlocked</LeaseStatus><LeaseState>expired</LeaseState><HasImmutabilityPolicy>false</HasImmutabilityPolicy><HasLegalHold>false</HasLegalHold><ImmutableStorageWithVersioningEnabled>false</ImmutableStorageWithVersioningEnabled><DeletedTime>Wed, 29 Jan 2025 08:38:21 GMT</DeletedTime><RemainingRetentionDays>7</RemainingRetentionDays></Properties><Metadata><key1>value1</key1></Metadata></Container></Containers><NextMarker>mycontainer2</NextMarker></EnumerationResults>',
|
||||
},
|
||||
{
|
||||
method: 'get',
|
||||
path: '/?comp=list&marker=mycontainer2',
|
||||
statusCode: 200,
|
||||
responseBody:
|
||||
'<?xml version="1.0" encoding="utf-8"?><EnumerationResults ServiceEndpoint="https://myaccount.blob.core.windows.net/"><Prefix>mycontainer</Prefix><MaxResults>1</MaxResults><Containers><Container><Name>mycontainer1</Name><Deleted>true</Deleted><Version>01DB7228F6BEE6E7</Version><Properties><Last-Modified>Wed, 29 Jan 2025 08:37:00 GMT</Last-Modified><Etag>"0x8DD40401935032C"</Etag><LeaseStatus>unlocked</LeaseStatus><LeaseState>expired</LeaseState><HasImmutabilityPolicy>false</HasImmutabilityPolicy><HasLegalHold>false</HasLegalHold><ImmutableStorageWithVersioningEnabled>false</ImmutableStorageWithVersioningEnabled><DeletedTime>Wed, 29 Jan 2025 08:38:21 GMT</DeletedTime><RemainingRetentionDays>7</RemainingRetentionDays></Properties><Metadata><key1>value1</key1></Metadata></Container></Containers><NextMarker></NextMarker></EnumerationResults>',
|
||||
},
|
||||
],
|
||||
};
|
||||
test(workflow.description, async () => await equalityTest(workflow, nodeTypes));
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,37 @@
|
|||
import nock from 'nock';
|
||||
|
||||
import { equalityTest, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||
|
||||
describe('Azure Storage Node', () => {
|
||||
const workflows = [
|
||||
'nodes/Microsoft/Storage/test/workflows/container_getAllLimitOptions.workflow.json',
|
||||
];
|
||||
const workflowTests = workflowToTests(workflows);
|
||||
|
||||
beforeEach(() => {
|
||||
// https://github.com/nock/nock/issues/2057#issuecomment-663665683
|
||||
if (!nock.isActive()) {
|
||||
nock.activate();
|
||||
}
|
||||
});
|
||||
|
||||
describe('should get all containers with limit and options', () => {
|
||||
const nodeTypes = setup(workflowTests);
|
||||
|
||||
for (const workflow of workflowTests) {
|
||||
workflow.nock = {
|
||||
baseUrl: 'https://myaccount.blob.core.windows.net',
|
||||
mocks: [
|
||||
{
|
||||
method: 'get',
|
||||
path: '/?comp=list&maxresults=1&include=metadata%2Cdeleted%2Csystem&prefix=mycontainer',
|
||||
statusCode: 200,
|
||||
responseBody:
|
||||
'<?xml version="1.0" encoding="utf-8"?><EnumerationResults ServiceEndpoint="https://myaccount.blob.core.windows.net/"><Prefix>mycontainer</Prefix><MaxResults>1</MaxResults><Containers><Container><Name>mycontainer1</Name><Deleted>true</Deleted><Version>01DB7228F6BEE6E7</Version><Properties><Last-Modified>Wed, 29 Jan 2025 08:37:00 GMT</Last-Modified><Etag>"0x8DD40401935032C"</Etag><LeaseStatus>unlocked</LeaseStatus><LeaseState>expired</LeaseState><HasImmutabilityPolicy>false</HasImmutabilityPolicy><HasLegalHold>false</HasLegalHold><ImmutableStorageWithVersioningEnabled>false</ImmutableStorageWithVersioningEnabled><DeletedTime>Wed, 29 Jan 2025 08:38:21 GMT</DeletedTime><RemainingRetentionDays>7</RemainingRetentionDays></Properties><Metadata><key1>value1</key1></Metadata></Container></Containers><NextMarker>mycontainer2</NextMarker></EnumerationResults>',
|
||||
},
|
||||
],
|
||||
};
|
||||
test(workflow.description, async () => await equalityTest(workflow, nodeTypes));
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,86 @@
|
|||
import type {
|
||||
ICredentialDataDecryptedObject,
|
||||
IDataObject,
|
||||
IHttpRequestOptions,
|
||||
} from 'n8n-workflow';
|
||||
import nock from 'nock';
|
||||
|
||||
import { CredentialsHelper, equalityTest, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||
|
||||
describe('Azure Storage Node', () => {
|
||||
const workflows = ['nodes/Microsoft/Storage/test/workflows/credentials_oauth2.workflow.json'];
|
||||
const workflowTests = workflowToTests(workflows);
|
||||
|
||||
beforeEach(() => {
|
||||
// https://github.com/nock/nock/issues/2057#issuecomment-663665683
|
||||
if (!nock.isActive()) {
|
||||
nock.activate();
|
||||
}
|
||||
});
|
||||
|
||||
describe('should use correct oauth2 credentials', () => {
|
||||
beforeAll(() => {
|
||||
nock.disableNetConnect();
|
||||
|
||||
jest
|
||||
.spyOn(CredentialsHelper.prototype, 'authenticate')
|
||||
.mockImplementation(
|
||||
async (
|
||||
credentials: ICredentialDataDecryptedObject,
|
||||
typeName: string,
|
||||
requestParams: IHttpRequestOptions,
|
||||
): Promise<IHttpRequestOptions> => {
|
||||
if (typeName === 'azureStorageOAuth2Api') {
|
||||
return {
|
||||
...requestParams,
|
||||
headers: {
|
||||
authorization: `bearer ${(credentials.oauthTokenData as IDataObject).access_token as string}`,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return requestParams;
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
nock.restore();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
const nodeTypes = setup(workflowTests);
|
||||
|
||||
for (const workflow of workflowTests) {
|
||||
workflow.nock = {
|
||||
baseUrl: 'https://myaccount.blob.core.windows.net',
|
||||
mocks: [
|
||||
{
|
||||
method: 'get',
|
||||
path: '/mycontainer?restype=container',
|
||||
statusCode: 200,
|
||||
requestHeaders: { authorization: 'bearer ACCESSTOKEN' },
|
||||
responseBody: '',
|
||||
responseHeaders: {
|
||||
'content-length': '0',
|
||||
'last-modified': 'Tue, 28 Jan 2025 16:40:21 GMT',
|
||||
etag: '"0x8DD3FBA74CF3620"',
|
||||
server: 'Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0',
|
||||
'x-ms-request-id': '49edb268-b01e-0053-6e29-72d574000000',
|
||||
'x-ms-version': '2020-10-02',
|
||||
'x-ms-lease-status': 'unlocked',
|
||||
'x-ms-lease-state': 'available',
|
||||
'x-ms-has-immutability-policy': 'false',
|
||||
'x-ms-has-legal-hold': 'false',
|
||||
date: 'Wed, 29 Jan 2025 08:43:08 GMT',
|
||||
'x-ms-meta-key1': 'field1',
|
||||
'x-ms-blob-public-access': 'blob',
|
||||
'x-ms-lease-duration': 'infinite',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
test(workflow.description, async () => await equalityTest(workflow, nodeTypes));
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,158 @@
|
|||
import type { ICredentialDataDecryptedObject, IHttpRequestOptions } from 'n8n-workflow';
|
||||
import nock from 'nock';
|
||||
|
||||
import { CredentialsHelper, equalityTest, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||
|
||||
import { AzureStorageSharedKeyApi } from '../../../../../credentials/AzureStorageSharedKeyApi.credentials';
|
||||
import { FAKE_CREDENTIALS_DATA } from '../../../../../test/nodes/FakeCredentialsMap';
|
||||
|
||||
describe('Azure Storage Node', () => {
|
||||
const workflows = ['nodes/Microsoft/Storage/test/workflows/credentials_sharedKey.workflow.json'];
|
||||
const workflowTests = workflowToTests(workflows);
|
||||
|
||||
beforeEach(() => {
|
||||
// https://github.com/nock/nock/issues/2057#issuecomment-663665683
|
||||
if (!nock.isActive()) {
|
||||
nock.activate();
|
||||
}
|
||||
});
|
||||
|
||||
describe('should use correct shared key credentials', () => {
|
||||
beforeAll(() => {
|
||||
nock.disableNetConnect();
|
||||
|
||||
jest
|
||||
.spyOn(CredentialsHelper.prototype, 'authenticate')
|
||||
.mockImplementation(
|
||||
async (
|
||||
_credentials: ICredentialDataDecryptedObject,
|
||||
typeName: string,
|
||||
requestParams: IHttpRequestOptions,
|
||||
): Promise<IHttpRequestOptions> => {
|
||||
if (typeName === 'azureStorageSharedKeyApi') {
|
||||
return {
|
||||
...requestParams,
|
||||
headers: {
|
||||
authorization:
|
||||
'SharedKey Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==',
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return requestParams;
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
nock.restore();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
const nodeTypes = setup(workflowTests);
|
||||
|
||||
for (const workflow of workflowTests) {
|
||||
workflow.nock = {
|
||||
baseUrl: 'https://myaccount.blob.core.windows.net',
|
||||
mocks: [
|
||||
{
|
||||
method: 'get',
|
||||
path: '/mycontainer?restype=container',
|
||||
statusCode: 200,
|
||||
requestHeaders: {
|
||||
authorization:
|
||||
'SharedKey Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==',
|
||||
},
|
||||
responseBody: '',
|
||||
responseHeaders: {
|
||||
'content-length': '0',
|
||||
'last-modified': 'Tue, 28 Jan 2025 16:40:21 GMT',
|
||||
etag: '"0x8DD3FBA74CF3620"',
|
||||
server: 'Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0',
|
||||
'x-ms-request-id': '49edb268-b01e-0053-6e29-72d574000000',
|
||||
'x-ms-version': '2020-10-02',
|
||||
'x-ms-lease-status': 'unlocked',
|
||||
'x-ms-lease-state': 'available',
|
||||
'x-ms-has-immutability-policy': 'false',
|
||||
'x-ms-has-legal-hold': 'false',
|
||||
date: 'Wed, 29 Jan 2025 08:43:08 GMT',
|
||||
'x-ms-meta-key1': 'field1',
|
||||
'x-ms-blob-public-access': 'blob',
|
||||
'x-ms-lease-duration': 'infinite',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
test(workflow.description, async () => await equalityTest(workflow, nodeTypes));
|
||||
}
|
||||
});
|
||||
|
||||
describe('authenticate', () => {
|
||||
const azureStorageSharedKeyApi = new AzureStorageSharedKeyApi();
|
||||
|
||||
it('should remove undefined query parameters and headers', async () => {
|
||||
const credentials: ICredentialDataDecryptedObject = {
|
||||
account: FAKE_CREDENTIALS_DATA.azureStorageSharedKeyApi.account,
|
||||
key: FAKE_CREDENTIALS_DATA.azureStorageSharedKeyApi.key,
|
||||
};
|
||||
const requestOptions: IHttpRequestOptions = {
|
||||
url: `${FAKE_CREDENTIALS_DATA.azureStorageSharedKeyApi.baseUrl}/mycontainer`,
|
||||
qs: { restype: 'container', query1: undefined },
|
||||
headers: {
|
||||
'Content-Length': undefined,
|
||||
},
|
||||
method: 'GET',
|
||||
};
|
||||
|
||||
const result = await azureStorageSharedKeyApi.authenticate(credentials, requestOptions);
|
||||
|
||||
expect(result.qs).toEqual({ restype: 'container' });
|
||||
expect(result.headers).not.toHaveProperty('Content-Length');
|
||||
});
|
||||
|
||||
it('should default method to GET if not provided', async () => {
|
||||
const credentials: ICredentialDataDecryptedObject = {
|
||||
account: FAKE_CREDENTIALS_DATA.azureStorageSharedKeyApi.account,
|
||||
key: FAKE_CREDENTIALS_DATA.azureStorageSharedKeyApi.key,
|
||||
};
|
||||
const requestOptions: IHttpRequestOptions = {
|
||||
url: `${FAKE_CREDENTIALS_DATA.azureStorageSharedKeyApi.baseUrl}/mycontainer`,
|
||||
qs: { restype: 'container' },
|
||||
headers: {
|
||||
'Content-Length': undefined,
|
||||
},
|
||||
};
|
||||
|
||||
const result = await azureStorageSharedKeyApi.authenticate(credentials, requestOptions);
|
||||
expect(result.method).toBe('GET');
|
||||
});
|
||||
|
||||
it('should generate a valid authorization header', async () => {
|
||||
const credentials: ICredentialDataDecryptedObject = {
|
||||
account: FAKE_CREDENTIALS_DATA.azureStorageSharedKeyApi.account,
|
||||
key: FAKE_CREDENTIALS_DATA.azureStorageSharedKeyApi.key,
|
||||
};
|
||||
const requestOptions: IHttpRequestOptions = {
|
||||
url: `${FAKE_CREDENTIALS_DATA.azureStorageSharedKeyApi.baseUrl}/mycontainer/myblob`,
|
||||
qs: { param1: 'value1' },
|
||||
headers: {
|
||||
'x-ms-date': 'Thu, 27 Feb 2025 11:05:49 GMT',
|
||||
'x-ms-version': '2021-12-02',
|
||||
'x-ms-blob-content-language': 'en-EN',
|
||||
'x-ms-blob-content-type': 'image/jpeg',
|
||||
'x-ms-expiry-option': 'Absolute',
|
||||
'x-ms-blob-type': 'BlockBlob',
|
||||
'x-ms-blob-content-disposition': 'attachment; filename="image.jpg"',
|
||||
'x-ms-meta-key1': 'value1',
|
||||
'x-ms-tags': 'tag1=value1',
|
||||
},
|
||||
method: 'PUT',
|
||||
};
|
||||
const result = await azureStorageSharedKeyApi.authenticate(credentials, requestOptions);
|
||||
|
||||
expect(result.headers?.authorization).toBe(
|
||||
'SharedKey devstoreaccount1:6sSQ3N4yNFQynBs/iLptIRPS5DQeaFBocW+dyYbAdOI=',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,123 @@
|
|||
import type { ILoadOptionsFunctions, INodeParameterResourceLocator } from 'n8n-workflow';
|
||||
import nock from 'nock';
|
||||
|
||||
import { FAKE_CREDENTIALS_DATA } from '../../../../../test/nodes/FakeCredentialsMap';
|
||||
import { AzureStorage } from '../../AzureStorage.node';
|
||||
import { XMsVersion } from '../../GenericFunctions';
|
||||
|
||||
describe('Azure Storage Node', () => {
|
||||
beforeEach(() => {
|
||||
// https://github.com/nock/nock/issues/2057#issuecomment-663665683
|
||||
if (!nock.isActive()) {
|
||||
nock.activate();
|
||||
}
|
||||
});
|
||||
|
||||
describe('List search', () => {
|
||||
it('should list search blobs', async () => {
|
||||
const mockResponse =
|
||||
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?><EnumerationResults ServiceEndpoint="https://myaccount.blob.core.windows.net" ContainerName="item1"><Prefix/><Marker/><MaxResults>1</MaxResults><Blobs><Blob><Name>myblob1</Name><Properties><Creation-Time>Wed, 22 Jan 2025 18:53:15 GMT</Creation-Time><Last-Modified>Wed, 22 Jan 2025 18:53:15 GMT</Last-Modified><Etag>0x1F8268B228AA730</Etag><Content-Length>37</Content-Length><Content-Type>application/json</Content-Type><Content-MD5>aWQGHD8kGQd5ZtEN/S1/aw==</Content-MD5><BlobType>BlockBlob</BlobType><LeaseStatus>unlocked</LeaseStatus><LeaseState>available</LeaseState><ServerEncrypted>true</ServerEncrypted><AccessTier>Hot</AccessTier><AccessTierInferred>true</AccessTierInferred><AccessTierChangeTime>Wed, 22 Jan 2025 18:53:15 GMT</AccessTierChangeTime></Properties></Blob></Blobs><NextMarker>myblob2</NextMarker></EnumerationResults>';
|
||||
const mockRequestWithAuthentication = jest.fn().mockReturnValue(mockResponse);
|
||||
const mockGetNodeParameter = jest.fn((parameterName, _fallbackValue, _options) => {
|
||||
if (parameterName === 'authentication') {
|
||||
return 'sharedKey';
|
||||
}
|
||||
if (parameterName === 'container') {
|
||||
return {
|
||||
value: 'mycontainer',
|
||||
} as INodeParameterResourceLocator;
|
||||
}
|
||||
// eslint-disable-next-line n8n-local-rules/no-plain-errors
|
||||
throw new Error('Unknown parameter');
|
||||
});
|
||||
const mockGetCredentials = jest.fn(async (type: string, _itemIndex?: number) => {
|
||||
if (type === 'azureStorageSharedKeyApi') {
|
||||
return FAKE_CREDENTIALS_DATA.azureStorageSharedKeyApi;
|
||||
}
|
||||
// eslint-disable-next-line n8n-local-rules/no-plain-errors
|
||||
throw new Error('Unknown credentials');
|
||||
});
|
||||
const mockContext = {
|
||||
getCredentials: mockGetCredentials,
|
||||
getNodeParameter: mockGetNodeParameter,
|
||||
helpers: {
|
||||
requestWithAuthentication: mockRequestWithAuthentication,
|
||||
},
|
||||
} as unknown as ILoadOptionsFunctions;
|
||||
jest.useFakeTimers().setSystemTime(new Date('2025-01-01T00:00:00Z'));
|
||||
const node = new AzureStorage();
|
||||
|
||||
const listSearchResult = await node.methods.listSearch.getBlobs.call(mockContext);
|
||||
|
||||
expect(mockRequestWithAuthentication).toHaveBeenCalledWith('azureStorageSharedKeyApi', {
|
||||
method: 'GET',
|
||||
url: 'https://myaccount.blob.core.windows.net/mycontainer',
|
||||
headers: {
|
||||
'x-ms-date': 'Wed, 01 Jan 2025 00:00:00 GMT',
|
||||
'x-ms-version': XMsVersion,
|
||||
},
|
||||
qs: {
|
||||
comp: 'list',
|
||||
maxresults: 5000,
|
||||
restype: 'container',
|
||||
},
|
||||
body: {},
|
||||
});
|
||||
expect(listSearchResult).toEqual({
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||
results: [{ name: 'myblob1', value: 'myblob1' }],
|
||||
paginationToken: 'myblob2',
|
||||
});
|
||||
});
|
||||
|
||||
it('should list search containers', async () => {
|
||||
const mockResponse =
|
||||
'<?xml version="1.0" encoding="utf-8"?><EnumerationResults ServiceEndpoint="https://myaccount.blob.core.windows.net/"><Prefix>mycontainer</Prefix><MaxResults>1</MaxResults><Containers><Container><Name>mycontainer1</Name><Deleted>true</Deleted><Version>01DB7228F6BEE6E7</Version><Properties><Last-Modified>Wed, 29 Jan 2025 08:37:00 GMT</Last-Modified><Etag>"0x8DD40401935032C"</Etag><LeaseStatus>unlocked</LeaseStatus><LeaseState>expired</LeaseState><HasImmutabilityPolicy>false</HasImmutabilityPolicy><HasLegalHold>false</HasLegalHold><ImmutableStorageWithVersioningEnabled>false</ImmutableStorageWithVersioningEnabled><DeletedTime>Wed, 29 Jan 2025 08:38:21 GMT</DeletedTime><RemainingRetentionDays>7</RemainingRetentionDays></Properties><Metadata><key1>value1</key1></Metadata></Container></Containers><NextMarker>mycontainer2</NextMarker></EnumerationResults>';
|
||||
const mockRequestWithAuthentication = jest.fn().mockReturnValue(mockResponse);
|
||||
const mockGetNodeParameter = jest.fn((parameterName, _fallbackValue, _options) => {
|
||||
if (parameterName === 'authentication') {
|
||||
return 'sharedKey';
|
||||
}
|
||||
// eslint-disable-next-line n8n-local-rules/no-plain-errors
|
||||
throw new Error('Unknown parameter');
|
||||
});
|
||||
const mockGetCredentials = jest.fn(async (type: string, _itemIndex?: number) => {
|
||||
if (type === 'azureStorageSharedKeyApi') {
|
||||
return FAKE_CREDENTIALS_DATA.azureStorageSharedKeyApi;
|
||||
}
|
||||
// eslint-disable-next-line n8n-local-rules/no-plain-errors
|
||||
throw new Error('Unknown credentials');
|
||||
});
|
||||
const mockContext = {
|
||||
getCredentials: mockGetCredentials,
|
||||
getNodeParameter: mockGetNodeParameter,
|
||||
helpers: {
|
||||
requestWithAuthentication: mockRequestWithAuthentication,
|
||||
},
|
||||
} as unknown as ILoadOptionsFunctions;
|
||||
jest.useFakeTimers().setSystemTime(new Date('2025-01-01T00:00:00Z'));
|
||||
const node = new AzureStorage();
|
||||
|
||||
const listSearchResult = await node.methods.listSearch.getContainers.call(mockContext);
|
||||
|
||||
expect(mockRequestWithAuthentication).toHaveBeenCalledWith('azureStorageSharedKeyApi', {
|
||||
method: 'GET',
|
||||
url: 'https://myaccount.blob.core.windows.net/',
|
||||
headers: {
|
||||
'x-ms-date': 'Wed, 01 Jan 2025 00:00:00 GMT',
|
||||
'x-ms-version': XMsVersion,
|
||||
},
|
||||
qs: {
|
||||
comp: 'list',
|
||||
maxresults: 5000,
|
||||
},
|
||||
body: {},
|
||||
});
|
||||
expect(listSearchResult).toEqual({
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||
results: [{ name: 'mycontainer1', value: 'mycontainer1' }],
|
||||
paginationToken: 'mycontainer2',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,109 @@
|
|||
{
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0],
|
||||
"id": "99f866fa-f63c-477d-a0d0-48fbdb8a344a",
|
||||
"name": "When clicking ‘Test workflow’"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "blob",
|
||||
"operation": "create",
|
||||
"container": { "__rl": true, "value": "mycontainer", "mode": "list" },
|
||||
"blobCreate": "myblob",
|
||||
"options": {
|
||||
"accessTier": "Hot",
|
||||
"blobType": "BlockBlob",
|
||||
"cacheControl": "no-cache",
|
||||
"contentCrc64": "3EDB64E77CB16A4C",
|
||||
"contentEncoding": "utf8",
|
||||
"contentLanguage": "en-US",
|
||||
"contentMd5": "b97f46db5f3be7709d942eefe30e5b45",
|
||||
"contentType": "application/json",
|
||||
"encryptionContext": "context",
|
||||
"encryptionScope": "encryptionScope",
|
||||
"expiryOption": "Absolute",
|
||||
"filename": "file.json",
|
||||
"immutabilityPolicyUntilDate": "2025-01-01T00:00:00",
|
||||
"immutabilityPolicyMode": "unlocked",
|
||||
"leaseId": "leaseId123",
|
||||
"legalHold": true,
|
||||
"metadata": { "metadataValues": [{ "fieldName": "key1", "fieldValue": "value1" }] },
|
||||
"origin": "http://contoso.com",
|
||||
"tags": { "tagValues": [{ "tagName": "tag1", "tagValue": "value1" }] }
|
||||
},
|
||||
"requestOptions": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.azureStorage",
|
||||
"typeVersion": 1,
|
||||
"position": [660, 0],
|
||||
"id": "ab1b6258-5c75-4893-90bf-ef591264420c",
|
||||
"name": "Azure Storage",
|
||||
"credentials": {
|
||||
"azureStorageSharedKeyApi": {
|
||||
"id": "VPmcFM58eDDexWQL",
|
||||
"name": "Azure Storage Shared Key account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "jsonToBinary",
|
||||
"convertAllData": false,
|
||||
"options": { "useRawData": true }
|
||||
},
|
||||
"name": "Move Binary Data",
|
||||
"type": "n8n-nodes-base.moveBinaryData",
|
||||
"typeVersion": 1,
|
||||
"position": [440, 0],
|
||||
"id": "221e00ed-8c9b-4313-a4d6-b87c64b09d80"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "raw",
|
||||
"jsonOutput": "{\n \"data\": {\n \"my_field_1\": \"value\",\n \"my_field_2\": 1\n }\n}\n",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [220, 0],
|
||||
"id": "6c03903b-0177-4e94-b1ad-4f6ad9e84f62",
|
||||
"name": "Edit Fields"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking ‘Test workflow’": {
|
||||
"main": [[{ "node": "Edit Fields", "type": "main", "index": 0 }]]
|
||||
},
|
||||
"Move Binary Data": { "main": [[{ "node": "Azure Storage", "type": "main", "index": 0 }]] },
|
||||
"Edit Fields": { "main": [[{ "node": "Move Binary Data", "type": "main", "index": 0 }]] }
|
||||
},
|
||||
"pinData": {
|
||||
"Azure Storage": [
|
||||
{
|
||||
"json": {
|
||||
"etag": "\"0x22769D26D3F3740\"",
|
||||
"lastModified": "Thu, 23 Jan 2025 17:53:23 GMT",
|
||||
"contentMd5": "aWQGHD8kGQd5ZtEN/S1/aw==",
|
||||
"requestId": "75b87ee3-a7f7-468d-b7d1-e7e7b3173dab",
|
||||
"version": "2025-01-05",
|
||||
"date": "Thu, 23 Jan 2025 17:53:23 GMT",
|
||||
"requestServerEncrypted": true,
|
||||
"accessControlAllowCredentials": "access-control-allow-credentials",
|
||||
"accessControlAllowOrigin": "access-control-allow-origin",
|
||||
"accessControlExposeHeaders": "access-control-expose-headers",
|
||||
"contentCrc64": "x-ms-content-crc64",
|
||||
"contentLength": 0,
|
||||
"encryptionKeySha256": "x-ms-encryption-key-sha256",
|
||||
"encryptionScope": "x-ms-encryption-scope",
|
||||
"versionId": "Thu, 23 Jan 2025 17:53:23 GMT",
|
||||
"keepAlive": "timeout=5",
|
||||
"server": "Azurite-Blob/3.33.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0],
|
||||
"id": "99f866fa-f63c-477d-a0d0-48fbdb8a344a",
|
||||
"name": "When clicking ‘Test workflow’"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "blob",
|
||||
"operation": "delete",
|
||||
"container": { "__rl": true, "mode": "id", "value": "mycontainer" },
|
||||
"blob": { "__rl": true, "mode": "id", "value": "myblob" },
|
||||
"options": { "leaseId": "leaseId123" },
|
||||
"requestOptions": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.azureStorage",
|
||||
"typeVersion": 1,
|
||||
"position": [660, 0],
|
||||
"id": "ab1b6258-5c75-4893-90bf-ef591264420c",
|
||||
"name": "Azure Storage",
|
||||
"credentials": {
|
||||
"azureStorageSharedKeyApi": {
|
||||
"id": "VPmcFM58eDDexWQL",
|
||||
"name": "Azure Storage Shared Key account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking ‘Test workflow’": {
|
||||
"main": [[{ "node": "Azure Storage", "type": "main", "index": 0 }]]
|
||||
}
|
||||
},
|
||||
"pinData": {
|
||||
"Azure Storage": [
|
||||
{
|
||||
"json": {
|
||||
"requestId": "75b87ee3-a7f7-468d-b7d1-e7e7b3173dab",
|
||||
"version": "2025-01-05",
|
||||
"date": "Thu, 23 Jan 2025 17:53:23 GMT",
|
||||
"deleteTypePermanent": true,
|
||||
"clientRequestId": "x-ms-client-request-id"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
{
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0],
|
||||
"id": "99f866fa-f63c-477d-a0d0-48fbdb8a344a",
|
||||
"name": "When clicking ‘Test workflow’"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "blob",
|
||||
"operation": "get",
|
||||
"container": { "__rl": true, "value": "mycontainer", "mode": "list" },
|
||||
"blob": { "__rl": true, "value": "myblob", "mode": "list" },
|
||||
"options": {
|
||||
"leaseId": "leaseId123",
|
||||
"origin": "origin123",
|
||||
"simplify": false,
|
||||
"upn": true
|
||||
},
|
||||
"requestOptions": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.azureStorage",
|
||||
"typeVersion": 1,
|
||||
"position": [660, 0],
|
||||
"id": "ab1b6258-5c75-4893-90bf-ef591264420c",
|
||||
"name": "Azure Storage",
|
||||
"credentials": {
|
||||
"azureStorageSharedKeyApi": {
|
||||
"id": "VPmcFM58eDDexWQL",
|
||||
"name": "Azure Storage Shared Key account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking ‘Test workflow’": {
|
||||
"main": [[{ "node": "Azure Storage", "type": "main", "index": 0 }]]
|
||||
}
|
||||
},
|
||||
|
||||
"pinData": {
|
||||
"Azure Storage": [
|
||||
{
|
||||
"binary": {
|
||||
"data": {
|
||||
"fileExtension": "json",
|
||||
"fileName": "file.json",
|
||||
"fileSize": "51 B",
|
||||
"fileType": "json",
|
||||
"mimeType": "application/json"
|
||||
}
|
||||
},
|
||||
"json": {
|
||||
"name": "myblob",
|
||||
"properties": {
|
||||
"clientRequestId": "x-ms-client-request-id",
|
||||
"lastModified": "last-modified",
|
||||
"creationTime": "x-ms-creation-time",
|
||||
"tagCount": "x-ms-tag-count",
|
||||
"contentType": "application/json",
|
||||
"contentRange": "content-range",
|
||||
"etag": "\"0x22769D26D3F3740\"",
|
||||
"contentMd5": "content-md5",
|
||||
"contentCrc64": "x-ms-content-crc64",
|
||||
"contentEncoding": "content-encoding",
|
||||
"contentLanguage": "content-language",
|
||||
"cacheControl": "cache-control",
|
||||
"contentDisposition": "attachment; filename=\"file.json\"",
|
||||
"blobSequenceNumber": "x-ms-blob-sequence-number",
|
||||
"blobType": "x-ms-blob-type",
|
||||
"copyCompletionTime": "x-ms-copy-completion-time",
|
||||
"copyStatusDescription": "x-ms-copy-status-description",
|
||||
"copyId": "x-ms-copy-id",
|
||||
"copyProgress": "x-ms-copy-progress",
|
||||
"copySource": "x-ms-copy-source",
|
||||
"copyStatus": "x-ms-copy-status",
|
||||
"incrementalCopy": "x-ms-incremental-copy",
|
||||
"leaseDuration": "x-ms-lease-duration",
|
||||
"leaseState": "x-ms-lease-state",
|
||||
"leaseStatus": "x-ms-lease-status",
|
||||
"accessControlAllowOrigin": "access-control-allow-origin",
|
||||
"accessControlExposeHeaders": "access-control-expose-headers",
|
||||
"vary": "vary",
|
||||
"accessControlAllowCredentials": "access-control-allow-credentials",
|
||||
"blobCommittedBlockCount": "x-ms-blob-committed-block-count",
|
||||
"serverEncrypted": "x-ms-server-encrypted",
|
||||
"encryptionKeySha256": "x-ms-encryption-key-sha256",
|
||||
"encryptionContext": "x-ms-encryption-context",
|
||||
"encryptionScope": "x-ms-encryption-scope",
|
||||
"blobContentMd5": "x-ms-blob-content-md5",
|
||||
"lastAccessTime": "x-ms-last-access-time",
|
||||
"blobSealed": "x-ms-blob-sealed",
|
||||
"immutabilityPolicyUntilDate": "x-ms-immutability-policy-until-date",
|
||||
"immutabilityPolicyMode": "x-ms-immutability-policy-mode",
|
||||
"legalHold": "x-ms-legal-hold",
|
||||
"owner": "x-ms-owner",
|
||||
"group": "x-ms-group",
|
||||
"permissions": "x-ms-permissions",
|
||||
"acl": "x-ms-acl",
|
||||
"resourceType": "x-ms-resource-type",
|
||||
"acceptRanges": "accept-ranges",
|
||||
"date": "Thu, 23 Jan 2025 17:53:23 GMT",
|
||||
"requestId": "75b87ee3-a7f7-468d-b7d1-e7e7b3173dab",
|
||||
"version": "2025-01-05"
|
||||
},
|
||||
"metadata": { "key1": "value1" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
{
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0],
|
||||
"id": "99f866fa-f63c-477d-a0d0-48fbdb8a344a",
|
||||
"name": "When clicking ‘Test workflow’"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "blob",
|
||||
"operation": "getAll",
|
||||
"container": { "__rl": true, "value": "mycontainer", "mode": "list" },
|
||||
"returnAll": true,
|
||||
"options": { "simplify": false },
|
||||
"requestOptions": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.azureStorage",
|
||||
"typeVersion": 1,
|
||||
"position": [660, 0],
|
||||
"id": "ab1b6258-5c75-4893-90bf-ef591264420c",
|
||||
"name": "Azure Storage",
|
||||
"credentials": {
|
||||
"azureStorageSharedKeyApi": {
|
||||
"id": "VPmcFM58eDDexWQL",
|
||||
"name": "Azure Storage Shared Key account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking ‘Test workflow’": {
|
||||
"main": [[{ "node": "Azure Storage", "type": "main", "index": 0 }]]
|
||||
}
|
||||
},
|
||||
"pinData": {
|
||||
"Azure Storage": [
|
||||
{
|
||||
"json": {
|
||||
"name": "myblob1",
|
||||
"properties": {
|
||||
"creationTime": "Wed, 22 Jan 2025 18:53:15 GMT",
|
||||
"lastModified": "Wed, 22 Jan 2025 18:53:15 GMT",
|
||||
"etag": "0x1F8268B228AA730",
|
||||
"contentLength": 37,
|
||||
"contentType": "application/json",
|
||||
"contentMD5": "aWQGHD8kGQd5ZtEN/S1/aw==",
|
||||
"blobType": "BlockBlob",
|
||||
"leaseStatus": "unlocked",
|
||||
"leaseState": "available",
|
||||
"serverEncrypted": true,
|
||||
"accessTier": "Hot",
|
||||
"accessTierInferred": true,
|
||||
"accessTierChangeTime": "Wed, 22 Jan 2025 18:53:15 GMT"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"name": "myblob1",
|
||||
"properties": {
|
||||
"creationTime": "Wed, 22 Jan 2025 18:53:15 GMT",
|
||||
"lastModified": "Wed, 22 Jan 2025 18:53:15 GMT",
|
||||
"etag": "0x1F8268B228AA730",
|
||||
"contentLength": 37,
|
||||
"contentType": "application/json",
|
||||
"contentMD5": "aWQGHD8kGQd5ZtEN/S1/aw==",
|
||||
"blobType": "BlockBlob",
|
||||
"leaseStatus": "unlocked",
|
||||
"leaseState": "available",
|
||||
"serverEncrypted": true,
|
||||
"accessTier": "Hot",
|
||||
"accessTierInferred": true,
|
||||
"accessTierChangeTime": "Wed, 22 Jan 2025 18:53:15 GMT"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
{
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0],
|
||||
"id": "99f866fa-f63c-477d-a0d0-48fbdb8a344a",
|
||||
"name": "When clicking ‘Test workflow’"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "blob",
|
||||
"operation": "getAll",
|
||||
"container": { "__rl": true, "value": "mycontainer", "mode": "list" },
|
||||
"limit": 1,
|
||||
"options": {
|
||||
"fields": [
|
||||
"copy",
|
||||
"deleted",
|
||||
"deletedwithversions",
|
||||
"immutabilitypolicy",
|
||||
"metadata",
|
||||
"legalhold",
|
||||
"versions",
|
||||
"uncommittedblobs",
|
||||
"tags",
|
||||
"snapshots",
|
||||
"permissions"
|
||||
],
|
||||
"filter": ["deleted", "files", "directories"],
|
||||
"simplify": true,
|
||||
"upn": true
|
||||
},
|
||||
"requestOptions": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.azureStorage",
|
||||
"typeVersion": 1,
|
||||
"position": [660, 0],
|
||||
"id": "ab1b6258-5c75-4893-90bf-ef591264420c",
|
||||
"name": "Azure Storage",
|
||||
"credentials": {
|
||||
"azureStorageSharedKeyApi": {
|
||||
"id": "VPmcFM58eDDexWQL",
|
||||
"name": "Azure Storage account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking ‘Test workflow’": {
|
||||
"main": [[{ "node": "Azure Storage", "type": "main", "index": 0 }]]
|
||||
}
|
||||
},
|
||||
"pinData": {
|
||||
"Azure Storage": [
|
||||
{
|
||||
"json": {
|
||||
"name": "myblob1",
|
||||
"properties": {
|
||||
"creationTime": "Wed, 22 Jan 2025 18:53:15 GMT",
|
||||
"lastModified": "Wed, 22 Jan 2025 18:53:15 GMT",
|
||||
"etag": "0x1F8268B228AA730",
|
||||
"contentLength": 37,
|
||||
"contentType": "application/json",
|
||||
"contentMD5": "aWQGHD8kGQd5ZtEN/S1/aw==",
|
||||
"blobType": "BlockBlob",
|
||||
"leaseStatus": "unlocked",
|
||||
"leaseState": "available",
|
||||
"serverEncrypted": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0],
|
||||
"id": "99f866fa-f63c-477d-a0d0-48fbdb8a344a",
|
||||
"name": "When clicking ‘Test workflow’"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "create",
|
||||
"containerCreate": "mycontainer",
|
||||
"options": {
|
||||
"accessLevel": "blob",
|
||||
"metadata": { "metadataValues": [{ "fieldName": "key1", "fieldValue": "value1" }] }
|
||||
},
|
||||
"requestOptions": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.azureStorage",
|
||||
"typeVersion": 1,
|
||||
"position": [220, 0],
|
||||
"id": "ab1b6258-5c75-4893-90bf-ef591264420c",
|
||||
"name": "Azure Storage",
|
||||
"credentials": {
|
||||
"azureStorageSharedKeyApi": {
|
||||
"id": "VPmcFM58eDDexWQL",
|
||||
"name": "Azure Storage Shared Key account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking ‘Test workflow’": {
|
||||
"main": [[{ "node": "Azure Storage", "type": "main", "index": 0 }]]
|
||||
}
|
||||
},
|
||||
"pinData": {
|
||||
"Azure Storage": [
|
||||
{
|
||||
"json": {
|
||||
"etag": "\"0x22769D26D3F3740\"",
|
||||
"lastModified": "Thu, 23 Jan 2025 17:53:23 GMT",
|
||||
"requestId": "75b87ee3-a7f7-468d-b7d1-e7e7b3173dab",
|
||||
"version": "2025-01-05",
|
||||
"date": "Thu, 23 Jan 2025 17:53:23 GMT",
|
||||
"clientRequestId": "client-request-id-123",
|
||||
"requestServerEncrypted": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0],
|
||||
"id": "99f866fa-f63c-477d-a0d0-48fbdb8a344a",
|
||||
"name": "When clicking ‘Test workflow’"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "delete",
|
||||
"container": { "__rl": true, "mode": "list", "value": "mycontainer" },
|
||||
"requestOptions": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.azureStorage",
|
||||
"typeVersion": 1,
|
||||
"position": [220, 0],
|
||||
"id": "ab1b6258-5c75-4893-90bf-ef591264420c",
|
||||
"name": "Azure Storage",
|
||||
"credentials": {
|
||||
"azureStorageSharedKeyApi": {
|
||||
"id": "VPmcFM58eDDexWQL",
|
||||
"name": "Azure Storage Shared Key account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking ‘Test workflow’": {
|
||||
"main": [[{ "node": "Azure Storage", "type": "main", "index": 0 }]]
|
||||
}
|
||||
},
|
||||
"pinData": {
|
||||
"Azure Storage": [
|
||||
{
|
||||
"json": {
|
||||
"contentLength": 0,
|
||||
"requestId": "ca3a8907-601e-0050-1929-723410000000",
|
||||
"server": "Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0",
|
||||
"version": "2020-10-02",
|
||||
"date": "Wed, 29 Jan 2025 08:38:21 GMT"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0],
|
||||
"id": "99f866fa-f63c-477d-a0d0-48fbdb8a344a",
|
||||
"name": "When clicking ‘Test workflow’"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "get",
|
||||
"container": { "__rl": true, "mode": "list", "value": "mycontainer" },
|
||||
"options": { "simplify": false },
|
||||
"requestOptions": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.azureStorage",
|
||||
"typeVersion": 1,
|
||||
"position": [220, 0],
|
||||
"id": "ab1b6258-5c75-4893-90bf-ef591264420c",
|
||||
"name": "Azure Storage",
|
||||
"credentials": {
|
||||
"azureStorageSharedKeyApi": {
|
||||
"id": "VPmcFM58eDDexWQL",
|
||||
"name": "Azure Storage Shared Key account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking ‘Test workflow’": {
|
||||
"main": [[{ "node": "Azure Storage", "type": "main", "index": 0 }]]
|
||||
}
|
||||
},
|
||||
"pinData": {
|
||||
"Azure Storage": [
|
||||
{
|
||||
"json": {
|
||||
"name": "mycontainer",
|
||||
"properties": {
|
||||
"lastModified": "Tue, 28 Jan 2025 16:40:21 GMT",
|
||||
"etag": "\"0x8DD3FBA74CF3620\"",
|
||||
"leaseStatus": "unlocked",
|
||||
"leaseState": "available",
|
||||
"hasImmutabilityPolicy": false,
|
||||
"hasLegalHold": false,
|
||||
"leaseDuration": "infinite",
|
||||
"blobPublicAccess": "blob",
|
||||
"contentLength": 0,
|
||||
"date": "Wed, 29 Jan 2025 08:43:08 GMT",
|
||||
"requestId": "49edb268-b01e-0053-6e29-72d574000000",
|
||||
"server": "Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0",
|
||||
"version": "2020-10-02"
|
||||
},
|
||||
"metadata": { "key1": "field1" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
{
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0],
|
||||
"id": "99f866fa-f63c-477d-a0d0-48fbdb8a344a",
|
||||
"name": "When clicking ‘Test workflow’"
|
||||
},
|
||||
{
|
||||
"parameters": { "operation": "getAll", "returnAll": true, "requestOptions": {} },
|
||||
"type": "n8n-nodes-base.azureStorage",
|
||||
"typeVersion": 1,
|
||||
"position": [220, 0],
|
||||
"id": "ab1b6258-5c75-4893-90bf-ef591264420c",
|
||||
"name": "Azure Storage",
|
||||
"credentials": {
|
||||
"azureStorageSharedKeyApi": {
|
||||
"id": "VPmcFM58eDDexWQL",
|
||||
"name": "Azure Storage Shared Key account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking ‘Test workflow’": {
|
||||
"main": [[{ "node": "Azure Storage", "type": "main", "index": 0 }]]
|
||||
}
|
||||
},
|
||||
"pinData": {
|
||||
"Azure Storage": [
|
||||
{
|
||||
"json": {
|
||||
"name": "mycontainer1",
|
||||
"deleted": true,
|
||||
"version": "01DB7228F6BEE6E7",
|
||||
"properties": {
|
||||
"lastModified": "Wed, 29 Jan 2025 08:37:00 GMT",
|
||||
"etag": "\"0x8DD40401935032C\"",
|
||||
"leaseStatus": "unlocked",
|
||||
"leaseState": "expired",
|
||||
"hasImmutabilityPolicy": false,
|
||||
"hasLegalHold": false,
|
||||
"immutableStorageWithVersioningEnabled": "false",
|
||||
"deletedTime": "Wed, 29 Jan 2025 08:38:21 GMT",
|
||||
"remainingRetentionDays": 7
|
||||
},
|
||||
"metadata": { "key1": "value1" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"name": "mycontainer1",
|
||||
"deleted": true,
|
||||
"version": "01DB7228F6BEE6E7",
|
||||
"properties": {
|
||||
"lastModified": "Wed, 29 Jan 2025 08:37:00 GMT",
|
||||
"etag": "\"0x8DD40401935032C\"",
|
||||
"leaseStatus": "unlocked",
|
||||
"leaseState": "expired",
|
||||
"hasImmutabilityPolicy": false,
|
||||
"hasLegalHold": false,
|
||||
"immutableStorageWithVersioningEnabled": "false",
|
||||
"deletedTime": "Wed, 29 Jan 2025 08:38:21 GMT",
|
||||
"remainingRetentionDays": 7
|
||||
},
|
||||
"metadata": { "key1": "value1" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0],
|
||||
"id": "99f866fa-f63c-477d-a0d0-48fbdb8a344a",
|
||||
"name": "When clicking ‘Test workflow’"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "getAll",
|
||||
"limit": 1,
|
||||
"options": { "fields": ["metadata", "deleted", "system"], "filter": "mycontainer" },
|
||||
"requestOptions": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.azureStorage",
|
||||
"typeVersion": 1,
|
||||
"position": [220, 0],
|
||||
"id": "ab1b6258-5c75-4893-90bf-ef591264420c",
|
||||
"name": "Azure Storage",
|
||||
"credentials": {
|
||||
"azureStorageSharedKeyApi": {
|
||||
"id": "VPmcFM58eDDexWQL",
|
||||
"name": "Azure Storage Shared Key account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking ‘Test workflow’": {
|
||||
"main": [[{ "node": "Azure Storage", "type": "main", "index": 0 }]]
|
||||
}
|
||||
},
|
||||
"pinData": {
|
||||
"Azure Storage": [
|
||||
{
|
||||
"json": {
|
||||
"name": "mycontainer1",
|
||||
"deleted": true,
|
||||
"version": "01DB7228F6BEE6E7",
|
||||
"properties": {
|
||||
"lastModified": "Wed, 29 Jan 2025 08:37:00 GMT",
|
||||
"etag": "\"0x8DD40401935032C\"",
|
||||
"leaseStatus": "unlocked",
|
||||
"leaseState": "expired",
|
||||
"hasImmutabilityPolicy": false,
|
||||
"hasLegalHold": false,
|
||||
"immutableStorageWithVersioningEnabled": "false",
|
||||
"deletedTime": "Wed, 29 Jan 2025 08:38:21 GMT",
|
||||
"remainingRetentionDays": 7
|
||||
},
|
||||
"metadata": { "key1": "value1" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
{
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0],
|
||||
"id": "1307e408-a8a5-464e-b858-494953e2f43b",
|
||||
"name": "When clicking 'Test workflow'"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "oAuth2",
|
||||
"resource": "container",
|
||||
"operation": "get",
|
||||
"container": { "__rl": true, "value": "mycontainer", "mode": "id" },
|
||||
"options": { "simplify": false },
|
||||
"requestOptions": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.azureStorage",
|
||||
"typeVersion": 1,
|
||||
"position": [220, 0],
|
||||
"id": "3429f7f2-dfca-4b72-8913-43a582e96e66",
|
||||
"name": "Azure Storage",
|
||||
"credentials": {
|
||||
"azureStorageOAuth2Api": {
|
||||
"id": "VPmcFM58eDDexWQL",
|
||||
"name": "Azure Storage OAuth2 account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking 'Test workflow'": {
|
||||
"main": [[{ "node": "Azure Storage", "type": "main", "index": 0 }]]
|
||||
}
|
||||
},
|
||||
"pinData": {
|
||||
"Azure Storage": [
|
||||
{
|
||||
"json": {
|
||||
"name": "mycontainer",
|
||||
"properties": {
|
||||
"lastModified": "Tue, 28 Jan 2025 16:40:21 GMT",
|
||||
"etag": "\"0x8DD3FBA74CF3620\"",
|
||||
"leaseStatus": "unlocked",
|
||||
"leaseState": "available",
|
||||
"hasImmutabilityPolicy": false,
|
||||
"hasLegalHold": false,
|
||||
"leaseDuration": "infinite",
|
||||
"blobPublicAccess": "blob",
|
||||
"contentLength": 0,
|
||||
"date": "Wed, 29 Jan 2025 08:43:08 GMT",
|
||||
"requestId": "49edb268-b01e-0053-6e29-72d574000000",
|
||||
"server": "Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0",
|
||||
"version": "2020-10-02"
|
||||
},
|
||||
"metadata": {
|
||||
"key1": "field1"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
{
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0],
|
||||
"id": "1307e408-a8a5-464e-b858-494953e2f43b",
|
||||
"name": "When clicking 'Test workflow'"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "container",
|
||||
"operation": "get",
|
||||
"container": { "__rl": true, "value": "mycontainer", "mode": "id" },
|
||||
"options": { "simplify": false },
|
||||
"requestOptions": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.azureStorage",
|
||||
"typeVersion": 1,
|
||||
"position": [220, 0],
|
||||
"id": "3429f7f2-dfca-4b72-8913-43a582e96e66",
|
||||
"name": "Azure Storage",
|
||||
"credentials": {
|
||||
"azureStorageSharedKeyApi": {
|
||||
"id": "VPmcFM58eDDexWQL",
|
||||
"name": "Azure Storage Shared Key account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking 'Test workflow'": {
|
||||
"main": [[{ "node": "Azure Storage", "type": "main", "index": 0 }]]
|
||||
}
|
||||
},
|
||||
"pinData": {
|
||||
"Azure Storage": [
|
||||
{
|
||||
"json": {
|
||||
"name": "mycontainer",
|
||||
"properties": {
|
||||
"lastModified": "Tue, 28 Jan 2025 16:40:21 GMT",
|
||||
"etag": "\"0x8DD3FBA74CF3620\"",
|
||||
"leaseStatus": "unlocked",
|
||||
"leaseState": "available",
|
||||
"hasImmutabilityPolicy": false,
|
||||
"hasLegalHold": false,
|
||||
"leaseDuration": "infinite",
|
||||
"blobPublicAccess": "blob",
|
||||
"contentLength": 0,
|
||||
"date": "Wed, 29 Jan 2025 08:43:08 GMT",
|
||||
"requestId": "49edb268-b01e-0053-6e29-72d574000000",
|
||||
"server": "Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0",
|
||||
"version": "2020-10-02"
|
||||
},
|
||||
"metadata": { "key1": "field1" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -39,6 +39,8 @@
|
|||
"dist/credentials/AutomizyApi.credentials.js",
|
||||
"dist/credentials/AutopilotApi.credentials.js",
|
||||
"dist/credentials/Aws.credentials.js",
|
||||
"dist/credentials/AzureStorageOAuth2Api.credentials.js",
|
||||
"dist/credentials/AzureStorageSharedKeyApi.credentials.js",
|
||||
"dist/credentials/BambooHrApi.credentials.js",
|
||||
"dist/credentials/BannerbearApi.credentials.js",
|
||||
"dist/credentials/BaserowApi.credentials.js",
|
||||
|
@ -640,6 +642,7 @@
|
|||
"dist/nodes/Microsoft/Outlook/MicrosoftOutlook.node.js",
|
||||
"dist/nodes/Microsoft/Outlook/MicrosoftOutlookTrigger.node.js",
|
||||
"dist/nodes/Microsoft/Sql/MicrosoftSql.node.js",
|
||||
"dist/nodes/Microsoft/Storage/AzureStorage.node.js",
|
||||
"dist/nodes/Microsoft/Teams/MicrosoftTeams.node.js",
|
||||
"dist/nodes/Microsoft/ToDo/MicrosoftToDo.node.js",
|
||||
"dist/nodes/Mindee/Mindee.node.js",
|
||||
|
|
|
@ -10,8 +10,29 @@ export async function executeWorkflow(testData: WorkflowTestData, nodeTypes: INo
|
|||
if (testData.nock) {
|
||||
const { baseUrl, mocks } = testData.nock;
|
||||
const agent = nock(baseUrl);
|
||||
mocks.forEach(({ method, path, statusCode, requestBody, responseBody }) =>
|
||||
agent[method](path, requestBody).reply(statusCode, responseBody),
|
||||
mocks.forEach(
|
||||
({
|
||||
method,
|
||||
path,
|
||||
statusCode,
|
||||
requestBody,
|
||||
requestHeaders,
|
||||
responseBody,
|
||||
responseHeaders,
|
||||
}) => {
|
||||
let mock = agent[method](path, requestBody);
|
||||
|
||||
// nock interceptor reqheaders option is ignored, so we chain matchHeader()
|
||||
// agent[method](path, requestBody, { reqheaders: requestHeaders }).reply(statusCode, responseBody, responseHeaders)
|
||||
// https://github.com/nock/nock/issues/2545
|
||||
if (requestHeaders && Object.keys(requestHeaders).length > 0) {
|
||||
Object.entries(requestHeaders).forEach(([key, value]) => {
|
||||
mock = mock.matchHeader(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
mock.reply(statusCode, responseBody, responseHeaders);
|
||||
},
|
||||
);
|
||||
}
|
||||
const executionMode = testData.trigger?.mode ?? 'manual';
|
||||
|
|
|
@ -54,6 +54,33 @@ BQIDAQAB
|
|||
airtableApi: {
|
||||
apiKey: 'key123',
|
||||
},
|
||||
azureStorageOAuth2Api: {
|
||||
grantType: 'authorizationCode',
|
||||
authUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
|
||||
accessTokenUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
|
||||
clientId: 'CLIENTID',
|
||||
clientSecret: 'CLIENTSECRET',
|
||||
scope: 'https://storage.azure.com/user_impersonation',
|
||||
authQueryParameters: 'response_mode=query',
|
||||
authentication: 'body',
|
||||
oauthTokenData: {
|
||||
token_type: 'Bearer',
|
||||
scope: 'https://storage.azure.com/user_impersonation',
|
||||
expires_in: 4730,
|
||||
ext_expires_in: 4730,
|
||||
access_token: 'ACCESSTOKEN',
|
||||
callbackQueryString: {
|
||||
session_state: 'SESSIONSTATE',
|
||||
},
|
||||
},
|
||||
account: 'myaccount',
|
||||
baseUrl: 'https://myaccount.blob.core.windows.net',
|
||||
},
|
||||
azureStorageSharedKeyApi: {
|
||||
account: 'devstoreaccount1',
|
||||
key: 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==',
|
||||
baseUrl: 'https://myaccount.blob.core.windows.net',
|
||||
},
|
||||
gongApi: {
|
||||
baseUrl: 'https://api.gong.io',
|
||||
accessKey: 'accessKey123',
|
||||
|
|
|
@ -6,7 +6,7 @@ import type * as express from 'express';
|
|||
import type FormData from 'form-data';
|
||||
import type { PathLike } from 'fs';
|
||||
import type { IncomingHttpHeaders } from 'http';
|
||||
import type { RequestBodyMatcher } from 'nock';
|
||||
import type { ReplyHeaders, RequestBodyMatcher, RequestHeaderMatcher } from 'nock';
|
||||
import type { Client as SSHClient } from 'ssh2';
|
||||
import type { Readable } from 'stream';
|
||||
import type { SecureContextOptions } from 'tls';
|
||||
|
@ -2412,8 +2412,10 @@ export interface WorkflowTestData {
|
|||
method: 'delete' | 'get' | 'patch' | 'post' | 'put';
|
||||
path: string;
|
||||
requestBody?: RequestBodyMatcher;
|
||||
requestHeaders?: Record<string, RequestHeaderMatcher>;
|
||||
statusCode: number;
|
||||
responseBody: string | object;
|
||||
responseHeaders?: ReplyHeaders;
|
||||
}>;
|
||||
};
|
||||
trigger?: {
|
||||
|
|
Loading…
Reference in a new issue