Skip to content

Instantly share code, notes, and snippets.

@pmuellr
Last active September 29, 2021 20:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pmuellr/17a2b9c88a1e0bdf39fbafc7c1e8130a to your computer and use it in GitHub Desktop.
Save pmuellr/17a2b9c88a1e0bdf39fbafc7c1e8130a to your computer and use it in GitHub Desktop.
A cli filter which reads a Kibana export file from stdin, and writes a Kibana export file to stdout. The export is changed to remove all alerting connectors, add a new server log connector, and change all referenced actions in alerting rules to the server log. This renders an export of Kibana alerting rules to the same rules which do not perform…
#!/usr/bin/env node
'use strict'
/*
Filter for Elastic Kibana .ndjson files with alerting rule exports,
that converts all the rule's actions to a new server log connector.
node export-to-server-log.js < original-export.ndjson > modified-export.ndjson
*/
const path = require('path')
const DEBUG = !!process.env.DEBUG
const argv = process.argv.slice(2)
if (argv.length !== 0) help()
const fs = require("fs")
const contents = fs.readFileSync(0, { encoding: 'utf8' }).toString('utf8')
debugLog('read stdin bytes: ', contents.length)
const lines = contents.trim().split(/\n/g)
debugLog('read stdin lines: ', lines.length)
const imports = lines.map(line => toJSON(line))
const summary = imports.pop()
const { excludedObjectsCount, exportedCount, missingRefCount } = summary
ensureSummaryOK(excludedObjectsCount, exportedCount, missingRefCount)
debugLog(`summary: `, summary)
imports.forEach((object) => ensureImportObject(object))
const baseImports = imports.filter(({ type }) => type !== 'action')
const date = new Date().toISOString()
const serverLogId = 'a3676547-ab0a-4710-830a-f93b09006b66'
const version = '7.14.0'
const serverLogConnector = {
attributes: {
actionTypeId: '.server-log',
isMissingSecrets: false,
name: `server log created for export-to-server-log on ${date}`
},
coreMigrationVersion: version,
id: serverLogId,
migrationVersion: {
action: version
},
references: [],
type: 'action',
updated_at: date,
version: 'WzQyLDQyXQo=' // echo '[42,42]' | base64
}
const serverLogAction = {
actionRef: 'action_0',
actionTypeId: '.server-log',
group: 'fill-in-the-blank',
params: {
message: 'fill-in-the-blank',
}
}
const finalSummary = {
excludedObjects: [],
excludedObjectsCount: 0,
exportedCount: baseImports.length + 1,
missingRefCount:0,
missingReferences:[]
}
const fixedImports = baseImports.map(object => fixImport(object))
console.log(JSON.stringify(serverLogConnector))
fixedImports.forEach(object => console.log(JSON.stringify(object)))
console.log(JSON.stringify(finalSummary))
/** @type { (object: any) => void */
function fixImport(object) {
const { type } = object
if (type !== 'alert') return object
const { id, attributes, references } = object
const { name, actions } = attributes
if (actions == null || actions.length === 0) return object
object.attributes.actions = actions.map(action => {
const newAction = JSON.parse(JSON.stringify(serverLogAction))
newAction.group = action.group
newAction.params.message = `export-to-server-log replacement of action ${action.actionTypeId} ${action.group} for rule "${name}" (${id}) in space "{{rule.spaceId}}"`
return newAction
})
object.references = [{
id: serverLogId,
name: 'action_0',
type: 'action'
}]
return object
}
/** @type { (object: any) => void */
function ensureImportObject(object) {
const { attributes, id, type } = object
if (typeof attributes !== 'object') logAndExit('attributes expected to be an object in ', object)
if (typeof id !== 'string') logAndExit('id expected to be a string in ', object)
if (typeof type !== 'string') logAndExit('type expected to be a string in ', object)
}
/** @type { (excludedObjectsCount: number, exportedCount: number, missingRefCount: number) => void */
function ensureSummaryOK(excludedObjectsCount, exportedCount, missingRefCount) {
const prefix = 'error in final summary line:'
ensureZero(`${prefix}: excludedObjectsCount`, excludedObjectsCount)
ensureZero(`${prefix}: missingRefCount`, missingRefCount)
ensureNotZero(`${prefix}: exportedCount`, exportedCount)
}
/** @type { (message: string, value: any) => void */
function ensureZero(message, value) {
if (typeof value !== 'number') {
logAndExit(message, ' is not a number:', value)
}
if (value !== 0) {
logAndExit(message, ' is not zero:', value)
}
}
/** @type { (message: string, value: any) => void */
function ensureNotZero(message, value) {
if (typeof value !== 'number') {
logAndExit(message, ' is not a number:', value)
}
if (value == 0) {
logAndExit(message, ' is zero:', value)
}
}
/** @type { (string: string) => any */
function toJSON(string) {
try {
return JSON.parse(string)
} catch (err) {
logAndExit(`error parsing JSON line: |${string}|`)
}
}
/**
* @param {...any} parts
*/
function debugLog(...parts) {
if (!DEBUG) return
console.error(toMessage(parts))
}
/** @type { () => void */
function help() {
const program = path.basename(process.argv[1])
logAndExit(`${program}:
A cli filter which reads a Kibana export file from stdin, and writes a Kibana
export file to stdout. The export is changed to remove all alerting connectors,
add a new server log connector, and change all referenced actions in alerting
rules to the server log. This renders an export of Kibana alerting rules to
the same rules which do not perform their usual actions, but log to the server
instead. You know, for testing.
`.trim())
}
/**
* @param {...any} parts
*/
function logAndExit(...parts) {
console.error(toMessage(parts))
process.exit(1)
}
/** @type { (parts: any[]) => string */
function toMessage(parts) {
const stringParts = parts.map(part => {
if (typeof part === 'string') {
return part
} else {
try {
return JSON.stringify(part)
} catch (err) {
return `${part}`
}
}
})
return stringParts.join('')
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment