mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 04:34:06 -08:00
feat(GoogleCloudStorage Node): add GCS Node with Bucket and Object operations
This commit is contained in:
parent
3a9c7acb04
commit
1e963d8e1e
|
@ -0,0 +1,24 @@
|
||||||
|
import { ICredentialType, INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
const scopes = [
|
||||||
|
'https://www.googleapis.com/auth/cloud-platform',
|
||||||
|
'https://www.googleapis.com/auth/cloud-platform.read-only',
|
||||||
|
'https://www.googleapis.com/auth/devstorage.full_control',
|
||||||
|
'https://www.googleapis.com/auth/devstorage.read_only',
|
||||||
|
'https://www.googleapis.com/auth/devstorage.read_write',
|
||||||
|
];
|
||||||
|
|
||||||
|
export class GoogleCloudStorageOAuth2Api implements ICredentialType {
|
||||||
|
name = 'googleCloudStorageOAuth2Api';
|
||||||
|
extends = ['googleOAuth2Api'];
|
||||||
|
displayName = 'Google Cloud Storage OAuth2 API';
|
||||||
|
documentationUrl = 'google';
|
||||||
|
properties: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Scope',
|
||||||
|
name: 'scope',
|
||||||
|
type: 'hidden',
|
||||||
|
default: scopes.join(' '),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
|
@ -0,0 +1,616 @@
|
||||||
|
import { IDataObject, IExecuteSingleFunctions, IHttpRequestOptions } from 'n8n-workflow';
|
||||||
|
import { INodeExecutionData, INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
// Projection field controls the page limit maximum
|
||||||
|
// When not returning all, return the max number for the current projection parameter
|
||||||
|
const PAGE_LIMITS = {
|
||||||
|
noAcl: 1000,
|
||||||
|
full: 200,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define a JSON parse function here to use it in two places
|
||||||
|
async function parseJSONBody(
|
||||||
|
this: IExecuteSingleFunctions,
|
||||||
|
requestOptions: IHttpRequestOptions,
|
||||||
|
): Promise<IHttpRequestOptions> {
|
||||||
|
if (!requestOptions.body) requestOptions.body = {};
|
||||||
|
const body = this.getNodeParameter('createBody') as IDataObject;
|
||||||
|
|
||||||
|
// Parse all the JSON fields
|
||||||
|
if (body.acl) {
|
||||||
|
try {
|
||||||
|
body.acl = JSON.parse(body.acl as string);
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
if (body.billing) {
|
||||||
|
try {
|
||||||
|
body.billing = JSON.parse(body.billing as string);
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
if (body.cors) {
|
||||||
|
try {
|
||||||
|
body.cors = JSON.parse(body.cors as string);
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
if (body.customPlacementConfig) {
|
||||||
|
try {
|
||||||
|
body.customPlacementConfig = JSON.parse(body.customPlacementConfig as string);
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
if (body.dataLocations) {
|
||||||
|
try {
|
||||||
|
body.dataLocations = JSON.parse(body.dataLocations as string);
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
if (body.defaultObjectAcl) {
|
||||||
|
try {
|
||||||
|
body.defaultObjectAcl = JSON.parse(body.defaultObjectAcl as string);
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
if (body.encryption) {
|
||||||
|
try {
|
||||||
|
body.encryption = JSON.parse(body.encryption as string);
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
if (body.iamConfiguration) {
|
||||||
|
try {
|
||||||
|
body.iamConfiguration = JSON.parse(body.iamConfiguration as string);
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
if (body.labels) {
|
||||||
|
try {
|
||||||
|
body.labels = JSON.parse(body.labels as string);
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
if (body.lifecycle) {
|
||||||
|
try {
|
||||||
|
body.lifecycle = JSON.parse(body.lifecycle as string);
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
if (body.logging) {
|
||||||
|
try {
|
||||||
|
body.logging = JSON.parse(body.logging as string);
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
if (body.retentionPolicy) {
|
||||||
|
try {
|
||||||
|
body.retentionPolicy = JSON.parse(body.retentionPolicy as string);
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
if (body.versioning) {
|
||||||
|
try {
|
||||||
|
body.versioning = JSON.parse(body.versioning as string);
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
if (body.website) {
|
||||||
|
try {
|
||||||
|
body.website = JSON.parse(body.website as string);
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
requestOptions.body = Object.assign(requestOptions.body, body);
|
||||||
|
return requestOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const bucketOperations: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['bucket'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
description: 'Create a new Bucket',
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/b/',
|
||||||
|
qs: {},
|
||||||
|
body: {
|
||||||
|
name: '={{$parameter["bucketName"]}}',
|
||||||
|
},
|
||||||
|
returnFullResponse: true,
|
||||||
|
},
|
||||||
|
send: {
|
||||||
|
preSend: [parseJSONBody],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
action: 'Create a new Bucket',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
value: 'delete',
|
||||||
|
description: 'Delete an empty Bucket',
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
method: 'DELETE',
|
||||||
|
url: '={{"/b/" + $parameter["bucketName"]}}',
|
||||||
|
returnFullResponse: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
action: 'Delete an empty Bucket',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Get metadata for a specific Bucket',
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
method: 'GET',
|
||||||
|
url: '={{"/b/" + $parameter["bucketName"]}}',
|
||||||
|
returnFullResponse: true,
|
||||||
|
qs: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
action: 'Get a Bucket',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Many',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'Get list of Buckets',
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/b/',
|
||||||
|
qs: {},
|
||||||
|
},
|
||||||
|
send: {
|
||||||
|
paginate: true,
|
||||||
|
preSend: [
|
||||||
|
async function (this, requestOptions) {
|
||||||
|
if (!requestOptions.qs) requestOptions.qs = {};
|
||||||
|
const returnAll = this.getNodeParameter('returnAll') as boolean;
|
||||||
|
|
||||||
|
if (!returnAll) {
|
||||||
|
const key = this.getNodeParameter('projection') as string;
|
||||||
|
requestOptions.qs.maxResults =
|
||||||
|
key === 'noAcl' ? PAGE_LIMITS.noAcl : PAGE_LIMITS.full;
|
||||||
|
}
|
||||||
|
return requestOptions;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
operations: {
|
||||||
|
async pagination(this, requestOptions) {
|
||||||
|
if (!requestOptions.options.qs) requestOptions.options.qs = {};
|
||||||
|
let executions: INodeExecutionData[] = [];
|
||||||
|
let responseData: INodeExecutionData[];
|
||||||
|
let nextPageToken: string | undefined = undefined;
|
||||||
|
const returnAll = this.getNodeParameter('returnAll') as boolean;
|
||||||
|
|
||||||
|
do {
|
||||||
|
requestOptions.options.qs.pageToken = nextPageToken;
|
||||||
|
responseData = await this.makeRoutingRequest(requestOptions);
|
||||||
|
|
||||||
|
// Check for another page
|
||||||
|
const lastItem = responseData[responseData.length - 1].json;
|
||||||
|
nextPageToken = lastItem.nextPageToken as string | undefined;
|
||||||
|
|
||||||
|
// Extract just the list of buckets from the page data
|
||||||
|
responseData.forEach((page) => {
|
||||||
|
const buckets = page.json.items as IDataObject[];
|
||||||
|
if (buckets) {
|
||||||
|
executions = executions.concat(buckets.map((bucket) => ({ json: bucket })));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// If we don't return all, just return the first page
|
||||||
|
} while (returnAll && nextPageToken);
|
||||||
|
|
||||||
|
// Return all execution responses as an array
|
||||||
|
return executions;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
action: 'Get a list of Buckets for a given project',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Update',
|
||||||
|
value: 'update',
|
||||||
|
description: 'Update the metadata of a bucket',
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
method: 'PATCH',
|
||||||
|
url: '={{"/b/" + $parameter["bucketName"]}}',
|
||||||
|
qs: {
|
||||||
|
project: '={{$parameter["projectId"]}}',
|
||||||
|
},
|
||||||
|
body: {},
|
||||||
|
returnFullResponse: true,
|
||||||
|
},
|
||||||
|
send: {
|
||||||
|
preSend: [parseJSONBody],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
action: 'Create a new Bucket',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'getAll',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const bucketFields: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Project ID',
|
||||||
|
name: 'projectId',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
placeholder: 'Project ID',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['bucket'],
|
||||||
|
operation: ['create', 'getAll'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
qs: {
|
||||||
|
project: '={{$value}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Bucket Name',
|
||||||
|
name: 'bucketName',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'Bucket Name',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['bucket'],
|
||||||
|
operation: ['create', 'get', 'update', 'delete'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Prefix',
|
||||||
|
name: 'prefix',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'Filter for Bucket Names',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['bucket'],
|
||||||
|
operation: ['getAll'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
qs: {
|
||||||
|
prefix: '={{$value}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Projection',
|
||||||
|
name: 'projection',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'All Properties',
|
||||||
|
value: 'full',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'No ACL',
|
||||||
|
value: 'noAcl',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'noAcl',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['bucket'],
|
||||||
|
operation: ['create', 'get', 'getAll', 'update'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
qs: {
|
||||||
|
projection: '={{$value}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Return All',
|
||||||
|
name: 'returnAll',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['bucket'],
|
||||||
|
operation: ['getAll'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: false,
|
||||||
|
description: 'Whether to return all results or only up to a given limit',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Filters',
|
||||||
|
name: 'getFilters',
|
||||||
|
type: 'collection',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['bucket'],
|
||||||
|
operation: ['delete', 'get', 'update'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
placeholder: 'Add Filter',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Metageneration Match',
|
||||||
|
name: 'ifMetagenerationMatch',
|
||||||
|
type: 'number',
|
||||||
|
description:
|
||||||
|
'Only return data if the metageneration value of the Bucket matches the sent value',
|
||||||
|
default: 0,
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
qs: {
|
||||||
|
ifMetagenerationMatch: '={{$value}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Metageneration Exclude',
|
||||||
|
name: 'ifMetagenerationNotMatch',
|
||||||
|
type: 'number',
|
||||||
|
description:
|
||||||
|
'Only return data if the metageneration value of the Bucket does not match the sent value',
|
||||||
|
default: 0,
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
qs: {
|
||||||
|
ifMetagenerationNotMatch: '={{$value}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Predefined Access Control',
|
||||||
|
name: 'createAcl',
|
||||||
|
type: 'collection',
|
||||||
|
noDataExpression: true,
|
||||||
|
default: {},
|
||||||
|
placeholder: 'Add Access Control Parameters',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['bucket'],
|
||||||
|
operation: ['create', 'update'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Predefined ACL',
|
||||||
|
name: 'predefinedAcl',
|
||||||
|
type: 'options',
|
||||||
|
default: 'authenticatedRead',
|
||||||
|
placeholder: 'Apply a predefined set of access controls to this bucket',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Authenticated Read',
|
||||||
|
value: 'authenticatedRead',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Private',
|
||||||
|
value: 'private',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Project Private',
|
||||||
|
value: 'projectPrivate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Public Read',
|
||||||
|
value: 'publicRead',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Public Read/Write',
|
||||||
|
value: 'publicReadWrite',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
qs: {
|
||||||
|
predefinedAcl: '={{$value}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Predefined Default Object ACL',
|
||||||
|
name: 'predefinedDefaultObjectAcl',
|
||||||
|
type: 'options',
|
||||||
|
default: 'authenticatedRead',
|
||||||
|
placeholder: 'Apply a predefined set of default object access controls to this bucket',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Authenticated Read',
|
||||||
|
value: 'authenticatedRead',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Bucket Owner Full Control',
|
||||||
|
value: 'bucketOwnerFullControl',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Bucket Owner Read',
|
||||||
|
value: 'bucketOwnerRead',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Private',
|
||||||
|
value: 'private',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Project Private',
|
||||||
|
value: 'projectPrivate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Public Read',
|
||||||
|
value: 'publicRead',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
qs: {
|
||||||
|
predefinedObjectAcl: '={{$value}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Parameters',
|
||||||
|
name: 'createBody',
|
||||||
|
type: 'collection',
|
||||||
|
noDataExpression: true,
|
||||||
|
default: {},
|
||||||
|
placeholder: 'Add Metadata Parameter',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['bucket'],
|
||||||
|
operation: ['create', 'update'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Access Control',
|
||||||
|
name: 'acl',
|
||||||
|
type: 'json',
|
||||||
|
default: '[]',
|
||||||
|
placeholder: 'Access controls on the Bucket',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Billing',
|
||||||
|
name: 'billing',
|
||||||
|
type: 'json',
|
||||||
|
default: '{}',
|
||||||
|
placeholder: "The bucket's billing configuration",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'CORS',
|
||||||
|
name: 'cors',
|
||||||
|
type: 'json',
|
||||||
|
default: '[]',
|
||||||
|
placeholder: "The bucket's Cross Origin Resource Sharing configuration",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Custom Placement Config',
|
||||||
|
name: 'customPlacementConfig',
|
||||||
|
type: 'json',
|
||||||
|
default: '{}',
|
||||||
|
placeholder: 'The configuration for the region(s) for the Bucket',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Data Locations',
|
||||||
|
name: 'dataLocations',
|
||||||
|
type: 'json',
|
||||||
|
default: '[]',
|
||||||
|
placeholder: 'The list of individual regions that comprise a dual-region Bucket',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Default Event Based Hold',
|
||||||
|
name: 'defaultEventBasedHold',
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
placeholder: 'Whether or not to automatically apply an event based hold to new objects',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Default Object ACL',
|
||||||
|
name: 'defaultObjectAcl',
|
||||||
|
type: 'json',
|
||||||
|
default: '[]',
|
||||||
|
placeholder: 'Default Access Controls for new objects when no ACL is provided',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Encryption',
|
||||||
|
name: 'encryption',
|
||||||
|
type: 'json',
|
||||||
|
default: '{}',
|
||||||
|
placeholder: 'Encryption configuration for a bucket',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'IAM Configuration',
|
||||||
|
name: 'iamConfiguration',
|
||||||
|
type: 'json',
|
||||||
|
default: '{}',
|
||||||
|
placeholder: "The bucket's IAM configuration",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Labels',
|
||||||
|
name: 'labels',
|
||||||
|
type: 'json',
|
||||||
|
default: '{}',
|
||||||
|
placeholder: 'User provided bucket labels, in key/value pairs',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Lifecycle',
|
||||||
|
name: 'lifecycle',
|
||||||
|
type: 'json',
|
||||||
|
default: '{}',
|
||||||
|
placeholder: "The bucket's lifecycle configuration",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Location',
|
||||||
|
name: 'location',
|
||||||
|
type: 'string',
|
||||||
|
default: 'US',
|
||||||
|
placeholder: 'The location of the bucket',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Logging',
|
||||||
|
name: 'logging',
|
||||||
|
type: 'json',
|
||||||
|
default: '{}',
|
||||||
|
placeholder: "The bucket's logging configuration",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Retention Policy',
|
||||||
|
name: 'retentionPolicy',
|
||||||
|
type: 'json',
|
||||||
|
default: '{}',
|
||||||
|
placeholder: "The bucket's retention policy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Recovery Point Objective',
|
||||||
|
name: 'rpo',
|
||||||
|
type: 'string',
|
||||||
|
default: 'DEFAULT',
|
||||||
|
placeholder: 'The recovery point objective for the bucket',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Storage Class',
|
||||||
|
name: 'storageClass',
|
||||||
|
type: 'string',
|
||||||
|
default: 'STANDARD',
|
||||||
|
placeholder: "The bucket's default storage class for objects that don't define one",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Versioning',
|
||||||
|
name: 'versioning',
|
||||||
|
type: 'json',
|
||||||
|
default: '{}',
|
||||||
|
placeholder: "The bucket's versioning configuration",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Website',
|
||||||
|
name: 'website',
|
||||||
|
type: 'json',
|
||||||
|
default: '{}',
|
||||||
|
placeholder: "The bucket's website configuration for when it is used to host a website",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"node": "n8n-nodes-base.googleCloudStorage",
|
||||||
|
"nodeVersion": "1.0",
|
||||||
|
"codexVersion": "1.0",
|
||||||
|
"categories": [
|
||||||
|
"Development",
|
||||||
|
"Data & Storage"
|
||||||
|
],
|
||||||
|
"resources": {
|
||||||
|
"credentialDocumentation": [
|
||||||
|
{
|
||||||
|
"url": "https://docs.n8n.io/credentials/google"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"generic": [
|
||||||
|
{
|
||||||
|
"label": "15 Google apps you can combine and automate to increase productivity",
|
||||||
|
"icon": "💡",
|
||||||
|
"url": "https://n8n.io/blog/automate-google-apps-for-productivity/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
import { INodeType, INodeTypeDescription } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { bucketFields, bucketOperations } from './BucketDescription';
|
||||||
|
import { objectFields, objectOperations } from './ObjectDescription';
|
||||||
|
|
||||||
|
export class GoogleCloudStorage implements INodeType {
|
||||||
|
description: INodeTypeDescription = {
|
||||||
|
displayName: 'Google Cloud Storage',
|
||||||
|
name: 'googleCloudStorage',
|
||||||
|
icon: 'file:googleCloudStorage.svg',
|
||||||
|
group: ['transform'],
|
||||||
|
version: 1,
|
||||||
|
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||||
|
description: 'Use the Google Cloud Storage API',
|
||||||
|
defaults: {
|
||||||
|
name: 'Google Cloud Storage',
|
||||||
|
color: '#ff0000',
|
||||||
|
},
|
||||||
|
inputs: ['main'],
|
||||||
|
outputs: ['main'],
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: 'googleCloudStorageOAuth2Api',
|
||||||
|
required: true,
|
||||||
|
testedBy: {
|
||||||
|
request: {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/b/',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
requestDefaults: {
|
||||||
|
returnFullResponse: true,
|
||||||
|
baseURL: 'https://storage.googleapis.com/storage/v1',
|
||||||
|
},
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Resource',
|
||||||
|
name: 'resource',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Bucket',
|
||||||
|
value: 'bucket',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Object',
|
||||||
|
value: 'object',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'bucket',
|
||||||
|
},
|
||||||
|
|
||||||
|
// BUCKET
|
||||||
|
...bucketOperations,
|
||||||
|
...bucketFields,
|
||||||
|
|
||||||
|
// OBJECT
|
||||||
|
...objectOperations,
|
||||||
|
...objectFields,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,911 @@
|
||||||
|
import FormData from 'form-data';
|
||||||
|
import { IDataObject, NodeOperationError } from 'n8n-workflow';
|
||||||
|
import { INodeExecutionData, INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
// Define these because we'll be using them in two separate places
|
||||||
|
const metagenerationFilters: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Generation',
|
||||||
|
name: 'generation',
|
||||||
|
type: 'number',
|
||||||
|
placeholder: 'Select a specific revision of the chosen object',
|
||||||
|
default: -1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Generation Match',
|
||||||
|
name: 'ifGenerationMatch',
|
||||||
|
type: 'number',
|
||||||
|
placeholder: 'Make operation conditional of the object generation matching this value',
|
||||||
|
default: -1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Generation Exclude',
|
||||||
|
name: 'ifGenerationNotMatch',
|
||||||
|
type: 'number',
|
||||||
|
placeholder: 'Make operation conditional of the object generation not matching this value',
|
||||||
|
default: -1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Metageneration Match',
|
||||||
|
name: 'ifMetagenerationMatch',
|
||||||
|
type: 'number',
|
||||||
|
placeholder:
|
||||||
|
"Make operation conditional of the object's current metageneration matching this value",
|
||||||
|
default: -1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Metageneration Exclude',
|
||||||
|
name: 'ifMetagenerationNotMatch',
|
||||||
|
type: 'number',
|
||||||
|
placeholder:
|
||||||
|
"Make operation conditional of the object's current metageneration not matching this value",
|
||||||
|
default: -1,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const predefinedAclOptions: INodeProperties = {
|
||||||
|
displayName: 'Predefined ACL',
|
||||||
|
name: 'predefinedAcl',
|
||||||
|
type: 'options',
|
||||||
|
placeholder: 'Apply a predefined set of Access Controls to the object',
|
||||||
|
default: 'authenticatedRead',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Authenticated Read',
|
||||||
|
value: 'authenticatedRead',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Bucket Owner Full Control',
|
||||||
|
value: 'bucketOwnerFullControl',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Bucket Owner Read',
|
||||||
|
value: 'bucketOwnerRead',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Private',
|
||||||
|
value: 'private',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Project Private',
|
||||||
|
value: 'projectPrivate',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Public Read',
|
||||||
|
value: 'publicRead',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const objectOperations: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['object'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
description: 'Create an object',
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
method: 'POST',
|
||||||
|
baseURL: 'https://storage.googleapis.com/upload/storage/v1',
|
||||||
|
url: '={{"/b/" + $parameter["bucketName"] + "/o/"}}',
|
||||||
|
qs: {
|
||||||
|
name: '={{$parameter["objectName"]}}',
|
||||||
|
uploadType: 'multipart',
|
||||||
|
},
|
||||||
|
headers: {},
|
||||||
|
},
|
||||||
|
send: {
|
||||||
|
preSend: [
|
||||||
|
// Handle setup of Query and Headers
|
||||||
|
async function (this, requestOptions) {
|
||||||
|
// Merge in the options into the queryset and headers objects
|
||||||
|
if (!requestOptions.qs) requestOptions.qs = {};
|
||||||
|
if (!requestOptions.headers) requestOptions.headers = {};
|
||||||
|
const options = this.getNodeParameter('createQuery') as IDataObject;
|
||||||
|
const headers = this.getNodeParameter('encryptionHeaders') as IDataObject;
|
||||||
|
requestOptions.qs = Object.assign(requestOptions.qs, options);
|
||||||
|
requestOptions.headers = Object.assign(requestOptions.headers, headers);
|
||||||
|
return requestOptions;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Handle body creation
|
||||||
|
async function (this, requestOptions) {
|
||||||
|
// Populate metadata JSON
|
||||||
|
let metadata: IDataObject = { name: this.getNodeParameter('objectName') as string };
|
||||||
|
const bodyData = this.getNodeParameter('createData') as IDataObject;
|
||||||
|
const useBinary = this.getNodeParameter('createFromBinary') as boolean;
|
||||||
|
|
||||||
|
// Parse JSON body parameters
|
||||||
|
if (bodyData.acl) {
|
||||||
|
try {
|
||||||
|
bodyData.acl = JSON.parse(bodyData.acl as string);
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
if (bodyData.metadata) {
|
||||||
|
try {
|
||||||
|
bodyData.metadata = JSON.parse(bodyData.metadata as string);
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
metadata = Object.assign(metadata, bodyData);
|
||||||
|
|
||||||
|
// Populate request body
|
||||||
|
const body = new FormData();
|
||||||
|
const item = this.getInputData();
|
||||||
|
body.append('metadata', JSON.stringify(metadata), {
|
||||||
|
contentType: 'application/json',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Determine content and content type
|
||||||
|
let content: string | Buffer;
|
||||||
|
let contentType: string;
|
||||||
|
if (useBinary) {
|
||||||
|
const binaryPropertyName = this.getNodeParameter(
|
||||||
|
'createBinaryPropertyName',
|
||||||
|
) as string;
|
||||||
|
if (!item.binary) {
|
||||||
|
throw new NodeOperationError(this.getNode(), 'No binary data exists on item!', {
|
||||||
|
itemIndex: this.getItemIndex(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (item.binary[binaryPropertyName] === undefined) {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
`No binary data property "${binaryPropertyName}" does not exist on item!`,
|
||||||
|
{ itemIndex: this.getItemIndex() },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const binaryData = item.binary[binaryPropertyName];
|
||||||
|
|
||||||
|
// Decode from base64 for upload
|
||||||
|
content = Buffer.from(binaryData.data, 'base64');
|
||||||
|
contentType = binaryData.mimeType;
|
||||||
|
} else {
|
||||||
|
content = this.getNodeParameter('createContent') as string;
|
||||||
|
contentType = 'text/plain';
|
||||||
|
}
|
||||||
|
body.append('file', content, { contentType });
|
||||||
|
|
||||||
|
// Set the headers
|
||||||
|
requestOptions.headers!!['Content-Length'] = body.getLengthSync();
|
||||||
|
requestOptions.headers!![
|
||||||
|
'Content-Type'
|
||||||
|
] = `multipart/related; boundary=${body.getBoundary()}`;
|
||||||
|
|
||||||
|
// Return the request data
|
||||||
|
requestOptions.body = body.getBuffer();
|
||||||
|
return requestOptions;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
action: 'Create an object',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
value: 'delete',
|
||||||
|
description: 'Delete an object',
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
method: 'DELETE',
|
||||||
|
url: '={{"/b/" + $parameter["bucketName"] + "/o/" + $parameter["objectName"]}}',
|
||||||
|
qs: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
action: 'Delete an object from a bucket',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Get object data or metadata',
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
method: 'GET',
|
||||||
|
url: '={{"/b/" + $parameter["bucketName"] + "/o/" + $parameter["objectName"]}}',
|
||||||
|
returnFullResponse: true,
|
||||||
|
qs: {
|
||||||
|
alt: '={{$parameter["alt"]}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
send: {
|
||||||
|
preSend: [
|
||||||
|
async function (this, requestOptions) {
|
||||||
|
if (!requestOptions.qs) requestOptions.qs = {};
|
||||||
|
if (!requestOptions.headers) requestOptions.headers = {};
|
||||||
|
const options = this.getNodeParameter('getParameters') as IDataObject;
|
||||||
|
const headers = this.getNodeParameter('encryptionHeaders') as IDataObject;
|
||||||
|
const datatype = this.getNodeParameter('alt') as string;
|
||||||
|
|
||||||
|
if (datatype === 'media') {
|
||||||
|
requestOptions.encoding = 'arraybuffer';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge in the options into the queryset and headers objects
|
||||||
|
requestOptions.qs = Object.assign(requestOptions.qs, options);
|
||||||
|
requestOptions.headers = Object.assign(requestOptions.headers, headers);
|
||||||
|
|
||||||
|
// Return the request data
|
||||||
|
return requestOptions;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
postReceive: [
|
||||||
|
async function (this, items, responseData) {
|
||||||
|
// If the request was for object data as opposed to metadata, change the json to binary field in the response
|
||||||
|
const datatype = this.getNodeParameter('alt') as string;
|
||||||
|
|
||||||
|
if (datatype === 'media') {
|
||||||
|
// Adapt the binaryProperty part of Routing Node since it's conditional
|
||||||
|
const destinationName = this.getNodeParameter('binaryPropertyName') as string;
|
||||||
|
const fileName = this.getNodeParameter('objectName') as string;
|
||||||
|
const binaryData = await this.helpers.prepareBinaryData(
|
||||||
|
responseData.body as Buffer,
|
||||||
|
fileName,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Transform items
|
||||||
|
items = items.map((item) => {
|
||||||
|
item.json = {};
|
||||||
|
item.binary = { [destinationName]: binaryData };
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
action: 'Get object data or metadata',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Many',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'Retrieve a list of objects',
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
method: 'GET',
|
||||||
|
url: '={{"/b/" + $parameter["bucketName"] + "/o/"}}',
|
||||||
|
returnFullResponse: true,
|
||||||
|
qs: {
|
||||||
|
},
|
||||||
|
},
|
||||||
|
send: {
|
||||||
|
preSend: [
|
||||||
|
async function (this, requestOptions) {
|
||||||
|
if (!requestOptions.qs) requestOptions.qs = {};
|
||||||
|
const options = this.getNodeParameter('listFilters') as IDataObject;
|
||||||
|
|
||||||
|
// Merge in the options into the queryset
|
||||||
|
requestOptions.qs = Object.assign(requestOptions.qs, options);
|
||||||
|
|
||||||
|
// Check if we send a limit
|
||||||
|
const returnAll = this.getNodeParameter('returnAll') as boolean;
|
||||||
|
if (!returnAll) requestOptions.qs.maxResults = this.getNodeParameter('maxResults');
|
||||||
|
|
||||||
|
// Return the request data
|
||||||
|
return requestOptions;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
paginate: true,
|
||||||
|
},
|
||||||
|
operations: {
|
||||||
|
async pagination(this, requestOptions) {
|
||||||
|
if (!requestOptions.options.qs) requestOptions.options.qs = {};
|
||||||
|
let executions: INodeExecutionData[] = [];
|
||||||
|
let responseData: INodeExecutionData[];
|
||||||
|
let nextPageToken: string | undefined = undefined;
|
||||||
|
const returnAll = this.getNodeParameter('returnAll') as boolean;
|
||||||
|
|
||||||
|
do {
|
||||||
|
requestOptions.options.qs.pageToken = nextPageToken;
|
||||||
|
responseData = await this.makeRoutingRequest(requestOptions);
|
||||||
|
|
||||||
|
// Check for another page
|
||||||
|
const lastItem = responseData[responseData.length - 1].json;
|
||||||
|
nextPageToken = lastItem.nextPageToken as string | undefined;
|
||||||
|
|
||||||
|
// Extract just the list of buckets from the page data
|
||||||
|
responseData.forEach((page) => {
|
||||||
|
const objects = page.json.items as IDataObject[];
|
||||||
|
if (objects) {
|
||||||
|
executions = executions.concat(objects.map((object) => ({ json: object })));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} while (returnAll && nextPageToken);
|
||||||
|
|
||||||
|
// Return all execution responses as an array
|
||||||
|
return executions;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
action: 'Get a list of objects',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Update',
|
||||||
|
value: 'update',
|
||||||
|
description: "Update an object's metadata",
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
method: 'PATCH',
|
||||||
|
url: '={{"/b/" + $parameter["bucketName"] + "/o/" + $parameter["objectName"]}}',
|
||||||
|
qs: {
|
||||||
|
},
|
||||||
|
body: {},
|
||||||
|
},
|
||||||
|
send: {
|
||||||
|
preSend: [
|
||||||
|
async function (this, requestOptions) {
|
||||||
|
if (!requestOptions.qs) requestOptions.qs = {};
|
||||||
|
if (!requestOptions.headers) requestOptions.headers = {};
|
||||||
|
if (!requestOptions.body) requestOptions.body = {};
|
||||||
|
const options = this.getNodeParameter('metagenAndAclQuery') as IDataObject;
|
||||||
|
const headers = this.getNodeParameter('encryptionHeaders') as IDataObject;
|
||||||
|
const body = this.getNodeParameter('updateData') as IDataObject;
|
||||||
|
|
||||||
|
// Parse JSON body parameters
|
||||||
|
if (body.acl) {
|
||||||
|
try {
|
||||||
|
body.acl = JSON.parse(body.acl as string);
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
if (body.metadata) {
|
||||||
|
try {
|
||||||
|
body.metadata = JSON.parse(body.metadata as string);
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge in the options into the queryset and headers objects
|
||||||
|
requestOptions.qs = Object.assign(requestOptions.qs, options);
|
||||||
|
requestOptions.headers = Object.assign(requestOptions.headers, headers);
|
||||||
|
requestOptions.body = Object.assign(requestOptions.body, body);
|
||||||
|
|
||||||
|
// Return the request data
|
||||||
|
return requestOptions;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
action: "Update an object's metadata",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'getAll',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const objectFields: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Bucket Name',
|
||||||
|
name: 'bucketName',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'Bucket Name',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['object'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Object Name',
|
||||||
|
name: 'objectName',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'Object Name',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['object'],
|
||||||
|
operation: ['create', 'delete', 'get', 'update'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Projection',
|
||||||
|
name: 'projection',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'All Properties',
|
||||||
|
value: 'full',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'No ACL',
|
||||||
|
value: 'noAcl',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'noAcl',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['object'],
|
||||||
|
operation: ['get', 'getAll'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
qs: {
|
||||||
|
projection: '={{$value}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Create / Update gets their own definition because the default value is swapped
|
||||||
|
{
|
||||||
|
displayName: 'Projection',
|
||||||
|
name: 'updateProjection',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'All Properties',
|
||||||
|
value: 'full',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'No ACL',
|
||||||
|
value: 'noAcl',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'full',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['object'],
|
||||||
|
operation: ['create', 'update'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
routing: {
|
||||||
|
request: {
|
||||||
|
qs: {
|
||||||
|
projection: '={{$value}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Return Data',
|
||||||
|
name: 'alt',
|
||||||
|
type: 'options',
|
||||||
|
placeholder: 'The type of data to return from the request',
|
||||||
|
default: 'json',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Metadata',
|
||||||
|
value: 'json',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Object Data',
|
||||||
|
value: 'media',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['object'],
|
||||||
|
operation: ['get'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Use Binary Property',
|
||||||
|
name: 'createFromBinary',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['object'],
|
||||||
|
operation: ['create'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: true,
|
||||||
|
noDataExpression: true,
|
||||||
|
description: 'Whether the data for creating a file should come from a binary field',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Binary Property',
|
||||||
|
name: 'createBinaryPropertyName',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['object'],
|
||||||
|
operation: ['create'],
|
||||||
|
createFromBinary: [true],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: 'data',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'File Content',
|
||||||
|
name: 'createContent',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['object'],
|
||||||
|
operation: ['create'],
|
||||||
|
createFromBinary: [false],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Content of the file to be uploaded',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Binary Property',
|
||||||
|
name: 'binaryPropertyName',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['object'],
|
||||||
|
operation: ['get'],
|
||||||
|
alt: ['media'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: 'data',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Return All',
|
||||||
|
name: 'returnAll',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['object'],
|
||||||
|
operation: ['getAll'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: false,
|
||||||
|
description: 'Whether to return all results or only up to a given limit',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Limit',
|
||||||
|
name: 'maxResults',
|
||||||
|
type: 'number',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['object'],
|
||||||
|
operation: ['getAll'],
|
||||||
|
returnAll: [false],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
typeOptions: {
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 1000,
|
||||||
|
},
|
||||||
|
default: 100,
|
||||||
|
description: 'Max number of results to return',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Create Fields',
|
||||||
|
name: 'createData',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Create Body Field',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['object'],
|
||||||
|
operation: ['create'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Access Control List',
|
||||||
|
name: 'acl',
|
||||||
|
type: 'json',
|
||||||
|
default: '[]',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Cache Control',
|
||||||
|
name: 'cacheControl',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Content Disposition',
|
||||||
|
name: 'contentDisposition',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Content Encoding',
|
||||||
|
name: 'contentEncoding',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Content Language',
|
||||||
|
name: 'contentLanguage',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Content Type',
|
||||||
|
name: 'contentType',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'CRC32c Checksum',
|
||||||
|
name: 'crc32c',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Custom Time',
|
||||||
|
name: 'customTime',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Event Based Hold',
|
||||||
|
name: 'eventBasedHold',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'MD5 Hash',
|
||||||
|
name: 'md5Hash',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Metadata',
|
||||||
|
name: 'metadata',
|
||||||
|
type: 'json',
|
||||||
|
default: '{}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Storage Class',
|
||||||
|
name: 'storageClass',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Temporary Hold',
|
||||||
|
name: 'temporaryHold',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Update Fields',
|
||||||
|
name: 'updateData',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Update Body Field',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['object'],
|
||||||
|
operation: ['update'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: {
|
||||||
|
acl: '[]',
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Access Control',
|
||||||
|
name: 'acl',
|
||||||
|
type: 'json',
|
||||||
|
default: '[]',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Cache Control',
|
||||||
|
name: 'cacheControl',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Content Disposition',
|
||||||
|
name: 'contentDisposition',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Content Encoding',
|
||||||
|
name: 'contentEncoding',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Content Language',
|
||||||
|
name: 'contentLanguage',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Content Type',
|
||||||
|
name: 'contentType',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Custom Time',
|
||||||
|
name: 'customTime',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Event Based Hold',
|
||||||
|
name: 'eventBasedHold',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Metadata',
|
||||||
|
name: 'metadata',
|
||||||
|
type: 'json',
|
||||||
|
default: '{}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Temporary Hold',
|
||||||
|
name: 'temporaryHold',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Parameters',
|
||||||
|
name: 'createQuery',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Additional Parameters',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['object'],
|
||||||
|
operation: ['create'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Content Encoding',
|
||||||
|
name: 'contentEncoding',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
...metagenerationFilters,
|
||||||
|
{
|
||||||
|
displayName: 'KMS Key Name',
|
||||||
|
name: 'kmsKeyName',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
predefinedAclOptions,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Parameters',
|
||||||
|
name: 'getParameters',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Additional Parameters',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['object'],
|
||||||
|
operation: ['delete', 'get'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [...metagenerationFilters],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Parameters',
|
||||||
|
name: 'metagenAndAclQuery',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Additional Parameters',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['object'],
|
||||||
|
operation: ['update'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [...metagenerationFilters, predefinedAclOptions],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Encryption Headers',
|
||||||
|
name: 'encryptionHeaders',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Encryption Headers',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['object'],
|
||||||
|
operation: ['create', 'get', 'update'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Encryption Algorithm',
|
||||||
|
name: 'X-Goog-Encryption-Algorithm',
|
||||||
|
type: 'options',
|
||||||
|
placeholder:
|
||||||
|
'The encryption algorithm to use, which must be AES256. Use to supply your own key in the request',
|
||||||
|
default: 'AES256',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'AES256',
|
||||||
|
value: 'AES256',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Encryption Key',
|
||||||
|
name: 'X-Goog-Encryption-Key',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'Base64 encoded string of your AES256 encryption key',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Encryption Key Hash',
|
||||||
|
name: 'X-Goog-Encryption-Key-Sha256',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'Base64 encoded string of the SHA256 hash of your encryption key',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Parameters',
|
||||||
|
name: 'listFilters',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Additional Parameters',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['object'],
|
||||||
|
operation: ['getAll'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Delimiter',
|
||||||
|
name: 'delimiter',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'Returns results in directory-like mode, using this value as the delimiter',
|
||||||
|
default: '/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'End Offset',
|
||||||
|
name: 'endOffset',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'Filter results to names lexicographically before this value',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Include Trailing Delimiter',
|
||||||
|
name: 'includeTrailingDelimiter',
|
||||||
|
type: 'boolean',
|
||||||
|
placeholder:
|
||||||
|
'If true, objects will appear with exactly one instance of delimiter at the end of the name',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Prefix',
|
||||||
|
name: 'prefix',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'Filter results to names that start with this value',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Start Offset',
|
||||||
|
name: 'startOffset',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'Filter results to names lexicographically equal or after this value',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Versions',
|
||||||
|
name: 'versions',
|
||||||
|
type: 'boolean',
|
||||||
|
placeholder: 'If true, list all versions of objects as distinct entries',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" viewBox="0 0 24 24"><defs><style>.cls-1{fill:#aecbfa;}.cls-2{fill:#669df6;}.cls-3{fill:#4285f4;}.cls-4{fill:#fff;}</style></defs><title>Icon_24px_CloudStorage_Color</title><g data-name="Product Icons"><rect class="cls-1" x="2" y="4" width="20" height="7"/><rect class="cls-2" x="20" y="4" width="2" height="7"/><polygon class="cls-3" points="22 4 20 4 20 11 22 4"/><rect class="cls-2" x="2" y="4" width="2" height="7"/><rect class="cls-4" x="6" y="7" width="6" height="1"/><rect class="cls-4" x="15" y="6" width="3" height="3" rx="1.5"/><rect class="cls-1" x="2" y="13" width="20" height="7"/><rect class="cls-2" x="20" y="13" width="2" height="7"/><polygon class="cls-3" points="22 13 20 13 20 20 22 13"/><rect class="cls-2" x="2" y="13" width="2" height="7"/><rect class="cls-4" x="6" y="16" width="6" height="1"/><rect class="cls-4" x="15" y="15" width="3" height="3" rx="1.5"/></g></svg>
|
After Width: | Height: | Size: 958 B |
|
@ -117,6 +117,7 @@
|
||||||
"dist/credentials/GoogleBooksOAuth2Api.credentials.js",
|
"dist/credentials/GoogleBooksOAuth2Api.credentials.js",
|
||||||
"dist/credentials/GoogleCalendarOAuth2Api.credentials.js",
|
"dist/credentials/GoogleCalendarOAuth2Api.credentials.js",
|
||||||
"dist/credentials/GoogleCloudNaturalLanguageOAuth2Api.credentials.js",
|
"dist/credentials/GoogleCloudNaturalLanguageOAuth2Api.credentials.js",
|
||||||
|
"dist/credentials/GoogleCloudStorageOAuth2Api.credentials.js",
|
||||||
"dist/credentials/GoogleContactsOAuth2Api.credentials.js",
|
"dist/credentials/GoogleContactsOAuth2Api.credentials.js",
|
||||||
"dist/credentials/GoogleDocsOAuth2Api.credentials.js",
|
"dist/credentials/GoogleDocsOAuth2Api.credentials.js",
|
||||||
"dist/credentials/GoogleDriveOAuth2Api.credentials.js",
|
"dist/credentials/GoogleDriveOAuth2Api.credentials.js",
|
||||||
|
@ -452,6 +453,7 @@
|
||||||
"dist/nodes/Google/Calendar/GoogleCalendarTrigger.node.js",
|
"dist/nodes/Google/Calendar/GoogleCalendarTrigger.node.js",
|
||||||
"dist/nodes/Google/Chat/GoogleChat.node.js",
|
"dist/nodes/Google/Chat/GoogleChat.node.js",
|
||||||
"dist/nodes/Google/CloudNaturalLanguage/GoogleCloudNaturalLanguage.node.js",
|
"dist/nodes/Google/CloudNaturalLanguage/GoogleCloudNaturalLanguage.node.js",
|
||||||
|
"dist/nodes/Google/CloudStorage/GoogleCloudStorage.node.js",
|
||||||
"dist/nodes/Google/Contacts/GoogleContacts.node.js",
|
"dist/nodes/Google/Contacts/GoogleContacts.node.js",
|
||||||
"dist/nodes/Google/Docs/GoogleDocs.node.js",
|
"dist/nodes/Google/Docs/GoogleDocs.node.js",
|
||||||
"dist/nodes/Google/Drive/GoogleDrive.node.js",
|
"dist/nodes/Google/Drive/GoogleDrive.node.js",
|
||||||
|
|
Loading…
Reference in a new issue