view mod_pubsub_alertmanager/mod_pubsub_alertmanager.lua @ 6029:cc665f343690

mod_firewall: SUBSCRIBED: Flip subscription check to match documentation The documentation claims that this condition checks whether the recipient is subscribed to the sender. However, it was using the wrong method, and actually checking whether the sender was subscribed to the recipient. A quick poll of folk suggested that the documentation's approach is the right one, so this should fix the code to match the documentation. This should also fix the bundled anti-spam rules from blocking presence from JIDs that you subscribe do (but don't have a mutual subscription with).
author Matthew Wild <mwild1@gmail.com>
date Fri, 22 Nov 2024 13:50:48 +0000
parents 67190744b1eb
children
line wrap: on
line source

local st = require "util.stanza";
local json = require "util.json";
local filters = { --[[ TODO what's useful? ]] };
local render = require "util.interpolation".new("%b{}", tostring, filters);
local uuid_generate = require "util.uuid".generate;

-- TODO alertmanager supports inclusion of HTTP auth and OAuth, worth looking
-- into for using instead of request IP

module:depends("http");

local pubsub_service = module:depends("pubsub").service;

local error_mapping = {
	["forbidden"] = 403;
	["item-not-found"] = 404;
	["internal-server-error"] = 500;
	["conflict"] = 409;
};

local function publish_payload(node, actor, item_id, payload)
	local post_item = st.stanza("item", { xmlns = "http://jabber.org/protocol/pubsub", id = item_id, })
		:add_child(payload);
	local ok, err = pubsub_service:publish(node, actor, item_id, post_item);
	module:log("debug", ":publish(%q, true, %q, %s) -> %q", node, item_id, payload:top_tag(), err or "");
	if not ok then
		return error_mapping[err] or 500;
	end
	return 202;
end

local global_node_template = module:get_option_string("alertmanager_node_template", "{path?alerts}");
local path_configs = module:get_option("alertmanager_path_configs", {});

function handle_POST(event, path)
	local request = event.request;

	local config = path_configs[path] or {};
	local node_template = config.node_template or global_node_template;
	local publisher = config.publisher or request.ip;

	local payload = json.decode(event.request.body);
	if type(payload) ~= "table" then return 400; end
	if payload.version ~= "4" then return 501; end

	for _, alert in ipairs(payload.alerts) do
		local item = st.stanza("alerts", {xmlns = "urn:uuid:e3bec775-c607-4e9b-9a3f-94de1316d861:v4", status=alert.status});
		for k, v in pairs(alert.annotations) do
			item:text_tag("annotation", v, { name=k });
		end
		for k, v in pairs(alert.labels) do
			item:text_tag("label", v, { name=k });
		end
		item:tag("starts", { at = alert.startsAt}):up();
		if alert.endsAt and alert.status == "resolved" then
			item:tag("ends", { at = alert.endsAt }):up();
		end
		if alert.generatorURL then
			item:tag("link", { href=alert.generatorURL }):up();
		end

		local node = render(node_template, {alert = alert, path = path, payload = payload, request = request});
		local ret = publish_payload(node, publisher, uuid_generate(), item);
		if ret ~= 202 then
			return ret
		end
	end
	return 202;
end

local template = module:get_option_string("alertmanager_body_template", [[
*ALARM!*
Status: {status}
Starts at: {startsAt}{endsAt&
Ends at: {endsAt}}
Labels: {labels%
  {idx}: {item}}
Annotations: {annotations%
  {idx}: {item}}
]]);

module:hook("pubsub-summary/urn:uuid:e3bec775-c607-4e9b-9a3f-94de1316d861:v4", function(event)
	local payload = event.payload;

	local data = {
		status = payload.attr.status,
		firing = "firing" == payload.attr.status,
		resolved = "resolved" == payload.attr.status,
		annotations = {},
		labels = {},
		endsAt = payload:find("ends/@at"),
		startsAt = payload:find("starts/@at"),
	};
	for label in payload:childtags("label") do
		data.labels[tostring(label.attr.name)] = label:get_text();
	end
	for annotation in payload:childtags("annotation") do
		data.annotations[tostring(annotation.attr.name)] = annotation:get_text();
	end

	return render(template, data);
end);

module:provides("http", {
	route = {
		["POST /*"] = handle_POST;
		["POST"] = handle_POST;
	};
});