-
Notifications
You must be signed in to change notification settings - Fork 9.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Cloudflare Node): add Cloudflare node (#4271)
* ✨ Cloudflare node * ⚡ Add paired items * Added codex file for Cloudflare * ⚡ Improvements Co-authored-by: Jonathan Bennetts <jonathan.bennetts@gmail.com>
- Loading branch information
1 parent
1b320cd
commit 94a02c6
Showing
7 changed files
with
523 additions
and
0 deletions.
There are no files selected for viewing
35 changes: 35 additions & 0 deletions
35
packages/nodes-base/credentials/CloudflareApi.credentials.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { | ||
IAuthenticateGeneric, | ||
ICredentialTestRequest, | ||
ICredentialType, | ||
INodeProperties, | ||
} from 'n8n-workflow'; | ||
|
||
export class CloudflareApi implements ICredentialType { | ||
name = 'cloudflareApi'; | ||
displayName = 'Cloudflare API'; | ||
documentationUrl = 'cloudflare'; | ||
properties: INodeProperties[] = [ | ||
{ | ||
displayName: 'API Token', | ||
name: 'apiToken', | ||
type: 'string', | ||
default: '', | ||
}, | ||
]; | ||
|
||
authenticate: IAuthenticateGeneric = { | ||
type: 'generic', | ||
properties: { | ||
headers: { | ||
'Authorization': '=Bearer {{$credentials.apiToken}}', | ||
}, | ||
}, | ||
}; | ||
|
||
test: ICredentialTestRequest = { | ||
request: { | ||
baseURL: 'https://api.cloudflare.com/client/v4/user/tokens/verify', | ||
}, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"node": "n8n-nodes-base.cloudflare", | ||
"nodeVersion": "1.0", | ||
"codexVersion": "1.0", | ||
"categories": ["Development"], | ||
"resources": { | ||
"credentialDocumentation": [ | ||
{ | ||
"url": "https://docs.n8n.io/credentials/cloudflare" | ||
} | ||
], | ||
"primaryDocumentation": [ | ||
{ | ||
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.cloudflare/" | ||
} | ||
] | ||
} | ||
} |
179 changes: 179 additions & 0 deletions
179
packages/nodes-base/nodes/Cloudflare/Cloudflare.node.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
import { IExecuteFunctions } from 'n8n-core'; | ||
|
||
import { | ||
IDataObject, | ||
ILoadOptionsFunctions, | ||
INodeExecutionData, | ||
INodePropertyOptions, | ||
INodeType, | ||
INodeTypeDescription, | ||
} from 'n8n-workflow'; | ||
|
||
import { cloudflareApiRequest, cloudflareApiRequestAllItems } from './GenericFunctions'; | ||
|
||
import { zoneCertificateFields, zoneCertificateOperations } from './ZoneCertificateDescription'; | ||
|
||
export class Cloudflare implements INodeType { | ||
description: INodeTypeDescription = { | ||
displayName: 'Cloudflare', | ||
name: 'cloudflare', | ||
icon: 'file:cloudflare.svg', | ||
group: ['input'], | ||
version: 1, | ||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', | ||
description: 'Consume Cloudflare API', | ||
defaults: { | ||
name: 'Cloudflare', | ||
color: '#000000', | ||
}, | ||
inputs: ['main'], | ||
outputs: ['main'], | ||
credentials: [ | ||
{ | ||
name: 'cloudflareApi', | ||
required: true, | ||
}, | ||
], | ||
properties: [ | ||
{ | ||
displayName: 'Resource', | ||
name: 'resource', | ||
type: 'options', | ||
noDataExpression: true, | ||
options: [ | ||
{ | ||
name: 'Zone Certificate', | ||
value: 'zoneCertificate', | ||
}, | ||
], | ||
default: 'zoneCertificate', | ||
}, | ||
...zoneCertificateOperations, | ||
...zoneCertificateFields, | ||
], | ||
}; | ||
|
||
methods = { | ||
loadOptions: { | ||
async getZones(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> { | ||
const returnData: INodePropertyOptions[] = []; | ||
const { result: zones } = await cloudflareApiRequest.call(this, 'GET', '/zones'); | ||
for (const zone of zones) { | ||
returnData.push({ | ||
name: zone.name, | ||
value: zone.id, | ||
}); | ||
} | ||
return returnData; | ||
}, | ||
}, | ||
}; | ||
|
||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> { | ||
const items = this.getInputData(); | ||
const returnData: IDataObject[] = []; | ||
const length = items.length; | ||
const qs: IDataObject = {}; | ||
let responseData; | ||
const resource = this.getNodeParameter('resource', 0) as string; | ||
const operation = this.getNodeParameter('operation', 0) as string; | ||
|
||
for (let i = 0; i < length; i++) { | ||
try { | ||
if (resource === 'zoneCertificate') { | ||
//https://api.cloudflare.com/#zone-level-authenticated-origin-pulls-delete-certificate | ||
if (operation === 'delete') { | ||
const zoneId = this.getNodeParameter('zoneId', i) as string; | ||
const certificateId = this.getNodeParameter('certificateId', i) as string; | ||
|
||
responseData = await cloudflareApiRequest.call( | ||
this, | ||
'DELETE', | ||
`/zones/${zoneId}/origin_tls_client_auth/${certificateId}`, | ||
{}, | ||
); | ||
responseData = responseData.result; | ||
} | ||
//https://api.cloudflare.com/#zone-level-authenticated-origin-pulls-get-certificate-details | ||
if (operation === 'get') { | ||
const zoneId = this.getNodeParameter('zoneId', i) as string; | ||
const certificateId = this.getNodeParameter('certificateId', i) as string; | ||
|
||
responseData = await cloudflareApiRequest.call( | ||
this, | ||
'GET', | ||
`/zones/${zoneId}/origin_tls_client_auth/${certificateId}`, | ||
{}, | ||
); | ||
responseData = responseData.result; | ||
} | ||
//https://api.cloudflare.com/#zone-level-authenticated-origin-pulls-list-certificates | ||
if (operation === 'getMany') { | ||
const zoneId = this.getNodeParameter('zoneId', i) as string; | ||
const returnAll = this.getNodeParameter('returnAll', i) as boolean; | ||
const filters = this.getNodeParameter('filters', i, {}) as IDataObject; | ||
|
||
Object.assign(qs, filters); | ||
|
||
if (returnAll) { | ||
responseData = await cloudflareApiRequestAllItems.call( | ||
this, | ||
'result', | ||
'GET', | ||
`/zones/${zoneId}/origin_tls_client_auth`, | ||
{}, | ||
qs, | ||
); | ||
} else { | ||
const limit = this.getNodeParameter('limit', i) as number; | ||
Object.assign(qs, { per_page: limit }); | ||
responseData = await cloudflareApiRequest.call( | ||
this, | ||
'GET', | ||
`/zones/${zoneId}/origin_tls_client_auth`, | ||
{}, | ||
qs, | ||
); | ||
responseData = responseData.result; | ||
} | ||
} | ||
//https://api.cloudflare.com/#zone-level-authenticated-origin-pulls-upload-certificate | ||
if (operation === 'upload') { | ||
const zoneId = this.getNodeParameter('zoneId', i) as string; | ||
const certificate = this.getNodeParameter('certificate', i) as string; | ||
const privateKey = this.getNodeParameter('privateKey', i) as string; | ||
|
||
const body: IDataObject = { | ||
certificate, | ||
private_key: privateKey, | ||
}; | ||
|
||
responseData = await cloudflareApiRequest.call( | ||
this, | ||
'POST', | ||
`/zones/${zoneId}/origin_tls_client_auth`, | ||
body, | ||
qs, | ||
); | ||
|
||
responseData = responseData.result; | ||
} | ||
} | ||
|
||
returnData.push( | ||
...this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(responseData), { | ||
itemData: { item: i }, | ||
}), | ||
); | ||
} catch (error) { | ||
if (this.continueOnFail()) { | ||
returnData.push({ json: { error: error.message } }); | ||
continue; | ||
} | ||
throw error; | ||
} | ||
} | ||
|
||
return [returnData as INodeExecutionData[]]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { | ||
OptionsWithUri, | ||
} from 'request'; | ||
|
||
import { | ||
IExecuteFunctions, | ||
IExecuteSingleFunctions, | ||
ILoadOptionsFunctions, | ||
IPollFunctions, | ||
} from 'n8n-core'; | ||
|
||
import { | ||
IDataObject, NodeApiError, | ||
} from 'n8n-workflow'; | ||
|
||
export async function cloudflareApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IPollFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any | ||
|
||
const options: OptionsWithUri = { | ||
method, | ||
body, | ||
qs, | ||
uri: `https://api.cloudflare.com/client/v4${resource}`, | ||
json: true, | ||
}; | ||
|
||
try { | ||
if (Object.keys(headers).length !== 0) { | ||
options.headers = Object.assign({}, options.headers, headers); | ||
} | ||
if (Object.keys(body).length === 0) { | ||
delete options.body; | ||
} | ||
return await this.helpers.requestWithAuthentication.call(this, 'cloudflareApi', options); | ||
} catch (error) { | ||
throw new NodeApiError(this.getNode(), error); | ||
} | ||
} | ||
|
||
export async function cloudflareApiRequestAllItems( | ||
this: IExecuteFunctions | ILoadOptionsFunctions, | ||
propertyName: string, | ||
method: string, | ||
endpoint: string, | ||
body: IDataObject = {}, | ||
query: IDataObject = {}, | ||
// tslint:disable-next-line:no-any | ||
): Promise<any> { | ||
const returnData: IDataObject[] = []; | ||
|
||
let responseData; | ||
query.page = 1; | ||
|
||
do { | ||
responseData = await cloudflareApiRequest.call( | ||
this, | ||
method, | ||
endpoint, | ||
body, | ||
query, | ||
); | ||
query.page++; | ||
returnData.push.apply(returnData, responseData[propertyName]); | ||
} while (responseData.result_info.total_pages !== responseData.result_info.page); | ||
return returnData; | ||
} |
Oops, something went wrong.