@certik/skynet 0.7.20 → 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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.0
4
+
5
+ - Added `app` library to support one binary developer experience
6
+
3
7
  ## 0.7.20
4
8
 
5
9
  - Added `listKeys` method to `s3.js`
package/app.js ADDED
@@ -0,0 +1,476 @@
1
+ const { EOL } = require("os");
2
+ const { createIndexerApp, createModeIndexerApp } = require("./indexer");
3
+ const { createDeploy, createModeDeploy } = require("./deploy");
4
+ const { createProducerApp, createConsumerApp } = require("./kafka");
5
+ const { createMonitor, ERROR_LEVEL } = require("./monitor");
6
+ const { getBinaryName, detectBin, detectWorkingDirectory } = require("./cli");
7
+
8
+ function printAppHelp() {
9
+ console.log(`
10
+ Usage
11
+ $ ${getBinaryName()} run <options>
12
+ $ ${getBinaryName()} check <options>
13
+ $ ${getBinaryName()} deploy <options>
14
+ $ ${getBinaryName()} delete <options>
15
+ `);
16
+ }
17
+
18
+ function isDeleteCommand(command) {
19
+ return ["delete", "stop", "remove"].includes(command);
20
+ }
21
+
22
+ function createApp({ parameterErrors, onRun, onDeploy, onCheck }) {
23
+ if (parameterErrors.length > 0) {
24
+ console.log(`Parameter Validation Failed:${EOL}- ${parameterErrors.join(EOL + "- ")}`);
25
+
26
+ process.exit(1);
27
+ }
28
+
29
+ return () => {
30
+ const subCommand = process.argv[2];
31
+
32
+ // emulate command line call without subcmd
33
+ process.argv = [process.argv[0], process.argv[1], ...process.argv.slice(3)];
34
+
35
+ if (subCommand === "run") {
36
+ onRun();
37
+ } else if (subCommand === "deploy" || isDeleteCommand(subCommand)) {
38
+ if (isDeleteCommand(subCommand)) {
39
+ process.argv.push("--stop");
40
+ }
41
+
42
+ onDeploy();
43
+ } else if (subCommand === "check") {
44
+ onCheck();
45
+ } else {
46
+ printAppHelp();
47
+ }
48
+ };
49
+ }
50
+
51
+ function checkEnvParameter(env, required = []) {
52
+ const errors = [];
53
+
54
+ const envKeys = Object.keys(env);
55
+
56
+ required.forEach((k) => {
57
+ if (!envKeys.includes(k)) {
58
+ errors.push(`must define env.${k}`);
59
+ }
60
+ });
61
+
62
+ envKeys.forEach((k) => {
63
+ if (!env[k] && env[k] !== SENSITIVE_VALUE) {
64
+ errors.push(`must have valid non-empty value for env.${k}`);
65
+ }
66
+ });
67
+
68
+ return errors;
69
+ }
70
+
71
+ function checkIndexerBuildParameter(build) {
72
+ const errors = [];
73
+
74
+ if (!build?.func) {
75
+ errors.push("must define build.func");
76
+ }
77
+
78
+ if (!build?.cpu) {
79
+ errors.push("must define build.cpu");
80
+ }
81
+
82
+ if (!build?.mem) {
83
+ errors.push("must define build.mem");
84
+ }
85
+
86
+ return errors;
87
+ }
88
+
89
+ function checkCheckParameter(check, env) {
90
+ const errors = [];
91
+
92
+ if (!check?.func) {
93
+ errors.push("must define check.func");
94
+ }
95
+
96
+ if (!check?.schedule) {
97
+ errors.push("must define check.schedule");
98
+ }
99
+
100
+ if (check.slackChannel && !Object.keys(env).includes("SKYNET_SLACK_TOKEN")) {
101
+ errors.push("must define env.SKYNET_SLACK_TOKEN when check.slackChannel is present");
102
+ }
103
+
104
+ return errors;
105
+ }
106
+
107
+ function checkStateParameter(state) {
108
+ const errors = [];
109
+
110
+ if (!state?.type) {
111
+ errors.push("must define state.type");
112
+ }
113
+
114
+ if (!state?.getMaxId) {
115
+ errors.push("must define state.getMaxId");
116
+ }
117
+
118
+ return errors;
119
+ }
120
+
121
+ function indexer({ name, selector, build, check, env = {}, region = "us-east-1" }) {
122
+ return createApp({
123
+ parameterErrors: [
124
+ ...checkIndexerBuildParameter(build),
125
+ ...checkCheckParameter(check, env),
126
+ ...checkEnvParameter(env),
127
+ ],
128
+ onRun: () => {
129
+ const { run } = createIndexerApp({
130
+ binaryName: `${getBinaryName()} run`,
131
+ name,
132
+ selector,
133
+ build: build.func,
134
+ maxRetry: build.maxRetry,
135
+ });
136
+
137
+ run();
138
+ },
139
+ onDeploy: () => {
140
+ const bin = detectBin();
141
+
142
+ const { deploy } = createDeploy({
143
+ binaryName: `${getBinaryName()} deploy`,
144
+ name,
145
+ workingDirectory: detectWorkingDirectory(),
146
+ bin: `${bin} run`,
147
+ selector,
148
+ region,
149
+ env,
150
+ schedule: build.schedule,
151
+ restart: build.restart,
152
+ cpu: build.cpu,
153
+ mem: build.mem,
154
+ check: {
155
+ bin: `${bin} check`,
156
+ schedule: check.schedule,
157
+ cpu: check.cpu || 100,
158
+ mem: check.mem || 100,
159
+ hasSlack: check.slackChannel,
160
+ },
161
+ });
162
+
163
+ deploy();
164
+ },
165
+ onCheck: () => {
166
+ const { monitor } = createMonitor({
167
+ binaryName: `${getBinaryName()} check`,
168
+ name,
169
+ type: "stateless",
170
+ selector,
171
+ check: check.func,
172
+ maxRetry: check.maxRetry,
173
+ slackChannel: check.slackChannel,
174
+ });
175
+
176
+ monitor();
177
+ },
178
+ });
179
+ }
180
+
181
+ function checkModeIndexerBuildParameter(build) {
182
+ const errors = [];
183
+
184
+ if (!build?.func) {
185
+ errors.push("must define build.func");
186
+ }
187
+
188
+ if (!build?.cpu) {
189
+ errors.push("must define build.cpu");
190
+ }
191
+
192
+ if (!build?.mem) {
193
+ errors.push("must define build.mem");
194
+ }
195
+
196
+ return errors;
197
+ }
198
+
199
+ function checkModeIndexerValidateParameter(validate) {
200
+ const errors = [];
201
+
202
+ if (validate) {
203
+ if (!validate.func) {
204
+ errors.push("must define validate.func");
205
+ }
206
+
207
+ if (!validate.cpu) {
208
+ errors.push("must define validate.cpu");
209
+ }
210
+
211
+ if (!validate.mem) {
212
+ errors.push("must define validate.mem");
213
+ }
214
+ }
215
+
216
+ return errors;
217
+ }
218
+
219
+ function modeIndexer({ name, selector, state, build, validate, check, env = {}, region = "us-east-1" }) {
220
+ return createApp({
221
+ parameterErrors: [
222
+ ...checkModeIndexerBuildParameter(build),
223
+ ...checkModeIndexerValidateParameter(validate),
224
+ ...checkStateParameter(state),
225
+ ...checkCheckParameter(check, env),
226
+ ...checkEnvParameter(env),
227
+ ],
228
+ onRun: () => {
229
+ const { run } = createModeIndexerApp({
230
+ binaryName: `${getBinaryName()} run`,
231
+ name,
232
+ selector,
233
+ build: build.func,
234
+ maxRetry: build.maxRetry,
235
+ buildBatchSize: build.batchSize,
236
+ buildConcurrency: build.concurrency,
237
+ validate: validate && validate.func,
238
+ validateBatchSize: validate && validate.batchSize,
239
+ validateConcurrency: validate && validate.concurrency,
240
+ state,
241
+ });
242
+
243
+ run();
244
+ },
245
+ onDeploy: () => {
246
+ const bin = detectBin();
247
+
248
+ const { deploy } = createModeDeploy({
249
+ binaryName: `${getBinaryName()} deploy`,
250
+ name,
251
+ workingDirectory: detectWorkingDirectory(),
252
+ bin: `${bin} run`,
253
+ selector,
254
+ region,
255
+ env,
256
+
257
+ deltaSchedule: build.schedule,
258
+ deltaCpu: build.cpu,
259
+ deltaMem: build.mem,
260
+ rebuildCpu: build.cpu,
261
+ rebuildMem: build.mem,
262
+
263
+ validateSchedule: validate && validate.schedule,
264
+ validateCpu: validate && validate.cpu,
265
+ validateMem: validate && validate.mem,
266
+
267
+ check: {
268
+ bin: `${bin} check`,
269
+ schedule: check.schedule,
270
+ cpu: check.cpu || 100,
271
+ mem: check.mem || 100,
272
+ hasSlack: check.slackChannel,
273
+ },
274
+ });
275
+
276
+ deploy();
277
+ },
278
+ onCheck: () => {
279
+ const { monitor } = createMonitor({
280
+ binaryName: `${getBinaryName()} check`,
281
+ name,
282
+ type: "stateful",
283
+ selector,
284
+ mode: true,
285
+ check: check.func,
286
+ maxRetry: check.maxRetry,
287
+ slackChannel: check.slackChannel,
288
+ });
289
+
290
+ monitor();
291
+ },
292
+ });
293
+ }
294
+
295
+ function checkProducerProduceParameter(produce) {
296
+ const errors = [];
297
+
298
+ if (!produce?.func) {
299
+ errors.push("must define produce.func");
300
+ }
301
+
302
+ if (!produce?.topic) {
303
+ errors.push("must define produce.topic");
304
+ }
305
+
306
+ if (!produce?.deadLetterTopic) {
307
+ errors.push("must define produce.deadLetterTopic");
308
+ }
309
+
310
+ if (!produce?.cpu) {
311
+ errors.push("must define produce.cpu");
312
+ }
313
+
314
+ if (!produce?.mem) {
315
+ errors.push("must define produce.mem");
316
+ }
317
+
318
+ return errors;
319
+ }
320
+
321
+ function producer({ name, selector, produce, check, state, env = {}, region = "us-east-1" }) {
322
+ return createApp({
323
+ parameterErrors: [
324
+ ...checkProducerProduceParameter(produce),
325
+ ...checkStateParameter(state),
326
+ ...checkCheckParameter(check, env),
327
+ ...checkEnvParameter(env, ["SKYNET_KAFKA_SERVER", "SKYNET_KAFKA_USERNAME", "SKYNET_KAFKA_PASSWORD"]),
328
+ ],
329
+ onRun: () => {
330
+ const { run } = createProducerApp({
331
+ binaryName: `${getBinaryName()} run`,
332
+ name,
333
+ selector,
334
+
335
+ producer: {
336
+ topic: produce.topic,
337
+ deadLetterTopic: produce.deadLetterTopic,
338
+ produce: produce.func,
339
+ batchSize: produce.batchSize,
340
+ maxRetry: produce.maxRetry,
341
+ },
342
+
343
+ state,
344
+ });
345
+
346
+ run();
347
+ },
348
+ onDeploy: () => {
349
+ const bin = detectBin();
350
+
351
+ const { deploy } = createDeploy({
352
+ binaryName: `${getBinaryName()} deploy`,
353
+ name,
354
+ workingDirectory: detectWorkingDirectory(),
355
+ bin: `${bin} run`,
356
+ selector,
357
+ region,
358
+ env,
359
+ schedule: "@minutely",
360
+ cpu: produce.cpu,
361
+ mem: produce.mem,
362
+ check: {
363
+ bin: `${bin} check`,
364
+ schedule: check.schedule,
365
+ cpu: check.cpu || 100,
366
+ mem: check.mem || 100,
367
+ hasSlack: check.slackChannel,
368
+ },
369
+ });
370
+
371
+ deploy();
372
+ },
373
+ onCheck: () => {
374
+ const { monitor } = createMonitor({
375
+ binaryName: `${getBinaryName()} check`,
376
+ name,
377
+ type: "stateful",
378
+ selector,
379
+ check: check.func,
380
+ maxRetry: check.maxRetry,
381
+ slackChannel: check.slackChannel,
382
+ });
383
+
384
+ monitor();
385
+ },
386
+ });
387
+ }
388
+
389
+ function checkConsumerConsumeParameter(consume) {
390
+ const errors = [];
391
+
392
+ if (!consume?.func) {
393
+ errors.push("must define consume.func");
394
+ }
395
+
396
+ if (!consume?.topic) {
397
+ errors.push("must define consume.topic");
398
+ }
399
+
400
+ if (!consume?.cpu) {
401
+ errors.push("must define consume.cpu");
402
+ }
403
+
404
+ if (!consume?.mem) {
405
+ errors.push("must define consume.mem");
406
+ }
407
+
408
+ return errors;
409
+ }
410
+
411
+ function consumer({ name, selector, consume, check, env = {}, region = "us-east-1" }) {
412
+ return createApp({
413
+ parameterErrors: [
414
+ ...checkConsumerConsumeParameter(consume),
415
+ ...checkCheckParameter(check, env),
416
+ ...checkEnvParameter(env, ["SKYNET_KAFKA_SERVER", "SKYNET_KAFKA_USERNAME", "SKYNET_KAFKA_PASSWORD"]),
417
+ ],
418
+ onRun: () => {
419
+ const { run } = createConsumerApp({
420
+ binaryName: `${getBinaryName()} run`,
421
+ name,
422
+ selector,
423
+
424
+ consumer: {
425
+ topic: consume.topic,
426
+ consume: consume.func,
427
+ maxRetry: consume.maxRetry,
428
+ },
429
+ });
430
+
431
+ run();
432
+ },
433
+ onDeploy: () => {
434
+ const bin = detectBin();
435
+
436
+ const { deploy } = createDeploy({
437
+ binaryName: `${getBinaryName()} deploy`,
438
+ name,
439
+ workingDirectory: detectWorkingDirectory(),
440
+ bin: `${bin} run`,
441
+ selector,
442
+ region,
443
+ env,
444
+ schedule: "@minutely",
445
+ cpu: consume.cpu,
446
+ mem: consume.mem,
447
+ check: {
448
+ bin: `${bin} check`,
449
+ schedule: check.schedule,
450
+ cpu: check.cpu || 100,
451
+ mem: check.mem || 100,
452
+ hasSlack: check.slackChannel,
453
+ },
454
+ });
455
+
456
+ deploy();
457
+ },
458
+ onCheck: () => {
459
+ const { monitor } = createMonitor({
460
+ binaryName: `${getBinaryName()} check`,
461
+ name,
462
+ type: "stateless",
463
+ selector,
464
+ check: check.func,
465
+ maxRetry: check.maxRetry,
466
+ slackChannel: check.slackChannel,
467
+ });
468
+
469
+ monitor();
470
+ },
471
+ });
472
+ }
473
+
474
+ const SENSITIVE_VALUE = null;
475
+
476
+ module.exports = { indexer, modeIndexer, producer, consumer, SENSITIVE_VALUE, ERROR_LEVEL };
package/cli.js ADDED
@@ -0,0 +1,51 @@
1
+ const path = require("path");
2
+ const fs = require("fs");
3
+
4
+ function getBinaryName() {
5
+ const binaryNameParts = process.argv[1].split(path.sep);
6
+ const binaryName = binaryNameParts[binaryNameParts.length - 1];
7
+
8
+ return binaryName;
9
+ }
10
+
11
+ function detectSkynetDirectory() {
12
+ return detectDirectory(process.argv[1], "SkynetAPIDefinitions.yml");
13
+ }
14
+
15
+ function detectWorkingDirectory() {
16
+ const wd = detectDirectory(process.argv[1], "package.json");
17
+ const skynetd = detectDirectory(process.argv[1], "SkynetAPIDefinitions.yml");
18
+
19
+ return wd.slice(skynetd.length + path.sep.length);
20
+ }
21
+
22
+ function detectDirectory(fullBinPath, sentinel = "package.json") {
23
+ const binaryNameParts = fullBinPath.split(path.sep);
24
+
25
+ const parentFolders = [];
26
+
27
+ for (let i = binaryNameParts.length - 1; i > 1; i--) {
28
+ parentFolders.push(path.join(path.sep, ...binaryNameParts.slice(0, i)));
29
+ }
30
+
31
+ for (let folder of parentFolders) {
32
+ if (fs.existsSync(path.join(folder, sentinel))) {
33
+ return folder;
34
+ }
35
+ }
36
+
37
+ throw new Error("Cannot detect current working directory");
38
+ }
39
+
40
+ function detectBin() {
41
+ const wd = detectDirectory(process.argv[1], "package.json");
42
+
43
+ return process.argv[1].slice(wd.length + path.sep.length);
44
+ }
45
+
46
+ module.exports = {
47
+ getBinaryName,
48
+ detectSkynetDirectory,
49
+ detectWorkingDirectory,
50
+ detectBin,
51
+ };