n8n/packages/nodes-base/nodes/Jenkins/Jenkins.node.ts
Iván Ovejero 0448feec56
refactor: Apply eslint-plugin-n8n-nodes-base autofixable rules (#3174)
*  Initial setup

* 👕 Update `.eslintignore`

* 👕 Autofix node-param-default-missing (#3173)

* 🔥 Remove duplicate key

* 👕 Add exceptions

* 📦 Update package-lock.json

* 👕 Apply `node-class-description-inputs-wrong-trigger-node` (#3176)

* 👕 Apply `node-class-description-inputs-wrong-regular-node` (#3177)

* 👕 Apply `node-class-description-outputs-wrong` (#3178)

* 👕 Apply `node-execute-block-double-assertion-for-items` (#3179)

* 👕 Apply `node-param-default-wrong-for-collection` (#3180)

* 👕 Apply node-param-default-wrong-for-boolean (#3181)

* Autofixed default missing

* Autofixed booleans, worked well

*  Fix params

*  Undo exempted autofixes

* 📦 Update package-lock.json

* 👕 Apply node-class-description-missing-subtitle (#3182)

*  Fix missing comma

* 👕 Apply `node-param-default-wrong-for-fixed-collection` (#3184)

* 👕 Add exception for `node-class-description-missing-subtitle`

* 👕 Apply `node-param-default-wrong-for-multi-options` (#3185)

* 👕 Apply `node-param-collection-type-unsorted-items` (#3186)

* Missing coma

* 👕 Apply `node-param-default-wrong-for-simplify` (#3187)

* 👕 Apply `node-param-description-comma-separated-hyphen` (#3190)

* 👕 Apply `node-param-description-empty-string` (#3189)

* 👕 Apply `node-param-description-excess-inner-whitespace` (#3191)

* Rule looks good

* Add whitespace rule in eslint config

* :zao: fix

* 👕 Apply `node-param-description-identical-to-display-name` (#3193)

* 👕 Apply `node-param-description-missing-for-ignore-ssl-issues` (#3195)

*  Revert ":zao: fix"

This reverts commit ef8a76f3df.

* 👕 Apply `node-param-description-missing-for-simplify`  (#3196)

* 👕 Apply `node-param-description-missing-final-period` (#3194)

* Rule working as intended

* Add rule to eslint

* 👕 Apply node-param-description-missing-for-return-all (#3197)

*  Restore `lintfix` command

Co-authored-by: agobrech <45268029+agobrech@users.noreply.github.com>
Co-authored-by: Omar Ajoue <krynble@gmail.com>
Co-authored-by: agobrech <ael.gobrecht@gmail.com>
Co-authored-by: Michael Kret <michael.k@radency.com>
2022-04-22 18:29:51 +02:00

683 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
IExecuteFunctions,
} from 'n8n-core';
import {
ICredentialsDecrypted,
ICredentialTestFunctions,
IDataObject,
ILoadOptionsFunctions,
INodeCredentialTestResult,
INodeExecutionData,
INodePropertyOptions,
INodeType,
INodeTypeDescription,
NodeApiError,
} from 'n8n-workflow';
import {
jenkinsApiRequest,
tolerateTrailingSlash
} from './GenericFunctions';
export type JenkinsApiCredentials = {
username: string;
apiKey: string;
baseUrl: string;
};
export class Jenkins implements INodeType {
description: INodeTypeDescription = {
displayName: 'Jenkins',
name: 'jenkins',
icon: 'file:jenkins.svg',
group: ['output'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume Jenkins API',
defaults: {
name: 'Jenkins',
color: '#04AA51',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'jenkinsApi',
required: true,
testedBy: 'jenkinApiCredentialTest',
},
],
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
options: [
{
name: 'Build',
value: 'build',
},
{
name: 'Instance',
value: 'instance',
},
{
name: 'Job',
value: 'job',
},
],
default: 'job',
noDataExpression: true,
},
// --------------------------------------------------------------------------------------------------------
// Job Operations
// --------------------------------------------------------------------------------------------------------
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'job',
],
},
},
options: [
{
name: 'Copy',
value: 'copy',
description: 'Copy a specific job',
},
{
name: 'Create',
value: 'create',
description: 'Create a new job',
},
{
name: 'Trigger',
value: 'trigger',
description: 'Trigger a specific job',
},
{
name: 'Trigger with Parameters',
value: 'triggerParams',
description: 'Trigger a specific job',
},
],
default: 'trigger',
description: 'Possible operations',
noDataExpression: true,
},
{
displayName: 'Make sure the job is setup to support triggering with parameters. <a href="https://wiki.jenkins.io/display/JENKINS/Parameterized+Build" target="_blank">More info</a>',
name: 'triggerParamsNotice',
type: 'notice',
displayOptions: {
show: {
resource: [
'job',
],
operation: [
'triggerParams',
],
},
},
default: '',
},
{
displayName: 'Job Name',
name: 'job',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getJobs',
},
displayOptions: {
show: {
resource: [
'job',
],
operation: [
'trigger',
'triggerParams',
'copy',
],
},
},
required: true,
default: '',
description: 'Name of the job',
},
// --------------------------------------------------------------------------------------------------------
// Trigger a Job
// --------------------------------------------------------------------------------------------------------
{
displayName: 'Parameters',
name: 'param',
type: 'fixedCollection',
placeholder: 'Add Parameter',
displayOptions: {
show: {
resource: [
'job',
],
operation: [
'triggerParams',
],
},
},
required: true,
default: {},
typeOptions: {
multipleValues: true,
},
options: [
{
name: 'params',
displayName: 'Parameters',
values: [
{
displayName: 'Name',
name: 'name',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getJobParameters',
loadOptionsDependsOn: [
'job',
],
},
default: '',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
},
],
},
],
description: 'Parameters for Jenkins job',
},
// --------------------------------------------------------------------------------------------------------
// Copy or Create a Job
// --------------------------------------------------------------------------------------------------------
{
displayName: 'New Job Name',
name: 'newJob',
type: 'string',
displayOptions: {
show: {
resource: [
'job',
],
operation: [
'copy',
'create',
],
},
},
required: true,
default: '',
description: 'Name of the new Jenkins job',
},
{
displayName: 'XML',
name: 'xml',
type: 'string',
typeOptions: {
alwaysOpenEditWindow: true,
},
displayOptions: {
show: {
resource: [
'job',
],
operation: [
'create',
],
},
},
required: true,
default: '',
description: 'XML of Jenkins config',
},
{
displayName: 'To get the XML of an existing job, add config.xml to the end of the job URL',
name: 'createNotice',
type: 'notice',
default: '',
displayOptions: {
show: {
resource: [
'job',
],
operation: [
'create',
],
},
},
},
// --------------------------------------------------------------------------------------------------------
// Jenkins operations
// --------------------------------------------------------------------------------------------------------
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'instance',
],
},
},
options: [
{
name: 'Cancel Quiet Down',
value: 'cancelQuietDown',
description: 'Cancel quiet down state',
},
{
name: 'Quiet Down',
value: 'quietDown',
description: 'Put Jenkins in quiet mode, no builds can be started, Jenkins is ready for shutdown',
},
{
name: 'Restart',
value: 'restart',
description: 'Restart Jenkins immediately on environments where it is possible',
},
{
name: 'Safely Restart',
value: 'safeRestart',
description: 'Restart Jenkins once no jobs are running on environments where it is possible',
},
{
name: 'Safely Shutdown',
value: 'safeExit',
description: 'Shutdown once no jobs are running',
},
{
name: 'Shutdown',
value: 'exit',
description: 'Shutdown Jenkins immediately',
},
],
default: 'safeRestart',
description: 'Jenkins instance operations',
noDataExpression: true,
},
{
displayName: 'Reason',
name: 'reason',
type: 'string',
displayOptions: {
show: {
resource: [
'instance',
],
operation: [
'quietDown',
],
},
},
required: false,
default: '',
description: 'Freeform reason for quiet down mode',
},
{
displayName: 'Instance operation can shutdown Jenkins instance and make it unresponsive. Some commands may not be available depending on instance implementation.',
name: 'instanceNotice',
type: 'notice',
default: '',
displayOptions: {
show: {
resource: [
'instance',
],
},
},
},
// --------------------------------------------------------------------------------------------------------
// Builds operations
// --------------------------------------------------------------------------------------------------------
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'build',
],
},
},
options: [
{
name: 'Get All',
value: 'getAll',
description: 'List Builds',
},
],
default: 'getAll',
noDataExpression: true,
},
{
displayName: 'Job Name',
name: 'job',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getJobs',
},
displayOptions: {
show: {
resource: [
'build',
],
operation: [
'getAll',
],
},
},
required: true,
default: '',
description: 'Name of the job',
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
default: false,
displayOptions: {
show: {
resource: [
'build',
],
operation: [
'getAll',
],
},
},
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
default: 50,
typeOptions: {
minValue: 1,
},
displayOptions: {
show: {
resource: [
'build',
],
operation: [
'getAll',
],
returnAll: [
false,
],
},
},
description: 'Max number of results to return',
},
],
};
methods = {
credentialTest: {
async jenkinApiCredentialTest(
this: ICredentialTestFunctions,
credential: ICredentialsDecrypted,
): Promise<INodeCredentialTestResult> {
const { baseUrl, username, apiKey } = credential.data as JenkinsApiCredentials;
const url = tolerateTrailingSlash(baseUrl);
const endpoint = '/api/json';
const options = {
auth: {
username,
password: apiKey,
},
method: 'GET',
body: {},
qs: {},
uri: `${url}${endpoint}`,
json: true,
};
try {
await this.helpers.request(options);
return {
status: 'OK',
message: 'Authentication successful',
};
} catch (error) {
return {
status: 'Error',
message: error.message,
};
}
},
},
loadOptions: {
async getJobs(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const endpoint = `/api/json`;
const { jobs } = await jenkinsApiRequest.call(this, 'GET', endpoint);
for (const job of jobs) {
returnData.push({
name: job.name,
value: job.name,
});
}
returnData.sort((a, b) => {
if (a.name < b.name) { return -1; }
if (a.name > b.name) { return 1; }
return 0;
});
return returnData;
},
async getJobParameters(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const job = this.getCurrentNodeParameter('job') as string;
const returnData: INodePropertyOptions[] = [];
const endpoint = `/job/${job}/api/json?tree=actions[parameterDefinitions[*]]`;
const { actions } = await jenkinsApiRequest.call(this, 'GET', endpoint);
for (const { _class, parameterDefinitions } of actions) {
if (_class?.includes('ParametersDefinitionProperty')) {
for (const { name, type } of parameterDefinitions) {
returnData.push({
name: `${name} - (${type})`,
value: name,
});
}
}
}
returnData.sort((a, b) => {
if (a.name < b.name) { return -1; }
if (a.name > b.name) { return 1; }
return 0;
});
return returnData;
},
},
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: IDataObject[] = [];
const length = items.length;
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 === 'job') {
if (operation === 'trigger') {
const job = this.getNodeParameter('job', i) as string;
const endpoint = `/job/${job}/build`;
await jenkinsApiRequest.call(this, 'POST', endpoint);
responseData = { success: true };
}
if (operation === 'triggerParams') {
const job = this.getNodeParameter('job', i) as string;
const params = this.getNodeParameter('param.params', i, []) as [];
let body = {};
if (params.length) {
body = params.reduce((body: IDataObject, param: { name: string; value: string }) => {
body[param.name] = param.value;
return body;
}, {});
}
const endpoint = `/job/${job}/buildWithParameters`;
await jenkinsApiRequest.call(this, 'POST', endpoint, {}, {},
{
form: body,
headers: {
'content-type': 'application/x-www-form-urlencoded',
},
},
);
responseData = { success: true };
}
if (operation === 'copy') {
const job = this.getNodeParameter('job', i) as string;
const name = this.getNodeParameter('newJob', i) as string;
const queryParams = {
name,
mode: 'copy',
from: job,
};
const endpoint = `/createItem`;
try {
await jenkinsApiRequest.call(this, 'POST', endpoint, queryParams);
responseData = { success: true };
}
catch (error) {
if (error.httpCode === '302') {
responseData = { success: true };
} else {
throw new NodeApiError(this.getNode(), error);
}
}
}
if (operation === 'create') {
const name = this.getNodeParameter('newJob', i) as string;
const queryParams = {
name,
};
const headers = {
'content-type': 'application/xml',
};
const body = this.getNodeParameter('xml', i) as string;
const endpoint = `/createItem`;
await jenkinsApiRequest.call(this, 'POST', endpoint, queryParams, body, { headers, json: false });
responseData = { success: true };
}
}
if (resource === 'instance') {
if (operation === 'quietDown') {
const reason = this.getNodeParameter('reason', i) as string;
let queryParams;
if (reason) {
queryParams = {
reason,
};
}
const endpoint = `/quietDown`;
await jenkinsApiRequest.call(this, 'POST', endpoint, queryParams);
responseData = { success: true };
}
if (operation === 'cancelQuietDown') {
const endpoint = `/cancelQuietDown`;
await jenkinsApiRequest.call(this, 'POST', endpoint);
responseData = { success: true };
}
if (operation === 'restart') {
const endpoint = `/restart`;
try {
await jenkinsApiRequest.call(this, 'POST', endpoint);
} catch (error) {
if (error.httpCode === '503') {
responseData = { success: true };
} else {
throw new NodeApiError(this.getNode(), error);
}
}
}
if (operation === 'safeRestart') {
const endpoint = `/safeRestart`;
try {
await jenkinsApiRequest.call(this, 'POST', endpoint);
} catch (error) {
if (error.httpCode === '503') {
responseData = { success: true };
} else {
throw new NodeApiError(this.getNode(), error);
}
}
}
if (operation === 'exit') {
const endpoint = `/exit`;
await jenkinsApiRequest.call(this, 'POST', endpoint);
responseData = { success: true };
}
if (operation === 'safeExit') {
const endpoint = `/safeExit`;
await jenkinsApiRequest.call(this, 'POST', endpoint);
responseData = { success: true };
}
}
if (resource === 'build') {
if (operation === 'getAll') {
const job = this.getNodeParameter('job', i) as string;
let endpoint = `/job/${job}/api/json?tree=builds[*]`;
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
if (!returnAll) {
const limit = this.getNodeParameter('limit', i) as number;
endpoint += `{0,${limit}}`;
}
responseData = await jenkinsApiRequest.call(this, 'GET', endpoint);
responseData = responseData.builds;
}
}
if (Array.isArray(responseData)) {
returnData.push.apply(returnData, responseData as IDataObject[]);
} else {
returnData.push(responseData as IDataObject);
}
} catch (error) {
if (this.continueOnFail()) {
returnData.push({ error: error.message });
continue;
}
throw error;
}
}
return [this.helpers.returnJsonArray(returnData)];
}
}