@certik/skynet 0.7.17 → 0.8.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/indexer.js CHANGED
@@ -1,10 +1,10 @@
1
- const path = require("path");
2
1
  const meow = require("meow");
3
2
  const { createRecord, getRecordByKey } = require("./dynamodb");
4
3
  const { getEnvironment } = require("./env");
5
4
  const { exponentialRetry } = require("./availability");
6
5
  const { range, fillRange } = require("./util");
7
6
  const { getSelectorFlags, getSelectorDesc, toSelectorString } = require("./selector");
7
+ const { getBinaryName } = require("./cli");
8
8
 
9
9
  const STATE_TABLE_NAME = "skynet-" + getEnvironment() + "-indexer-state";
10
10
 
@@ -49,12 +49,21 @@ async function getIndexerValidatedId(name, selectorFlags) {
49
49
  // managing state would be helpful to reduce the build time
50
50
  // and avoid unnecessary computation & storage
51
51
  function createModeIndexerApp({
52
+ binaryName,
52
53
  name,
53
54
  selector = {},
54
55
  build,
56
+ // number of items run in a batch, determines the { from, to } to the build function
55
57
  buildBatchSize = 1,
58
+ // number of build functions calling at a time
59
+ buildConcurrency = 1,
60
+ // commit updates every rolling window = buildBatchSize * buildConcurrency
56
61
  validate,
62
+ // number of items run in a batch, determines the { from, to } to the validate function
57
63
  validateBatchSize = 1,
64
+ // number of validate functions calling at a time
65
+ validateConcurrency = 1,
66
+ // commit updates every rolling window = validateBatchSize * validateConcurrency
58
67
  maxRetry = 2,
59
68
  state,
60
69
  }) {
@@ -91,10 +100,9 @@ function createModeIndexerApp({
91
100
  }
92
101
 
93
102
  console.log(
94
- `mode=${mode}, from=${from}, to=${to > 0 ? to : "undefined"}, env=${getEnvironment()}, ${toSelectorString(
95
- selectorFlags,
96
- ", "
97
- )}`
103
+ `[MODE INDEXER] mode=${mode}, from=${from}, to=${
104
+ to > 0 ? to : "undefined"
105
+ }, env=${getEnvironment()}, ${toSelectorString(selectorFlags, ", ")}`
98
106
  );
99
107
 
100
108
  if (mode === "reset") {
@@ -127,7 +135,7 @@ function createModeIndexerApp({
127
135
  );
128
136
  } else if (mode === "one") {
129
137
  if (to > 0) {
130
- console.log("one mode ignores --to option. you may want to use range mode instead");
138
+ console.log("[MODE INDEXER] one mode ignores --to option. you may want to use range mode instead");
131
139
  }
132
140
  await runRange(selectorFlags, from, from, verbose);
133
141
  } else if (mode === "range") {
@@ -140,48 +148,60 @@ function createModeIndexerApp({
140
148
  async function runRange(selectorFlags, from, to, verbose) {
141
149
  const startTime = Date.now();
142
150
 
143
- console.log(`building range, from=${from}, to=${to}, ${toSelectorString(selectorFlags, ", ")}`);
151
+ console.log(
152
+ `[MODE INDEXER] building range, from=${from}, to=${to}, ${toSelectorString(
153
+ selectorFlags,
154
+ ", "
155
+ )}, batchSize=${buildBatchSize}, concurrency=${buildConcurrency}`
156
+ );
144
157
 
145
158
  const failedIds = await execBuild(selectorFlags, from, to, verbose, false);
146
159
 
147
160
  if (failedIds.length > 0) {
148
- console.log(`built with some failed ${finalState.type}`, failedIds);
161
+ console.log(`[MODE INDEXER] built with some failed ${finalState.type}`, failedIds);
149
162
  process.exit(1);
150
163
  } else {
151
- console.log(`built successfully in ${Date.now() - startTime}ms`);
164
+ console.log(`[MODE INDEXER] built successfully in ${Date.now() - startTime}ms`);
152
165
  process.exit(0);
153
166
  }
154
167
  }
155
168
 
156
169
  async function runValidate(selectorFlags, from, to, shouldSaveState, verbose) {
157
170
  if (!validate) {
158
- console.log(`the indexer doesn't support validate mode, validate function not implemented`);
171
+ console.log(`[MODE INDEXER] the indexer doesn't support validate mode, validate function not implemented`);
159
172
  process.exit(1);
160
173
  }
161
174
 
162
175
  const startTime = Date.now();
163
176
 
164
- console.log(`validating, from=${from}, to=${to}, ${toSelectorString(selectorFlags, ", ")}`);
177
+ console.log(
178
+ `[MODE INDEXER] validating, from=${from}, to=${to}, ${toSelectorString(
179
+ selectorFlags,
180
+ ", "
181
+ )}, batchSize=${validateBatchSize}, concurrency=${validateConcurrency}`
182
+ );
165
183
 
166
- const batches = range(from, to, validateBatchSize);
184
+ const windows = range(from, to, validateBatchSize * validateConcurrency);
167
185
 
168
- console.log(`from=${from}, to=${to}, num batches=${batches.length}`);
186
+ console.log(
187
+ `[MODE INDEXER] from=${from}, to=${to}, batchSize=${validateBatchSize}, concurrency=${validateConcurrency}`
188
+ );
169
189
 
170
- for (let [batchStart, batchEnd] of batches) {
171
- console.log(`validating batch ${batchStart}~${batchEnd}`);
190
+ for (let [windowStart, windowEnd] of windows) {
191
+ console.log(`[MODE INDEXER] validating window ${windowStart}~${windowEnd}, concurrency=${validateConcurrency}`);
172
192
 
173
- const batchIds = fillRange(batchStart, batchEnd);
193
+ const batches = range(windowStart, windowEnd, validateBatchSize);
174
194
 
175
195
  // add a retry for errors
176
196
  await Promise.all(
177
- batchIds.map(async (id) => {
197
+ batches.map(async ([batchStart, batchEnd]) => {
178
198
  const result = await exponentialRetry(
179
199
  async () => {
180
200
  try {
181
201
  await validate({
182
202
  ...selectorFlags,
183
- from: id,
184
- to: id,
203
+ from: batchStart,
204
+ to: batchEnd,
185
205
  verbose,
186
206
  });
187
207
 
@@ -204,50 +224,56 @@ function createModeIndexerApp({
204
224
  }
205
225
  })
206
226
  );
207
- }
208
227
 
209
- console.log(`validated ${to - from + 1} ${finalState.type} successfully in ${Date.now() - startTime}ms`);
228
+ if (shouldSaveState) {
229
+ await createRecord(STATE_TABLE_NAME, {
230
+ name: `${name}Validate(${toSelectorString(selectorFlags)})`,
231
+ value: to,
232
+ });
210
233
 
211
- if (shouldSaveState) {
212
- await createRecord(STATE_TABLE_NAME, {
213
- name: `${name}Validate(${toSelectorString(selectorFlags)})`,
214
- value: to,
215
- });
234
+ if (verbose) {
235
+ console.log(`[MODE INDEXER] updated processed ${finalState.type} to ${windowEnd}`);
236
+ }
237
+ }
216
238
  }
239
+
240
+ console.log(
241
+ `[MODE INDEXER] validated ${to - from + 1} ${finalState.type} successfully in ${Date.now() - startTime}ms`
242
+ );
217
243
  }
218
244
 
219
245
  async function execBuild(selectorFlags, from, to, verbose, shouldSaveState = false) {
220
246
  let failedIds = [];
221
247
 
222
- const batches = range(from, to, buildBatchSize);
248
+ const windows = range(from, to, buildBatchSize * buildConcurrency);
223
249
 
224
- for (let [batchStart, batchEnd] of batches) {
225
- console.log(`building batch ${batchStart}~${batchEnd}`);
250
+ for (let [windowStart, windowEnd] of windows) {
251
+ console.log(`[MODE INDEXER] building window ${windowStart}~${windowEnd}, concurrency = ${buildConcurrency}`);
226
252
 
227
- const batchIds = fillRange(batchStart, batchEnd);
253
+ const batches = range(windowStart, windowEnd, buildBatchSize);
228
254
 
229
255
  // add a retry for errors
230
256
  const batchResults = await Promise.all(
231
- batchIds.map(async (id) => {
257
+ batches.map(async ([batchStart, batchEnd]) => {
232
258
  await exponentialRetry(
233
259
  async () => {
234
260
  try {
235
261
  const ids = await build({
236
262
  ...selectorFlags,
237
- from: id,
238
- to: id,
263
+ from: batchStart,
264
+ to: batchEnd,
239
265
  verbose,
240
266
  });
241
267
 
242
268
  if (ids && ids.length > 0) {
243
- return id;
269
+ return ids;
244
270
  } else {
245
271
  return false;
246
272
  }
247
273
  } catch (err) {
248
- console.error(`got error in build`, err);
274
+ console.error(`[MODE INDEXER] got error in build`, err);
249
275
 
250
- return id;
276
+ return fillRange(batchStart, batchEnd);
251
277
  }
252
278
  },
253
279
  {
@@ -262,11 +288,19 @@ function createModeIndexerApp({
262
288
  if (shouldSaveState) {
263
289
  await createRecord(STATE_TABLE_NAME, {
264
290
  name: `${name}Since(${toSelectorString(selectorFlags)})`,
265
- value: batchEnd,
291
+ value: windowEnd,
266
292
  });
293
+
294
+ if (verbose) {
295
+ console.log(`[MODE INDEXER] updated processed ${finalState.type} to ${windowEnd}`);
296
+ }
267
297
  }
268
298
 
269
- failedIds = failedIds.concat(batchResults.filter((r) => r));
299
+ batchResults.forEach((ids) => {
300
+ if (ids) {
301
+ failedIds = failedIds.concat(ids);
302
+ }
303
+ });
270
304
  }
271
305
 
272
306
  failedIds.sort();
@@ -277,7 +311,12 @@ function createModeIndexerApp({
277
311
  async function runRebuild(selectorFlags, from, to, verbose) {
278
312
  const startTime = Date.now();
279
313
 
280
- console.log(`rebuilding, from=${from}, to=${to}, ${toSelectorString(selectorFlags, ", ")}`);
314
+ console.log(
315
+ `[MODE INDEXER] rebuilding, from=${from}, to=${to}, ${toSelectorString(
316
+ selectorFlags,
317
+ ", "
318
+ )}, batchSize=${buildBatchSize}, concurrency=${buildConcurrency}`
319
+ );
281
320
 
282
321
  // add a flag to stop delta from running
283
322
  await createRecord(STATE_TABLE_NAME, {
@@ -300,10 +339,15 @@ function createModeIndexerApp({
300
339
  });
301
340
 
302
341
  if (failedIds.length > 0) {
303
- console.log(`built ${to - from + 1} ${finalState.type} with some failed ${finalState.type}`, failedIds);
342
+ console.log(
343
+ `[MODE INDEXER] built ${to - from + 1} ${finalState.type} with some failed ${finalState.type}`,
344
+ failedIds
345
+ );
304
346
  process.exit(1);
305
347
  } else {
306
- console.log(`built ${to - from + 1} ${finalState.type} successfully in ${Date.now() - startTime}ms`);
348
+ console.log(
349
+ `[MODE INDEXER] built ${to - from + 1} ${finalState.type} successfully in ${Date.now() - startTime}ms`
350
+ );
307
351
  process.exit(0);
308
352
  }
309
353
  }
@@ -322,19 +366,27 @@ function createModeIndexerApp({
322
366
 
323
367
  if (to <= from) {
324
368
  console.log(
325
- `skip delta because there're no items need to be processed, ${toSelectorString(selectorFlags, ", ")}`
369
+ `[MODE INDEXER] skip delta because there're no items need to be processed, ${toSelectorString(
370
+ selectorFlags,
371
+ ", "
372
+ )}`
326
373
  );
327
374
 
328
375
  return;
329
376
  }
330
377
 
331
- console.log(`starting delta, from=${from}, to=${to}, ${toSelectorString(selectorFlags, ", ")}`);
378
+ console.log(
379
+ `[MODE INDEXER] starting delta, from=${from}, to=${to}, ${toSelectorString(
380
+ selectorFlags,
381
+ ", "
382
+ )}, batchSize=${buildBatchSize}, concurrency=${buildConcurrency}`
383
+ );
332
384
 
333
385
  try {
334
386
  const failedIds = await execBuild(selectorFlags, from, to, verbose, true);
335
387
 
336
388
  if (failedIds.length > 0) {
337
- console.log("built with some failed txs", failedIds);
389
+ console.log("[MODE INDEXER] built with some failed txs", failedIds);
338
390
 
339
391
  await createRecord(STATE_TABLE_NAME, {
340
392
  name: `${name}DeltaState(${toSelectorString(selectorFlags)})`,
@@ -358,25 +410,25 @@ function createModeIndexerApp({
358
410
  value: to,
359
411
  });
360
412
 
361
- console.log(`built successfully in ${Date.now() - startTime}ms`);
413
+ console.log(`[MODE INDEXER] built successfully in ${Date.now() - startTime}ms`);
362
414
  process.exit(0);
363
415
  }
364
416
  } catch (err) {
365
- console.error("delta build failed", from, to, err);
417
+ console.error("[MODE INDEXER] delta build failed", from, to, err);
366
418
 
367
419
  process.exit(1);
368
420
  }
369
421
  } else {
370
- console.log("skip because rebuild hasn't done yet");
422
+ console.log("[MODE INDEXER] skip because rebuild hasn't done yet");
371
423
  }
372
424
  }
373
425
 
374
426
  async function runReset(selectorFlags) {
375
427
  const startTime = Date.now();
376
428
 
377
- console.log(`starting reset, ${toSelectorString(selectorFlags, ", ")}`);
429
+ console.log(`[MODE INDEXER] starting reset, ${toSelectorString(selectorFlags, ", ")}`);
378
430
 
379
- console.log("reset state", STATE_TABLE_NAME);
431
+ console.log("[MODE INDEXER] reset state", STATE_TABLE_NAME);
380
432
  await createRecord(STATE_TABLE_NAME, {
381
433
  name: `${name}Since(${toSelectorString(selectorFlags)})`,
382
434
  value: 0,
@@ -392,12 +444,13 @@ function createModeIndexerApp({
392
444
  value: "init",
393
445
  });
394
446
 
395
- console.log(`reset successfully in ${Date.now() - startTime}ms`);
447
+ console.log(`[MODE INDEXER] reset successfully in ${Date.now() - startTime}ms`);
396
448
  }
397
449
 
398
450
  function run() {
399
- const binaryNameParts = process.argv[1].split(path.sep);
400
- const binaryName = binaryNameParts[binaryNameParts.length - 1];
451
+ if (!binaryName) {
452
+ binaryName = getBinaryName();
453
+ }
401
454
 
402
455
  const cli = meow(
403
456
  `
@@ -455,10 +508,11 @@ ${getSelectorDesc(selector)}
455
508
  // for those indexers that does not rely on a curosr
456
509
  // e.g. should always rebuild everything from scratch
457
510
  // or that the state can be easily inferred from existing data
458
- function createIndexerApp({ name, selector = {}, build, maxRetry = 2 }) {
511
+ function createIndexerApp({ binaryName, name, selector = {}, build, maxRetry = 2 }) {
459
512
  function run() {
460
- const binaryNameParts = process.argv[1].split(path.sep);
461
- const binaryName = binaryNameParts[binaryNameParts.length - 1];
513
+ if (!binaryName) {
514
+ binaryName = getBinaryName();
515
+ }
462
516
 
463
517
  const cli = meow(
464
518
  `
@@ -483,6 +537,9 @@ ${getSelectorDesc(selector)}
483
537
  );
484
538
 
485
539
  async function runBuild({ verbose, ...selectorFlags }) {
540
+ const startTime = Date.now();
541
+ console.log(`[INDEXER] starting build, ${toSelectorString(selectorFlags, ", ")}`);
542
+
486
543
  const result = await exponentialRetry(
487
544
  async () => {
488
545
  try {
@@ -490,7 +547,7 @@ ${getSelectorDesc(selector)}
490
547
 
491
548
  return true;
492
549
  } catch (err) {
493
- console.log(`got error in build`, err);
550
+ console.log(`[INDEXER] got error in build`, err);
494
551
 
495
552
  return false;
496
553
  }
@@ -503,13 +560,15 @@ ${getSelectorDesc(selector)}
503
560
  );
504
561
 
505
562
  if (!result) {
506
- throw new Error(`Build failed due to critical errors`);
563
+ throw new Error(`[INDEXER] Build failed due to critical errors`);
507
564
  }
508
565
 
509
566
  await createRecord(STATE_TABLE_NAME, {
510
567
  name: `${name}UpdatedAt(${toSelectorString(selectorFlags)})`,
511
568
  value: new Date().toISOString(),
512
569
  });
570
+
571
+ console.log(`[INDEXER] build successfully in ${Date.now() - startTime}ms`);
513
572
  }
514
573
 
515
574
  runBuild(cli.flags).catch((err) => {
package/kafka.js CHANGED
@@ -1,4 +1,3 @@
1
- const path = require("path");
2
1
  const meow = require("meow");
3
2
  const { getEnvironment, getEnvOrThrow } = require("./env");
4
3
  const { wait } = require("./availability");
@@ -6,6 +5,7 @@ const { Kafka, logLevel } = require("kafkajs");
6
5
  const { getSelectorFlags, getSelectorDesc, toSelectorString } = require("./selector");
7
6
  const { createRecord, getRecordByKey, deleteRecordsByHashKey } = require("./dynamodb");
8
7
  const { exponentialRetry } = require("./availability");
8
+ const { getBinaryName } = require("./cli");
9
9
 
10
10
  const STATE_TABLE_NAME = "skynet-" + getEnvironment() + "-indexer-state";
11
11
 
@@ -137,10 +137,11 @@ async function consumeMessages(consumerId, topic, callback) {
137
137
  return stopConsumeMessages;
138
138
  }
139
139
 
140
- function createProducerApp({ name, selector = {}, producer, state }) {
140
+ function createProducerApp({ binaryName, name, selector = {}, producer, state }) {
141
141
  function run() {
142
- const binaryNameParts = process.argv[1].split(path.sep);
143
- const binaryName = binaryNameParts[binaryNameParts.length - 1];
142
+ if (!binaryName) {
143
+ binaryName = getBinaryName();
144
+ }
144
145
 
145
146
  const finalState = {
146
147
  type: "block",
@@ -337,10 +338,11 @@ ${getSelectorDesc(selector)}
337
338
  return { run };
338
339
  }
339
340
 
340
- function createConsumerApp({ name, selector = {}, consumer }) {
341
+ function createConsumerApp({ binaryName, name, selector = {}, consumer }) {
341
342
  function run() {
342
- const binaryNameParts = process.argv[1].split(path.sep);
343
- const binaryName = binaryNameParts[binaryNameParts.length - 1];
343
+ if (!binaryName) {
344
+ binaryName = getBinaryName();
345
+ }
344
346
 
345
347
  const cli = meow(
346
348
  `
package/monitor.js ADDED
@@ -0,0 +1,246 @@
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 { postMessage } = require("./slack");
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
+ function getNomadUrl() {
33
+ return process.env.SKYNET_NOMAD_URL || "http://localhost:4646";
34
+ }
35
+
36
+ async function checkMostRecentAllocationStatus(name) {
37
+ const jobsRes = await fetch(`http://localhost:4646/v1/jobs?prefix=${name}`);
38
+
39
+ if (!jobsRes.ok) {
40
+ console.log(`request local nomad API failed`);
41
+
42
+ return [];
43
+ }
44
+
45
+ const jobs = await jobsRes.json();
46
+
47
+ if (jobs.length === 0) {
48
+ return [];
49
+ }
50
+
51
+ const mostRecentJob = jobs[jobs.length - 1];
52
+
53
+ const allocationsRes = await fetch(`http://localhost:4646/v1/job/${mostRecentJob.ID}/allocations`);
54
+
55
+ if (!allocationsRes.ok) {
56
+ console.log(`request local nomad API failed`);
57
+
58
+ return [];
59
+ }
60
+
61
+ const allocations = await allocationsRes.json();
62
+
63
+ if (allocations.length === 0) {
64
+ return [];
65
+ }
66
+
67
+ const mostRecentAllocation = allocations[allocations.length - 1];
68
+
69
+ const tasks = Object.keys(mostRecentAllocation.TaskStates);
70
+
71
+ for (let task of tasks) {
72
+ if (mostRecentAllocation.TaskStates[task].Failed) {
73
+ // TODO if we could include a link to the failed allocation
74
+ // which could be very useful
75
+ return [
76
+ {
77
+ type: ERROR_LEVEL.CRITICAL,
78
+ message: `Job ${name}'s most recent allocation failed, please investigate`,
79
+ },
80
+ ];
81
+ }
82
+ }
83
+
84
+ return [];
85
+ }
86
+
87
+ function createMonitor({
88
+ binaryName,
89
+ name,
90
+ type = "stateless",
91
+ mode = false,
92
+ slackChannel,
93
+ selector = {},
94
+ check,
95
+ maxRetry = 2,
96
+ }) {
97
+ function monitor() {
98
+ if (!binaryName) {
99
+ binaryName = getBinaryName();
100
+ }
101
+
102
+ const cli = meow(
103
+ `
104
+ Usage
105
+ $ ${binaryName} <options>
106
+
107
+ Options
108
+ ${
109
+ mode ? " --mode could be delta/rebuild/resume-rebuild/validate/one/range/reset\n" : ""
110
+ }${getSelectorDesc(selector)}
111
+ --verbose Output debug messages
112
+ `,
113
+ {
114
+ description: false,
115
+ version: false,
116
+ flags: {
117
+ ...getSelectorFlags(selector),
118
+ ...(mode && {
119
+ mode: {
120
+ type: "string",
121
+ default: "delta",
122
+ },
123
+ }),
124
+ verbose: {
125
+ type: "boolean",
126
+ default: false,
127
+ },
128
+ },
129
+ }
130
+ );
131
+
132
+ async function runCheck({ verbose, mode, ...selectorFlags }) {
133
+ const startTime = Date.now();
134
+ console.log(`[MONITOR] starting check, ${toSelectorString(selectorFlags, ", ")}`);
135
+
136
+ const state = {};
137
+
138
+ if (type === "stateful") {
139
+ state.latestId = await getIndexerLatestId(name, selectorFlags);
140
+ state.validatedId = await getIndexerValidatedId(name, selectorFlags);
141
+ state.buildState = await getIndexerState(name, selectorFlags);
142
+ }
143
+
144
+ let result = await exponentialRetry(
145
+ async () => {
146
+ try {
147
+ const errors = await check({ verbose, state, mode, ...selectorFlags });
148
+
149
+ if (!Array.isArray(errors)) {
150
+ throw new Error(`check function must return array of error messages`);
151
+ }
152
+
153
+ return errors;
154
+ } catch (err) {
155
+ console.log(`[MONITOR] got error in check`, err);
156
+
157
+ return [`${err.message}`];
158
+ }
159
+ },
160
+ {
161
+ maxRetry,
162
+ initialDuration: 10000,
163
+ growFactor: 3,
164
+ test: (r) => r.length === 0,
165
+ verbose,
166
+ }
167
+ );
168
+
169
+ const allocationErrors = await checkMostRecentAllocationStatus(name);
170
+
171
+ result = result.concat(allocationErrors);
172
+
173
+ if (result.length > 0) {
174
+ console.log("Found Errors", result);
175
+
176
+ if (slackChannel) {
177
+ await postMessage(slackChannel, {
178
+ text: `[Monitor] ${name} Job Errors: ${result.join("\n")}`,
179
+ blocks: [
180
+ {
181
+ type: "header",
182
+ text: {
183
+ type: "plain_text",
184
+ text: `${name} Monitor Errors`,
185
+ emoji: true,
186
+ },
187
+ },
188
+ {
189
+ type: "divider",
190
+ },
191
+ ...sortErrors(result)
192
+ .map((m) => {
193
+ return [
194
+ {
195
+ type: "context",
196
+ elements: [
197
+ {
198
+ type: "plain_text",
199
+ text: `${LEVEL_EMOJI[m.type || ERROR_LEVEL.CRITICAL]} ${m.type || ERROR_LEVEL.CRITICAL}`,
200
+ },
201
+ ],
202
+ },
203
+ {
204
+ type: "section",
205
+ text: {
206
+ type: "mrkdwn",
207
+ text: m.message || m,
208
+ },
209
+ },
210
+ ];
211
+ })
212
+ .flat(),
213
+ {
214
+ type: "actions",
215
+ elements: [
216
+ {
217
+ type: "button",
218
+ text: {
219
+ type: "plain_text",
220
+ text: "View Details",
221
+ },
222
+ value: "view_details",
223
+ url: `${getNomadUrl()}/ui/jobs/${getJobName(name, selectorFlags, mode)}`,
224
+ },
225
+ ],
226
+ },
227
+ ],
228
+ });
229
+ }
230
+
231
+ throw new Error(`[MONITOR] failed due to critical errors`);
232
+ }
233
+
234
+ console.log(`[MONITOR] check successfully in ${Date.now() - startTime}ms`);
235
+ }
236
+
237
+ runCheck(cli.flags).catch((err) => {
238
+ console.error(err);
239
+ process.exit(1);
240
+ });
241
+ }
242
+
243
+ return { monitor };
244
+ }
245
+
246
+ module.exports = { createMonitor, ERROR_LEVEL };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@certik/skynet",
3
- "version": "0.7.17",
3
+ "version": "0.8.0",
4
4
  "description": "Skynet Shared JS library",
5
5
  "main": "index.js",
6
6
  "author": "CertiK Engineering",