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 CHANGED
@@ -1,7 +1,5 @@
1
1
  [![NPM version][npm-image]][npm-url]
2
- [![Build Status][travis-image]][travis-url]
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
- [travis-url]: https://travis-ci.com/pirxpilot/zgrzyt
92
- [travis-image]: https://img.shields.io/travis/com/pirxpilot/zgrzyt.svg
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
- lines.forEach(l => console.log(l));
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
- ok: await checkApi(api, address)
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
- return new Promise(resolve => request(url, {
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: { 'User-Agent': USER_AGENT }
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
- function isOk({ statusCode }) {
56
- return statusCode >= 200 && statusCode < 300;
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 (noops.length > 0) {
38
- console.log('No changes:\n');
39
- lines.push(...noops, '\n');
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
- console.log('Switched DNS for:\n');
43
- lines.push(...switched, '\n');
42
+ lines.push('Switched DNS for:\n', ...switched, '\n');
44
43
  exitCode += 1;
45
44
  }
46
- if (missing.length > 0) {
47
- lines.push('No good servers for:\n');
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.5",
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": "^3.0.2",
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