samuel-p
789a5416ac
Some checks failed
continuous-integration/drone/push Build is failing
added arm docker image added renovate.json improved README.md
163 lines
6.3 KiB
JavaScript
163 lines
6.3 KiB
JavaScript
const config = require('./data/config');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const cron = require('node-cron');
|
|
const fetch = require('node-fetch');
|
|
const abort = require('abort-controller');
|
|
const nmap = require('libnmap');
|
|
|
|
let cachePath = path.resolve("./data/cache.json");
|
|
const cache = fs.existsSync(cachePath) ? JSON.parse(fs.readFileSync(cachePath, {encoding: "utf8"})) : {};
|
|
// delete cache file to remove orphaned cache values
|
|
if (fs.existsSync(cachePath)) {
|
|
fs.unlinkSync(cachePath);
|
|
}
|
|
|
|
process.on('SIGINT', () => {
|
|
fs.writeFileSync(cachePath, JSON.stringify(cache), {encoding: "utf8"});
|
|
process.exit(0);
|
|
});
|
|
|
|
const cachetStatusMapping = {
|
|
"ONLINE": 1,
|
|
"SLOW": 2,
|
|
"OFFLINE": 3,
|
|
"INCIDENT": 4
|
|
};
|
|
|
|
config.cron = config.cron || "* * * * *";
|
|
config.defaults = config.defaults || {};
|
|
config.defaults.retry = config.defaults.retry || 0;
|
|
config.defaults.waitUntilRetry = config.defaults.waitUntilRetry || 5;
|
|
config.defaults.performanceTimeout = config.defaults.performanceTimeout || 1;
|
|
config.defaults.requestTimeout = config.defaults.requestTimeout || 30;
|
|
config.defaults.offlineTimeUntilMajor = config.defaults.offlineTimeUntilMajor || 300;
|
|
|
|
const checkHttp = async (url, performanceTimeout, requestTimeout) => {
|
|
const controller = new abort.AbortController();
|
|
const timeout = setTimeout(() => controller.abort(), requestTimeout * 1000);
|
|
try {
|
|
const start = new Date().getTime();
|
|
const response = await fetch(url, {signal: controller.signal});
|
|
const stop = new Date().getTime();
|
|
if (response.ok) {
|
|
if (stop - start > performanceTimeout * 1000) {
|
|
return {status: "SLOW", message: response.statusText};
|
|
}
|
|
return {status: "ONLINE", message: response.statusText};
|
|
}
|
|
return {status: "OFFLINE", message: response.statusText};
|
|
} catch (e) {
|
|
return {status: "OFFLINE", message: e.message};
|
|
} finally {
|
|
clearTimeout(timeout);
|
|
}
|
|
};
|
|
|
|
const checkPort = async (host, port, type, performanceTimeout, requestTimeout) => {
|
|
return await new Promise(resolve => {
|
|
nmap.scan({
|
|
flags: ['-Pn'],
|
|
range: [host],
|
|
ports: port.toString(),
|
|
timeout: requestTimeout,
|
|
udp: type === 'udp'
|
|
}, (error, report) => {
|
|
try {
|
|
if (error) {
|
|
resolve({status: "OFFLINE", message: error});
|
|
} else {
|
|
const result = report[host].host[0];
|
|
const time = parseInt(result.item.endtime) - parseInt(result.item.starttime);
|
|
const status = result.ports[0].port[0].state[0].item;
|
|
if (status.state.includes('open')) {
|
|
if (time > performanceTimeout * 1000) {
|
|
resolve({status: "SLOW", message: status.state});
|
|
} else {
|
|
resolve({status: "ONLINE", message: status.state});
|
|
}
|
|
} else {
|
|
resolve({status: "OFFLINE", message: status.state});
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
resolve({status: "OFFLINE", message: 'an unexpected error occurred'});
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
async function checkStatus(service) {
|
|
const performanceTimeout = service.performanceTimeout || config.defaults.performanceTimeout;
|
|
const requestTimeout = service.requestTimeout || config.defaults.requestTimeout;
|
|
switch (service.type) {
|
|
case 'HTTP':
|
|
return await checkHttp(service.url, performanceTimeout, requestTimeout);
|
|
case 'TCP':
|
|
return await checkPort(service.host, service.port, 'tcp', performanceTimeout, requestTimeout);
|
|
case 'UDP':
|
|
return await checkPort(service.host, service.port, 'udp', performanceTimeout, requestTimeout);
|
|
default:
|
|
throw new Error('unsupported type "' + type + '"')
|
|
}
|
|
}
|
|
|
|
const checkService = async (service, oldStatus) => {
|
|
const retry = service.retry || config.defaults.retry;
|
|
const waitUntilRetry = (service.waitUntilRetry || config.defaults.waitUntilRetry) * 1000;
|
|
let newStatus;
|
|
for (let i = retry; i >= 0; i--) {
|
|
newStatus = await checkStatus(service);
|
|
newStatus.changed = new Date().getTime();
|
|
if (newStatus.status === "ONLINE") {
|
|
return newStatus;
|
|
}
|
|
const offlineTimeUntilMajor = (service.offlineTimeUntilMajor || config.defaults.offlineTimeUntilMajor) * 1000;
|
|
if (newStatus === "OFFLINE" && oldStatus && (oldStatus.status === "INCIDENT" || oldStatus.status === "OFFLINE" && oldStatus.changed + offlineTimeUntilMajor < newStatus.changed)) {
|
|
newStatus.status = "INCIDENT";
|
|
}
|
|
if (i >= 0 && waitUntilRetry > 0) {
|
|
await new Promise(r => setTimeout(r, waitUntilRetry));
|
|
}
|
|
}
|
|
return newStatus;
|
|
};
|
|
|
|
const pushStatusToCachet = async (id, status) => {
|
|
try {
|
|
let currentCachetStatus = cachetStatusMapping[status];
|
|
const oldState = await fetch(config.api + '/components/' + id).then(r => r.json());
|
|
if (oldState.data.status === currentCachetStatus) {
|
|
console.log('state already set');
|
|
return;
|
|
}
|
|
const update = await fetch(config.api + '/components/' + id, {
|
|
method: 'PUT',
|
|
body: JSON.stringify({status: currentCachetStatus}),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-Cachet-Token': config.token
|
|
}
|
|
});
|
|
if (!update.ok) {
|
|
console.log('failed to update status: ' + update.statusText);
|
|
}
|
|
} catch (e) {
|
|
console.log('failed to update status', e);
|
|
}
|
|
};
|
|
|
|
const check = async () => {
|
|
await Promise.all(config.services.map(async service => {
|
|
const oldStatus = cache[service.id];
|
|
const newStatus = await checkService(service, oldStatus);
|
|
if (!oldStatus || oldStatus.status !== newStatus.status) {
|
|
console.log(service.id + ' status changed: ' + newStatus.status);
|
|
await pushStatusToCachet(service.id, newStatus.status);
|
|
cache[service.id] = newStatus;
|
|
}
|
|
}));
|
|
};
|
|
|
|
cron.schedule(config.cron, async () => await check(), {});
|