@certik/skynet 0.17.0 → 0.18.1
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/CHANGELOG.md +11 -0
- package/app.d.ts +0 -24
- package/app.js +2 -170
- package/bun.lockb +0 -0
- package/deploy.js +4 -2
- package/dynamodb.js +1 -2
- package/env.d.ts +0 -10
- package/env.js +0 -30
- package/indexer.js +1 -1
- package/opsgenie.d.ts +1 -0
- package/opsgenie.js +2 -2
- package/package.json +11 -12
- package/s3.js +5 -2
- package/search.d.ts +6 -0
- package/search.js +29 -0
- package/sqs.js +1 -2
- package/kafka.d.ts +0 -31
- package/kafka.js +0 -445
- package/monitor.d.ts +0 -24
- package/monitor.js +0 -197
- package/opensearch.d.ts +0 -16
- package/opensearch.js +0 -34
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.18.1
|
|
4
|
+
|
|
5
|
+
- BREAKING: opensearch feature has been replaced by elastic search
|
|
6
|
+
- Fixed: s3 library listKeys function when search result is empty
|
|
7
|
+
|
|
8
|
+
## 0.18.0
|
|
9
|
+
|
|
10
|
+
- BREAKING: remove dependencies on `SKYNET_AWS_ACCESS_KEY_ID` and `SKYNET_AWS_SECRET_ACCESS_KEY` and `SKYNET_AWS_REGION`
|
|
11
|
+
- BREAKING: rename OPSGENIE_API_KEY to SKYNET_OPSGENIE_API_KEY
|
|
12
|
+
- BREAKING: removed producer and consumer support
|
|
13
|
+
|
|
3
14
|
## 0.17.0
|
|
4
15
|
|
|
5
16
|
- BREAKING: changed the way an indexer integrate with added doppler integration support in deploy
|
package/app.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ERROR_LEVEL } from "./monitor";
|
|
2
1
|
import { Selector } from "./selector";
|
|
3
2
|
import express from "express";
|
|
4
3
|
|
|
@@ -110,8 +109,6 @@ type ValidateWithConcurrency = Validate & { concurrency: number | undefined };
|
|
|
110
109
|
|
|
111
110
|
export const SENSITIVE_VALUE: null;
|
|
112
111
|
|
|
113
|
-
export { ERROR_LEVEL };
|
|
114
|
-
|
|
115
112
|
export function every(n: number | undefined): {
|
|
116
113
|
second: string;
|
|
117
114
|
seconds: string;
|
|
@@ -134,30 +131,10 @@ export function api(apiParams: {
|
|
|
134
131
|
beforeListen: (args: { app: ReturnType<typeof express> }) => void;
|
|
135
132
|
}): () => void;
|
|
136
133
|
|
|
137
|
-
export function consumer(consumerParams: {
|
|
138
|
-
name: string;
|
|
139
|
-
selector?: Selector;
|
|
140
|
-
consume: Consume;
|
|
141
|
-
check?: Check;
|
|
142
|
-
env: Env | undefined;
|
|
143
|
-
region: string | undefined;
|
|
144
|
-
}): () => void;
|
|
145
|
-
|
|
146
|
-
export function producer(producerParams: {
|
|
147
|
-
name: string;
|
|
148
|
-
selector?: Selector;
|
|
149
|
-
produce: Produce;
|
|
150
|
-
check?: Check;
|
|
151
|
-
state: State;
|
|
152
|
-
env?: Env;
|
|
153
|
-
region?: string;
|
|
154
|
-
}): () => void;
|
|
155
|
-
|
|
156
134
|
export function indexer(indexerParams: {
|
|
157
135
|
name: string;
|
|
158
136
|
selector?: Selector;
|
|
159
137
|
build: BuildWithRestart;
|
|
160
|
-
check?: Check;
|
|
161
138
|
env?: Env;
|
|
162
139
|
region?: string;
|
|
163
140
|
}): () => void;
|
|
@@ -168,7 +145,6 @@ export function modeIndexer(modeIndexerParams: {
|
|
|
168
145
|
state: State;
|
|
169
146
|
build: BuildWithConcurrency;
|
|
170
147
|
validate?: ValidateWithConcurrency;
|
|
171
|
-
check?: Check;
|
|
172
148
|
env?: Env;
|
|
173
149
|
region?: string;
|
|
174
150
|
}): () => void;
|
package/app.js
CHANGED
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
import { EOL } from "os";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
createIndexerApp,
|
|
5
|
-
createModeIndexerApp,
|
|
6
|
-
getIndexerLatestId,
|
|
7
|
-
getIndexerValidatedId,
|
|
8
|
-
getIndexerState,
|
|
9
|
-
} from "./indexer.js";
|
|
10
|
-
|
|
2
|
+
import { createIndexerApp, createModeIndexerApp } from "./indexer.js";
|
|
11
3
|
import { createDeploy, createModeDeploy } from "./deploy.js";
|
|
12
|
-
import { createProducerApp, createConsumerApp, getProducerLatestId } from "./kafka.js";
|
|
13
4
|
import { startApiApp } from "./api.js";
|
|
14
|
-
import { createMonitor, ERROR_LEVEL } from "./monitor.js";
|
|
15
5
|
import { getBinaryName, detectBin, detectWorkingDirectory } from "./cli.js";
|
|
16
6
|
|
|
17
7
|
function printAppHelp() {
|
|
@@ -261,164 +251,6 @@ function modeIndexer({ name, selector, state, build, validate, env = {}, region
|
|
|
261
251
|
});
|
|
262
252
|
}
|
|
263
253
|
|
|
264
|
-
function checkProducerProduceParameter(produce) {
|
|
265
|
-
const errors = [];
|
|
266
|
-
|
|
267
|
-
if (!produce?.func) {
|
|
268
|
-
errors.push("must define produce.func");
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (!produce?.topic) {
|
|
272
|
-
errors.push("must define produce.topic");
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (!produce?.deadLetterTopic) {
|
|
276
|
-
errors.push("must define produce.deadLetterTopic");
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (!produce?.cpu) {
|
|
280
|
-
errors.push("must define produce.cpu");
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
if (!produce?.mem) {
|
|
284
|
-
errors.push("must define produce.mem");
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return errors;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function producer({ name, selector, produce, state, env = {}, region = "skynet-dc1" }) {
|
|
291
|
-
const envWithDefaultValues = {
|
|
292
|
-
SKYNET_KAFKA_SERVER: SENSITIVE_VALUE,
|
|
293
|
-
SKYNET_KAFKA_USERNAME: SENSITIVE_VALUE,
|
|
294
|
-
SKYNET_KAFKA_PASSWORD: SENSITIVE_VALUE,
|
|
295
|
-
...env,
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
return createApp({
|
|
299
|
-
parameterErrors: [
|
|
300
|
-
...checkProducerProduceParameter(produce),
|
|
301
|
-
...checkStateParameter(state),
|
|
302
|
-
...checkEnvParameter(env),
|
|
303
|
-
],
|
|
304
|
-
env: envWithDefaultValues,
|
|
305
|
-
onRun: () => {
|
|
306
|
-
const { run } = createProducerApp({
|
|
307
|
-
binaryName: `${getBinaryName()} run`,
|
|
308
|
-
name,
|
|
309
|
-
selector,
|
|
310
|
-
|
|
311
|
-
producer: {
|
|
312
|
-
topic: produce.topic,
|
|
313
|
-
deadLetterTopic: produce.deadLetterTopic,
|
|
314
|
-
produce: produce.func,
|
|
315
|
-
batchSize: produce.batchSize,
|
|
316
|
-
maxRetry: produce.maxRetry,
|
|
317
|
-
},
|
|
318
|
-
|
|
319
|
-
state,
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
process.title = name;
|
|
323
|
-
|
|
324
|
-
return run();
|
|
325
|
-
},
|
|
326
|
-
onDeploy: () => {
|
|
327
|
-
const bin = detectBin();
|
|
328
|
-
const needDoppler = Object.values(env).some((v) => v === SENSITIVE_VALUE);
|
|
329
|
-
|
|
330
|
-
const { deploy } = createDeploy({
|
|
331
|
-
binaryName: `${getBinaryName()} deploy`,
|
|
332
|
-
name,
|
|
333
|
-
workingDirectory: detectWorkingDirectory(),
|
|
334
|
-
bin: needDoppler ? `doppler run -- ${bin} run` : `${bin} run`,
|
|
335
|
-
selector,
|
|
336
|
-
region,
|
|
337
|
-
env: envWithDefaultValues,
|
|
338
|
-
schedule: "@minutely",
|
|
339
|
-
killTimeout: produce.killTimeout,
|
|
340
|
-
cpu: produce.cpu,
|
|
341
|
-
mem: produce.mem,
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
return deploy();
|
|
345
|
-
},
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
function checkConsumerConsumeParameter(consume) {
|
|
350
|
-
const errors = [];
|
|
351
|
-
|
|
352
|
-
if (!consume?.func) {
|
|
353
|
-
errors.push("must define consume.func");
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
if (!consume?.topic) {
|
|
357
|
-
errors.push("must define consume.topic");
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
if (!consume?.cpu) {
|
|
361
|
-
errors.push("must define consume.cpu");
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
if (!consume?.mem) {
|
|
365
|
-
errors.push("must define consume.mem");
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
return errors;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function consumer({ name, selector, consume, env = {}, region = "skynet-dc1" }) {
|
|
372
|
-
const envWithDefaultValues = {
|
|
373
|
-
SKYNET_KAFKA_SERVER: SENSITIVE_VALUE,
|
|
374
|
-
SKYNET_KAFKA_USERNAME: SENSITIVE_VALUE,
|
|
375
|
-
SKYNET_KAFKA_PASSWORD: SENSITIVE_VALUE,
|
|
376
|
-
...env,
|
|
377
|
-
};
|
|
378
|
-
|
|
379
|
-
return createApp({
|
|
380
|
-
parameterErrors: [...checkConsumerConsumeParameter(consume), ...checkEnvParameter(env)],
|
|
381
|
-
env: envWithDefaultValues,
|
|
382
|
-
onRun: () => {
|
|
383
|
-
const { run } = createConsumerApp({
|
|
384
|
-
binaryName: `${getBinaryName()} run`,
|
|
385
|
-
name,
|
|
386
|
-
selector,
|
|
387
|
-
|
|
388
|
-
consumer: {
|
|
389
|
-
topic: consume.topic,
|
|
390
|
-
consume: consume.func,
|
|
391
|
-
maxRetry: consume.maxRetry,
|
|
392
|
-
},
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
process.title = name;
|
|
396
|
-
|
|
397
|
-
return run();
|
|
398
|
-
},
|
|
399
|
-
onDeploy: () => {
|
|
400
|
-
const bin = detectBin();
|
|
401
|
-
const needDoppler = Object.values(env).some((v) => v === SENSITIVE_VALUE);
|
|
402
|
-
|
|
403
|
-
const { deploy } = createDeploy({
|
|
404
|
-
binaryName: `${getBinaryName()} deploy`,
|
|
405
|
-
name,
|
|
406
|
-
workingDirectory: detectWorkingDirectory(),
|
|
407
|
-
bin: needDoppler ? `doppler run -- ${bin} run` : `${bin} run`,
|
|
408
|
-
selector,
|
|
409
|
-
region,
|
|
410
|
-
env: envWithDefaultValues,
|
|
411
|
-
schedule: "@minutely",
|
|
412
|
-
killTimeout: consume.killTimeout,
|
|
413
|
-
cpu: consume.cpu,
|
|
414
|
-
mem: consume.mem,
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
return deploy();
|
|
418
|
-
},
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
|
-
|
|
422
254
|
function checkApiServeParameter(serve, routes) {
|
|
423
255
|
const errors = [];
|
|
424
256
|
|
|
@@ -560,4 +392,4 @@ const every = (n = 1) => {
|
|
|
560
392
|
};
|
|
561
393
|
};
|
|
562
394
|
|
|
563
|
-
export { indexer, modeIndexer,
|
|
395
|
+
export { indexer, modeIndexer, api, every, SENSITIVE_VALUE };
|
package/bun.lockb
CHANGED
|
Binary file
|
package/deploy.js
CHANGED
|
@@ -63,7 +63,9 @@ const genConfig = ({
|
|
|
63
63
|
|
|
64
64
|
group "default" {
|
|
65
65
|
${count && count > 1 ? `count = ${count}` : ""}
|
|
66
|
-
${
|
|
66
|
+
${
|
|
67
|
+
count && count > 1
|
|
68
|
+
? `# Rolling Update
|
|
67
69
|
update {
|
|
68
70
|
max_parallel = 1
|
|
69
71
|
min_healthy_time = "10s"
|
|
@@ -137,7 +139,7 @@ const genConfig = ({
|
|
|
137
139
|
DOPPLER_CONFIG="${isProduction ? "prd" : "dev"}"
|
|
138
140
|
SKYNET_ENVIRONMENT="${isProduction ? "prd" : "dev"}"
|
|
139
141
|
${Object.entries(additionalEnv)
|
|
140
|
-
.filter((
|
|
142
|
+
.filter((kv) => !!kv[1])
|
|
141
143
|
.map(([key, value]) => `${key}="${value}"`)
|
|
142
144
|
.join(" \n")}
|
|
143
145
|
}
|
package/dynamodb.js
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
TransactWriteCommand,
|
|
10
10
|
} from "@aws-sdk/lib-dynamodb";
|
|
11
11
|
import { DynamoDBClient, DescribeTableCommand } from "@aws-sdk/client-dynamodb";
|
|
12
|
-
import { getAWSSDKConfig } from "./env.js";
|
|
13
12
|
import { wait } from "./availability.js";
|
|
14
13
|
|
|
15
14
|
let _dynamoDB;
|
|
@@ -18,7 +17,7 @@ let _docClient;
|
|
|
18
17
|
|
|
19
18
|
function getDynamoDB(forceNew = false) {
|
|
20
19
|
if (!_dynamoDB || forceNew) {
|
|
21
|
-
_dynamoDB = new DynamoDBClient(
|
|
20
|
+
_dynamoDB = new DynamoDBClient();
|
|
22
21
|
}
|
|
23
22
|
return _dynamoDB;
|
|
24
23
|
}
|
package/env.d.ts
CHANGED
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
export function ensureAndGet<T>(envName: string, defaultValue: T): string | T;
|
|
2
2
|
export function getEnvOrThrow(envName: string): string;
|
|
3
|
-
export function getAWSAccessKeyId(): string | undefined;
|
|
4
|
-
export function getAWSSecretAccessKey(): string | undefined;
|
|
5
|
-
export function getAWSSDKConfig(): {
|
|
6
|
-
region: string;
|
|
7
|
-
credentials?: {
|
|
8
|
-
accessKeyId: string;
|
|
9
|
-
secretAccessKey: string;
|
|
10
|
-
};
|
|
11
|
-
};
|
|
12
|
-
export function getAWSRegion(): string;
|
|
13
3
|
export function getEnvironment(): string;
|
|
14
4
|
export function getNodeRealApiKey(identifier: string): string | undefined;
|
|
15
5
|
export function isProduction(): boolean;
|
package/env.js
CHANGED
|
@@ -1,29 +1,3 @@
|
|
|
1
|
-
function getAWSAccessKeyId() {
|
|
2
|
-
return getEnvOrThrow("SKYNET_AWS_ACCESS_KEY_ID");
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
function getAWSSecretAccessKey() {
|
|
6
|
-
return getEnvOrThrow("SKYNET_AWS_SECRET_ACCESS_KEY");
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function getAWSSDKConfig() {
|
|
10
|
-
const region = getAWSRegion();
|
|
11
|
-
const accessKeyId = getAWSAccessKeyId();
|
|
12
|
-
const secretAccessKey = getAWSSecretAccessKey();
|
|
13
|
-
|
|
14
|
-
return {
|
|
15
|
-
region,
|
|
16
|
-
credentials: {
|
|
17
|
-
accessKeyId,
|
|
18
|
-
secretAccessKey,
|
|
19
|
-
},
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function getAWSRegion() {
|
|
24
|
-
return ensureAndGet(["SKYNET_AWS_REGION"], "us-east-1");
|
|
25
|
-
}
|
|
26
|
-
|
|
27
1
|
function getEnvironment() {
|
|
28
2
|
return ensureAndGet("SKYNET_ENVIRONMENT", "dev");
|
|
29
3
|
}
|
|
@@ -68,10 +42,6 @@ function isDev() {
|
|
|
68
42
|
export {
|
|
69
43
|
ensureAndGet,
|
|
70
44
|
getEnvOrThrow,
|
|
71
|
-
getAWSAccessKeyId,
|
|
72
|
-
getAWSSecretAccessKey,
|
|
73
|
-
getAWSSDKConfig,
|
|
74
|
-
getAWSRegion,
|
|
75
45
|
getEnvironment,
|
|
76
46
|
isProduction,
|
|
77
47
|
isDev,
|
package/indexer.js
CHANGED
|
@@ -547,7 +547,7 @@ ${getSelectorDesc(selector)}
|
|
|
547
547
|
// for those indexers that does not rely on a curosr
|
|
548
548
|
// e.g. should always rebuild everything from scratch
|
|
549
549
|
// or that the state can be easily inferred from existing data
|
|
550
|
-
function createIndexerApp({ binaryName,
|
|
550
|
+
function createIndexerApp({ binaryName, selector = {}, build, maxRetry = 2 }) {
|
|
551
551
|
function run() {
|
|
552
552
|
if (!binaryName) {
|
|
553
553
|
binaryName = getBinaryName();
|
package/opsgenie.d.ts
CHANGED
package/opsgenie.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import md5 from "md5";
|
|
2
2
|
|
|
3
3
|
function getGenieKey(key) {
|
|
4
|
-
return key || process.env.
|
|
4
|
+
return key || process.env.SKYNET_OPSGENIE_API_KEY;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
function getGenieEndPoint() {
|
|
8
|
-
return process.env.
|
|
8
|
+
return process.env.SKYNET_OPSGENIE_END_POINT || "https://api.opsgenie.com/v2/alerts";
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export async function postGenieMessage(body, apiKey, verbose) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@certik/skynet",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.1",
|
|
4
4
|
"description": "Skynet Shared JS library",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -19,25 +19,24 @@
|
|
|
19
19
|
"@aws-sdk/client-s3": "^3.478.0",
|
|
20
20
|
"@aws-sdk/client-sqs": "^3.478.0",
|
|
21
21
|
"@aws-sdk/lib-dynamodb": "^3.478.0",
|
|
22
|
-
"@
|
|
22
|
+
"@elastic/elasticsearch": "^8.14.0",
|
|
23
23
|
"@slack/web-api": "^6.11.0",
|
|
24
24
|
"bottleneck": "^2.19.5",
|
|
25
25
|
"chalk": "^5.3.0",
|
|
26
|
-
"execa": "^
|
|
27
|
-
"express": "^4.
|
|
28
|
-
"kafkajs": "^2.2.4",
|
|
26
|
+
"execa": "^9.3.0",
|
|
27
|
+
"express": "^4.19.2",
|
|
29
28
|
"md5": "^2.3.0",
|
|
30
|
-
"meow": "^
|
|
31
|
-
"snowflake-sdk": "^1.
|
|
32
|
-
"web3": "^4.
|
|
29
|
+
"meow": "^13.2.0",
|
|
30
|
+
"snowflake-sdk": "^1.11.0",
|
|
31
|
+
"web3": "^4.11.0",
|
|
33
32
|
"which": "^4.0.0"
|
|
34
33
|
},
|
|
35
34
|
"devDependencies": {
|
|
36
|
-
"ava": "^6.
|
|
37
|
-
"eslint": "
|
|
35
|
+
"ava": "^6.1.3",
|
|
36
|
+
"eslint": "8",
|
|
38
37
|
"eslint-plugin-import": "^2.29.1",
|
|
39
|
-
"prettier": "^3.
|
|
40
|
-
"sinon": "^
|
|
38
|
+
"prettier": "^3.3.3",
|
|
39
|
+
"sinon": "^18.0.0"
|
|
41
40
|
},
|
|
42
41
|
"license": "MIT",
|
|
43
42
|
"publishConfig": {
|
package/s3.js
CHANGED
|
@@ -8,13 +8,12 @@ import {
|
|
|
8
8
|
NotFound,
|
|
9
9
|
NoSuchKey,
|
|
10
10
|
} from "@aws-sdk/client-s3";
|
|
11
|
-
import { getAWSSDKConfig } from "./env.js";
|
|
12
11
|
|
|
13
12
|
let _s3Client;
|
|
14
13
|
|
|
15
14
|
function getS3(forceNew = false) {
|
|
16
15
|
if (!_s3Client || forceNew) {
|
|
17
|
-
_s3Client = new S3Client(
|
|
16
|
+
_s3Client = new S3Client();
|
|
18
17
|
}
|
|
19
18
|
return _s3Client;
|
|
20
19
|
}
|
|
@@ -136,6 +135,10 @@ async function listKeys(bucketname, prefix, continuationToken) {
|
|
|
136
135
|
throw `unable to list keys with prefix ${prefix}: ${err}`;
|
|
137
136
|
}
|
|
138
137
|
|
|
138
|
+
if (!data.Contents) {
|
|
139
|
+
return { keys: [] };
|
|
140
|
+
}
|
|
141
|
+
|
|
139
142
|
let res = { keys: data.Contents.map(({ Key }) => Key) };
|
|
140
143
|
if (data.IsTruncated) {
|
|
141
144
|
res.continuationToken = data.NextContinuationToken;
|
package/search.d.ts
ADDED
package/search.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { isProduction } from "./env.js";
|
|
2
|
+
import { inline } from "./log.js";
|
|
3
|
+
import { Client } from "@elastic/elasticsearch";
|
|
4
|
+
import osModule from "os";
|
|
5
|
+
|
|
6
|
+
export async function sendToSearch(appName, indexPrefix, record, throws = false) {
|
|
7
|
+
const client = new Client({
|
|
8
|
+
cloud: { id: process.env.SKYNET_ELASTICSEARCH_CLOUD_ID },
|
|
9
|
+
auth: { apiKey: process.env.SKYNET_ELASTICSEARCH_API_KEY },
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const now = new Date();
|
|
13
|
+
const indexName = [indexPrefix, isProduction() ? "prod" : "dev", now.toISOString().slice(0, 7)].join("-");
|
|
14
|
+
|
|
15
|
+
// best as possible delivery
|
|
16
|
+
// by default not throw
|
|
17
|
+
try {
|
|
18
|
+
await client.index({
|
|
19
|
+
index: indexName,
|
|
20
|
+
document: { app: appName, instance: osModule.hostname(), timestamp: now, record },
|
|
21
|
+
});
|
|
22
|
+
} catch (err) {
|
|
23
|
+
inline.error(err);
|
|
24
|
+
|
|
25
|
+
if (throws) {
|
|
26
|
+
throw err;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
package/sqs.js
CHANGED
package/kafka.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { KafkaMessage } from "kafkajs";
|
|
2
|
-
import { Produce, Consume, State } from "./app";
|
|
3
|
-
import { SelectorFlags, Selector } from "./selector";
|
|
4
|
-
|
|
5
|
-
export function createProducerApp(createProducerAppParams: {
|
|
6
|
-
binaryName: string;
|
|
7
|
-
name: string;
|
|
8
|
-
selector?: Selector;
|
|
9
|
-
producer: Produce;
|
|
10
|
-
state: State;
|
|
11
|
-
}): { run: () => Promise<void> };
|
|
12
|
-
|
|
13
|
-
export function createConsumerApp(createConsumerAppParams: {
|
|
14
|
-
binaryName: string;
|
|
15
|
-
name: string;
|
|
16
|
-
selector?: Selector;
|
|
17
|
-
consumer: Consume;
|
|
18
|
-
}): { run: () => Promise<void> };
|
|
19
|
-
|
|
20
|
-
export function produceMessages(
|
|
21
|
-
producerId: string,
|
|
22
|
-
callback: (send: <T>(topic: string, messages: T) => Promise<void>) => Promise<void>,
|
|
23
|
-
): Promise<void>;
|
|
24
|
-
|
|
25
|
-
export function consumeMessages(
|
|
26
|
-
consumerId: string,
|
|
27
|
-
topic: string | RegExp,
|
|
28
|
-
callback: (message: KafkaMessage, stopConsumeMessages: () => Promise<void>) => Promise<void>,
|
|
29
|
-
): Promise<() => Promise<void>>;
|
|
30
|
-
|
|
31
|
-
export function getProducerLatestId(name: string, selectorFlags: SelectorFlags): Promise<number>;
|
package/kafka.js
DELETED
|
@@ -1,445 +0,0 @@
|
|
|
1
|
-
import meow from "meow";
|
|
2
|
-
import { Kafka, logLevel } from "kafkajs";
|
|
3
|
-
import { getEnvironment, getEnvOrThrow } from "./env.js";
|
|
4
|
-
import { getSelectorFlags, getSelectorDesc, toSelectorString } from "./selector.js";
|
|
5
|
-
import { createRecord, getRecordByKey, deleteRecordsByHashKey } from "./dynamodb.js";
|
|
6
|
-
import { wait, exponentialRetry } from "./availability.js";
|
|
7
|
-
import { getBinaryName } from "./cli.js";
|
|
8
|
-
import { inline } from "./log.js";
|
|
9
|
-
|
|
10
|
-
const STATE_TABLE_NAME = "skynet-" + getEnvironment() + "-indexer-state";
|
|
11
|
-
|
|
12
|
-
async function getProducerLatestId(name, selectorFlags) {
|
|
13
|
-
const record = await getRecordByKey(STATE_TABLE_NAME, {
|
|
14
|
-
name: `${name}At(${toSelectorString(selectorFlags)})`,
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
if (record) {
|
|
18
|
-
return record.value;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return 0;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async function setProducerLatestId(name, selectorFlags, value) {
|
|
25
|
-
await createRecord(STATE_TABLE_NAME, {
|
|
26
|
-
name: `${name}At(${toSelectorString(selectorFlags)})`,
|
|
27
|
-
value,
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function deleteProducerLatestId(name, selectorFlags, verbose) {
|
|
32
|
-
await deleteRecordsByHashKey(STATE_TABLE_NAME, null, `${name}At(${toSelectorString(selectorFlags)})`, verbose);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function sendToTopic(producer, topic, verbose) {
|
|
36
|
-
async function send(records) {
|
|
37
|
-
if (verbose) {
|
|
38
|
-
inline.log(`sending ${records.length} records to topic`, records);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
await producer.send({
|
|
42
|
-
topic,
|
|
43
|
-
messages: records.map((r) => ({
|
|
44
|
-
value: Buffer.from(typeof r === "string" ? r : JSON.stringify(r)),
|
|
45
|
-
})),
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return send;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function getDefaultKafkaCredential() {
|
|
53
|
-
return {
|
|
54
|
-
server: getEnvOrThrow("SKYNET_KAFKA_SERVER"),
|
|
55
|
-
username: getEnvOrThrow("SKYNET_KAFKA_USERNAME"),
|
|
56
|
-
password: getEnvOrThrow("SKYNET_KAFKA_PASSWORD"),
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function initProducer(clientId, customKafkaCredential = undefined) {
|
|
61
|
-
const kafkaCredential = customKafkaCredential ?? getDefaultKafkaCredential();
|
|
62
|
-
const kafka = new Kafka({
|
|
63
|
-
clientId,
|
|
64
|
-
brokers: [kafkaCredential.server],
|
|
65
|
-
logLevel: logLevel.ERROR,
|
|
66
|
-
ssl: true,
|
|
67
|
-
sasl: {
|
|
68
|
-
mechanism: "plain",
|
|
69
|
-
username: kafkaCredential.username,
|
|
70
|
-
password: kafkaCredential.password,
|
|
71
|
-
},
|
|
72
|
-
});
|
|
73
|
-
const producerInstance = kafka.producer();
|
|
74
|
-
await producerInstance.connect();
|
|
75
|
-
|
|
76
|
-
return producerInstance;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function closeProducer(producerInstance) {
|
|
80
|
-
await producerInstance.disconnect();
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async function initConsumer(clientId, customKafkaCredential = undefined) {
|
|
84
|
-
const kafkaCredential = customKafkaCredential ? customKafkaCredential : getDefaultKafkaCredential();
|
|
85
|
-
const kafka = new Kafka({
|
|
86
|
-
clientId,
|
|
87
|
-
brokers: [kafkaCredential.server],
|
|
88
|
-
logLevel: logLevel.ERROR,
|
|
89
|
-
ssl: true,
|
|
90
|
-
sasl: {
|
|
91
|
-
mechanism: "plain",
|
|
92
|
-
username: kafkaCredential.username,
|
|
93
|
-
password: kafkaCredential.password,
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
const consumerInstance = kafka.consumer({ groupId: clientId });
|
|
97
|
-
|
|
98
|
-
await consumerInstance.connect();
|
|
99
|
-
|
|
100
|
-
return consumerInstance;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async function closeConsumer(consumerInstance) {
|
|
104
|
-
await consumerInstance.disconnect();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async function produceMessages(producerId, callback) {
|
|
108
|
-
const producerInstance = await initProducer(producerId);
|
|
109
|
-
const send = async (topic, messages) => {
|
|
110
|
-
let records;
|
|
111
|
-
if (Array.isArray(messages)) {
|
|
112
|
-
records = messages;
|
|
113
|
-
} else {
|
|
114
|
-
records = [messages];
|
|
115
|
-
}
|
|
116
|
-
return sendToTopic(producerInstance, topic, false)(records);
|
|
117
|
-
};
|
|
118
|
-
await callback(send);
|
|
119
|
-
await closeProducer(producerInstance);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async function consumeMessages(consumerId, topic, callback) {
|
|
123
|
-
const consumerInstance = await initConsumer(consumerId);
|
|
124
|
-
|
|
125
|
-
await consumerInstance.subscribe({ topic });
|
|
126
|
-
|
|
127
|
-
const stopConsumeMessages = async () => {
|
|
128
|
-
await closeConsumer(consumerInstance);
|
|
129
|
-
};
|
|
130
|
-
await consumerInstance.run({
|
|
131
|
-
eachMessage: async ({ topic: receivedTopic, message }) => {
|
|
132
|
-
if (topic === receivedTopic) {
|
|
133
|
-
await callback(message, stopConsumeMessages);
|
|
134
|
-
}
|
|
135
|
-
},
|
|
136
|
-
});
|
|
137
|
-
return stopConsumeMessages;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function createProducerApp({ binaryName, name, selector = {}, producer, state }) {
|
|
141
|
-
function run() {
|
|
142
|
-
if (!binaryName) {
|
|
143
|
-
binaryName = getBinaryName();
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const finalState = {
|
|
147
|
-
type: "block",
|
|
148
|
-
updateInterval: 5000,
|
|
149
|
-
getMinId: async () => 1,
|
|
150
|
-
getMaxId: async () => {
|
|
151
|
-
throw new Error("must implement state.getMaxId");
|
|
152
|
-
},
|
|
153
|
-
...state,
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
const cli = meow(
|
|
157
|
-
`
|
|
158
|
-
Usage
|
|
159
|
-
$ ${binaryName} <options>
|
|
160
|
-
|
|
161
|
-
Options
|
|
162
|
-
${getSelectorDesc(selector)}
|
|
163
|
-
--from min ${finalState.type} to produce
|
|
164
|
-
--to max ${finalState.type} to produce
|
|
165
|
-
--status print current status and exit
|
|
166
|
-
--verbose Output debug messages
|
|
167
|
-
--reset restart producer
|
|
168
|
-
`,
|
|
169
|
-
{
|
|
170
|
-
importMeta: import.meta,
|
|
171
|
-
description: false,
|
|
172
|
-
version: false,
|
|
173
|
-
flags: {
|
|
174
|
-
...getSelectorFlags(selector),
|
|
175
|
-
from: {
|
|
176
|
-
aliases: ["since"],
|
|
177
|
-
type: "number",
|
|
178
|
-
default: 0,
|
|
179
|
-
},
|
|
180
|
-
to: {
|
|
181
|
-
aliases: ["until"],
|
|
182
|
-
type: "number",
|
|
183
|
-
default: 0,
|
|
184
|
-
},
|
|
185
|
-
verbose: {
|
|
186
|
-
type: "boolean",
|
|
187
|
-
default: false,
|
|
188
|
-
},
|
|
189
|
-
},
|
|
190
|
-
}
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
const { topic, deadLetterTopic, produce, batchSize, maxRetry } = {
|
|
194
|
-
batchSize: 50,
|
|
195
|
-
maxRetry: 2,
|
|
196
|
-
...producer,
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
const kafkaCredential = (producer && producer.kafkaCredential) || getDefaultKafkaCredential();
|
|
200
|
-
|
|
201
|
-
async function build({ from, to, status, reset, verbose, ...selectorFlags }) {
|
|
202
|
-
if (reset) {
|
|
203
|
-
await deleteProducerLatestId(name, selectorFlags);
|
|
204
|
-
if (finalState.onReset) {
|
|
205
|
-
await finalState.onReset(selectorFlags, verbose);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
inline.log("producer reset");
|
|
209
|
-
|
|
210
|
-
process.exit(0);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (status) {
|
|
214
|
-
const latestId = await getProducerLatestId(name, selectorFlags);
|
|
215
|
-
|
|
216
|
-
inline.log(`LatestID=${latestId}`);
|
|
217
|
-
|
|
218
|
-
process.exit(0);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (!from) {
|
|
222
|
-
const prevId = await getProducerLatestId(name, selectorFlags);
|
|
223
|
-
|
|
224
|
-
if (prevId) {
|
|
225
|
-
from = prevId + 1;
|
|
226
|
-
} else {
|
|
227
|
-
// initial build
|
|
228
|
-
from = await finalState.getMinId(selectorFlags);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const finalTopic = typeof topic === "function" ? topic(selectorFlags) : topic;
|
|
233
|
-
const finalDeadLetterTopic =
|
|
234
|
-
typeof deadLetterTopic === "function" ? deadLetterTopic(selectorFlags) : deadLetterTopic;
|
|
235
|
-
|
|
236
|
-
const updateInterval =
|
|
237
|
-
typeof finalState.updateInterval === "function"
|
|
238
|
-
? finalState.updateInterval(selectorFlags)
|
|
239
|
-
: finalState.updateInterval;
|
|
240
|
-
|
|
241
|
-
const producerInstance = await initProducer(name, kafkaCredential);
|
|
242
|
-
|
|
243
|
-
inline.log("producing to topic", finalTopic);
|
|
244
|
-
|
|
245
|
-
// if to flag was not provided, run this program indefinitely
|
|
246
|
-
const isLongRunning = to === 0;
|
|
247
|
-
|
|
248
|
-
let lastId = to > 0 ? to : await finalState.getMaxId(selectorFlags);
|
|
249
|
-
|
|
250
|
-
let pollHandle;
|
|
251
|
-
|
|
252
|
-
async function cleanUpForLongRunningMode() {
|
|
253
|
-
clearInterval(pollHandle);
|
|
254
|
-
|
|
255
|
-
await closeProducer(producerInstance);
|
|
256
|
-
|
|
257
|
-
process.exit(0);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
if (isLongRunning) {
|
|
261
|
-
pollHandle = setInterval(async () => {
|
|
262
|
-
lastId = await finalState.getMaxId(selectorFlags);
|
|
263
|
-
}, updateInterval);
|
|
264
|
-
|
|
265
|
-
// clean up before exit
|
|
266
|
-
process.on("SIGINT", cleanUpForLongRunningMode);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// the main loop
|
|
270
|
-
for (let i = from; isLongRunning || i <= lastId; ) {
|
|
271
|
-
if (i > lastId) {
|
|
272
|
-
console.log("no more items to process, sleep for", updateInterval, "ms");
|
|
273
|
-
|
|
274
|
-
await wait(updateInterval);
|
|
275
|
-
} else {
|
|
276
|
-
const batchStart = i;
|
|
277
|
-
const batchEnd = Math.min(batchStart + batchSize - 1, lastId);
|
|
278
|
-
|
|
279
|
-
inline.log(`building batch ${batchStart}~${batchEnd}`);
|
|
280
|
-
|
|
281
|
-
// add a retry for errors
|
|
282
|
-
const result = await exponentialRetry(
|
|
283
|
-
async () => {
|
|
284
|
-
try {
|
|
285
|
-
const failed = await produce({
|
|
286
|
-
...selectorFlags,
|
|
287
|
-
from: batchStart,
|
|
288
|
-
to: batchEnd,
|
|
289
|
-
verbose,
|
|
290
|
-
send: sendToTopic(producerInstance, finalTopic, verbose),
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
if (Array.isArray(failed) && failed.length > 0) {
|
|
294
|
-
await sendToTopic(producerInstance, finalDeadLetterTopic, verbose)(failed);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
return true;
|
|
298
|
-
} catch (err) {
|
|
299
|
-
inline.error(`Critical error processing batch ${batchStart}~${batchEnd}`, err);
|
|
300
|
-
|
|
301
|
-
return false;
|
|
302
|
-
}
|
|
303
|
-
},
|
|
304
|
-
{
|
|
305
|
-
maxRetry,
|
|
306
|
-
test: (r) => r,
|
|
307
|
-
verbose,
|
|
308
|
-
}
|
|
309
|
-
);
|
|
310
|
-
|
|
311
|
-
if (!result) {
|
|
312
|
-
if (isLongRunning) {
|
|
313
|
-
cleanUpForLongRunningMode();
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
throw new Error(`Terminate producer due to critical errors, batch ${batchStart}~${batchEnd}`);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
await setProducerLatestId(name, selectorFlags, batchEnd);
|
|
320
|
-
|
|
321
|
-
if (i + batchSize <= lastId) i += batchSize;
|
|
322
|
-
else i = batchEnd + 1;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
if (isLongRunning) {
|
|
326
|
-
lastId = await finalState.getMaxId(selectorFlags);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
await closeProducer(producerInstance);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
return build(cli.flags).catch((err) => {
|
|
334
|
-
inline.error(err);
|
|
335
|
-
process.exit(1);
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
return { run };
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
function createConsumerApp({ binaryName, name, selector = {}, consumer }) {
|
|
343
|
-
function run() {
|
|
344
|
-
if (!binaryName) {
|
|
345
|
-
binaryName = getBinaryName();
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
const cli = meow(
|
|
349
|
-
`
|
|
350
|
-
Usage
|
|
351
|
-
$ ${binaryName} <options>
|
|
352
|
-
|
|
353
|
-
Options
|
|
354
|
-
${getSelectorDesc(selector)}
|
|
355
|
-
--verbose Output debug messages
|
|
356
|
-
`,
|
|
357
|
-
{
|
|
358
|
-
importMeta: import.meta,
|
|
359
|
-
description: false,
|
|
360
|
-
version: false,
|
|
361
|
-
flags: {
|
|
362
|
-
...getSelectorFlags(selector),
|
|
363
|
-
verbose: {
|
|
364
|
-
type: "boolean",
|
|
365
|
-
default: false,
|
|
366
|
-
},
|
|
367
|
-
},
|
|
368
|
-
}
|
|
369
|
-
);
|
|
370
|
-
|
|
371
|
-
const { topic, consume, maxRetry } = {
|
|
372
|
-
maxRetry: 2,
|
|
373
|
-
...consumer,
|
|
374
|
-
};
|
|
375
|
-
|
|
376
|
-
const kafkaCredential = (consume && consumer.kafkaCredential) || getDefaultKafkaCredential();
|
|
377
|
-
|
|
378
|
-
async function build({ verbose, ...selectorFlags }) {
|
|
379
|
-
const consumerInstance = await initConsumer(name, kafkaCredential);
|
|
380
|
-
|
|
381
|
-
try {
|
|
382
|
-
const finalTopic = typeof topic === "function" ? topic(selectorFlags) : topic;
|
|
383
|
-
|
|
384
|
-
await consumerInstance.subscribe({ topic: finalTopic });
|
|
385
|
-
|
|
386
|
-
inline.log("subscribed to topic", finalTopic);
|
|
387
|
-
|
|
388
|
-
await consumerInstance.run({
|
|
389
|
-
eachBatch: async ({ batch }) => {
|
|
390
|
-
if (verbose) {
|
|
391
|
-
inline.log("received batch, number of items =", batch.messages.length);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
const messages = batch.messages.map((m) => JSON.parse(m.value));
|
|
395
|
-
|
|
396
|
-
// add a retry for errors
|
|
397
|
-
const result = await exponentialRetry(
|
|
398
|
-
async () => {
|
|
399
|
-
try {
|
|
400
|
-
await consume({ ...selectorFlags, messages, verbose });
|
|
401
|
-
|
|
402
|
-
return true;
|
|
403
|
-
} catch (err) {
|
|
404
|
-
inline.error(`got critical error`, err);
|
|
405
|
-
|
|
406
|
-
return false;
|
|
407
|
-
}
|
|
408
|
-
},
|
|
409
|
-
{
|
|
410
|
-
maxRetry,
|
|
411
|
-
test: (r) => r,
|
|
412
|
-
verbose,
|
|
413
|
-
}
|
|
414
|
-
);
|
|
415
|
-
|
|
416
|
-
if (!result) {
|
|
417
|
-
inline.error("Terminate consumer due to consume errors, likely bug");
|
|
418
|
-
|
|
419
|
-
process.exit(1);
|
|
420
|
-
}
|
|
421
|
-
},
|
|
422
|
-
});
|
|
423
|
-
} catch (err) {
|
|
424
|
-
inline.error("Terminate consumer due to critical kafka connection error", err);
|
|
425
|
-
|
|
426
|
-
process.exit(1);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
return build(cli.flags).catch((err) => {
|
|
431
|
-
inline.error(err);
|
|
432
|
-
process.exit(1);
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
return { run };
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
export {
|
|
440
|
-
createProducerApp,
|
|
441
|
-
createConsumerApp,
|
|
442
|
-
produceMessages,
|
|
443
|
-
consumeMessages,
|
|
444
|
-
getProducerLatestId,
|
|
445
|
-
};
|
package/monitor.d.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { Check } from "./app";
|
|
2
|
-
import { SelectorFlags, Selector } from "./selector";
|
|
3
|
-
|
|
4
|
-
export const ERROR_LEVEL: {
|
|
5
|
-
INFO: string;
|
|
6
|
-
CRITICAL: string;
|
|
7
|
-
WARNING: string;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export const LEVEL_EMOJI: {
|
|
11
|
-
Info: string;
|
|
12
|
-
Critical: string;
|
|
13
|
-
Warning: string;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export function createMonitor(createMonitorParams: {
|
|
17
|
-
binaryName: string;
|
|
18
|
-
name: string;
|
|
19
|
-
getState?: (name: string, selectorFlags: SelectorFlags) => Promise<void>;
|
|
20
|
-
mode?: boolean;
|
|
21
|
-
selector?: Selector;
|
|
22
|
-
check?: Check;
|
|
23
|
-
maxRetry?: number;
|
|
24
|
-
}): { monitor: () => void };
|
package/monitor.js
DELETED
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import meow from "meow";
|
|
2
|
-
import { getSelectorFlags, getSelectorDesc, toSelectorString } from "./selector.js";
|
|
3
|
-
import { getBinaryName } from "./cli.js";
|
|
4
|
-
import { exponentialRetry } from "./availability.js";
|
|
5
|
-
import { postGenieMessage } from "./opsgenie.js";
|
|
6
|
-
import { getJobName } from "./deploy.js";
|
|
7
|
-
|
|
8
|
-
const ERROR_LEVEL = {
|
|
9
|
-
INFO: "Info",
|
|
10
|
-
CRITICAL: "Critical",
|
|
11
|
-
WARNING: "Warning",
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const LEVEL_EMOJI = {
|
|
15
|
-
Critical: ":exclamation:",
|
|
16
|
-
Warning: ":warning:",
|
|
17
|
-
Info: "",
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const LEVEL_PRIORITY = {
|
|
21
|
-
Critical: 3,
|
|
22
|
-
Warning: 2,
|
|
23
|
-
Info: 1,
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
function sortErrors(errors) {
|
|
27
|
-
return errors.sort((e1, e2) => (LEVEL_PRIORITY[e2.type] || 0) - (LEVEL_PRIORITY[e1.type] || 0));
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async function getMostRecentJobLaunch(name) {
|
|
31
|
-
try {
|
|
32
|
-
const jobsRes = await fetch(`http://localhost:4646/v1/jobs?prefix=${name}`);
|
|
33
|
-
|
|
34
|
-
if (!jobsRes.ok) {
|
|
35
|
-
console.log(`[MONITOR] request local nomad API failed`);
|
|
36
|
-
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const jobs = await jobsRes.json();
|
|
41
|
-
|
|
42
|
-
if (jobs.length === 0) {
|
|
43
|
-
console.log(`[MONITOR] did not see any jobs prefixed with ${name}`);
|
|
44
|
-
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const recentFinishedJob = jobs.reverse().find((job) => {
|
|
49
|
-
// filter out monitor job
|
|
50
|
-
return job.ID.indexOf("-monitor") === -1 && job.Status === "dead";
|
|
51
|
-
|
|
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
|
-
importMeta: import.meta,
|
|
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
|
-
|
|
111
|
-
if (Object.keys(selectorFlags).length > 0) {
|
|
112
|
-
console.log(`[MONITOR] starting check, ${toSelectorString(selectorFlags, ", ")}`);
|
|
113
|
-
} else {
|
|
114
|
-
console.log(`[MONITOR] starting check`);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
let checkState = {};
|
|
118
|
-
|
|
119
|
-
if (getState) {
|
|
120
|
-
checkState = await getState(name, selectorFlags);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
let result = await exponentialRetry(
|
|
124
|
-
async () => {
|
|
125
|
-
try {
|
|
126
|
-
const errors = await check({ verbose, state: checkState, mode, ...selectorFlags });
|
|
127
|
-
|
|
128
|
-
if (!Array.isArray(errors)) {
|
|
129
|
-
throw new Error(`check function must return array of error messages`);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return errors;
|
|
133
|
-
} catch (err) {
|
|
134
|
-
console.log(`[MONITOR] got error in check`, err);
|
|
135
|
-
|
|
136
|
-
return [{ type: ERROR_LEVEL.ERROR, message: `${err.message}` }];
|
|
137
|
-
}
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
maxRetry,
|
|
141
|
-
initialDuration: 10000,
|
|
142
|
-
growFactor: 3,
|
|
143
|
-
test: (r) => r.length === 0,
|
|
144
|
-
verbose,
|
|
145
|
-
}
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
const jobName = getJobName(name, selectorFlags, mode);
|
|
149
|
-
const mostRecentJob = await getMostRecentJobLaunch(jobName);
|
|
150
|
-
|
|
151
|
-
if (result.length > 0) {
|
|
152
|
-
console.log("Found Errors", result);
|
|
153
|
-
|
|
154
|
-
if (production) {
|
|
155
|
-
const nomadAddr = process.env.SKYNET_NOMAD_PRODUCTION_ADDR;
|
|
156
|
-
|
|
157
|
-
// alert on opsgenie
|
|
158
|
-
await postGenieMessage(
|
|
159
|
-
{
|
|
160
|
-
message: `Failed Service Check: ${jobName}`,
|
|
161
|
-
description: `<p><b>Service:</b><a href="${nomadAddr}/ui/jobs/${jobName}" target="_blank">${jobName}</a></p><p><b>Issues</b></p><ul>${sortErrors(
|
|
162
|
-
result
|
|
163
|
-
)
|
|
164
|
-
.map((m) => `<li><b>${m.type}:</b> ${m.message}</li>`)
|
|
165
|
-
.join("")}</ul>`,
|
|
166
|
-
},
|
|
167
|
-
verbose
|
|
168
|
-
);
|
|
169
|
-
} else {
|
|
170
|
-
console.log("skip sending messages to opsgenie in dev env");
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
console.log(`[MONITOR] check successfully in ${Date.now() - startTime}ms`);
|
|
175
|
-
|
|
176
|
-
if (mostRecentJob && mostRecentJob.ParentID === "" && mostRecentJob.Periodic === false) {
|
|
177
|
-
// stop monitor job if the main job was a one time job and completed
|
|
178
|
-
const stopRes = await fetch(`http://localhost:4646/v1/job/${jobName}-monitor`, { method: "DELETE" });
|
|
179
|
-
|
|
180
|
-
if (stopRes.ok) {
|
|
181
|
-
console.log(`[MONITOR] monitor job stopped since the completed main job was a one time job`);
|
|
182
|
-
} else {
|
|
183
|
-
console.log(`[MONITOR] error stopping monitor job`);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return runCheck(cli.flags).catch((err) => {
|
|
189
|
-
console.log("[MONITOR]", err);
|
|
190
|
-
process.exit(1);
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return { monitor };
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
export { createMonitor, ERROR_LEVEL, LEVEL_EMOJI };
|
package/opensearch.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { Client, ClientOptions } from "@opensearch-project/opensearch";
|
|
2
|
-
|
|
3
|
-
export declare function newOpensearchClient(
|
|
4
|
-
endpoint: string,
|
|
5
|
-
key: string,
|
|
6
|
-
secret: string,
|
|
7
|
-
options?: ClientOptions,
|
|
8
|
-
): Client;
|
|
9
|
-
|
|
10
|
-
export declare async function sendToOpensearch<T>(
|
|
11
|
-
client: Client,
|
|
12
|
-
appName: string,
|
|
13
|
-
index: string,
|
|
14
|
-
record: string,
|
|
15
|
-
indexSuffixes?: string[],
|
|
16
|
-
): Promise<{ status: string; result: T }>;
|
package/opensearch.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { Client } from "@opensearch-project/opensearch";
|
|
2
|
-
|
|
3
|
-
// newOpensearchClient creates a new opensearch client with the given endpoint, key, secret and extra opensearch client options.
|
|
4
|
-
export function newOpensearchClient(endpoint, key, secret, options = {}) {
|
|
5
|
-
return new Client({
|
|
6
|
-
node: endpoint,
|
|
7
|
-
auth: {
|
|
8
|
-
username: key,
|
|
9
|
-
password: secret,
|
|
10
|
-
},
|
|
11
|
-
...options,
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// sendToOpensearch sends a record object to the given index using the given opensearch client. An appName is also attached to the record
|
|
16
|
-
// as an identifier for who sends the record. The indexSuffixes is an array of strings that will be appended to the index name using `-`.
|
|
17
|
-
// A typical usage of indexSuffixes could be the date e.g. ["2023", "08"] so that new indexes are created on the monthly basis to prevent
|
|
18
|
-
// the index from growing too large.
|
|
19
|
-
// Returns an object with status and result properties. The status is either "ok" or "error". The result is the response from opensearch
|
|
20
|
-
// if the status is "ok" or the error message if the status is "error".
|
|
21
|
-
export async function sendToOpensearch(client, appName, index, record, indexSuffixes = []) {
|
|
22
|
-
try {
|
|
23
|
-
const result = await client.index({
|
|
24
|
-
index: indexSuffixes ? [index, ...indexSuffixes].join("-") : index,
|
|
25
|
-
body: { app: appName, timestamp: new Date(), record },
|
|
26
|
-
});
|
|
27
|
-
if (result.statusCode === 201) {
|
|
28
|
-
return { status: "ok", result };
|
|
29
|
-
}
|
|
30
|
-
return { status: "error", result };
|
|
31
|
-
} catch (err) {
|
|
32
|
-
return { status: "error", result: err.stack ? err.stack : err };
|
|
33
|
-
}
|
|
34
|
-
}
|