From 926bafce46613fb6b046d7706231dbc148e30b45 Mon Sep 17 00:00:00 2001 From: Richard Hagen Date: Fri, 19 Nov 2021 09:20:03 +0100 Subject: [PATCH] include script to load all ingresses --- extra/map-ingress.js | 165 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 extra/map-ingress.js diff --git a/extra/map-ingress.js b/extra/map-ingress.js new file mode 100644 index 000000000..ac3aab13c --- /dev/null +++ b/extra/map-ingress.js @@ -0,0 +1,165 @@ +const { debug } = require("../src/util"); +const fs = require("fs"); +const axios = require("axios"); +const https = require("https"); +const args = require("args-parser")(process.argv); +let kubernetes = process.env.KUBERNETES_MASTER || args.kubernetes || "https://kubernetes.default"; +let ingressAnnotation = "uptime-kuma.ingress.kubernetes.io/monitor"; +let importAllIngresses = process.env.IMPORT_ALL_INGRESSES || args.importAllIngresses || false; +let ingressNamespaces = process.env.INGRESS_NAMESPACES || args.ingressNamespaces || ""; +let defaultInterval = process.env.MONITOR_INTERVAL || args.monitorInterval || "60"; +let kubernetesTokenFile = process.env.KUBERNETES_TOKEN_FILE || args.kubernetesTokenFile || "/run/secrets/kubernetes.io/serviceaccount/token"; +let kubernetesToken = process.env.KUBERNETES_TOKEN || args.kubernetesToken || null; +let ownerId = process.env.OWNER_ID || args.ownerId || 1; + +debug("Importing Database"); +const Database = require("../server/database"); +const { R } = require("redbean-node"); + +const findIngresses = async () => { + debug("Connecting to " + kubernetes + " to collect ingresses"); + if (!kubernetesToken) { + kubernetesToken = fs.readFileSync(kubernetesTokenFile).toString().replace("\n", ""); + } + + const promises = []; + + const config = { + httpsAgent: new https.Agent({ + rejectUnauthorized: false, + }), + headers: { + Authorization: "Bearer " + kubernetesToken, + Accept: "application/json", + }, + }; + let namespaces = ingressNamespaces.split(",").filter(ns => ns !== ""); + if (namespaces.length === 0) { + promises.push(axios.get(kubernetes + "/apis/networking.k8s.io/v1/ingresses", config).then(res => res.data)); + } else { + namespaces.forEach(ns => { + promises.push(axios.get(kubernetes + "/apis/networking.k8s.io/v1/namespaces/" + ns + "/ingresses", config).then(res => res.data)); + }); + } + + return Promise.all(promises).then(ingressLists => { + return ingressLists.reduce((ingresses, ingressList) => { + return ingresses.concat(ingressList.items.filter(i => { + return importAllIngresses || !!i.metadata.annotations[ingressAnnotation]; + })); + }, []); + }); +}; +const addMissingIngressesToDatabase = async (ingresses) => { + let monitorList = await R.find("monitor"); + + const domains = ingresses.reduce((domains, ingress) => { + const namespace = ingress.metadata.namespace; + + // Check TLS hosts and routes + if (ingress.spec.tls) { + ingress.spec.tls.forEach(tls => { + tls.hosts.forEach(host => { + if (!host) { + return; + } + + const domain = host.toLowerCase(); + if (host && !domains.find(d => d.domain === domain)) { + domains.push({ domain, + namespace }); + } + }); + }); + } + + if (ingress.spec.rules) { + ingress.spec.rules.forEach(rule => { + if (!rule.host) { + return; + } + + const domain = rule.host.toLowerCase(); + if ( !domains.find(d => d.domain === domain)) { + domains.push({ domain, + namespace }); + } + }); + } + + return domains; + }, []); + debug("Found " + domains.length + " domains"); + + const missingDomains = domains.filter(domain => !monitorList.find(monitor => monitor.url.toLowerCase() === "https://" + domain.domain)); + debug("Found " + missingDomains.length + " missing monitors"); + + for (let domain of missingDomains) { + const monitor = await addMonitor(domain.domain); + await addMonitorToGroup(monitor, "All Ingresses"); + await addMonitorToGroup(monitor, domain.namespace); + } +}; + +const addMonitorToGroup = async (monitor, groupName) => { + let group = await R.findOne("group", "name = ?", [groupName]); + if (!group) { + group = await R.dispense("group"); + group.name = groupName; + group.public = 1; + await R.store(group); + } + + let monitorGroup = await R.findOne("monitor_group", "group_id = ? and monitor_id = ?", [group.id, monitor.id]); + if (monitorGroup) { + return monitorGroup; + } + + let list = await group.getMonitorList(); + monitorGroup = await R.dispense("monitor_group"); + monitorGroup.group_id = group.id; + monitorGroup.monitor_id = monitor.id; + monitorGroup.weight = list.length + 1; + await R.store(monitorGroup); + + return monitorGroup; +}; + +const addMonitor = async (domain) => { + let bean = R.dispense("monitor"); + + const monitor = { + "type": "http", + "name": domain, + "url": "https://" + domain, + "method": "GET", + "interval": defaultInterval, + "retryInterval": defaultInterval, + "maxretries": 0, + "ignoreTls": false, + "upsideDown": false, + "maxredirects": 10, + "accepted_statuscodes_json": "[\"200-299\"]", + "dns_resolve_type": "A", + "dns_resolve_server": "1.1.1.1", + "user_id": ownerId, + }; + + bean.import(monitor); + await R.store(bean); + return bean; +}; + +const main = async () => { + Database.init(args); + await Database.connect(); + + const ingresses = await findIngresses(); + debug("found " + ingresses.length + " ingresses!"); + + await addMissingIngressesToDatabase(ingresses); +}; +main().then(() => { + debug("Finished"); + process.exit(0); +});