zgrzyt 1.3.5 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +5 -12
- package/index.js +3 -1
- package/lib/check.js +40 -10
- package/lib/config.js +17 -3
- package/lib/report.js +6 -9
- package/lib/state.js +62 -0
- package/lib/zgrzyt.js +2 -2
- package/package.json +2 -2
- package/History.md +0 -67
package/Readme.md
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
[![NPM version][npm-image]][npm-url]
|
|
2
|
-
[![Build Status][
|
|
3
|
-
[![Dependency Status][deps-image]][deps-url]
|
|
4
|
-
[![Dev Dependency Status][deps-dev-image]][deps-dev-url]
|
|
2
|
+
[![Build Status][build-image]][build-url]
|
|
5
3
|
|
|
6
4
|
# zgrzyt
|
|
7
5
|
|
|
@@ -47,12 +45,13 @@ token=XXXXXX
|
|
|
47
45
|
; API URL to be tested on each of the servers
|
|
48
46
|
url=https://api.example.net/status
|
|
49
47
|
timeout=250 ; optional, in millis
|
|
48
|
+
retry=2 ; optional, by default zgrzyt retries 2 times before assuming API endpoint is down
|
|
50
49
|
domain=example.net ; optional, domain for which zgrzyt will update DNS record
|
|
51
50
|
method=HEAD ; optional, HTTP method used by zgrzyt
|
|
52
51
|
```
|
|
53
52
|
|
|
54
53
|
If `api.domain` is not specified its value is deduced from `api.url`.
|
|
55
|
-
If `api.method` is not specified zgrzyt will send `HEAD` requests to poll the servers. `api.method` can be set to `GET` if needed.
|
|
54
|
+
If `api.method` is not specified zgrzyt will send `HEAD` requests to poll the servers. `api.method` can be set to `GET` if needed. `retry` and `timeout` can be either configured globally or separately for each API.
|
|
56
55
|
|
|
57
56
|
### Multiple checks
|
|
58
57
|
|
|
@@ -88,11 +87,5 @@ MIT © [Damian Krzeminski](https://pirxpilot.me)
|
|
|
88
87
|
[npm-image]: https://img.shields.io/npm/v/zgrzyt.svg
|
|
89
88
|
[npm-url]: https://npmjs.org/package/zgrzyt
|
|
90
89
|
|
|
91
|
-
[
|
|
92
|
-
[
|
|
93
|
-
|
|
94
|
-
[deps-image]: https://img.shields.io/david/pirxpilot/zgrzyt.svg
|
|
95
|
-
[deps-url]: https://david-dm.org/pirxpilot/zgrzyt
|
|
96
|
-
|
|
97
|
-
[deps-dev-image]: https://img.shields.io/david/dev/pirxpilot/zgrzyt.svg
|
|
98
|
-
[deps-dev-url]: https://david-dm.org/pirxpilot/zgrzyt?type=dev
|
|
90
|
+
[build-url]: https://github.com/pirxpilot/zgrzyt/actions/workflows/check.yaml
|
|
91
|
+
[build-image]: https://img.shields.io/github/workflow/status/pirxpilot/zgrzyt/check
|
package/index.js
CHANGED
|
@@ -2,6 +2,7 @@ const conf = require('rc')('zgrzyt');
|
|
|
2
2
|
const prepareConfig = require('./lib/config');
|
|
3
3
|
const zgrzyt = require('./lib/zgrzyt');
|
|
4
4
|
const report = require('./lib/report');
|
|
5
|
+
const { onExit } = require('./lib/state');
|
|
5
6
|
|
|
6
7
|
const apis = prepareConfig(conf);
|
|
7
8
|
|
|
@@ -17,6 +18,7 @@ main(apis).catch(e => {
|
|
|
17
18
|
async function main(apis) {
|
|
18
19
|
const results = await Promise.all(apis.map(zgrzyt));
|
|
19
20
|
const { exitCode, lines } = report(results);
|
|
20
|
-
|
|
21
|
+
console.log(lines.join('\n'));
|
|
22
|
+
await onExit();
|
|
21
23
|
process.exit(exitCode);
|
|
22
24
|
}
|
package/lib/check.js
CHANGED
|
@@ -3,6 +3,7 @@ const https = require('https');
|
|
|
3
3
|
const debug = require('debug')('zgrzyt:check');
|
|
4
4
|
|
|
5
5
|
const { resolve } = require('./dns');
|
|
6
|
+
const { updateHealth } = require('./state');
|
|
6
7
|
|
|
7
8
|
const {
|
|
8
9
|
name,
|
|
@@ -21,14 +22,17 @@ module.exports = {
|
|
|
21
22
|
async function checkService(server, api) {
|
|
22
23
|
const address = await resolve(server) ;
|
|
23
24
|
debug('Resolved %s to %s', server, address);
|
|
25
|
+
const ok = await checkApi(api, address);
|
|
26
|
+
const health = await updateHealth(api, server, ok);
|
|
24
27
|
return {
|
|
25
28
|
address,
|
|
26
29
|
server,
|
|
27
|
-
|
|
30
|
+
health,
|
|
31
|
+
ok
|
|
28
32
|
};
|
|
29
33
|
}
|
|
30
34
|
|
|
31
|
-
function checkApi({ url, timeout, method = 'HEAD' }, address) {
|
|
35
|
+
async function checkApi({ url, timeout, method = 'HEAD', headers = {}, retry = 2 }, address) {
|
|
32
36
|
debug('Checking %s on %s with timeout %dms', url, address, timeout);
|
|
33
37
|
|
|
34
38
|
const { protocol } = new URL(url);
|
|
@@ -37,23 +41,41 @@ function checkApi({ url, timeout, method = 'HEAD' }, address) {
|
|
|
37
41
|
const agent = new Agent({
|
|
38
42
|
lookup: makeLookup(address)
|
|
39
43
|
});
|
|
40
|
-
|
|
44
|
+
|
|
45
|
+
let result;
|
|
46
|
+
do {
|
|
47
|
+
result = await makeRequest();
|
|
48
|
+
if (result) {
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
await waitRandom(timeout);
|
|
52
|
+
} while(--retry);
|
|
53
|
+
|
|
54
|
+
return result;
|
|
55
|
+
|
|
56
|
+
function makeRequest() {
|
|
57
|
+
return new Promise(resolve => request(url, {
|
|
41
58
|
method,
|
|
42
59
|
agent,
|
|
43
60
|
timeout,
|
|
44
|
-
headers: {
|
|
61
|
+
headers: {
|
|
62
|
+
'User-Agent': USER_AGENT,
|
|
63
|
+
...headers
|
|
64
|
+
}
|
|
45
65
|
})
|
|
46
|
-
.on('timeout', function() {
|
|
66
|
+
.on('timeout', function () {
|
|
47
67
|
debug('Timeout for %s on %s', url, address);
|
|
48
68
|
this.destroy(new Error('Request timeout.'));
|
|
49
69
|
})
|
|
50
70
|
.on('response', res => resolve(isOk(res)))
|
|
51
71
|
.on('error', () => resolve(false))
|
|
52
|
-
.end()
|
|
53
|
-
);
|
|
72
|
+
.end());
|
|
54
73
|
|
|
55
|
-
|
|
56
|
-
|
|
74
|
+
function isOk({ statusCode }) {
|
|
75
|
+
const ok = statusCode >= 200 && statusCode < 300;
|
|
76
|
+
debug('Result for %s on %s is %s', url, address, ok);
|
|
77
|
+
return ok;
|
|
78
|
+
}
|
|
57
79
|
}
|
|
58
80
|
|
|
59
81
|
function makeLookup(address) {
|
|
@@ -61,12 +83,20 @@ function checkApi({ url, timeout, method = 'HEAD' }, address) {
|
|
|
61
83
|
}
|
|
62
84
|
}
|
|
63
85
|
|
|
64
|
-
async function checkServices(servers, selected, api, force) {
|
|
86
|
+
async function checkServices(servers, selected, api, { force, repair }) {
|
|
65
87
|
const items = await Promise.all(servers.map(s => checkService(s, api)));
|
|
66
88
|
const okItems = items.filter(item => item.ok);
|
|
67
89
|
if (force) {
|
|
68
90
|
return okItems[0];
|
|
69
91
|
}
|
|
92
|
+
if (repair && okItems[0].health >= repair) {
|
|
93
|
+
return okItems[0];
|
|
94
|
+
}
|
|
70
95
|
const best = okItems.find(item => item.server === selected);
|
|
71
96
|
return best ? best : okItems[0];
|
|
72
97
|
}
|
|
98
|
+
|
|
99
|
+
function waitRandom(millisMax) {
|
|
100
|
+
const millis = Math.floor(millisMax * Math.random());
|
|
101
|
+
return new Promise(resolve => setTimeout(resolve, millis));
|
|
102
|
+
}
|
package/lib/config.js
CHANGED
|
@@ -18,7 +18,6 @@ function prepareConfig(config) {
|
|
|
18
18
|
const {
|
|
19
19
|
cloudflare,
|
|
20
20
|
api = {},
|
|
21
|
-
force,
|
|
22
21
|
cluster = {}
|
|
23
22
|
} = config;
|
|
24
23
|
|
|
@@ -44,7 +43,10 @@ function prepareConfig(config) {
|
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
const { domain, zone } = getDomainAndZone(api);
|
|
47
|
-
const timeout = parseInt(api.timeout, 10) || 250;
|
|
46
|
+
const timeout = parseInt(api.timeout || config.timeout, 10) || 250;
|
|
47
|
+
const retry = parseInt(api.retry || config.retry, 10) || 2;
|
|
48
|
+
const force = 'force' in api ? api.force : config.force;
|
|
49
|
+
const repair = parseInt(api.repair || config.repair, 10) || 5;
|
|
48
50
|
|
|
49
51
|
const { servers } = api.cluster in cluster ? cluster[api.cluster] : config;
|
|
50
52
|
if (!servers || !servers.length) {
|
|
@@ -52,17 +54,29 @@ function prepareConfig(config) {
|
|
|
52
54
|
return;
|
|
53
55
|
}
|
|
54
56
|
|
|
57
|
+
if (api.header && !Array.isArray(api.header)) {
|
|
58
|
+
api.header = [ api.header ];
|
|
59
|
+
}
|
|
60
|
+
const headers = (api.header || []).reduce((headers, str) => {
|
|
61
|
+
const [ name, value ] = str.split('=', 2);
|
|
62
|
+
headers[name] = value;
|
|
63
|
+
return headers;
|
|
64
|
+
}, {});
|
|
65
|
+
|
|
55
66
|
return {
|
|
56
67
|
servers,
|
|
57
68
|
api: {
|
|
58
69
|
url: api.url,
|
|
59
70
|
method: api.method || 'HEAD',
|
|
71
|
+
headers,
|
|
60
72
|
timeout,
|
|
73
|
+
retry,
|
|
61
74
|
domain,
|
|
62
75
|
zone
|
|
63
76
|
},
|
|
64
77
|
client,
|
|
65
|
-
force
|
|
78
|
+
force,
|
|
79
|
+
repair
|
|
66
80
|
};
|
|
67
81
|
|
|
68
82
|
}
|
package/lib/report.js
CHANGED
|
@@ -34,19 +34,16 @@ function reportAll(collected) {
|
|
|
34
34
|
let exitCode = 0;
|
|
35
35
|
const lines = [];
|
|
36
36
|
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
if (missing.length > 0) {
|
|
38
|
+
lines.push('No good servers for:\n', ...missing, '\n');
|
|
39
|
+
exitCode += 2;
|
|
40
40
|
}
|
|
41
41
|
if (switched.length > 0) {
|
|
42
|
-
|
|
43
|
-
lines.push(...switched, '\n');
|
|
42
|
+
lines.push('Switched DNS for:\n', ...switched, '\n');
|
|
44
43
|
exitCode += 1;
|
|
45
44
|
}
|
|
46
|
-
if (
|
|
47
|
-
lines.push('No
|
|
48
|
-
lines.push(...missing, '\n');
|
|
49
|
-
exitCode += 2;
|
|
45
|
+
if (noops.length > 0) {
|
|
46
|
+
lines.push('No changes:\n', ...noops, '\n');
|
|
50
47
|
}
|
|
51
48
|
|
|
52
49
|
return { exitCode, lines: lines.slice(0, -1) };
|
package/lib/state.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const {
|
|
2
|
+
readFile,
|
|
3
|
+
writeFile,
|
|
4
|
+
} = require('fs').promises;
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
updateHealth,
|
|
8
|
+
onExit
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
ZGRZYT_STATE = '/var/lib/zgrzyt/state.json'
|
|
13
|
+
} = process.env;
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
const state_p = read();
|
|
17
|
+
let hook;
|
|
18
|
+
|
|
19
|
+
async function onExit() {
|
|
20
|
+
if(hook) {
|
|
21
|
+
await hook();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function updateHealth({ url }, serverName, ok) {
|
|
26
|
+
const state = await peak(url, serverName);
|
|
27
|
+
let health = 0;
|
|
28
|
+
if (ok) {
|
|
29
|
+
health = (state.health || 0) + 1;
|
|
30
|
+
}
|
|
31
|
+
state.health = health;
|
|
32
|
+
return health;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function peak(url, serverName) {
|
|
36
|
+
const state = await state_p;
|
|
37
|
+
if (!hook) {
|
|
38
|
+
hook = async () => await write(state);
|
|
39
|
+
}
|
|
40
|
+
let api = state[url];
|
|
41
|
+
if (!api) {
|
|
42
|
+
api = state[url] = Object.create(null);
|
|
43
|
+
}
|
|
44
|
+
let server = api[serverName];
|
|
45
|
+
if (!server) {
|
|
46
|
+
server = api[serverName] = Object.create(null);
|
|
47
|
+
}
|
|
48
|
+
return server;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function read(path = ZGRZYT_STATE) {
|
|
52
|
+
try {
|
|
53
|
+
const str = await readFile(path);
|
|
54
|
+
return JSON.parse(str);
|
|
55
|
+
} catch(e) {
|
|
56
|
+
return Object.create(null);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function write(state, path = ZGRZYT_STATE) {
|
|
61
|
+
await writeFile(path, JSON.stringify(state, null, 2));
|
|
62
|
+
}
|
package/lib/zgrzyt.js
CHANGED
|
@@ -4,10 +4,10 @@ const { checkServices } = require('./check');
|
|
|
4
4
|
module.exports = zgrzyt;
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
async function zgrzyt({ servers, api, client, force }) {
|
|
7
|
+
async function zgrzyt({ servers, api, client, force, repair }) {
|
|
8
8
|
const { domain, zone } = api;
|
|
9
9
|
const record = await client.listRecords(zone, domain);
|
|
10
|
-
const good = await checkServices(servers, record.content, api, force);
|
|
10
|
+
const good = await checkServices(servers, record.content, api, { force, repair });
|
|
11
11
|
const result = {
|
|
12
12
|
url: api.url,
|
|
13
13
|
domain,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zgrzyt",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Poor man's load balancing DNS switcher.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Damian Krzeminski",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"debug": "~4",
|
|
21
21
|
"got": "^11.8.0",
|
|
22
|
-
"parse-domain": "
|
|
22
|
+
"parse-domain": "~4",
|
|
23
23
|
"rc": "^1.2.8",
|
|
24
24
|
"sprintfjs": "^1.2.16"
|
|
25
25
|
},
|
package/History.md
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
1.3.5 / 2020-12-09
|
|
3
|
-
==================
|
|
4
|
-
|
|
5
|
-
* handle request timeout properly
|
|
6
|
-
|
|
7
|
-
1.3.4 / 2020-11-29
|
|
8
|
-
==================
|
|
9
|
-
|
|
10
|
-
* add support for large (> 50 zones) accounts
|
|
11
|
-
* use `got` instead of `fetch` for Cloudflare API
|
|
12
|
-
|
|
13
|
-
1.3.3 / 2020-11-28
|
|
14
|
-
==================
|
|
15
|
-
|
|
16
|
-
* optimize requests for zone data
|
|
17
|
-
* cache DNS requests for servers
|
|
18
|
-
|
|
19
|
-
1.3.2 / 2020-11-28
|
|
20
|
-
==================
|
|
21
|
-
|
|
22
|
-
* throttle request to Cloudflare API
|
|
23
|
-
* fix displaying error massages for CloudFlare API
|
|
24
|
-
|
|
25
|
-
1.3.1 / 2020-11-26
|
|
26
|
-
==================
|
|
27
|
-
|
|
28
|
-
* better formating for reports
|
|
29
|
-
* report results grouped by action
|
|
30
|
-
* add debug module
|
|
31
|
-
|
|
32
|
-
1.3.0 / 2020-11-25
|
|
33
|
-
==================
|
|
34
|
-
|
|
35
|
-
* use `HTTP HEAD` by default
|
|
36
|
-
|
|
37
|
-
1.2.1 / 2020-11-24
|
|
38
|
-
==================
|
|
39
|
-
|
|
40
|
-
* use package.homepage to format User Agent
|
|
41
|
-
|
|
42
|
-
1.2.0 / 2020-10-29
|
|
43
|
-
==================
|
|
44
|
-
|
|
45
|
-
* make zgrzyt less verbose
|
|
46
|
-
* add support for multiple API in one config
|
|
47
|
-
* allow for specifying API domain
|
|
48
|
-
* add User-Agent header to HTTP checks
|
|
49
|
-
|
|
50
|
-
1.1.0 / 2020-10-25
|
|
51
|
-
==================
|
|
52
|
-
|
|
53
|
-
* handle proxied records properly
|
|
54
|
-
|
|
55
|
-
1.0.1 / 2020-10-05
|
|
56
|
-
==================
|
|
57
|
-
|
|
58
|
-
* update dependencies
|
|
59
|
-
* fix docs typos
|
|
60
|
-
|
|
61
|
-
1.0.0 / 2020-10-04
|
|
62
|
-
==================
|
|
63
|
-
|
|
64
|
-
* implement switching records when forced
|
|
65
|
-
* add bin/zgrzyt executable
|
|
66
|
-
* add support for switching dbs record
|
|
67
|
-
* add http/https support
|