@certik/skynet 0.10.4 → 0.10.7

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/monitor.js CHANGED
@@ -1,192 +1,191 @@
1
- const meow = require("meow");
2
- const fetch = require("node-fetch");
3
- const { getSelectorFlags, getSelectorDesc, toSelectorString } = require("./selector");
4
- const { getBinaryName } = require("./cli");
5
- const { exponentialRetry } = require("./availability");
6
- const { getIndexerLatestId, getIndexerValidatedId, getIndexerState } = require("./indexer");
7
- const { postGenieMessage } = require("./opsgenie");
8
- const { getJobName } = require("./deploy");
9
-
10
- const ERROR_LEVEL = {
11
- INFO: "Info",
12
- CRITICAL: "Critical",
13
- WARNING: "Warning",
14
- };
15
-
16
- const LEVEL_EMOJI = {
17
- Critical: ":exclamation:",
18
- Warning: ":warning:",
19
- Info: "",
20
- };
21
-
22
- const LEVEL_PRIORITY = {
23
- Critical: 3,
24
- Warning: 2,
25
- Info: 1,
26
- };
27
-
28
- function sortErrors(errors) {
29
- return errors.sort((e1, e2) => (LEVEL_PRIORITY[e2.type] || 0) - (LEVEL_PRIORITY[e1.type] || 0));
30
- }
31
-
32
- async function getMostRecentJobLaunch(name) {
33
- try {
34
- const jobsRes = await fetch(`http://localhost:4646/v1/jobs?prefix=${name}`);
35
-
36
- if (!jobsRes.ok) {
37
- console.log(`[MONITOR] request local nomad API failed`);
38
-
39
- return null;
40
- }
41
-
42
- const jobs = await jobsRes.json();
43
-
44
- if (jobs.length === 0) {
45
- console.log(`[MONITOR] did not see any jobs prefixed with ${name}`);
46
-
47
- return null;
48
- }
49
-
50
- const recentFinishedJob = jobs.reverse().find((job) => {
51
- // filter out monitor job
52
- return job.ID.indexOf("-monitor") === -1 && job.Status === "dead";
53
- });
54
-
55
- if (!recentFinishedJob) {
56
- console.log(`[MONITOR] did not see any dead jobs`);
57
-
58
- return null;
59
- }
60
-
61
- console.log("[MONITOR]", "most recent job info", recentFinishedJob.ID, recentFinishedJob.JobSummary.Summary);
62
-
63
- return recentFinishedJob;
64
- } catch (getJobError) {
65
- console.log("[MONITOR]", "cannot get most recent job", getJobError.message);
66
- return null;
67
- }
68
- }
69
-
70
- function createMonitor({ binaryName, name, type = "stateless", mode = false, selector = {}, check, maxRetry = 2 }) {
71
- function monitor() {
72
- if (!binaryName) {
73
- binaryName = getBinaryName();
74
- }
75
-
76
- const cli = meow(
77
- `
78
- Usage
79
-
80
- $ ${binaryName} <options>
81
-
82
- Options
83
- ${
84
- mode ? " --mode could be delta/rebuild/resume-rebuild/validate/one/range/reset\n" : ""
85
- }${getSelectorDesc(selector)}
86
- --production run in production mode
87
- --verbose Output debug messages
88
- `,
89
- {
90
- description: false,
91
- version: false,
92
- flags: {
93
- ...getSelectorFlags(selector),
94
- ...(mode && {
95
- mode: {
96
- type: "string",
97
- default: "delta",
98
- },
99
- }),
100
- verbose: {
101
- type: "boolean",
102
- default: false,
103
- },
104
- },
105
- }
106
- );
107
-
108
- async function runCheck({ verbose, production, mode, ...selectorFlags }) {
109
- const startTime = Date.now();
110
- console.log(`[MONITOR] starting check, ${toSelectorString(selectorFlags, ", ")}`);
111
-
112
- const state = {};
113
-
114
- if (type === "stateful") {
115
- state.latestId = await getIndexerLatestId(name, selectorFlags);
116
- state.validatedId = await getIndexerValidatedId(name, selectorFlags);
117
- state.buildState = await getIndexerState(name, selectorFlags);
118
- }
119
-
120
- let result = await exponentialRetry(
121
- async () => {
122
- try {
123
- const errors = await check({ verbose, state, mode, ...selectorFlags });
124
-
125
- if (!Array.isArray(errors)) {
126
- throw new Error(`check function must return array of error messages`);
127
- }
128
-
129
- return errors;
130
- } catch (err) {
131
- console.log(`[MONITOR] got error in check`, err);
132
-
133
- return [`${err.message}`];
134
- }
135
- },
136
- {
137
- maxRetry,
138
- initialDuration: 10000,
139
- growFactor: 3,
140
- test: (r) => r.length === 0,
141
- verbose,
142
- }
143
- );
144
-
145
- const jobName = getJobName(name, selectorFlags, mode);
146
- const mostRecentJob = await getMostRecentJobLaunch(jobName);
147
-
148
- if (result.length > 0) {
149
- console.log("Found Errors", result);
150
-
151
- if (production) {
152
- const nomadAddr = process.env.SKYNET_NOMAD_PRODUCTION_ADDR;
153
-
154
- // alert on opsgenie
155
- await postGenieMessage(
156
- `Failed Service Check: ${jobName}`,
157
- `<p><b>Service:</b><a href="${nomadAddr}/ui/jobs/${jobName}" target="_blank">${jobName}</a></p><p><b>Issues</b></p><ul>${sortErrors(
158
- result
159
- )
160
- .map((m) => `<li><b>${m.type}:</b> ${m.message}</li>`)
161
- .join("")}</ul>`,
162
- verbose
163
- );
164
- } else {
165
- console.log("skip sending messages to opsgenie in dev env");
166
- }
167
- }
168
-
169
- console.log(`[MONITOR] check successfully in ${Date.now() - startTime}ms`);
170
-
171
- if (mostRecentJob && mostRecentJob.ParentID === "" && mostRecentJob.Periodic === false) {
172
- // stop monitor job if the main job was a one time job and completed
173
- const stopRes = await fetch(`http://localhost:4646/v1/job/${jobName}-monitor`, { method: "DELETE" });
174
-
175
- if (stopRes.ok) {
176
- console.log(`[MONITOR] monitor job stopped since the completed main job was a one time job`);
177
- } else {
178
- console.log(`[MONITOR] error stopping monitor job`);
179
- }
180
- }
181
- }
182
-
183
- runCheck(cli.flags).catch((err) => {
184
- console.log("[MONITOR]", err);
185
- process.exit(1);
186
- });
187
- }
188
-
189
- return { monitor };
190
- }
191
-
192
- module.exports = { createMonitor, ERROR_LEVEL, LEVEL_EMOJI };
1
+ const meow = require("meow");
2
+ const fetch = require("node-fetch");
3
+ const { getSelectorFlags, getSelectorDesc, toSelectorString } = require("./selector");
4
+ const { getBinaryName } = require("./cli");
5
+ const { exponentialRetry } = require("./availability");
6
+ const { postGenieMessage } = require("./opsgenie");
7
+ const { getJobName } = require("./deploy");
8
+
9
+ const ERROR_LEVEL = {
10
+ INFO: "Info",
11
+ CRITICAL: "Critical",
12
+ WARNING: "Warning",
13
+ };
14
+
15
+ const LEVEL_EMOJI = {
16
+ Critical: ":exclamation:",
17
+ Warning: ":warning:",
18
+ Info: "",
19
+ };
20
+
21
+ const LEVEL_PRIORITY = {
22
+ Critical: 3,
23
+ Warning: 2,
24
+ Info: 1,
25
+ };
26
+
27
+ function sortErrors(errors) {
28
+ return errors.sort((e1, e2) => (LEVEL_PRIORITY[e2.type] || 0) - (LEVEL_PRIORITY[e1.type] || 0));
29
+ }
30
+
31
+ async function getMostRecentJobLaunch(name) {
32
+ try {
33
+ const jobsRes = await fetch(`http://localhost:4646/v1/jobs?prefix=${name}`);
34
+
35
+ if (!jobsRes.ok) {
36
+ console.log(`[MONITOR] request local nomad API failed`);
37
+
38
+ return null;
39
+ }
40
+
41
+ const jobs = await jobsRes.json();
42
+
43
+ if (jobs.length === 0) {
44
+ console.log(`[MONITOR] did not see any jobs prefixed with ${name}`);
45
+
46
+ return null;
47
+ }
48
+
49
+ const recentFinishedJob = jobs.reverse().find((job) => {
50
+ // filter out monitor job
51
+ return job.ID.indexOf("-monitor") === -1 && job.Status === "dead";
52
+ });
53
+
54
+ if (!recentFinishedJob) {
55
+ console.log(`[MONITOR] did not see any dead jobs`);
56
+
57
+ return null;
58
+ }
59
+
60
+ console.log("[MONITOR]", "most recent job info", recentFinishedJob.ID, recentFinishedJob.JobSummary.Summary);
61
+
62
+ return recentFinishedJob;
63
+ } catch (getJobError) {
64
+ console.log("[MONITOR]", "cannot get most recent job", getJobError.message);
65
+ return null;
66
+ }
67
+ }
68
+
69
+ function createMonitor({ binaryName, name, getState = null, mode = false, selector = {}, check, maxRetry = 2 }) {
70
+ function monitor() {
71
+ if (!binaryName) {
72
+ binaryName = getBinaryName();
73
+ }
74
+
75
+ const cli = meow(
76
+ `
77
+ Usage
78
+
79
+ $ ${binaryName} <options>
80
+
81
+ Options
82
+ ${
83
+ mode ? " --mode could be delta/rebuild/resume-rebuild/validate/one/range/reset\n" : ""
84
+ }${getSelectorDesc(selector)}
85
+ --production run in production mode
86
+ --verbose Output debug messages
87
+ `,
88
+ {
89
+ description: false,
90
+ version: false,
91
+ flags: {
92
+ ...getSelectorFlags(selector),
93
+ ...(mode && {
94
+ mode: {
95
+ type: "string",
96
+ default: "delta",
97
+ },
98
+ }),
99
+ verbose: {
100
+ type: "boolean",
101
+ default: false,
102
+ },
103
+ },
104
+ }
105
+ );
106
+
107
+ async function runCheck({ verbose, production, mode, ...selectorFlags }) {
108
+ const startTime = Date.now();
109
+ console.log(`[MONITOR] starting check, ${toSelectorString(selectorFlags, ", ")}`);
110
+
111
+ let checkState = {};
112
+
113
+ if (getState) {
114
+ checkState = await getState(name, selectorFlags);
115
+ }
116
+
117
+ let result = await exponentialRetry(
118
+ async () => {
119
+ try {
120
+ const errors = await check({ verbose, state: checkState, mode, ...selectorFlags });
121
+
122
+ if (!Array.isArray(errors)) {
123
+ throw new Error(`check function must return array of error messages`);
124
+ }
125
+
126
+ return errors;
127
+ } catch (err) {
128
+ console.log(`[MONITOR] got error in check`, err);
129
+
130
+ return [`${err.message}`];
131
+ }
132
+ },
133
+ {
134
+ maxRetry,
135
+ initialDuration: 10000,
136
+ growFactor: 3,
137
+ test: (r) => r.length === 0,
138
+ verbose,
139
+ }
140
+ );
141
+
142
+ const jobName = getJobName(name, selectorFlags, mode);
143
+ const mostRecentJob = await getMostRecentJobLaunch(jobName);
144
+
145
+ if (result.length > 0) {
146
+ console.log("Found Errors", result);
147
+
148
+ if (production) {
149
+ const nomadAddr = process.env.SKYNET_NOMAD_PRODUCTION_ADDR;
150
+
151
+ // alert on opsgenie
152
+ await postGenieMessage(
153
+ {
154
+ message: `Failed Service Check: ${jobName}`,
155
+ description: `<p><b>Service:</b><a href="${nomadAddr}/ui/jobs/${jobName}" target="_blank">${jobName}</a></p><p><b>Issues</b></p><ul>${sortErrors(
156
+ result
157
+ )
158
+ .map((m) => `<li><b>${m.type}:</b> ${m.message}</li>`)
159
+ .join("")}</ul>`,
160
+ },
161
+ verbose
162
+ );
163
+ } else {
164
+ console.log("skip sending messages to opsgenie in dev env");
165
+ }
166
+ }
167
+
168
+ console.log(`[MONITOR] check successfully in ${Date.now() - startTime}ms`);
169
+
170
+ if (mostRecentJob && mostRecentJob.ParentID === "" && mostRecentJob.Periodic === false) {
171
+ // stop monitor job if the main job was a one time job and completed
172
+ const stopRes = await fetch(`http://localhost:4646/v1/job/${jobName}-monitor`, { method: "DELETE" });
173
+
174
+ if (stopRes.ok) {
175
+ console.log(`[MONITOR] monitor job stopped since the completed main job was a one time job`);
176
+ } else {
177
+ console.log(`[MONITOR] error stopping monitor job`);
178
+ }
179
+ }
180
+ }
181
+
182
+ runCheck(cli.flags).catch((err) => {
183
+ console.log("[MONITOR]", err);
184
+ process.exit(1);
185
+ });
186
+ }
187
+
188
+ return { monitor };
189
+ }
190
+
191
+ module.exports = { createMonitor, ERROR_LEVEL, LEVEL_EMOJI };
package/opsgenie.js CHANGED
@@ -1,60 +1,55 @@
1
- const fetch = require("node-fetch");
2
- const hash = require("object-hash");
3
-
4
- function getGenieKey() {
5
- return process.env.OPSGENIE_API_KEY;
6
- }
7
-
8
- function getGenieEndPoint() {
9
- const key = getGenieKey();
10
-
11
- if (!key) {
12
- throw new Error("Cannot communicate with opsgenie due to missing API key: process.env.OPSGENIE_API_KEY");
13
- }
14
-
15
- return process.env.OPSGENIE_END_POINT;
16
- }
17
-
18
- async function postGenieMessage(msg, desc, verbose) {
19
- try {
20
- const genieKey = getGenieKey();
21
- const genieEndPoint = getGenieEndPoint();
22
-
23
- const body = {
24
- message: msg,
25
- description: desc
26
- };
27
-
28
- // Prevents duplicate alerts (See Opsgenie doc about alias)
29
- const bodyHash = hash(body);
30
- body.alias = bodyHash;
31
-
32
- if (verbose) {
33
- console.log(`Making API call to Opsgenie ${msg} (${desc}):`, JSON.stringify(body, null, 2));
34
- }
35
-
36
- // Makes the call using fetch and ENV variables
37
- const response = await fetch(genieEndPoint, {
38
- method: "POST",
39
- headers: {
40
- "Content-Type": "application/json",
41
- "Authorization": "GenieKey " + genieKey
42
- },
43
- body: JSON.stringify(body)
44
- });
45
-
46
- const result = await response.json();
47
- if (verbose) {
48
- console.log(`Result of API call to Opsgenie... ${result}`);
49
- }
50
-
51
- return result
52
-
53
- } catch (error) {
54
- console.error("Failed to make opsgenie API call", error);
55
-
56
- throw error;
57
- }
58
- }
59
-
60
- module.exports = { postGenieMessage };
1
+ const fetch = require("node-fetch");
2
+ const md5 = require("md5");
3
+
4
+ function getGenieKey() {
5
+ return process.env.OPSGENIE_API_KEY;
6
+ }
7
+
8
+ function getGenieEndPoint() {
9
+ const key = getGenieKey();
10
+
11
+ if (!key) {
12
+ throw new Error("Cannot communicate with opsgenie due to missing API key: process.env.OPSGENIE_API_KEY");
13
+ }
14
+
15
+ return process.env.OPSGENIE_END_POINT;
16
+ }
17
+
18
+ async function postGenieMessage(body, verbose) {
19
+ try {
20
+ const genieKey = getGenieKey();
21
+ const genieEndPoint = getGenieEndPoint();
22
+
23
+ // Prevents duplicate alerts (See Opsgenie doc about alias)
24
+ if (!body.alias) {
25
+ body.alias = md5(body.message);
26
+ }
27
+
28
+ if (verbose) {
29
+ console.log(`Making API call to Opsgenie`, JSON.stringify(body, null, 2));
30
+ }
31
+
32
+ // Makes the call using fetch and ENV variables
33
+ const response = await fetch(genieEndPoint, {
34
+ method: "POST",
35
+ headers: {
36
+ "Content-Type": "application/json",
37
+ Authorization: `GenieKey ${genieKey}`,
38
+ },
39
+ body: JSON.stringify(body),
40
+ });
41
+
42
+ const result = await response.json();
43
+ if (verbose) {
44
+ console.log(`Result of API call to Opsgenie... ${result}`);
45
+ }
46
+
47
+ return result;
48
+ } catch (error) {
49
+ console.error("Failed to make opsgenie API call", error);
50
+
51
+ throw error;
52
+ }
53
+ }
54
+
55
+ module.exports = { postGenieMessage };
package/package.json CHANGED
@@ -1,34 +1,37 @@
1
- {
2
- "name": "@certik/skynet",
3
- "version": "0.10.4",
4
- "description": "Skynet Shared JS library",
5
- "main": "index.js",
6
- "author": "CertiK Engineering",
7
- "scripts": {
8
- "lint": "eslint . test",
9
- "test": "ava"
10
- },
11
- "engines": {
12
- "node": ">= 14"
13
- },
14
- "dependencies": {
15
- "@slack/web-api": "^6.4.0",
16
- "aws-sdk": "^2.932.0",
17
- "chalk": "^4.1.1",
18
- "execa": "^5.0.0",
19
- "express": "^4.18.1",
20
- "kafkajs": "^1.15.0",
21
- "meow": "^7.0.1",
22
- "node-fetch": "^2.6.1",
23
- "object-hash": "^2.2.0",
24
- "snowflake-sdk": "^1.6.3",
25
- "web3": "^1.3.5",
26
- "which": "^2.0.2"
27
- },
28
- "devDependencies": {
29
- "ava": "^3.13.0",
30
- "eslint": "^7.22.0",
31
- "sinon": "^11.1.2"
32
- },
33
- "license": "MIT"
34
- }
1
+ {
2
+ "name": "@certik/skynet",
3
+ "version": "0.10.7",
4
+ "description": "Skynet Shared JS library",
5
+ "main": "index.js",
6
+ "author": "CertiK Engineering",
7
+ "scripts": {
8
+ "lint": "eslint *.js test",
9
+ "test": "ava"
10
+ },
11
+ "engines": {
12
+ "node": ">= 14"
13
+ },
14
+ "dependencies": {
15
+ "@slack/web-api": "^6.4.0",
16
+ "ably": "^1.2.22",
17
+ "aws-sdk": "^2.932.0",
18
+ "bottleneck": "^2.19.5",
19
+ "chalk": "^4.1.1",
20
+ "execa": "^5.0.0",
21
+ "express": "^4.18.1",
22
+ "kafkajs": "^1.15.0",
23
+ "md5": "^2.3.0",
24
+ "meow": "^7.0.1",
25
+ "node-fetch": "^2.6.1",
26
+ "snowflake-sdk": "^1.6.3",
27
+ "web3": "^1.3.5",
28
+ "which": "^2.0.2"
29
+ },
30
+ "devDependencies": {
31
+ "ava": "^3.13.0",
32
+ "eslint": "^7.22.0",
33
+ "eslint-plugin-import": "^2.26.0",
34
+ "sinon": "^11.1.2"
35
+ },
36
+ "license": "MIT"
37
+ }