@brimble/consul 2.0.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/lib/utils.js ADDED
@@ -0,0 +1,655 @@
1
+ "use strict";
2
+
3
+ const http = require("http");
4
+ const https = require("https");
5
+
6
+ const constants = require("./constants");
7
+
8
+ /**
9
+ * Get HTTP agent
10
+ */
11
+ function getAgent(baseUrl) {
12
+ if (!baseUrl) return;
13
+
14
+ let secure;
15
+ if (typeof baseUrl === "string") {
16
+ secure = !!baseUrl.match(/^https:/i);
17
+ } else if (baseUrl.protocol) {
18
+ secure = baseUrl.protocol === "https:";
19
+ } else {
20
+ return;
21
+ }
22
+
23
+ const Agent = secure ? https.Agent : http.Agent;
24
+ return new Agent({ keepAlive: true });
25
+ }
26
+
27
+ /**
28
+ * Inject response result
29
+ */
30
+ function responseResult(request, ...args) {
31
+ if (request.ctx && request.ctx.includeResponse) {
32
+ return [request.res, ...args];
33
+ } else {
34
+ return args[0];
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Inject response into error
40
+ */
41
+ function applyErrorResponse(request) {
42
+ if (request.err && request.ctx && request.ctx.includeResponse) {
43
+ request.err.response = request.res;
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Body
49
+ */
50
+ function body(request, next) {
51
+ if (request.err) {
52
+ applyErrorResponse(request);
53
+ return next(false, request.err);
54
+ }
55
+
56
+ next(false, undefined, responseResult(request, request.res.body));
57
+ }
58
+
59
+ /**
60
+ * First item in body
61
+ */
62
+ function bodyItem(request, next) {
63
+ if (request.err) {
64
+ applyErrorResponse(request);
65
+ return next(false, request.err);
66
+ }
67
+
68
+ if (request.res.body && request.res.body.length) {
69
+ return next(false, undefined, responseResult(request, request.res.body[0]));
70
+ }
71
+
72
+ next(false, undefined, responseResult(request));
73
+ }
74
+
75
+ /**
76
+ * Empty
77
+ */
78
+ function empty(request, next) {
79
+ if (request.err) {
80
+ applyErrorResponse(request);
81
+ return next(false, request.err);
82
+ }
83
+
84
+ next(false, undefined, responseResult(request));
85
+ }
86
+
87
+ /**
88
+ * Normalize keys
89
+ */
90
+ function normalizeKeys(obj) {
91
+ const result = {};
92
+
93
+ if (obj) {
94
+ for (const name in obj) {
95
+ if (obj.hasOwnProperty(name)) {
96
+ result[name.replace(/_/g, "").toLowerCase()] = obj[name];
97
+ }
98
+ }
99
+ }
100
+
101
+ return result;
102
+ }
103
+
104
+ /**
105
+ * Defaults
106
+ */
107
+ function defaults(obj) {
108
+ if (!obj) obj = {};
109
+
110
+ let src;
111
+ for (let i = 0; i < arguments.length; i++) {
112
+ src = arguments[i];
113
+ for (const p in src) {
114
+ if (src.hasOwnProperty(p) && !obj.hasOwnProperty(p)) {
115
+ obj[p] = src[p];
116
+ }
117
+ }
118
+ }
119
+
120
+ return obj;
121
+ }
122
+
123
+ /**
124
+ * Parse duration
125
+ */
126
+ function parseDuration(value) {
127
+ if (typeof value === "number") return value / 1e6;
128
+ if (typeof value !== "string") return;
129
+
130
+ let n;
131
+ let m = value.match(/^(\d*\.?\d*)$/);
132
+
133
+ if (m) {
134
+ n = parseFloat(m[1]);
135
+
136
+ if (!isNaN(n)) return n / 1e6;
137
+ }
138
+
139
+ m = value.match(/^([\d.]*)(ns|us|ms|s|m|h)$/);
140
+
141
+ if (!m) return;
142
+
143
+ n = parseFloat(m[1]);
144
+
145
+ if (isNaN(n)) return;
146
+
147
+ return (n * constants.DURATION_UNITS[m[2]]) / 1e6;
148
+ }
149
+
150
+ /**
151
+ * Return BigInt or undefined if parse failed.
152
+ */
153
+ function safeBigInt(value) {
154
+ if (value === undefined || value === null || value === "") {
155
+ return undefined;
156
+ }
157
+
158
+ try {
159
+ return BigInt(value);
160
+ } catch (err) {
161
+ return undefined;
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Common options
167
+ */
168
+ function options(req, opts, ignore) {
169
+ if (!opts) opts = {};
170
+ if (!ignore) ignore = {};
171
+
172
+ if (!req.headers) req.headers = {};
173
+
174
+ // headers
175
+ if (opts.token && !ignore.token) req.headers["x-consul-token"] = opts.token;
176
+
177
+ // query
178
+ if (!req.query) req.query = {};
179
+
180
+ if (opts.dc && !ignore.dc) req.query.dc = opts.dc;
181
+ if (opts.partition && !ignore.partition) req.query.partition = opts.partition;
182
+ if (opts.wan && !ignore.wan) req.query.wan = "1";
183
+
184
+ if (opts.consistent && !ignore.consistent) {
185
+ req.query.consistent = "1";
186
+ } else if (opts.stale && !ignore.stale) {
187
+ req.query.stale = "1";
188
+ }
189
+
190
+ if (opts.hasOwnProperty("index") && !ignore.index)
191
+ req.query.index = opts.index;
192
+ if (opts.hasOwnProperty("wait") && !ignore.wait) req.query.wait = opts.wait;
193
+ if (opts.hasOwnProperty("near") && !ignore.near) req.query.near = opts.near;
194
+ if (opts.hasOwnProperty("node-meta") && !ignore["node-meta"]) {
195
+ req.query["node-meta"] = opts["node-meta"];
196
+ }
197
+ if (opts.hasOwnProperty("filter") && !ignore.filter)
198
+ req.query.filter = opts.filter;
199
+
200
+ // papi
201
+ if (opts.hasOwnProperty("ctx") && !ignore.ctx) req.ctx = opts.ctx;
202
+ if (opts.hasOwnProperty("timeout") && !ignore.timeout) {
203
+ if (typeof opts.timeout === "string") {
204
+ req.timeout = parseDuration(opts.timeout);
205
+ } else {
206
+ req.timeout = opts.timeout;
207
+ }
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Default common options
213
+ */
214
+ function defaultCommonOptions(opts) {
215
+ opts = normalizeKeys(opts);
216
+ let defaults;
217
+
218
+ constants.DEFAULT_OPTIONS.forEach(function (key) {
219
+ if (!opts.hasOwnProperty(key)) return;
220
+ if (!defaults) defaults = {};
221
+ defaults[key] = opts[key];
222
+ });
223
+
224
+ return defaults;
225
+ }
226
+
227
+ /**
228
+ * Decode value
229
+ */
230
+ function decode(value, opts) {
231
+ if (typeof value !== "string") return value;
232
+ value = Buffer.from(value, "base64");
233
+ if (!opts || !opts.buffer) value = value.toString();
234
+ return value;
235
+ }
236
+
237
+ /**
238
+ * Shallow clone
239
+ */
240
+ function clone(src) {
241
+ const dst = {};
242
+
243
+ for (const key in src) {
244
+ if (src.hasOwnProperty(key)) {
245
+ dst[key] = src[key];
246
+ }
247
+ }
248
+
249
+ return dst;
250
+ }
251
+
252
+ /**
253
+ * Set timeout with cancel support
254
+ */
255
+ function setTimeoutContext(fn, ctx, timeout) {
256
+ let id;
257
+
258
+ const cancel = function () {
259
+ clearTimeout(id);
260
+ };
261
+
262
+ id = setTimeout(function () {
263
+ ctx.removeListener("cancel", cancel);
264
+
265
+ fn();
266
+ }, timeout);
267
+
268
+ ctx.once("cancel", cancel);
269
+ }
270
+
271
+ function _createTaggedAddress(src) {
272
+ const dst = {};
273
+
274
+ if (src.hasOwnProperty("address")) dst.Address = src.address;
275
+ if (src.hasOwnProperty("port")) dst.Port = src.port;
276
+
277
+ return dst;
278
+ }
279
+
280
+ function _createTaggedAddresses(src) {
281
+ const dst = {};
282
+
283
+ if (src.lan) {
284
+ dst.lan = _createTaggedAddress(normalizeKeys(src.lan));
285
+ }
286
+ if (src.wan) {
287
+ dst.wan = _createTaggedAddress(normalizeKeys(src.wan));
288
+ }
289
+
290
+ return dst;
291
+ }
292
+
293
+ /**
294
+ * Create node/server-level check object
295
+ * Corresponds to CheckType in Consul Agent Endpoint:
296
+ * https://developer.hashicorp.com/consul/docs/services/usage/checks
297
+ */
298
+ function _createServiceCheck(src) {
299
+ const dst = {};
300
+ if (src.hasOwnProperty("checkid")) dst.CheckID = src.checkid;
301
+ if (src.hasOwnProperty("name")) dst.Name = src.name;
302
+ if (src.hasOwnProperty("notes")) dst.Notes = src.notes;
303
+
304
+ if (
305
+ (src.grpc ||
306
+ src.h2ping ||
307
+ src.http ||
308
+ src.tcp ||
309
+ src.udp ||
310
+ src.args ||
311
+ src.script) &&
312
+ src.interval
313
+ ) {
314
+ if (src.grpc) {
315
+ dst.GRPC = src.grpc;
316
+ if (src.hasOwnProperty("grpcusetls")) dst.GRPCUseTLS = src.grpcusetls;
317
+ } else if (src.h2ping) {
318
+ dst.H2Ping = src.h2ping;
319
+ if (src.hasOwnProperty("h2pingusetls")) {
320
+ dst.H2PingUseTLS = src.h2pingusetls;
321
+ }
322
+ } else if (src.http) {
323
+ dst.HTTP = src.http;
324
+ if (src.hasOwnProperty("body")) dst.Body = src.body;
325
+ if (src.hasOwnProperty("disableredirects")) {
326
+ dst.DisableRedirects = src.disableredirects;
327
+ }
328
+ if (src.hasOwnProperty("header")) dst.Header = src.header;
329
+ if (src.hasOwnProperty("method")) dst.Method = src.method;
330
+ } else if (src.tcp) {
331
+ dst.TCP = src.tcp;
332
+ if (src.hasOwnProperty("tcpusetls")) dst.TCPUseTLS = src.tcpusetls;
333
+ } else if (src.udp) {
334
+ dst.UDP = src.udp;
335
+ } else {
336
+ if (src.args) {
337
+ dst.Args = src.args;
338
+ } else {
339
+ dst.Script = src.script;
340
+ }
341
+ if (src.hasOwnProperty("dockercontainerid"))
342
+ dst.DockerContainerID = src.dockercontainerid;
343
+ if (src.hasOwnProperty("shell")) dst.Shell = src.shell;
344
+ }
345
+ dst.Interval = src.interval;
346
+ if (src.hasOwnProperty("outputmaxsize")) {
347
+ dst.OutputMaxSize = src.outputmaxsize;
348
+ }
349
+ if (src.hasOwnProperty("timeout")) dst.Timeout = src.timeout;
350
+ if (src.hasOwnProperty("tlsservername")) {
351
+ dst.TLSServerName = src.tlsservername;
352
+ }
353
+ if (src.hasOwnProperty("tlsskipverify")) {
354
+ dst.TLSSkipVerify = src.tlsskipverify;
355
+ }
356
+ } else if (src.ttl) {
357
+ dst.TTL = src.ttl;
358
+ } else if (src.aliasnode || src.aliasservice) {
359
+ if (src.hasOwnProperty("aliasnode")) dst.AliasNode = src.aliasnode;
360
+ if (src.hasOwnProperty("aliasservice")) dst.AliasService = src.aliasservice;
361
+ } else {
362
+ throw new Error(
363
+ "args/grpc/h2ping/http/tcp/udp and interval, ttl, or aliasnode/aliasservice",
364
+ );
365
+ }
366
+ if (src.hasOwnProperty("notes")) dst.Notes = src.notes;
367
+ if (src.hasOwnProperty("status")) dst.Status = src.status;
368
+ if (src.hasOwnProperty("deregistercriticalserviceafter")) {
369
+ dst.DeregisterCriticalServiceAfter = src.deregistercriticalserviceafter;
370
+ }
371
+ if (src.hasOwnProperty("failuresbeforewarning")) {
372
+ dst.FailuresBeforeWarning = src.failuresbeforewarning;
373
+ }
374
+ if (src.hasOwnProperty("failuresbeforecritical")) {
375
+ dst.FailuresBeforeCritical = src.failuresbeforecritical;
376
+ }
377
+ if (src.hasOwnProperty("successbeforepassing")) {
378
+ dst.SuccessBeforePassing = src.successbeforepassing;
379
+ }
380
+
381
+ return dst;
382
+ }
383
+
384
+ function createServiceCheck(src) {
385
+ return _createServiceCheck(normalizeKeys(src));
386
+ }
387
+
388
+ function _createServiceProxy(src) {
389
+ const dst = {};
390
+
391
+ if (src.destinationservicename) {
392
+ dst.DestinationServiceName = src.destinationservicename;
393
+ } else {
394
+ throw Error("destinationservicename required");
395
+ }
396
+ if (src.destinationserviceid)
397
+ dst.DestinationServiceID = src.destinationserviceid;
398
+ if (src.localserviceaddress)
399
+ dst.LocalServiceAddress = src.localserviceaddress;
400
+ if (src.hasOwnProperty("localserviceport"))
401
+ dst.LocalServicePort = src.localserviceport;
402
+ if (src.config) dst.Config = src.config;
403
+ if (src.upstreams) dst.Upstreams = src.upstreams;
404
+ if (src.meshgateway) dst.MeshGateway = src.meshgateway;
405
+ if (src.expose) dst.Expose = src.expose;
406
+
407
+ return dst;
408
+ }
409
+
410
+ function _createService(src, isSidecar) {
411
+ const dst = {};
412
+
413
+ if (src.name) dst.Name = src.name;
414
+ if (src.id) dst.ID = src.id;
415
+ if (src.tags) dst.Tags = src.tags;
416
+ if (src.meta) dst.Meta = src.meta;
417
+ if (src.hasOwnProperty("address")) dst.Address = src.address;
418
+ if (src.hasOwnProperty("port")) dst.Port = src.port;
419
+
420
+ if (Array.isArray(src.checks)) {
421
+ dst.Checks = src.checks.map(createServiceCheck);
422
+ } else if (src.check) {
423
+ dst.Check = createServiceCheck(src.check);
424
+ }
425
+
426
+ if (src.connect) {
427
+ const connect = normalizeKeys(src.connect);
428
+
429
+ dst.Connect = {};
430
+ if (connect.hasOwnProperty("native")) dst.Connect.Native = connect.native;
431
+
432
+ if (connect.proxy) {
433
+ dst.Connect.Proxy = _createServiceProxy(normalizeKeys(connect.proxy));
434
+ }
435
+
436
+ if (connect.sidecarservice) {
437
+ if (!isSidecar) {
438
+ dst.Connect.SidecarService = _createService(
439
+ normalizeKeys(connect.sidecarservice),
440
+ true,
441
+ );
442
+ } else {
443
+ throw new Error("sidecarservice cannot be nested");
444
+ }
445
+ }
446
+ }
447
+
448
+ if (src.proxy) {
449
+ dst.Proxy = _createServiceProxy(normalizeKeys(src.proxy));
450
+ }
451
+
452
+ if (src.taggedaddresses) {
453
+ dst.TaggedAddresses = _createTaggedAddresses(
454
+ normalizeKeys(src.taggedaddresses),
455
+ );
456
+ }
457
+
458
+ return dst;
459
+ }
460
+
461
+ function _createCatalogService(src) {
462
+ const dst = {};
463
+
464
+ if (src.id) dst.ID = src.id;
465
+ if (src.service) dst.Service = src.service;
466
+ if (src.tags) dst.Tags = src.tags;
467
+ if (src.meta) dst.Meta = src.meta;
468
+ if (src.hasOwnProperty("address")) dst.Address = src.address;
469
+ if (src.hasOwnProperty("port")) dst.Port = src.port;
470
+
471
+ return dst;
472
+ }
473
+
474
+ function createCatalogService(src) {
475
+ return _createCatalogService(normalizeKeys(src));
476
+ }
477
+
478
+ function _createHealthCheckDefinition(src) {
479
+ const dst = {};
480
+
481
+ if (src.http) {
482
+ dst.HTTP = src.http;
483
+ if (src.hasOwnProperty("tlsskipverify"))
484
+ dst.TLSSkipVerify = src.tlsskipverify;
485
+ if (src.hasOwnProperty("tlsservername"))
486
+ dst.TLSServerName = src.tlsservername;
487
+ } else if (src.tcp) {
488
+ dst.TCP = src.tcp;
489
+ } else {
490
+ throw Error("at least one of http/tcp is required");
491
+ }
492
+
493
+ dst.IntervalDuration = src.intervalduration;
494
+ if (src.hasOwnProperty("timeoutduration"))
495
+ dst.TimeoutDuration = src.timeoutduration;
496
+ if (src.hasOwnProperty("deregistercriticalserviceafterduration")) {
497
+ dst.DeregisterCriticalServiceAfterDuration =
498
+ src.deregistercriticalserviceafterduration;
499
+ }
500
+
501
+ return dst;
502
+ }
503
+
504
+ function createHealthCheckDefininition(src) {
505
+ return _createHealthCheckDefinition(normalizeKeys(src));
506
+ }
507
+
508
+ function _createCatalogCheck(src) {
509
+ const dst = {};
510
+
511
+ if (src.hasOwnProperty("checkid")) dst.CheckID = src.checkid;
512
+ if (src.hasOwnProperty("name")) dst.Name = src.name;
513
+ if (src.hasOwnProperty("node")) dst.Node = src.node;
514
+ if (src.hasOwnProperty("serviceid")) dst.ServiceID = src.serviceid;
515
+ if (src.hasOwnProperty("definition"))
516
+ dst.Definition = createHealthCheckDefininition(src.definition);
517
+ if (src.hasOwnProperty("notes")) dst.Notes = src.notes;
518
+ if (src.hasOwnProperty("status")) dst.Status = src.status;
519
+
520
+ return dst;
521
+ }
522
+
523
+ function createCatalogCheck(src) {
524
+ return _createCatalogCheck(normalizeKeys(src));
525
+ }
526
+
527
+ function _createCatalogRegistration(src) {
528
+ const dst = {};
529
+
530
+ if (src.id) dst.ID = src.id;
531
+ if (src.node) dst.Node = src.node;
532
+ if (src.nodemeta) dst.NodeMeta = src.nodemeta;
533
+ if (src.skipnodeupdate) dst.SkipNodeUpdate = src.skipnodeupdate;
534
+ if (src.hasOwnProperty("address")) dst.Address = src.address;
535
+
536
+ if (src.service) dst.Service = createCatalogService(src.service);
537
+
538
+ if (Array.isArray(src.checks)) {
539
+ dst.Checks = src.checks.map(createCatalogCheck);
540
+ } else if (src.check) {
541
+ dst.Check = createCatalogCheck(src.check);
542
+ }
543
+
544
+ if (src.taggedaddresses) {
545
+ dst.TaggedAddresses = _createTaggedAddresses(
546
+ normalizeKeys(src.taggedaddresses),
547
+ );
548
+ }
549
+
550
+ return dst;
551
+ }
552
+
553
+ function _createCatalogDeregistration(src) {
554
+ const dst = {};
555
+
556
+ if (src.node) dst.Node = src.node;
557
+ if (src.checkid) dst.CheckID = src.checkid;
558
+ if (src.serviceid) dst.ServiceID = src.serviceid;
559
+
560
+ return dst;
561
+ }
562
+
563
+ function createCatalogRegistration(src) {
564
+ return _createCatalogRegistration(normalizeKeys(src));
565
+ }
566
+
567
+ function createCatalogDeregistration(src) {
568
+ return _createCatalogDeregistration(normalizeKeys(src));
569
+ }
570
+
571
+ function createService(src) {
572
+ return _createService(normalizeKeys(src));
573
+ }
574
+
575
+ /**
576
+ * Create standalone check object
577
+ * Corresponds to CheckDefinition in Consul Agent Endpoint:
578
+ * https://github.com/hashicorp/consul/blob/master/command/agent/structs.go#L47
579
+ * Corresponds to AgentCheckRegistration in Consul Go API:
580
+ * https://github.com/hashicorp/consul/blob/master/api/agent.go#L57
581
+ */
582
+ function createCheck(src) {
583
+ src = normalizeKeys(src);
584
+
585
+ const dst = _createServiceCheck(src);
586
+
587
+ if (src.name) {
588
+ dst.Name = src.name;
589
+ } else {
590
+ throw new Error("name required");
591
+ }
592
+
593
+ if (src.hasOwnProperty("id")) dst.ID = src.id;
594
+ if (src.hasOwnProperty("serviceid")) dst.ServiceID = src.serviceid;
595
+
596
+ return dst;
597
+ }
598
+
599
+ /**
600
+ * Has the Consul index changed.
601
+ */
602
+ function hasIndexChanged(index, prevIndex) {
603
+ if (typeof index !== "bigint" || index <= 0) return false;
604
+ if (typeof prevIndex !== "bigint") return true;
605
+ return index > prevIndex;
606
+ }
607
+
608
+ /**
609
+ * Parse query meta
610
+ */
611
+ function parseQueryMeta(res) {
612
+ const meta = {};
613
+
614
+ if (res && res.headers) {
615
+ if (res.headers["x-consul-index"]) {
616
+ meta.LastIndex = res.headers["x-consul-index"];
617
+ }
618
+ if (res.headers["x-consul-lastcontact"]) {
619
+ meta.LastContact = parseInt(res.headers["x-consul-lastcontact"], 10);
620
+ }
621
+ if (res.headers["x-consul-knownleader"]) {
622
+ meta.KnownLeader = res.headers["x-consul-knownleader"] === "true";
623
+ }
624
+ if (res.headers["x-consul-translate-addresses"]) {
625
+ meta.AddressTranslationEnabled =
626
+ res.headers["x-consul-translate-addresses"] === "true";
627
+ }
628
+ }
629
+
630
+ return meta;
631
+ }
632
+
633
+ exports.getAgent = getAgent;
634
+ exports.responseResult = responseResult;
635
+ exports.applyErrorResponse = applyErrorResponse;
636
+ exports.body = body;
637
+ exports.bodyItem = bodyItem;
638
+ exports.decode = decode;
639
+ exports.empty = empty;
640
+ exports.normalizeKeys = normalizeKeys;
641
+ exports.defaults = defaults;
642
+ exports.options = options;
643
+ exports.defaultCommonOptions = defaultCommonOptions;
644
+ exports.clone = clone;
645
+ exports.parseDuration = parseDuration;
646
+ exports.safeBigInt = safeBigInt;
647
+ exports.setTimeoutContext = setTimeoutContext;
648
+ exports.createServiceCheck = createServiceCheck;
649
+ exports.createService = createService;
650
+ exports.createCheck = createCheck;
651
+ exports.createCatalogRegistration = createCatalogRegistration;
652
+ exports.createCatalogDeregistration = createCatalogDeregistration;
653
+ exports.createCatalogService = createCatalogService;
654
+ exports.hasIndexChanged = hasIndexChanged;
655
+ exports.parseQueryMeta = parseQueryMeta;
package/lib/watch.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { EventEmitter } from "events";
2
+ import { CommonOptions, Consul } from "./consul";
3
+
4
+ interface WatchOptions extends CommonOptions {
5
+ method: Function;
6
+ options: Record<string, string>;
7
+ backoffFactor?: number;
8
+ backoffMax?: number;
9
+ maxAttempts?: number;
10
+ }
11
+
12
+ declare class Watch extends EventEmitter {
13
+ constructor(consul: Consul, options: WatchOptions);
14
+
15
+ consul: Consul;
16
+
17
+ isRunning(): boolean;
18
+
19
+ updateTime(): number;
20
+
21
+ end(): void;
22
+ }