diff --git a/packages/nodes-base/credentials/Rundeck.credentials.ts b/packages/nodes-base/credentials/Rundeck.credentials.ts new file mode 100644 index 0000000000..5285e59c5a --- /dev/null +++ b/packages/nodes-base/credentials/Rundeck.credentials.ts @@ -0,0 +1,30 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + + +export class Rundeck implements ICredentialType { + name = 'rundeck'; + displayName = 'Rundeck'; + properties = [ + { + displayName: 'Url', + name: 'url', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Api Version', + name: 'apiVersion', + type: 'number' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Token', + name: 'token', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Rundeck/Api.ts b/packages/nodes-base/nodes/Rundeck/Api.ts new file mode 100644 index 0000000000..28f64c9d34 --- /dev/null +++ b/packages/nodes-base/nodes/Rundeck/Api.ts @@ -0,0 +1,44 @@ +import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; + +export class Api { + private api: AxiosInstance; + + constructor (config: AxiosRequestConfig) { + this.api = axios.create(config); + this.api.interceptors.request.use((param: AxiosRequestConfig) => ({ + ...param + })); + } + + protected getUri (config?: AxiosRequestConfig): string { + return this.api.getUri(config); + } + + protected request> (config: AxiosRequestConfig): Promise { + return this.api.request(config); + } + + protected get> (url: string, config?: AxiosRequestConfig): Promise { + return this.api.get(url, config); + } + + protected delete> (url: string, config?: AxiosRequestConfig): Promise { + return this.api.delete(url, config); + } + + protected head> (url: string, config?: AxiosRequestConfig): Promise { + return this.api.head(url, config); + } + + protected post> (url: string, data?: string, config?: AxiosRequestConfig): Promise { + return this.api.post(url, data, config); + } + + protected put> (url: string, data?: string, config?: AxiosRequestConfig): Promise { + return this.api.put(url, data, config); + } + + protected patch> (url: string, data?: string, config?: AxiosRequestConfig): Promise { + return this.api.patch(url, data, config); + } +} \ No newline at end of file diff --git a/packages/nodes-base/nodes/Rundeck/Rundeck.node.ts b/packages/nodes-base/nodes/Rundeck/Rundeck.node.ts new file mode 100644 index 0000000000..d9cb059cea --- /dev/null +++ b/packages/nodes-base/nodes/Rundeck/Rundeck.node.ts @@ -0,0 +1,171 @@ +import { IExecuteFunctions } from 'n8n-core'; +import { + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; +import { Parser } from 'xml2js'; +import { RundeckApi, RundeckCredentials } from "./RundeckApi"; + +export class Rundeck implements INodeType { + description: INodeTypeDescription = { + displayName: 'Rundeck', + name: 'rundeck', + icon: 'file:rundeck.png', + group: ['transform'], + version: 1, + description: 'Manage Rundeck API', + defaults: { + name: 'Rundeck', + color: '#F73F39', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'rundeck', + required: true, + } + ], + properties: [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + options: [ + { + name: 'Execute Job', + value: 'executeJob', + description: 'Executes Job.', + }, + ], + default: 'executeJob', + description: 'The operation to perform.', + }, + + // ---------------------------------- + // JobId + // ---------------------------------- + { + displayName: 'Job Id', + name: 'jobid', + type: 'string', + displayOptions: { + show: { + operation: [ + 'executeJob' + ], + }, + }, + default: '', + placeholder: 'Rundeck Job Id', + required: true, + description: 'The job Id to execute.', + }, + + // ---------------------------------- + // Arguments + // ---------------------------------- + { + displayName: 'Arguments', + name: 'arguments', + placeholder: 'Arguments', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + operation: [ + 'executeJob' + ], + }, + }, + default: {}, + options: [ + { + name: 'arguments', + displayName: 'Add argument', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + }, + ] + }, + ], + }, + ] + }; + + + async execute(this: IExecuteFunctions): Promise { + + // Input data + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length as unknown as number; + + const credentials = this.getCredentials('rundeck'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const rundeckCredentials: RundeckCredentials = { + url: credentials.url as string, + apiVersion: credentials.apiVersion as number, + token: credentials.token as string + }; + + const operation = this.getNodeParameter('operation', 0) as string; + + for (let i = 0; i < length; i++) { + if (operation === 'executeJob') { + // ---------------------------------- + // executeJob + // ---------------------------------- + const rundeckApi = new RundeckApi(rundeckCredentials); + const jobid = this.getNodeParameter('jobid', i) as string; + const rundeckArguments = (this.getNodeParameter('arguments', i) as IDataObject).arguments as IDataObject[]; + let response; + + try { + + response = await rundeckApi.executeJob(jobid, rundeckArguments); + + const parser = new Parser({ + mergeAttrs: true, + explicitArray: false, + }); + + // @ts-ignore + const json = await parser.parseStringPromise(response); + returnData.push({ json }); + + } catch(error) { + if(error.response && error.response.data && error.response.status) { + throw Error(`status: ${error.response.status}, response: ${(error.response.data).replace('\n', '')}`); + } else { + throw error; + } + } + + } else { + throw new Error(`The operation "${operation}" is not supported!`); + } + } + + return [this.helpers.returnJsonArray(returnData)]; + + } +} diff --git a/packages/nodes-base/nodes/Rundeck/RundeckApi.ts b/packages/nodes-base/nodes/Rundeck/RundeckApi.ts new file mode 100644 index 0000000000..137ed2bd57 --- /dev/null +++ b/packages/nodes-base/nodes/Rundeck/RundeckApi.ts @@ -0,0 +1,63 @@ +import { AxiosError, AxiosRequestConfig, AxiosResponse } from "axios"; +import { Api } from "./Api"; +import * as https from 'https'; +import { Xml } from "../Xml.node"; +import { IDataObject } from "n8n-workflow"; + +export interface RundeckCredentials { + url: string; + apiVersion: number; + token: string; +} + +function acceptVersion(credentialsApiVersion: number, apiVersion: number) { + if(apiVersion > credentialsApiVersion) { + throw Error('This endpoint is not supported for this version!'); + } +} + +export class RundeckApi extends Api { + + private credentials: RundeckCredentials; + + constructor (credentials: RundeckCredentials) { + + const config: AxiosRequestConfig = { + httpsAgent: new https.Agent({ + rejectUnauthorized: false + }), + headers: { + 'Accept': 'application/xml', + 'user-agent': 'n8n', + 'X-Rundeck-Auth-Token': credentials.token, + } + }; + + super(config); + this.credentials = credentials; + + } + + executeJob(jobId: string, args: IDataObject[]): Promise { + + acceptVersion(this.credentials.apiVersion, 12); + + let params = ''; + + if(args) { + for(const arg of args) { + params += "-" + arg.name + " " + arg.value + " "; + } + } + + return this.post(this.credentials.url + '/api/12/job/' + jobId + '/run?argString=' + params) + .then((response: AxiosResponse) => { + const { data } = response; + return data; + }) + .catch((error: AxiosError) => { + throw error; + }); + } + +} \ No newline at end of file diff --git a/packages/nodes-base/nodes/Rundeck/rundeck.png b/packages/nodes-base/nodes/Rundeck/rundeck.png new file mode 100644 index 0000000000..b55f1973ae Binary files /dev/null and b/packages/nodes-base/nodes/Rundeck/rundeck.png differ diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index c83d7f80ab..00e62abb29 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -81,6 +81,7 @@ "dist/credentials/Postgres.credentials.js", "dist/credentials/Redis.credentials.js", "dist/credentials/RocketchatApi.credentials.js", + "dist/credentials/Rundeck.credentials.js", "dist/credentials/ShopifyApi.credentials.js", "dist/credentials/SlackApi.credentials.js", "dist/credentials/Smtp.credentials.js", @@ -193,6 +194,7 @@ "dist/nodes/RenameKeys.node.js", "dist/nodes/Rocketchat/Rocketchat.node.js", "dist/nodes/RssFeedRead.node.js", + "dist/nodes/Rundeck/Rundeck.node.js", "dist/nodes/Set.node.js", "dist/nodes/Shopify/ShopifyTrigger.node.js", "dist/nodes/Slack/Slack.node.js",