mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-12-05 01:10:52 +00:00
Extracted the postgresql monitor to its own monitor-type (#6443)
Co-authored-by: Dalton Pearson <dalton.pearson@praemo.com> Co-authored-by: Frank Elsinga <frank@elsinga.de>
This commit is contained in:
64
package-lock.json
generated
64
package-lock.json
generated
@@ -98,6 +98,7 @@
|
|||||||
"@playwright/test": "~1.39.0",
|
"@playwright/test": "~1.39.0",
|
||||||
"@popperjs/core": "~2.10.2",
|
"@popperjs/core": "~2.10.2",
|
||||||
"@testcontainers/hivemq": "^10.13.1",
|
"@testcontainers/hivemq": "^10.13.1",
|
||||||
|
"@testcontainers/postgresql": "^11.9.0",
|
||||||
"@testcontainers/rabbitmq": "^10.13.2",
|
"@testcontainers/rabbitmq": "^10.13.2",
|
||||||
"@types/bootstrap": "~5.1.9",
|
"@types/bootstrap": "~5.1.9",
|
||||||
"@types/node": "^20.8.6",
|
"@types/node": "^20.8.6",
|
||||||
@@ -5680,6 +5681,63 @@
|
|||||||
"testcontainers": "^10.28.0"
|
"testcontainers": "^10.28.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@testcontainers/postgresql": {
|
||||||
|
"version": "11.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@testcontainers/postgresql/-/postgresql-11.9.0.tgz",
|
||||||
|
"integrity": "sha512-beLyLdLygFllktviM132Xd6tQ4i5FnuyZP+4BQEjUb5sJYHYnIrV/ZBzRRflIlF8gugt1GXgudkmr/HxM9vtKw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"testcontainers": "^11.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@testcontainers/postgresql/node_modules/docker-compose": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-7Gevk/5eGD50+eMD+XDnFnOrruFkL0kSd7jEG4cjmqweDSUhB7i0g8is/nBdVpl+Bx338SqIB2GLKm32M+Vs6g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"yaml": "^2.2.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@testcontainers/postgresql/node_modules/testcontainers": {
|
||||||
|
"version": "11.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-11.9.0.tgz",
|
||||||
|
"integrity": "sha512-SQ6OqQUig7HcGVF72i+ZVIMvxPSpEz8cgC/B63ekqMzgf98DnveoBbOmqux/Wa5wQAQCt4mEPNMa/Jz7vMg9fQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@balena/dockerignore": "^1.0.2",
|
||||||
|
"@types/dockerode": "^3.3.47",
|
||||||
|
"archiver": "^7.0.1",
|
||||||
|
"async-lock": "^1.4.1",
|
||||||
|
"byline": "^5.0.0",
|
||||||
|
"debug": "^4.4.3",
|
||||||
|
"docker-compose": "^1.3.0",
|
||||||
|
"dockerode": "^4.0.9",
|
||||||
|
"get-port": "^7.1.0",
|
||||||
|
"proper-lockfile": "^4.1.2",
|
||||||
|
"properties-reader": "^2.3.0",
|
||||||
|
"ssh-remote-port-forward": "^1.0.4",
|
||||||
|
"tar-fs": "^3.1.1",
|
||||||
|
"tmp": "^0.2.5",
|
||||||
|
"undici": "^7.16.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@testcontainers/postgresql/node_modules/undici": {
|
||||||
|
"version": "7.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici/-/undici-7.16.0.tgz",
|
||||||
|
"integrity": "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.18.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@testcontainers/rabbitmq": {
|
"node_modules/@testcontainers/rabbitmq": {
|
||||||
"version": "10.28.0",
|
"version": "10.28.0",
|
||||||
"resolved": "https://registry.npmjs.org/@testcontainers/rabbitmq/-/rabbitmq-10.28.0.tgz",
|
"resolved": "https://registry.npmjs.org/@testcontainers/rabbitmq/-/rabbitmq-10.28.0.tgz",
|
||||||
@@ -5789,9 +5847,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/dockerode": {
|
"node_modules/@types/dockerode": {
|
||||||
"version": "3.3.44",
|
"version": "3.3.47",
|
||||||
"resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.44.tgz",
|
"resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.47.tgz",
|
||||||
"integrity": "sha512-fUpIHlsbYpxAJb285xx3vp7q5wf5mjqSn3cYwl/MhiM+DB99OdO5sOCPlO0PjO+TyOtphPs7tMVLU/RtOo/JjA==",
|
"integrity": "sha512-ShM1mz7rCjdssXt7Xz0u1/R2BJC7piWa3SJpUBiVjCf2A3XNn4cP6pUVaD8bLanpPVVn4IKzJuw3dOvkJ8IbYw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -121,6 +121,7 @@
|
|||||||
"nanoid": "~3.3.4",
|
"nanoid": "~3.3.4",
|
||||||
"net-snmp": "^3.11.2",
|
"net-snmp": "^3.11.2",
|
||||||
"node-cloudflared-tunnel": "~1.0.9",
|
"node-cloudflared-tunnel": "~1.0.9",
|
||||||
|
"node-radius-utils": "~1.2.0",
|
||||||
"nodemailer": "~6.9.13",
|
"nodemailer": "~6.9.13",
|
||||||
"nostr-tools": "^2.10.4",
|
"nostr-tools": "^2.10.4",
|
||||||
"notp": "~2.0.3",
|
"notp": "~2.0.3",
|
||||||
@@ -135,7 +136,6 @@
|
|||||||
"protobufjs": "~7.2.4",
|
"protobufjs": "~7.2.4",
|
||||||
"qs": "~6.10.4",
|
"qs": "~6.10.4",
|
||||||
"radius": "~1.1.4",
|
"radius": "~1.1.4",
|
||||||
"node-radius-utils": "~1.2.0",
|
|
||||||
"redbean-node": "~0.3.0",
|
"redbean-node": "~0.3.0",
|
||||||
"redis": "~5.9.0",
|
"redis": "~5.9.0",
|
||||||
"semver": "~7.5.4",
|
"semver": "~7.5.4",
|
||||||
@@ -159,6 +159,7 @@
|
|||||||
"@playwright/test": "~1.39.0",
|
"@playwright/test": "~1.39.0",
|
||||||
"@popperjs/core": "~2.10.2",
|
"@popperjs/core": "~2.10.2",
|
||||||
"@testcontainers/hivemq": "^10.13.1",
|
"@testcontainers/hivemq": "^10.13.1",
|
||||||
|
"@testcontainers/postgresql": "^11.9.0",
|
||||||
"@testcontainers/rabbitmq": "^10.13.2",
|
"@testcontainers/rabbitmq": "^10.13.2",
|
||||||
"@types/bootstrap": "~5.1.9",
|
"@types/bootstrap": "~5.1.9",
|
||||||
"@types/node": "^20.8.6",
|
"@types/node": "^20.8.6",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const { log, UP, DOWN, PENDING, MAINTENANCE, flipStatus, MAX_INTERVAL_SECOND, MI
|
|||||||
PING_COUNT_MIN, PING_COUNT_MAX, PING_COUNT_DEFAULT,
|
PING_COUNT_MIN, PING_COUNT_MAX, PING_COUNT_DEFAULT,
|
||||||
PING_PER_REQUEST_TIMEOUT_MIN, PING_PER_REQUEST_TIMEOUT_MAX, PING_PER_REQUEST_TIMEOUT_DEFAULT
|
PING_PER_REQUEST_TIMEOUT_MIN, PING_PER_REQUEST_TIMEOUT_MAX, PING_PER_REQUEST_TIMEOUT_DEFAULT
|
||||||
} = require("../../src/util");
|
} = require("../../src/util");
|
||||||
const { ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mysqlQuery, setSetting, httpNtlm, radius,
|
const { ping, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, mysqlQuery, setSetting, httpNtlm, radius,
|
||||||
kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal, checkCertificateHostname
|
kafkaProducerAsync, getOidcTokenClientCredentials, rootCertificatesFingerprints, axiosAbortSignal, checkCertificateHostname
|
||||||
} = require("../util-server");
|
} = require("../util-server");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
@@ -783,14 +783,6 @@ class Monitor extends BeanModel {
|
|||||||
|
|
||||||
await mssqlQuery(this.databaseConnectionString, this.databaseQuery || "SELECT 1");
|
await mssqlQuery(this.databaseConnectionString, this.databaseQuery || "SELECT 1");
|
||||||
|
|
||||||
bean.msg = "";
|
|
||||||
bean.status = UP;
|
|
||||||
bean.ping = dayjs().valueOf() - startTime;
|
|
||||||
} else if (this.type === "postgres") {
|
|
||||||
let startTime = dayjs().valueOf();
|
|
||||||
|
|
||||||
await postgresQuery(this.databaseConnectionString, this.databaseQuery || "SELECT 1");
|
|
||||||
|
|
||||||
bean.msg = "";
|
bean.msg = "";
|
||||||
bean.status = UP;
|
bean.status = UP;
|
||||||
bean.ping = dayjs().valueOf() - startTime;
|
bean.ping = dayjs().valueOf() - startTime;
|
||||||
|
|||||||
83
server/monitor-types/postgres.js
Normal file
83
server/monitor-types/postgres.js
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
const { MonitorType } = require("./monitor-type");
|
||||||
|
const { log, UP } = require("../../src/util");
|
||||||
|
const dayjs = require("dayjs");
|
||||||
|
const postgresConParse = require("pg-connection-string").parse;
|
||||||
|
const { Client } = require("pg");
|
||||||
|
|
||||||
|
class PostgresMonitorType extends MonitorType {
|
||||||
|
name = "postgres";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async check(monitor, heartbeat, _server) {
|
||||||
|
let startTime = dayjs().valueOf();
|
||||||
|
|
||||||
|
let query = monitor.databaseQuery;
|
||||||
|
// No query provided by user, use SELECT 1
|
||||||
|
if (!query || (typeof query === "string" && query.trim() === "")) {
|
||||||
|
query = "SELECT 1";
|
||||||
|
}
|
||||||
|
await this.postgresQuery(monitor.databaseConnectionString, query);
|
||||||
|
|
||||||
|
heartbeat.msg = "";
|
||||||
|
heartbeat.status = UP;
|
||||||
|
heartbeat.ping = dayjs().valueOf() - startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a query on Postgres
|
||||||
|
* @param {string} connectionString The database connection string
|
||||||
|
* @param {string} query The query to validate the database with
|
||||||
|
* @returns {Promise<(string[] | object[] | object)>} Response from
|
||||||
|
* server
|
||||||
|
*/
|
||||||
|
async postgresQuery(connectionString, query) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const config = postgresConParse(connectionString);
|
||||||
|
|
||||||
|
// Fix #3868, which true/false is not parsed to boolean
|
||||||
|
if (typeof config.ssl === "string") {
|
||||||
|
config.ssl = config.ssl === "true";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.password === "") {
|
||||||
|
// See https://github.com/brianc/node-postgres/issues/1927
|
||||||
|
reject(new Error("Password is undefined."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const client = new Client(config);
|
||||||
|
|
||||||
|
client.on("error", (error) => {
|
||||||
|
log.debug("postgres", "Error caught in the error event handler.");
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.connect((err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
client.end();
|
||||||
|
} else {
|
||||||
|
// Connected here
|
||||||
|
try {
|
||||||
|
client.query(query, (err, res) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(res);
|
||||||
|
}
|
||||||
|
client.end();
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
client.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
PostgresMonitorType,
|
||||||
|
};
|
||||||
@@ -113,6 +113,7 @@ class UptimeKumaServer {
|
|||||||
UptimeKumaServer.monitorTypeList["tailscale-ping"] = new TailscalePing();
|
UptimeKumaServer.monitorTypeList["tailscale-ping"] = new TailscalePing();
|
||||||
UptimeKumaServer.monitorTypeList["websocket-upgrade"] = new WebSocketMonitorType();
|
UptimeKumaServer.monitorTypeList["websocket-upgrade"] = new WebSocketMonitorType();
|
||||||
UptimeKumaServer.monitorTypeList["dns"] = new DnsMonitorType();
|
UptimeKumaServer.monitorTypeList["dns"] = new DnsMonitorType();
|
||||||
|
UptimeKumaServer.monitorTypeList["postgres"] = new PostgresMonitorType();
|
||||||
UptimeKumaServer.monitorTypeList["mqtt"] = new MqttMonitorType();
|
UptimeKumaServer.monitorTypeList["mqtt"] = new MqttMonitorType();
|
||||||
UptimeKumaServer.monitorTypeList["smtp"] = new SMTPMonitorType();
|
UptimeKumaServer.monitorTypeList["smtp"] = new SMTPMonitorType();
|
||||||
UptimeKumaServer.monitorTypeList["group"] = new GroupMonitorType();
|
UptimeKumaServer.monitorTypeList["group"] = new GroupMonitorType();
|
||||||
@@ -558,6 +559,7 @@ const { RealBrowserMonitorType } = require("./monitor-types/real-browser-monitor
|
|||||||
const { TailscalePing } = require("./monitor-types/tailscale-ping");
|
const { TailscalePing } = require("./monitor-types/tailscale-ping");
|
||||||
const { WebSocketMonitorType } = require("./monitor-types/websocket-upgrade");
|
const { WebSocketMonitorType } = require("./monitor-types/websocket-upgrade");
|
||||||
const { DnsMonitorType } = require("./monitor-types/dns");
|
const { DnsMonitorType } = require("./monitor-types/dns");
|
||||||
|
const { PostgresMonitorType } = require("./monitor-types/postgres");
|
||||||
const { MqttMonitorType } = require("./monitor-types/mqtt");
|
const { MqttMonitorType } = require("./monitor-types/mqtt");
|
||||||
const { SMTPMonitorType } = require("./monitor-types/smtp");
|
const { SMTPMonitorType } = require("./monitor-types/smtp");
|
||||||
const { GroupMonitorType } = require("./monitor-types/group");
|
const { GroupMonitorType } = require("./monitor-types/group");
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ const iconv = require("iconv-lite");
|
|||||||
const chardet = require("chardet");
|
const chardet = require("chardet");
|
||||||
const chroma = require("chroma-js");
|
const chroma = require("chroma-js");
|
||||||
const mssql = require("mssql");
|
const mssql = require("mssql");
|
||||||
const { Client } = require("pg");
|
|
||||||
const postgresConParse = require("pg-connection-string").parse;
|
|
||||||
const mysql = require("mysql2");
|
const mysql = require("mysql2");
|
||||||
const { NtlmClient } = require("./modules/axios-ntlm/lib/ntlmClient.js");
|
const { NtlmClient } = require("./modules/axios-ntlm/lib/ntlmClient.js");
|
||||||
const { Settings } = require("./settings");
|
const { Settings } = require("./settings");
|
||||||
@@ -349,64 +347,6 @@ exports.mssqlQuery = async function (connectionString, query) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Run a query on Postgres
|
|
||||||
* @param {string} connectionString The database connection string
|
|
||||||
* @param {string} query The query to validate the database with
|
|
||||||
* @returns {Promise<(string[] | object[] | object)>} Response from
|
|
||||||
* server
|
|
||||||
*/
|
|
||||||
exports.postgresQuery = function (connectionString, query) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const config = postgresConParse(connectionString);
|
|
||||||
|
|
||||||
// Fix #3868, which true/false is not parsed to boolean
|
|
||||||
if (typeof config.ssl === "string") {
|
|
||||||
config.ssl = config.ssl === "true";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.password === "") {
|
|
||||||
// See https://github.com/brianc/node-postgres/issues/1927
|
|
||||||
reject(new Error("Password is undefined."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const client = new Client(config);
|
|
||||||
|
|
||||||
client.on("error", (error) => {
|
|
||||||
log.debug("postgres", "Error caught in the error event handler.");
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
client.connect((err) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
client.end();
|
|
||||||
} else {
|
|
||||||
// Connected here
|
|
||||||
try {
|
|
||||||
// No query provided by user, use SELECT 1
|
|
||||||
if (!query || (typeof query === "string" && query.trim() === "")) {
|
|
||||||
query = "SELECT 1";
|
|
||||||
}
|
|
||||||
|
|
||||||
client.query(query, (err, res) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
resolve(res);
|
|
||||||
}
|
|
||||||
client.end();
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
reject(e);
|
|
||||||
client.end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run a query on MySQL/MariaDB
|
* Run a query on MySQL/MariaDB
|
||||||
* @param {string} connectionString The database connection string
|
* @param {string} connectionString The database connection string
|
||||||
|
|||||||
60
test/backend-test/test-postgres.js
Normal file
60
test/backend-test/test-postgres.js
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
const { describe, test } = require("node:test");
|
||||||
|
const assert = require("node:assert");
|
||||||
|
const { PostgreSqlContainer } = require("@testcontainers/postgresql");
|
||||||
|
const { PostgresMonitorType } = require("../../server/monitor-types/postgres");
|
||||||
|
const { UP, PENDING } = require("../../src/util");
|
||||||
|
|
||||||
|
describe(
|
||||||
|
"Postgres Single Node",
|
||||||
|
{
|
||||||
|
skip:
|
||||||
|
!!process.env.CI &&
|
||||||
|
(process.platform !== "linux" || process.arch !== "x64"),
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
test("Postgres is running", async () => {
|
||||||
|
// The default timeout of 30 seconds might not be enough for the container to start
|
||||||
|
const postgresContainer = await new PostgreSqlContainer(
|
||||||
|
"postgres:latest"
|
||||||
|
)
|
||||||
|
.withStartupTimeout(60000)
|
||||||
|
.start();
|
||||||
|
const postgresMonitor = new PostgresMonitorType();
|
||||||
|
const monitor = {
|
||||||
|
databaseConnectionString: postgresContainer.getConnectionUri(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const heartbeat = {
|
||||||
|
msg: "",
|
||||||
|
status: PENDING,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await postgresMonitor.check(monitor, heartbeat, {});
|
||||||
|
assert.strictEqual(heartbeat.status, UP);
|
||||||
|
} finally {
|
||||||
|
postgresContainer.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Postgres is not running", async () => {
|
||||||
|
const postgresMonitor = new PostgresMonitorType();
|
||||||
|
const monitor = {
|
||||||
|
databaseConnectionString: "http://localhost:15432",
|
||||||
|
};
|
||||||
|
|
||||||
|
const heartbeat = {
|
||||||
|
msg: "",
|
||||||
|
status: PENDING,
|
||||||
|
};
|
||||||
|
|
||||||
|
// regex match any string
|
||||||
|
const regex = /.+/;
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
postgresMonitor.check(monitor, heartbeat, {}),
|
||||||
|
regex
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user