2023-03-09 09:13:15 -08:00
|
|
|
import type {
|
|
|
|
IExecuteFunctions,
|
|
|
|
IDataObject,
|
|
|
|
ILoadOptionsFunctions,
|
|
|
|
JsonObject,
|
2024-02-14 07:29:09 -08:00
|
|
|
IRequestOptions,
|
|
|
|
IHttpRequestMethods,
|
2023-03-09 09:13:15 -08:00
|
|
|
} from 'n8n-workflow';
|
2024-04-24 01:05:53 -07:00
|
|
|
import { NodeApiError, NodeOperationError, sleep } from 'n8n-workflow';
|
2021-09-28 11:50:15 -07:00
|
|
|
|
2022-08-17 08:50:24 -07:00
|
|
|
import { parseString } from 'xml2js';
|
2021-09-28 11:50:15 -07:00
|
|
|
|
2023-01-27 03:22:44 -08:00
|
|
|
import type {
|
2021-09-28 11:50:15 -07:00
|
|
|
SplunkCredentials,
|
|
|
|
SplunkError,
|
|
|
|
SplunkFeedResponse,
|
|
|
|
SplunkResultResponse,
|
|
|
|
SplunkSearchResponse,
|
|
|
|
} from './types';
|
|
|
|
|
2023-01-13 09:11:56 -08:00
|
|
|
// ----------------------------------------
|
|
|
|
// entry formatting
|
|
|
|
// ----------------------------------------
|
|
|
|
function compactEntryContent(splunkObject: any): any {
|
|
|
|
if (typeof splunkObject !== 'object') {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Array.isArray(splunkObject)) {
|
|
|
|
return splunkObject.reduce((acc, cur) => {
|
|
|
|
acc = { ...acc, ...compactEntryContent(cur) };
|
|
|
|
return acc;
|
|
|
|
}, {});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (splunkObject['s:dict']) {
|
|
|
|
const obj = splunkObject['s:dict']['s:key'];
|
|
|
|
return { [splunkObject.$.name]: compactEntryContent(obj) };
|
|
|
|
}
|
|
|
|
|
|
|
|
if (splunkObject['s:list']) {
|
|
|
|
const items = splunkObject['s:list']['s:item'];
|
|
|
|
return { [splunkObject.$.name]: items };
|
|
|
|
}
|
|
|
|
|
|
|
|
if (splunkObject._) {
|
|
|
|
return {
|
|
|
|
[splunkObject.$.name]: splunkObject._,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
[splunkObject.$.name]: '',
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function formatEntryContent(content: any): any {
|
|
|
|
return content['s:dict']['s:key'].reduce((acc: any, cur: any) => {
|
|
|
|
acc = { ...acc, ...compactEntryContent(cur) };
|
|
|
|
return acc;
|
|
|
|
}, {});
|
|
|
|
}
|
|
|
|
|
|
|
|
function formatEntry(entry: any): any {
|
|
|
|
const { content, link, ...rest } = entry;
|
|
|
|
const formattedEntry = { ...rest, ...formatEntryContent(content) };
|
|
|
|
|
|
|
|
if (formattedEntry.id) {
|
|
|
|
formattedEntry.entryUrl = formattedEntry.id;
|
|
|
|
formattedEntry.id = formattedEntry.id.split('/').pop();
|
|
|
|
}
|
|
|
|
|
|
|
|
return formattedEntry;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------
|
|
|
|
// search formatting
|
|
|
|
// ----------------------------------------
|
|
|
|
|
|
|
|
export function formatSearch(responseData: SplunkSearchResponse) {
|
|
|
|
const { entry: entries } = responseData;
|
|
|
|
|
|
|
|
if (!entries) return [];
|
|
|
|
|
|
|
|
return Array.isArray(entries) ? entries.map(formatEntry) : [formatEntry(entries)];
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------
|
|
|
|
// utils
|
|
|
|
// ----------------------------------------
|
|
|
|
|
|
|
|
export async function parseXml(xml: string) {
|
2024-01-17 07:08:50 -08:00
|
|
|
return await new Promise((resolve, reject) => {
|
2023-01-13 09:11:56 -08:00
|
|
|
parseString(xml, { explicitArray: false }, (error, result) => {
|
|
|
|
error ? reject(error) : resolve(result);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
export function extractErrorDescription(rawError: SplunkError) {
|
|
|
|
const messages = rawError.response?.messages;
|
|
|
|
return messages ? { [messages.msg.$.type.toLowerCase()]: messages.msg._ } : rawError;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function toUnixEpoch(timestamp: string) {
|
|
|
|
return Date.parse(timestamp) / 1000;
|
|
|
|
}
|
|
|
|
|
2021-09-28 11:50:15 -07:00
|
|
|
export async function splunkApiRequest(
|
|
|
|
this: IExecuteFunctions | ILoadOptionsFunctions,
|
2024-02-14 07:29:09 -08:00
|
|
|
method: IHttpRequestMethods,
|
2021-09-28 11:50:15 -07:00
|
|
|
endpoint: string,
|
|
|
|
body: IDataObject = {},
|
|
|
|
qs: IDataObject = {},
|
2022-12-23 09:27:07 -08:00
|
|
|
): Promise<any> {
|
2022-08-17 08:50:24 -07:00
|
|
|
const { authToken, baseUrl, allowUnauthorizedCerts } = (await this.getCredentials(
|
|
|
|
'splunkApi',
|
|
|
|
)) as SplunkCredentials;
|
2021-09-28 11:50:15 -07:00
|
|
|
|
2024-02-14 07:29:09 -08:00
|
|
|
const options: IRequestOptions = {
|
2021-09-28 11:50:15 -07:00
|
|
|
headers: {
|
2022-08-17 08:50:24 -07:00
|
|
|
Authorization: `Bearer ${authToken}`,
|
2021-09-28 11:50:15 -07:00
|
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
|
|
},
|
|
|
|
method,
|
|
|
|
form: body,
|
|
|
|
qs,
|
|
|
|
uri: `${baseUrl}${endpoint}`,
|
|
|
|
json: true,
|
|
|
|
rejectUnauthorized: !allowUnauthorizedCerts,
|
|
|
|
useQuerystring: true, // serialize roles array as `roles=A&roles=B`
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!Object.keys(body).length) {
|
|
|
|
delete options.body;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Object.keys(qs).length) {
|
|
|
|
delete options.qs;
|
|
|
|
}
|
|
|
|
|
2024-04-24 01:05:53 -07:00
|
|
|
let result;
|
2021-09-28 11:50:15 -07:00
|
|
|
try {
|
2024-04-24 01:05:53 -07:00
|
|
|
let attempts = 0;
|
|
|
|
|
|
|
|
do {
|
|
|
|
try {
|
|
|
|
const response = await this.helpers.request(options);
|
|
|
|
result = await parseXml(response);
|
|
|
|
return result;
|
|
|
|
} catch (error) {
|
|
|
|
if (attempts >= 5) {
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
await sleep(1000);
|
|
|
|
attempts++;
|
|
|
|
}
|
|
|
|
} while (true);
|
2021-09-28 11:50:15 -07:00
|
|
|
} catch (error) {
|
2024-04-24 01:05:53 -07:00
|
|
|
if (result === undefined) {
|
|
|
|
throw new NodeOperationError(this.getNode(), 'No response from API call', {
|
|
|
|
description: "Try to use 'Retry On Fail' option from node's settings",
|
|
|
|
});
|
|
|
|
}
|
2021-09-28 11:50:15 -07:00
|
|
|
if (error?.cause?.code === 'ECONNREFUSED') {
|
2023-02-27 19:39:43 -08:00
|
|
|
throw new NodeApiError(this.getNode(), { ...(error as JsonObject), code: 401 });
|
2021-09-28 11:50:15 -07:00
|
|
|
}
|
|
|
|
|
2023-02-27 19:39:43 -08:00
|
|
|
const rawError = (await parseXml(error.error as string)) as SplunkError;
|
2021-09-28 11:50:15 -07:00
|
|
|
error = extractErrorDescription(rawError);
|
|
|
|
|
|
|
|
if ('fatal' in error) {
|
|
|
|
error = { error: error.fatal };
|
|
|
|
}
|
|
|
|
|
2023-02-27 19:39:43 -08:00
|
|
|
throw new NodeApiError(this.getNode(), error as JsonObject);
|
2021-09-28 11:50:15 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------
|
|
|
|
// feed formatting
|
|
|
|
// ----------------------------------------
|
|
|
|
|
|
|
|
export function formatFeed(responseData: SplunkFeedResponse) {
|
|
|
|
const { entry: entries } = responseData.feed;
|
|
|
|
|
|
|
|
if (!entries) return [];
|
|
|
|
|
2022-08-17 08:50:24 -07:00
|
|
|
return Array.isArray(entries) ? entries.map(formatEntry) : [formatEntry(entries)];
|
2021-09-28 11:50:15 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------
|
|
|
|
// result formatting
|
|
|
|
// ----------------------------------------
|
|
|
|
function compactResult(splunkObject: any): any {
|
|
|
|
if (typeof splunkObject !== 'object') {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2022-08-17 08:50:24 -07:00
|
|
|
if (Array.isArray(splunkObject?.value) && splunkObject?.value[0]?.text) {
|
2021-09-28 11:50:15 -07:00
|
|
|
return {
|
2022-08-17 08:50:24 -07:00
|
|
|
[splunkObject.$.k]: splunkObject.value.map((v: { text: string }) => v.text).join(','),
|
2021-09-28 11:50:15 -07:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!splunkObject?.$?.k || !splunkObject?.value?.text) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
[splunkObject.$.k]: splunkObject.value.text,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-01-13 09:11:56 -08:00
|
|
|
function formatResult(field: any): any {
|
|
|
|
return field.reduce((acc: any, cur: any) => {
|
|
|
|
acc = { ...acc, ...compactResult(cur) };
|
2021-09-28 11:50:15 -07:00
|
|
|
return acc;
|
|
|
|
}, {});
|
|
|
|
}
|
|
|
|
|
2023-01-13 09:11:56 -08:00
|
|
|
export function formatResults(responseData: SplunkResultResponse) {
|
|
|
|
const results = responseData.results.result;
|
|
|
|
if (!results) return [];
|
2021-09-28 11:50:15 -07:00
|
|
|
|
2023-01-13 09:11:56 -08:00
|
|
|
return Array.isArray(results)
|
|
|
|
? results.map((r) => formatResult(r.field))
|
|
|
|
: [formatResult(results.field)];
|
2021-09-28 11:50:15 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------
|
|
|
|
// param loaders
|
|
|
|
// ----------------------------------------
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set count of entries to retrieve.
|
|
|
|
*/
|
|
|
|
export function setCount(this: IExecuteFunctions, qs: IDataObject) {
|
2022-11-18 06:26:22 -08:00
|
|
|
qs.count = this.getNodeParameter('returnAll', 0) ? 0 : this.getNodeParameter('limit', 0);
|
2021-09-28 11:50:15 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
export function populate(source: IDataObject, destination: IDataObject) {
|
|
|
|
if (Object.keys(source).length) {
|
|
|
|
Object.assign(destination, source);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve an ID, with tolerance when contained in an endpoint.
|
|
|
|
* The field `id` in Splunk API responses is a full link.
|
|
|
|
*/
|
|
|
|
export function getId(
|
|
|
|
this: IExecuteFunctions,
|
|
|
|
i: number,
|
|
|
|
idType: 'userId' | 'searchJobId' | 'searchConfigurationId',
|
|
|
|
endpoint: string,
|
|
|
|
) {
|
|
|
|
const id = this.getNodeParameter(idType, i) as string;
|
|
|
|
|
2022-08-17 08:50:24 -07:00
|
|
|
return id.includes(endpoint) ? id.split(endpoint).pop()! : id;
|
2021-09-28 11:50:15 -07:00
|
|
|
}
|