first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,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,98 @@
|
||||
import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
|
||||
import { NodeConnectionTypes } 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',
|
||||
schemaPath: 'Microsoft/Storage',
|
||||
defaults: {
|
||||
name: 'Azure Storage',
|
||||
},
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.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,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,587 @@
|
||||
import { camelCase } from 'change-case';
|
||||
import type {
|
||||
JsonObject,
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
IExecuteSingleFunctions,
|
||||
IHttpRequestMethods,
|
||||
IHttpRequestOptions,
|
||||
ILoadOptionsFunctions,
|
||||
INodeExecutionData,
|
||||
IN8nHttpFullResponse,
|
||||
INodeListSearchResult,
|
||||
INodeListSearchItems,
|
||||
INodeParameterResourceLocator,
|
||||
ICredentialDataDecryptedObject,
|
||||
IRequestOptions,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeApiError } from 'n8n-workflow';
|
||||
import { Parser } from 'xml2js';
|
||||
import { firstCharLowerCase, parseBooleans, parseNumbers } from 'xml2js/lib/processors';
|
||||
|
||||
import { getUrl } from '@credentials/common/http';
|
||||
|
||||
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 | IRequestOptions,
|
||||
): 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 | IRequestOptions,
|
||||
credentials: ICredentialDataDecryptedObject,
|
||||
): string {
|
||||
const path: string = new URL(getUrl(requestOptions)).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,
|
||||
};
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"connection": {
|
||||
"type": "string"
|
||||
},
|
||||
"contentCrc64": {
|
||||
"type": "string"
|
||||
},
|
||||
"contentLength": {
|
||||
"type": "integer"
|
||||
},
|
||||
"contentMd5": {
|
||||
"type": "string"
|
||||
},
|
||||
"date": {
|
||||
"type": "string"
|
||||
},
|
||||
"etag": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastModified": {
|
||||
"type": "string"
|
||||
},
|
||||
"requestId": {
|
||||
"type": "string"
|
||||
},
|
||||
"requestServerEncrypted": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"server": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"blobType": {
|
||||
"type": "string"
|
||||
},
|
||||
"contentDisposition": {
|
||||
"type": "string"
|
||||
},
|
||||
"contentLength": {
|
||||
"type": "integer"
|
||||
},
|
||||
"contentMd5": {
|
||||
"type": "string"
|
||||
},
|
||||
"contentType": {
|
||||
"type": "string"
|
||||
},
|
||||
"creationTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"etag": {
|
||||
"type": "string"
|
||||
},
|
||||
"group": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastModified": {
|
||||
"type": "string"
|
||||
},
|
||||
"leaseState": {
|
||||
"type": "string"
|
||||
},
|
||||
"leaseStatus": {
|
||||
"type": "string"
|
||||
},
|
||||
"owner": {
|
||||
"type": "string"
|
||||
},
|
||||
"permissions": {
|
||||
"type": "string"
|
||||
},
|
||||
"resourceType": {
|
||||
"type": "string"
|
||||
},
|
||||
"serverEncrypted": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version": 3
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"blobType": {
|
||||
"type": "string"
|
||||
},
|
||||
"contentLength": {
|
||||
"type": "integer"
|
||||
},
|
||||
"contentMD5": {
|
||||
"type": "string"
|
||||
},
|
||||
"contentType": {
|
||||
"type": "string"
|
||||
},
|
||||
"creationTime": {
|
||||
"type": "string"
|
||||
},
|
||||
"etag": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastModified": {
|
||||
"type": "string"
|
||||
},
|
||||
"leaseState": {
|
||||
"type": "string"
|
||||
},
|
||||
"leaseStatus": {
|
||||
"type": "string"
|
||||
},
|
||||
"serverEncrypted": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"defaultEncryptionScope": {
|
||||
"type": "string"
|
||||
},
|
||||
"denyEncryptionScopeOverride": {
|
||||
"type": "string"
|
||||
},
|
||||
"etag": {
|
||||
"type": "string"
|
||||
},
|
||||
"hasImmutabilityPolicy": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"hasLegalHold": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"immutableStorageWithVersioningEnabled": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastModified": {
|
||||
"type": "string"
|
||||
},
|
||||
"leaseState": {
|
||||
"type": "string"
|
||||
},
|
||||
"leaseStatus": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"defaultEncryptionScope": {
|
||||
"type": "string"
|
||||
},
|
||||
"denyEncryptionScopeOverride": {
|
||||
"type": "string"
|
||||
},
|
||||
"etag": {
|
||||
"type": "string"
|
||||
},
|
||||
"hasImmutabilityPolicy": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"hasLegalHold": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"immutableStorageWithVersioningEnabled": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastModified": {
|
||||
"type": "string"
|
||||
},
|
||||
"leaseState": {
|
||||
"type": "string"
|
||||
},
|
||||
"leaseStatus": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -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: 1015 B |
@@ -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: 1015 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,109 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0],
|
||||
"id": "99f866fa-f63c-477d-a0d0-48fbdb8a344a",
|
||||
"name": "When clicking ‘Execute 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 ‘Execute 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 ‘Execute 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 ‘Execute 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,115 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0],
|
||||
"id": "99f866fa-f63c-477d-a0d0-48fbdb8a344a",
|
||||
"name": "When clicking ‘Execute 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 ‘Execute workflow’": {
|
||||
"main": [[{ "node": "Azure Storage", "type": "main", "index": 0 }]]
|
||||
}
|
||||
},
|
||||
|
||||
"pinData": {
|
||||
"Azure Storage": [
|
||||
{
|
||||
"binary": {
|
||||
"data": {
|
||||
"data": "ewoiZGF0YSI6ewoibXlfZmllbGRfMSI6InZhbHVlIiwKIm15X2ZpZWxkXzIiOjEKfQp9",
|
||||
"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 ‘Execute 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 ‘Execute 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+76
@@ -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 ‘Execute 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 ‘Execute 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,61 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
|
||||
import { credentials } from '../credentials';
|
||||
|
||||
describe('Azure Storage Node', () => {
|
||||
const { baseUrl } = credentials.azureStorageOAuth2Api;
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['blob_create.workflow.json'],
|
||||
nock: {
|
||||
baseUrl,
|
||||
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',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
|
||||
import { credentials } from '../credentials';
|
||||
|
||||
describe('Azure Storage Node', () => {
|
||||
const { baseUrl } = credentials.azureStorageOAuth2Api;
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['blob_delete.workflow.json'],
|
||||
nock: {
|
||||
baseUrl,
|
||||
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',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,82 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
|
||||
import { credentials } from '../credentials';
|
||||
|
||||
describe('Azure Storage Node', () => {
|
||||
const { baseUrl } = credentials.azureStorageOAuth2Api;
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['blob_get.workflow.json'],
|
||||
assertBinaryData: true,
|
||||
nock: {
|
||||
baseUrl,
|
||||
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',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
|
||||
import { credentials } from '../credentials';
|
||||
|
||||
describe('Azure Storage Node', () => {
|
||||
const { baseUrl } = credentials.azureStorageOAuth2Api;
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['blob_getAll.workflow.json'],
|
||||
nock: {
|
||||
baseUrl,
|
||||
mocks: [
|
||||
{
|
||||
method: 'get',
|
||||
path: '/mycontainer?restype=container&comp=list',
|
||||
statusCode: 200,
|
||||
responseBody: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><EnumerationResults ServiceEndpoint="${baseUrl}" 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="${baseUrl}" 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>`,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
|
||||
import { credentials } from '../credentials';
|
||||
|
||||
describe('Azure Storage Node', () => {
|
||||
const { baseUrl } = credentials.azureStorageOAuth2Api;
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['blob_getAllLimitOptions.workflow.json'],
|
||||
nock: {
|
||||
baseUrl,
|
||||
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="${baseUrl}" 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>`,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
+54
@@ -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 ‘Execute 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 ‘Execute 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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+48
@@ -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 ‘Execute 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 ‘Execute 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+61
@@ -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 ‘Execute 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 ‘Execute 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" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+73
@@ -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 ‘Execute 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 ‘Execute 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" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+59
@@ -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 ‘Execute 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 ‘Execute 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,32 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
|
||||
import { credentials } from '../credentials';
|
||||
|
||||
describe('Azure Storage Node', () => {
|
||||
const { baseUrl } = credentials.azureStorageOAuth2Api;
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['container_create.workflow.json'],
|
||||
nock: {
|
||||
baseUrl,
|
||||
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',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
|
||||
import { credentials } from '../credentials';
|
||||
|
||||
describe('Azure Storage Node', () => {
|
||||
const { baseUrl } = credentials.azureStorageOAuth2Api;
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['container_delete.workflow.json'],
|
||||
nock: {
|
||||
baseUrl,
|
||||
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',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
|
||||
import { credentials } from '../credentials';
|
||||
|
||||
describe('Azure Storage Node', () => {
|
||||
const { baseUrl } = credentials.azureStorageOAuth2Api;
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['container_get.workflow.json'],
|
||||
nock: {
|
||||
baseUrl,
|
||||
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',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
|
||||
import { credentials } from '../credentials';
|
||||
|
||||
describe('Azure Storage Node', () => {
|
||||
const { baseUrl } = credentials.azureStorageOAuth2Api;
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['container_getAll.workflow.json'],
|
||||
nock: {
|
||||
baseUrl,
|
||||
mocks: [
|
||||
{
|
||||
method: 'get',
|
||||
path: '/?comp=list',
|
||||
statusCode: 200,
|
||||
responseBody: `<?xml version="1.0" encoding="utf-8"?><EnumerationResults ServiceEndpoint="${baseUrl}/"><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="${baseUrl}/"><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>`,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
|
||||
import { credentials } from '../credentials';
|
||||
|
||||
describe('Azure Storage Node', () => {
|
||||
const { baseUrl } = credentials.azureStorageOAuth2Api;
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['container_getAllLimitOptions.workflow.json'],
|
||||
nock: {
|
||||
baseUrl,
|
||||
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="${baseUrl}/"><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>`,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
export const credentials = {
|
||||
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',
|
||||
},
|
||||
};
|
||||
+65
@@ -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 ‘Execute 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 ‘Execute 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+62
@@ -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 ‘Execute 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 ‘Execute 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,38 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
|
||||
import { credentials } from '../credentials';
|
||||
|
||||
describe('Azure Storage Node', () => {
|
||||
const { baseUrl } = credentials.azureStorageOAuth2Api;
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['credentials_oauth2.workflow.json'],
|
||||
nock: {
|
||||
baseUrl,
|
||||
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',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,110 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import type { ICredentialDataDecryptedObject, IHttpRequestOptions } from 'n8n-workflow';
|
||||
|
||||
import { AzureStorageSharedKeyApi } from '@credentials/AzureStorageSharedKeyApi.credentials';
|
||||
|
||||
import { credentials } from '../credentials';
|
||||
|
||||
describe('Azure Storage Node', () => {
|
||||
const { account, baseUrl, key } = credentials.azureStorageSharedKeyApi;
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['credentials_sharedKey.workflow.json'],
|
||||
nock: {
|
||||
baseUrl,
|
||||
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',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
describe('authenticate', () => {
|
||||
const azureStorageSharedKeyApi = new AzureStorageSharedKeyApi();
|
||||
|
||||
it('should remove undefined query parameters and headers', async () => {
|
||||
const authCredentials: ICredentialDataDecryptedObject = {
|
||||
account,
|
||||
key,
|
||||
};
|
||||
const requestOptions: IHttpRequestOptions = {
|
||||
url: `${baseUrl}/mycontainer`,
|
||||
qs: { restype: 'container', query1: undefined },
|
||||
headers: {
|
||||
'Content-Length': undefined,
|
||||
},
|
||||
method: 'GET',
|
||||
};
|
||||
|
||||
const result = await azureStorageSharedKeyApi.authenticate(authCredentials, 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 authCredentials: ICredentialDataDecryptedObject = {
|
||||
account,
|
||||
key,
|
||||
};
|
||||
const requestOptions: IHttpRequestOptions = {
|
||||
url: `${baseUrl}/mycontainer`,
|
||||
qs: { restype: 'container' },
|
||||
headers: {
|
||||
'Content-Length': undefined,
|
||||
},
|
||||
};
|
||||
|
||||
const result = await azureStorageSharedKeyApi.authenticate(authCredentials, requestOptions);
|
||||
expect(result.method).toBe('GET');
|
||||
});
|
||||
|
||||
it('should generate a valid authorization header', async () => {
|
||||
const authCredentials: ICredentialDataDecryptedObject = {
|
||||
account,
|
||||
key,
|
||||
};
|
||||
const requestOptions: IHttpRequestOptions = {
|
||||
url: `${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(authCredentials, requestOptions);
|
||||
|
||||
expect(result.headers?.authorization).toBe(
|
||||
'SharedKey devstoreaccount1:6sSQ3N4yNFQynBs/iLptIRPS5DQeaFBocW+dyYbAdOI=',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,115 @@
|
||||
import type { ILoadOptionsFunctions, INodeParameterResourceLocator } from 'n8n-workflow';
|
||||
|
||||
import { AzureStorage } from '../../AzureStorage.node';
|
||||
import { XMsVersion } from '../../GenericFunctions';
|
||||
import { credentials } from '../credentials';
|
||||
|
||||
describe('Azure Storage Node', () => {
|
||||
const { baseUrl } = credentials.azureStorageOAuth2Api;
|
||||
|
||||
describe('List search', () => {
|
||||
it('should list search blobs', async () => {
|
||||
const mockResponse = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><EnumerationResults ServiceEndpoint="${baseUrl}" 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;
|
||||
}
|
||||
|
||||
throw new Error('Unknown parameter');
|
||||
});
|
||||
const mockGetCredentials = jest.fn(async (type: string, _itemIndex?: number) => {
|
||||
if (type === 'azureStorageSharedKeyApi') {
|
||||
return credentials.azureStorageSharedKeyApi;
|
||||
}
|
||||
|
||||
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: `${baseUrl}/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="${baseUrl}/"><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';
|
||||
}
|
||||
|
||||
throw new Error('Unknown parameter');
|
||||
});
|
||||
const mockGetCredentials = jest.fn(async (type: string, _itemIndex?: number) => {
|
||||
if (type === 'azureStorageSharedKeyApi') {
|
||||
return credentials.azureStorageSharedKeyApi;
|
||||
}
|
||||
|
||||
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: `${baseUrl}/`,
|
||||
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',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user