@pipeline-builder/api-core 5.2.2 → 5.2.4
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/constants/ai-providers.d.ts +3 -22
- package/lib/constants/ai-providers.js +6 -56
- package/lib/errors/app-errors.d.ts +14 -0
- package/lib/errors/app-errors.js +21 -1
- package/lib/helpers/index.d.ts +1 -0
- package/lib/helpers/index.js +2 -1
- package/lib/helpers/sse-helpers.d.ts +33 -3
- package/lib/helpers/sse-helpers.js +91 -19
- package/lib/helpers/support-calendar.d.ts +69 -0
- package/lib/helpers/support-calendar.js +179 -0
- package/lib/middleware/auth.js +6 -3
- package/lib/middleware/deferred-quota-check.d.ts +5 -0
- package/lib/middleware/deferred-quota-check.js +33 -0
- package/lib/middleware/ecosystem-guard.d.ts +3 -3
- package/lib/middleware/ecosystem-guard.js +5 -5
- package/lib/middleware/index.d.ts +5 -2
- package/lib/middleware/index.js +6 -3
- package/lib/middleware/on-behalf.d.ts +37 -0
- package/lib/middleware/on-behalf.js +143 -0
- package/lib/middleware/permission-gates.d.ts +11 -0
- package/lib/middleware/permission-gates.js +7 -1
- package/lib/middleware/quota-exempt-routes.d.ts +36 -0
- package/lib/middleware/quota-exempt-routes.js +47 -0
- package/lib/middleware/route-table.d.ts +104 -0
- package/lib/middleware/route-table.js +72 -2
- package/lib/middleware/service-tokens.d.ts +8 -0
- package/lib/middleware/service-tokens.js +5 -3
- package/lib/middleware/step-up.js +11 -1
- package/lib/services/ai-call-weight.d.ts +24 -0
- package/lib/services/ai-call-weight.js +77 -0
- package/lib/services/alert-delivery.d.ts +99 -0
- package/lib/services/alert-delivery.js +132 -0
- package/lib/services/audit-context.d.ts +105 -0
- package/lib/services/audit-context.js +134 -0
- package/lib/services/index.d.ts +10 -2
- package/lib/services/index.js +10 -2
- package/lib/services/org-ai-client.d.ts +61 -0
- package/lib/services/org-ai-client.js +112 -0
- package/lib/services/pagerduty-inbound.d.ts +101 -0
- package/lib/services/pagerduty-inbound.js +195 -0
- package/lib/services/pagerduty.d.ts +109 -0
- package/lib/services/pagerduty.js +186 -0
- package/lib/services/quota-increment-batcher.d.ts +81 -0
- package/lib/services/quota-increment-batcher.js +166 -0
- package/lib/services/quota.d.ts +158 -14
- package/lib/services/quota.js +252 -22
- package/lib/services/remote-audit-client.d.ts +44 -1
- package/lib/services/remote-audit-client.js +106 -20
- package/lib/services/version-vuln-policy.d.ts +174 -0
- package/lib/services/version-vuln-policy.js +242 -0
- package/lib/types/ai-catalog.d.ts +63 -0
- package/lib/types/ai-catalog.js +82 -0
- package/lib/types/ask-proposals.js +6 -6
- package/lib/types/build-catalog.d.ts +154 -0
- package/lib/types/build-catalog.js +158 -0
- package/lib/types/common.d.ts +37 -3
- package/lib/types/common.js +20 -2
- package/lib/types/ecosystem-notifications.d.ts +5 -5
- package/lib/types/ecosystem-notifications.js +9 -3
- package/lib/types/error-codes.d.ts +13 -6
- package/lib/types/error-codes.js +18 -8
- package/lib/types/feature-flags.d.ts +71 -1
- package/lib/types/feature-flags.js +131 -11
- package/lib/types/index.d.ts +6 -4
- package/lib/types/index.js +12 -5
- package/lib/types/metadata-keys.d.ts +6 -0
- package/lib/types/metadata-keys.js +4 -1
- package/lib/types/permissions.d.ts +351 -3
- package/lib/types/permissions.js +610 -21
- package/lib/types/pipeline.d.ts +39 -0
- package/lib/types/pipeline.js +57 -1
- package/lib/types/plugin-health.d.ts +0 -2
- package/lib/types/plugin-health.js +1 -11
- package/lib/types/plugin-scan.d.ts +192 -42
- package/lib/types/plugin-scan.js +159 -43
- package/lib/types/quota-catalog.d.ts +247 -0
- package/lib/types/quota-catalog.js +122 -0
- package/lib/types/quota-tiers.d.ts +150 -0
- package/lib/types/quota-tiers.js +167 -2
- package/lib/types/remote-audit-actions.d.ts +1 -1
- package/lib/types/remote-audit-actions.js +217 -4
- package/lib/types/wire-vocabulary.js +8 -1
- package/lib/utils/index.d.ts +1 -1
- package/lib/utils/index.js +2 -2
- package/lib/utils/response.d.ts +35 -2
- package/lib/utils/response.js +42 -3
- package/lib/validation/ai-schemas.d.ts +27 -0
- package/lib/validation/ai-schemas.js +31 -5
- package/lib/validation/plugin-schemas.d.ts +15 -0
- package/lib/validation/plugin-schemas.js +28 -1
- package/package.json +31 -2
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* The INBOUND half of on-call: what the provider tells us back.
|
|
5
|
+
*
|
|
6
|
+
* Everything else in this feature pushes outward — an alert fires, a case
|
|
7
|
+
* approaches its deadline, an event goes to Events v2. That direction can only
|
|
8
|
+
* ever report that a page was ACCEPTED, which is a 202 and not a phone ringing.
|
|
9
|
+
* This module is the only source of the two facts that actually matter:
|
|
10
|
+
*
|
|
11
|
+
* - SOMEBODY PICKED IT UP, and who. `oncall_pages_total{outcome="accepted"}`
|
|
12
|
+
* cannot answer "was anyone awake at 03:14"; an acknowledgement can, and
|
|
13
|
+
* bound to a user id it becomes an audit record rather than a hint. The
|
|
14
|
+
* provider's own ack view is not auditable here — a click on a link in their
|
|
15
|
+
* UI is not an identity this system can attest to — so the webhook is what
|
|
16
|
+
* turns their state change into our record.
|
|
17
|
+
* - NOBODY IS SCHEDULED. A rota with a hole next Tuesday has to be visible on
|
|
18
|
+
* MONDAY, not at 02:00 Tuesday. That is an absence check, and absence checks
|
|
19
|
+
* are the ones this codebase keeps finding it had missed. It is answered by
|
|
20
|
+
* READING the provider's schedule rather than owning one.
|
|
21
|
+
*
|
|
22
|
+
* TWO CREDENTIALS, DELIBERATELY SEPARATE AND BOTH OPTIONAL.
|
|
23
|
+
* `PAGERDUTY_WEBHOOK_SECRET` verifies what they send us. `PAGERDUTY_API_TOKEN`
|
|
24
|
+
* is a REST token for reading the rota and is strictly more powerful than the
|
|
25
|
+
* per-service integration key used for sending — an integration key can only
|
|
26
|
+
* create events on one service, while a REST token reads incidents, schedules
|
|
27
|
+
* and users account-wide. So the rota check is opt-in on its own credential,
|
|
28
|
+
* and a deployment that does not want to hand over a REST token simply does not
|
|
29
|
+
* get the gauge. It must never be inferred from the routing key.
|
|
30
|
+
*/
|
|
31
|
+
import { createHmac, timingSafeEqual } from 'crypto';
|
|
32
|
+
import { envInt, envStr } from '../utils/env.js';
|
|
33
|
+
import { createLogger } from '../utils/logger.js';
|
|
34
|
+
import { errorMessage } from '../utils/response.js';
|
|
35
|
+
import { safeFetch } from '../utils/ssrf.js';
|
|
36
|
+
const logger = createLogger('pagerduty-inbound');
|
|
37
|
+
export const PAGERDUTY_API_URL = 'https://api.pagerduty.com';
|
|
38
|
+
/**
|
|
39
|
+
* Verify a PagerDuty v3 webhook signature.
|
|
40
|
+
*
|
|
41
|
+
* The scheme: `X-PagerDuty-Signature: v1=<lowercase hex>`, an HMAC-SHA256 over
|
|
42
|
+
* the RAW request body. Three properties of it are easy to get wrong and each
|
|
43
|
+
* one is a hole:
|
|
44
|
+
*
|
|
45
|
+
* - THE RAW BODY, not a re-serialized object. `JSON.stringify(req.body)` after
|
|
46
|
+
* a JSON parser has run produces different bytes (key order, whitespace,
|
|
47
|
+
* number formatting), so every signature fails and the obvious "fix" is to
|
|
48
|
+
* stop checking. The route therefore mounts `express.raw`.
|
|
49
|
+
* - MULTIPLE SIGNATURES. The header may carry several comma-separated `v1=`
|
|
50
|
+
* values, which is how PagerDuty rotates a secret without downtime. Checking
|
|
51
|
+
* only the first breaks during their rotation, so ANY match is accepted —
|
|
52
|
+
* the same overlap this repo already runs for its own relay token.
|
|
53
|
+
* - CONSTANT TIME. Compared with `timingSafeEqual` on equal-length buffers,
|
|
54
|
+
* because a byte-at-a-time comparison leaks the expected digest to anyone
|
|
55
|
+
* who can post here repeatedly.
|
|
56
|
+
*
|
|
57
|
+
* NO SECRET MEANS NO. An unverified webhook that moves an audit record and a
|
|
58
|
+
* metric is a way for anyone who learns the URL to assert that a page was
|
|
59
|
+
* handled — the one lie that would make the whole feature report coverage it
|
|
60
|
+
* does not have. Fails closed.
|
|
61
|
+
*/
|
|
62
|
+
export function verifyPagerDutySignature(rawBody, header, secret) {
|
|
63
|
+
if (!secret || !header)
|
|
64
|
+
return false;
|
|
65
|
+
const expected = createHmac('sha256', secret).update(rawBody).digest('hex');
|
|
66
|
+
const expectedBuf = Buffer.from(expected, 'utf8');
|
|
67
|
+
for (const part of header.split(',')) {
|
|
68
|
+
const value = part.trim();
|
|
69
|
+
if (!value.startsWith('v1='))
|
|
70
|
+
continue;
|
|
71
|
+
const given = Buffer.from(value.slice(3), 'utf8');
|
|
72
|
+
// Length-guarded: timingSafeEqual THROWS on a length mismatch, and an
|
|
73
|
+
// attacker controls this length.
|
|
74
|
+
if (given.length === expectedBuf.length && timingSafeEqual(given, expectedBuf))
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
export function onCallInboundConfig() {
|
|
80
|
+
return {
|
|
81
|
+
webhookSecret: envStr('PAGERDUTY_WEBHOOK_SECRET', ''),
|
|
82
|
+
apiToken: envStr('PAGERDUTY_API_TOKEN', ''),
|
|
83
|
+
apiUrl: envStr('PAGERDUTY_API_URL', PAGERDUTY_API_URL),
|
|
84
|
+
// 48 hours: far enough to catch a hole over a weekend on the Friday, and
|
|
85
|
+
// short enough that a rota maintained a week out does not alert forever.
|
|
86
|
+
lookaheadHours: envInt('ONCALL_SCHEDULE_GAP_LOOKAHEAD_HOURS', 48, { min: 1, max: 2160 }),
|
|
87
|
+
escalationPolicyIds: envStr('PAGERDUTY_ESCALATION_POLICY_IDS', '')
|
|
88
|
+
.split(',').map((x) => x.trim()).filter(Boolean),
|
|
89
|
+
timeoutMs: envInt('ONCALL_EVENTS_TIMEOUT_MS', 5000, { min: 1 }),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Ask the provider whether anyone is on call across the lookahead window.
|
|
94
|
+
*
|
|
95
|
+
* WHAT THIS CAN AND CANNOT CONCLUDE, because the difference decides whether the
|
|
96
|
+
* alert built on it is honest. `GET /oncalls?since&until` returns the on-call
|
|
97
|
+
* entries OVERLAPPING a window; it does not return the holes. Reconstructing
|
|
98
|
+
* exact holes would mean stitching every entry's interval, and an entry's
|
|
99
|
+
* `start`/`end` are null for an always-on policy — so a precise
|
|
100
|
+
* "uncovered seconds" is not available from this endpoint without a lot of
|
|
101
|
+
* arithmetic that would be wrong in the always-on case.
|
|
102
|
+
*
|
|
103
|
+
* So this answers the question it CAN answer exactly: is the window covered at
|
|
104
|
+
* all. Zero entries over the next N hours means nobody is scheduled, which is
|
|
105
|
+
* the failure worth paging about and the one a rota maintainer actually makes.
|
|
106
|
+
* `uncoveredSeconds` is therefore the whole window or nothing, and it is a
|
|
107
|
+
* deliberate under-report rather than a guess — a partial hole reads as covered
|
|
108
|
+
* here, and claiming otherwise would be inventing precision.
|
|
109
|
+
*
|
|
110
|
+
* `known: false` when there is no API token or the call fails, and the caller
|
|
111
|
+
* must publish NOTHING in that case. A gauge of 0 would say "fully covered",
|
|
112
|
+
* which is exactly the lie this check exists to prevent.
|
|
113
|
+
*/
|
|
114
|
+
export async function fetchRotaCoverage(cfg = onCallInboundConfig()) {
|
|
115
|
+
const windowSeconds = cfg.lookaheadHours * 3600;
|
|
116
|
+
if (!cfg.apiToken)
|
|
117
|
+
return { known: false, entries: 0, uncoveredSeconds: 0 };
|
|
118
|
+
const since = new Date();
|
|
119
|
+
const until = new Date(since.getTime() + windowSeconds * 1000);
|
|
120
|
+
const params = new URLSearchParams({
|
|
121
|
+
since: since.toISOString(),
|
|
122
|
+
until: until.toISOString(),
|
|
123
|
+
limit: '50',
|
|
124
|
+
});
|
|
125
|
+
for (const id of cfg.escalationPolicyIds)
|
|
126
|
+
params.append('escalation_policy_ids[]', id);
|
|
127
|
+
try {
|
|
128
|
+
const resp = await safeFetch(`${cfg.apiUrl}/oncalls?${params.toString()}`, {
|
|
129
|
+
method: 'GET',
|
|
130
|
+
headers: {
|
|
131
|
+
// PagerDuty's REST scheme. `Accept` pins the API version; without it
|
|
132
|
+
// the response shape is whatever their default happens to be.
|
|
133
|
+
Authorization: `Token token=${cfg.apiToken}`,
|
|
134
|
+
Accept: 'application/vnd.pagerduty+json;version=2',
|
|
135
|
+
},
|
|
136
|
+
timeoutMs: cfg.timeoutMs,
|
|
137
|
+
});
|
|
138
|
+
if (resp.redirected || !resp.ok) {
|
|
139
|
+
logger.warn('Could not read the on-call rota', { status: resp.status, redirected: resp.redirected });
|
|
140
|
+
return { known: false, entries: 0, uncoveredSeconds: 0 };
|
|
141
|
+
}
|
|
142
|
+
const body = await resp.json();
|
|
143
|
+
const entries = Array.isArray(body.oncalls) ? body.oncalls.length : 0;
|
|
144
|
+
return { known: true, entries, uncoveredSeconds: entries > 0 ? 0 : windowSeconds };
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
logger.warn('Could not reach the on-call rota API', { error: errorMessage(err) });
|
|
148
|
+
return { known: false, entries: 0, uncoveredSeconds: 0 };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Reduce a v3 webhook body to the fields this system uses.
|
|
153
|
+
*
|
|
154
|
+
* Returns `null` for anything that is not a recognised incident event, which
|
|
155
|
+
* the caller COUNTS rather than drops silently — an unrecognised event type is
|
|
156
|
+
* usually a subscription configured wider than the handler, and a count is how
|
|
157
|
+
* that becomes visible instead of being inferred from nothing happening.
|
|
158
|
+
*/
|
|
159
|
+
export function parseOnCallWebhook(body) {
|
|
160
|
+
const event = body?.event;
|
|
161
|
+
if (!event || typeof event.event_type !== 'string')
|
|
162
|
+
return null;
|
|
163
|
+
const data = (event.data ?? {});
|
|
164
|
+
const agency = (event.agent ?? {});
|
|
165
|
+
return {
|
|
166
|
+
eventType: event.event_type,
|
|
167
|
+
incidentId: typeof data.id === 'string' ? data.id : undefined,
|
|
168
|
+
// PagerDuty echoes the trigger's dedup_key on the incident, which is the
|
|
169
|
+
// only link back to WHICH case or alert this was — their incident id is
|
|
170
|
+
// theirs and means nothing on this side.
|
|
171
|
+
dedupKey: typeof data.dedup_key === 'string' ? data.dedup_key : undefined,
|
|
172
|
+
agentId: typeof agency.id === 'string' ? agency.id : undefined,
|
|
173
|
+
agentSummary: typeof agency.summary === 'string' ? agency.summary : undefined,
|
|
174
|
+
createdAt: typeof data.created_at === 'string' ? data.created_at : undefined,
|
|
175
|
+
occurredAt: typeof event.occurred_at === 'string' ? event.occurred_at : undefined,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Seconds between the incident being raised and this event.
|
|
180
|
+
*
|
|
181
|
+
* Returns `undefined` rather than 0 when either timestamp is missing or the
|
|
182
|
+
* arithmetic is nonsense: a 0 would land in the first histogram bucket and read
|
|
183
|
+
* as an instant acknowledgement, which is the most flattering possible lie
|
|
184
|
+
* about response time.
|
|
185
|
+
*/
|
|
186
|
+
export function ackLatencySeconds(event) {
|
|
187
|
+
if (!event.createdAt || !event.occurredAt)
|
|
188
|
+
return undefined;
|
|
189
|
+
const from = new Date(event.createdAt).getTime();
|
|
190
|
+
const to = new Date(event.occurredAt).getTime();
|
|
191
|
+
if (!Number.isFinite(from) || !Number.isFinite(to) || to < from)
|
|
192
|
+
return undefined;
|
|
193
|
+
return (to - from) / 1000;
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGFnZXJkdXR5LWluYm91bmQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VydmljZXMvcGFnZXJkdXR5LWluYm91bmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0Qzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBMkJHO0FBRUgsT0FBTyxFQUFFLFVBQVUsRUFBRSxlQUFlLEVBQUUsTUFBTSxRQUFRLENBQUM7QUFDckQsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUNqRCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDbEQsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQ3BELE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUU3QyxNQUFNLE1BQU0sR0FBRyxZQUFZLENBQUMsbUJBQW1CLENBQUMsQ0FBQztBQUVqRCxNQUFNLENBQUMsTUFBTSxpQkFBaUIsR0FBRywyQkFBMkIsQ0FBQztBQUU3RDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0F1Qkc7QUFDSCxNQUFNLFVBQVUsd0JBQXdCLENBQUMsT0FBd0IsRUFBRSxNQUEwQixFQUFFLE1BQWM7SUFDM0csSUFBSSxDQUFDLE1BQU0sSUFBSSxDQUFDLE1BQU07UUFBRSxPQUFPLEtBQUssQ0FBQztJQUNyQyxNQUFNLFFBQVEsR0FBRyxVQUFVLENBQUMsUUFBUSxFQUFFLE1BQU0sQ0FBQyxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDNUUsTUFBTSxXQUFXLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsTUFBTSxDQUFDLENBQUM7SUFDbEQsS0FBSyxNQUFNLElBQUksSUFBSSxNQUFNLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxFQUFF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QUM7QUFFRDs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxVQUFVLGlCQUFpQixDQUFDLEtBQXlCO0lBQ3pELElBQUksQ0FBQyxLQUFLLENBQUMsU0FBUyxJQUFJLENBQUMsS0FBSyxDQUFDLFVBQVU7UUFBRSxPQUFPLFNBQVMsQ0FBQztJQUM1RCxNQUFNLElBQUksR0FBRyxJQUFJLElBQUksQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUM7SUFDakQsTUFBTSxFQUFFLEdBQUcsSUFBSSxJQUFJLENBQUMsS0FBSyxDQUFDLFVBQVUsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQ2hELElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUMsSUFBSSxFQUFFLEdBQUcsSUFBSTtRQUFFLE9BQU8sU0FBUyxDQUFDO0lBQ2xGLE9BQU8sQ0FBQyxFQUFFLEdBQUcsSUFBSSxDQUFDLEdBQUcsSUFBSSxDQUFDO0FBQzVCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBUaGUgSU5CT1VORCBoYWxmIG9mIG9uLWNhbGw6IHdoYXQgdGhlIHByb3ZpZGVyIHRlbGxzIHVzIGJhY2suXG4gKlxuICogRXZlcnl0aGluZyBlbHNlIGluIHRoaXMgZmVhdHVyZSBwdXNoZXMgb3V0d2FyZCDigJQgYW4gYWxlcnQgZmlyZXMsIGEgY2FzZVxuICogYXBwcm9hY2hlcyBpdHMgZGVhZGxpbmUsIGFuIGV2ZW50IGdvZXMgdG8gRXZlbnRzIHYyLiBUaGF0IGRpcmVjdGlvbiBjYW4gb25seVxuICogZXZlciByZXBvcnQgdGhhdCBhIHBhZ2Ugd2FzIEFDQ0VQVEVELCB3aGljaCBpcyBhIDIwMiBhbmQgbm90IGEgcGhvbmUgcmluZ2luZy5cbiAqIFRoaXMgbW9kdWxlIGlzIHRoZSBvbmx5IHNvdXJjZSBvZiB0aGUgdHdvIGZhY3RzIHRoYXQgYWN0dWFsbHkgbWF0dGVyOlxuICpcbiAqICAtIFNPTUVCT0RZIFBJQ0tFRCBJVCBVUCwgYW5kIHdoby4gYG9uY2FsbF9wYWdlc190b3RhbHtvdXRjb21lPVwiYWNjZXB0ZWRcIn1gXG4gKiAgICBjYW5ub3QgYW5zd2VyIFwid2FzIGFueW9uZSBhd2FrZSBhdCAwMzoxNFwiOyBhbiBhY2tub3dsZWRnZW1lbnQgY2FuLCBhbmRcbiAqICAgIGJvdW5kIHRvIGEgdXNlciBpZCBpdCBiZWNvbWVzIGFuIGF1ZGl0IHJlY29yZCByYXRoZXIgdGhhbiBhIGhpbnQuIFRoZVxuICogICAgcHJvdmlkZXIncyBvd24gYWNrIHZpZXcgaXMgbm90IGF1ZGl0YWJsZSBoZXJlIOKAlCBhIGNsaWNrIG9uIGEgbGluayBpbiB0aGVpclxuICogICAgVUkgaXMgbm90IGFuIGlkZW50aXR5IHRoaXMgc3lzdGVtIGNhbiBhdHRlc3QgdG8g4oCUIHNvIHRoZSB3ZWJob29rIGlzIHdoYXRcbiAqICAgIHR1cm5zIHRoZWlyIHN0YXRlIGNoYW5nZSBpbnRvIG91ciByZWNvcmQuXG4gKiAgLSBOT0JPRFkgSVMgU0NIRURVTEVELiBBIHJvdGEgd2l0aCBhIGhvbGUgbmV4dCBUdWVzZGF5IGhhcyB0byBiZSB2aXNpYmxlIG9uXG4gKiAgICBNT05EQVksIG5vdCBhdCAwMjowMCBUdWVzZGF5LiBUaGF0IGlzIGFuIGFic2VuY2UgY2hlY2ssIGFuZCBhYnNlbmNlIGNoZWNrc1xuICogICAgYXJlIHRoZSBvbmVzIHRoaXMgY29kZWJhc2Uga2VlcHMgZmluZGluZyBpdCBoYWQgbWlzc2VkLiBJdCBpcyBhbnN3ZXJlZCBieVxuICogICAgUkVBRElORyB0aGUgcHJvdmlkZXIncyBzY2hlZHVsZSByYXRoZXIgdGhhbiBvd25pbmcgb25lLlxuICpcbiAqIFRXTyBDUkVERU5USUFMUywgREVMSUJFUkFURUxZIFNFUEFSQVRFIEFORCBCT1RIIE9QVElPTkFMLlxuICogYFBBR0VSRFVUWV9XRUJIT09LX1NFQ1JFVGAgdmVyaWZpZXMgd2hhdCB0aGV5IHNlbmQgdXMuIGBQQUdFUkRVVFlfQVBJX1RPS0VOYFxuICogaXMgYSBSRVNUIHRva2VuIGZvciByZWFkaW5nIHRoZSByb3RhIGFuZCBpcyBzdHJpY3RseSBtb3JlIHBvd2VyZnVsIHRoYW4gdGhlXG4gKiBwZXItc2VydmljZSBpbnRlZ3JhdGlvbiBrZXkgdXNlZCBmb3Igc2VuZGluZyDigJQgYW4gaW50ZWdyYXRpb24ga2V5IGNhbiBvbmx5XG4gKiBjcmVhdGUgZXZlbnRzIG9uIG9uZSBzZXJ2aWNlLCB3aGlsZSBhIFJFU1QgdG9rZW4gcmVhZHMgaW5jaWRlbnRzLCBzY2hlZHVsZXNcbiAqIGFuZCB1c2VycyBhY2NvdW50LXdpZGUuIFNvIHRoZSByb3RhIGNoZWNrIGlzIG9wdC1pbiBvbiBpdHMgb3duIGNyZWRlbnRpYWwsXG4gKiBhbmQgYSBkZXBsb3ltZW50IHRoYXQgZG9lcyBub3Qgd2FudCB0byBoYW5kIG92ZXIgYSBSRVNUIHRva2VuIHNpbXBseSBkb2VzIG5vdFxuICogZ2V0IHRoZSBnYXVnZS4gSXQgbXVzdCBuZXZlciBiZSBpbmZlcnJlZCBmcm9tIHRoZSByb3V0aW5nIGtleS5cbiAqL1xuXG5pbXBvcnQgeyBjcmVhdGVIbWFjLCB0aW1pbmdTYWZlRXF1YWwgfSBmcm9tICdjcnlwdG8nO1xuaW1wb3J0IHsgZW52SW50LCBlbnZTdHIgfSBmcm9tICcuLi91dGlscy9lbnYuanMnO1xuaW1wb3J0IHsgY3JlYXRlTG9nZ2VyIH0gZnJvbSAnLi4vdXRpbHMvbG9nZ2VyLmpzJztcbmltcG9ydCB7IGVycm9yTWVzc2FnZSB9IGZyb20gJy4uL3V0aWxzL3Jlc3BvbnNlLmpzJztcbmltcG9ydCB7IHNhZmVGZXRjaCB9IGZyb20gJy4uL3V0aWxzL3NzcmYuanMnO1xuXG5jb25zdCBsb2dnZXIgPSBjcmVhdGVMb2dnZXIoJ3BhZ2VyZHV0eS1pbmJvdW5kJyk7XG5cbmV4cG9ydCBjb25zdCBQQUdFUkRVVFlfQVBJX1VSTCA9ICdodHRwczovL2FwaS5wYWdlcmR1dHkuY29tJztcblxuLyoqXG4gKiBWZXJpZnkgYSBQYWdlckR1dHkgdjMgd2ViaG9vayBzaWduYXR1cmUuXG4gKlxuICogVGhlIHNjaGVtZTogYFgtUGFnZXJEdXR5LVNpZ25hdHVyZTogdjE9PGxvd2VyY2FzZSBoZXg+YCwgYW4gSE1BQy1TSEEyNTYgb3ZlclxuICogdGhlIFJBVyByZXF1ZXN0IGJvZHkuIFRocmVlIHByb3BlcnRpZXMgb2YgaXQgYXJlIGVhc3kgdG8gZ2V0IHdyb25nIGFuZCBlYWNoXG4gKiBvbmUgaXMgYSBob2xlOlxuICpcbiAqICAtIFRIRSBSQVcgQk9EWSwgbm90IGEgcmUtc2VyaWFsaXplZCBvYmplY3QuIGBKU09OLnN0cmluZ2lmeShyZXEuYm9keSlgIGFmdGVyXG4gKiAgICBhIEpTT04gcGFyc2VyIGhhcyBydW4gcHJvZHVjZXMgZGlmZmVyZW50IGJ5dGVzIChrZXkgb3JkZXIsIHdoaXRlc3BhY2UsXG4gKiAgICBudW1iZXIgZm9ybWF0dGluZyksIHNvIGV2ZXJ5IHNpZ25hdHVyZSBmYWlscyBhbmQgdGhlIG9idmlvdXMgXCJmaXhcIiBpcyB0b1xuICogICAgc3RvcCBjaGVja2luZy4gVGhlIHJvdXRlIHRoZXJlZm9yZSBtb3VudHMgYGV4cHJlc3MucmF3YC5cbiAqICAtIE1VTFRJUExFIFNJR05BVFVSRVMuIFRoZSBoZWFkZXIgbWF5IGNhcnJ5IHNldmVyYWwgY29tbWEtc2VwYXJhdGVkIGB2MT1gXG4gKiAgICB2YWx1ZXMsIHdoaWNoIGlzIGhvdyBQYWdlckR1dHkgcm90YXRlcyBhIHNlY3JldCB3aXRob3V0IGRvd250aW1lLiBDaGVja2luZ1xuICogICAgb25seSB0aGUgZmlyc3QgYnJlYWtzIGR1cmluZyB0aGVpciByb3RhdGlvbiwgc28gQU5ZIG1hdGNoIGlzIGFjY2VwdGVkIOKAlFxuICogICAgdGhlIHNhbWUgb3ZlcmxhcCB0aGlzIHJlcG8gYWxyZWFkeSBydW5zIGZvciBpdHMgb3duIHJlbGF5IHRva2VuLlxuICogIC0gQ09OU1RBTlQgVElNRS4gQ29tcGFyZWQgd2l0aCBgdGltaW5nU2FmZUVxdWFsYCBvbiBlcXVhbC1sZW5ndGggYnVmZmVycyxcbiAqICAgIGJlY2F1c2UgYSBieXRlLWF0LWEtdGltZSBjb21wYXJpc29uIGxlYWtzIHRoZSBleHBlY3RlZCBkaWdlc3QgdG8gYW55b25lXG4gKiAgICB3aG8gY2FuIHBvc3QgaGVyZSByZXBlYXRlZGx5LlxuICpcbiAqIE5PIFNFQ1JFVCBNRUFOUyBOTy4gQW4gdW52ZXJpZmllZCB3ZWJob29rIHRoYXQgbW92ZXMgYW4gYXVkaXQgcmVjb3JkIGFuZCBhXG4gKiBtZXRyaWMgaXMgYSB3YXkgZm9yIGFueW9uZSB3aG8gbGVhcm5zIHRoZSBVUkwgdG8gYXNzZXJ0IHRoYXQgYSBwYWdlIHdhc1xuICogaGFuZGxlZCDigJQgdGhlIG9uZSBsaWUgdGhhdCB3b3VsZCBtYWtlIHRoZSB3aG9sZSBmZWF0dXJlIHJlcG9ydCBjb3ZlcmFnZSBpdFxuICogZG9lcyBub3QgaGF2ZS4gRmFpbHMgY2xvc2VkLlxuICovXG5leHBvcnQgZnVuY3Rpb24gdmVyaWZ5UGFnZXJEdXR5U2lnbmF0dXJlKHJhd0JvZHk6IEJ1ZmZlciB8IHN0cmluZywgaGVhZGVyOiBzdHJpbmcgfCB1bmRlZmluZWQsIHNlY3JldDogc3RyaW5nKTogYm9vbGVhbiB7XG4gIGlmICghc2VjcmV0IHx8ICFoZWFkZXIpIHJldHVybiBmYWxzZTtcbiAgY29uc3QgZXhwZWN0ZWQgPSBjcmVhdGVIbWFjKCdzaGEyNTYnLCBzZWNyZXQpLnVwZGF0ZShyYXdCb2R5KS5kaWdlc3QoJ2hleCcpO1xuICBjb25zdCBleHBlY3RlZEJ1ZiA9IEJ1ZmZlci5mcm9tKGV4cGVjdGVkLCAndXRmOCcpO1xuICBmb3IgKGNvbnN0IHBhcnQgb2YgaGVhZGVyLnNwbGl0KCcsJykpIHtcbiAgICBjb25zdCB2YWx1ZSA9IHBhcnQudHJpbSgpO1xuICAgIGlmICghdmFsdWUuc3RhcnRzV2l0aCgndjE9JykpIGNvbnRpbnVlO1xuICAgIGNvbnN0IGdpdmVuID0gQnVmZmVyLmZyb20odmFsdWUuc2xpY2UoMyksICd1dGY4Jyk7XG4gICAgLy8gTGVuZ3RoLWd1YXJkZWQ6IHRpbWluZ1NhZmVFcXVhbCBUSFJPV1Mgb24gYSBsZW5ndGggbWlzbWF0Y2gsIGFuZCBhblxuICAgIC8vIGF0dGFja2VyIGNvbnRyb2xzIHRoaXMgbGVuZ3RoLlxuICAgIGlmIChnaXZlbi5sZW5ndGggPT09IGV4cGVjdGVkQnVmLmxlbmd0aCAmJiB0aW1pbmdTYWZlRXF1YWwoZ2l2ZW4sIGV4cGVjdGVkQnVmKSkgcmV0dXJuIHRydWU7XG4gIH1cbiAgcmV0dXJuIGZhbHNlO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIE9uQ2FsbEluYm91bmRDb25maWcge1xuICB3ZWJob29rU2VjcmV0OiBzdHJpbmc7XG4gIGFwaVRva2VuOiBzdHJpbmc7XG4gIGFwaVVybDogc3RyaW5nO1xuICAvKiogSG93IGZhciBhaGVhZCB0aGUgcm90YSBjaGVjayBsb29rcy4gVGhlIGdhcCBhbGVydCBpcyBvbmx5IGFzIHVzZWZ1bCBhcyB0aGlzLiAqL1xuICBsb29rYWhlYWRIb3VyczogbnVtYmVyO1xuICAvKiogRXNjYWxhdGlvbiBwb2xpY3kgaWRzIHRvIGNoZWNrIGNvdmVyYWdlIGZvcjsgZW1wdHkgbWVhbnMgdGhlIHdob2xlIGFjY291bnQuICovXG4gIGVzY2FsYXRpb25Qb2xpY3lJZHM6IHN0cmluZ1tdO1xuICB0aW1lb3V0TXM6IG51bWJlcjtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIG9uQ2FsbEluYm91bmRDb25maWcoKTogT25DYWxsSW5ib3VuZENvbmZpZyB7XG4gIHJldHVybiB7XG4gICAgd2ViaG9va1NlY3JldDogZW52U3RyKCdQQUdFUkRVVFlfV0VCSE9PS19TRUNSRVQnLCAnJyksXG4gICAgYXBpVG9rZW46IGVudlN0cignUEFHRVJEVVRZX0FQSV9UT0tFTicsICcnKSxcbiAgICBhcGlVcmw6IGVudlN0cignUEFHRVJEVVRZX0FQSV9VUkwnLCBQQUdFUkRVVFlfQVBJX1VSTCksXG4gICAgLy8gNDggaG91cnM6IGZhciBlbm91Z2ggdG8gY2F0Y2ggYSBob2xlIG92ZXIgYSB3ZWVrZW5kIG9uIHRoZSBGcmlkYXksIGFuZFxuICAgIC8vIHNob3J0IGVub3VnaCB0aGF0IGEgcm90YSBtYWludGFpbmVkIGEgd2VlayBvdXQgZG9lcyBub3QgYWxlcnQgZm9yZXZlci5cbiAgICBsb29rYWhlYWRIb3VyczogZW52SW50KCdPTkNBTExfU0NIRURVTEVfR0FQX0xPT0tBSEVBRF9IT1VSUycsIDQ4LCB7IG1pbjogMSwgbWF4OiAyMTYwIH0pLFxuICAgIGVzY2FsYXRpb25Qb2xpY3lJZHM6IGVudlN0cignUEFHRVJEVVRZX0VTQ0FMQVRJT05fUE9MSUNZX0lEUycsICcnKVxuICAgICAgLnNwbGl0KCcsJykubWFwKCh4KSA9PiB4LnRyaW0oKSkuZmlsdGVyKEJvb2xlYW4pLFxuICAgIHRpbWVvdXRNczogZW52SW50KCdPTkNBTExfRVZFTlRTX1RJTUVPVVRfTVMnLCA1MDAwLCB7IG1pbjogMSB9KSxcbiAgfTtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBSb3RhQ292ZXJhZ2Uge1xuICAvKiogV2hldGhlciB0aGUgcXVlc3Rpb24gY291bGQgYmUgYW5zd2VyZWQgYXQgYWxsLiAqL1xuICBrbm93bjogYm9vbGVhbjtcbiAgLyoqIERpc3RpbmN0IG9uLWNhbGwgZW50cmllcyBjb3ZlcmluZyBhbnkgcGFydCBvZiB0aGUgd2luZG93LiAqL1xuICBlbnRyaWVzOiBudW1iZXI7XG4gIC8qKiBTZWNvbmRzIG9mIHRoZSBsb29rYWhlYWQgd2luZG93IHdpdGggbm9ib2R5IG9uIGNhbGwsIDAgd2hlbiBmdWxseSBjb3ZlcmVkLiAqL1xuICB1bmNvdmVyZWRTZWNvbmRzOiBudW1iZXI7XG59XG5cbi8qKlxuICogQXNrIHRoZSBwcm92aWRlciB3aGV0aGVyIGFueW9uZSBpcyBvbiBjYWxsIGFjcm9zcyB0aGUgbG9va2FoZWFkIHdpbmRvdy5cbiAqXG4gKiBXSEFUIFRISVMgQ0FOIEFORCBDQU5OT1QgQ09OQ0xVREUsIGJlY2F1c2UgdGhlIGRpZmZlcmVuY2UgZGVjaWRlcyB3aGV0aGVyIHRoZVxuICogYWxlcnQgYnVpbHQgb24gaXQgaXMgaG9uZXN0LiBgR0VUIC9vbmNhbGxzP3NpbmNlJnVudGlsYCByZXR1cm5zIHRoZSBvbi1jYWxsXG4gKiBlbnRyaWVzIE9WRVJMQVBQSU5HIGEgd2luZG93OyBpdCBkb2VzIG5vdCByZXR1cm4gdGhlIGhvbGVzLiBSZWNvbnN0cnVjdGluZ1xuICogZXhhY3QgaG9sZXMgd291bGQgbWVhbiBzdGl0Y2hpbmcgZXZlcnkgZW50cnkncyBpbnRlcnZhbCwgYW5kIGFuIGVudHJ5J3NcbiAqIGBzdGFydGAvYGVuZGAgYXJlIG51bGwgZm9yIGFuIGFsd2F5cy1vbiBwb2xpY3kg4oCUIHNvIGEgcHJlY2lzZVxuICogXCJ1bmNvdmVyZWQgc2Vjb25kc1wiIGlzIG5vdCBhdmFpbGFibGUgZnJvbSB0aGlzIGVuZHBvaW50IHdpdGhvdXQgYSBsb3Qgb2ZcbiAqIGFyaXRobWV0aWMgdGhhdCB3b3VsZCBiZSB3cm9uZyBpbiB0aGUgYWx3YXlzLW9uIGNhc2UuXG4gKlxuICogU28gdGhpcyBhbnN3ZXJzIHRoZSBxdWVzdGlvbiBpdCBDQU4gYW5zd2VyIGV4YWN0bHk6IGlzIHRoZSB3aW5kb3cgY292ZXJlZCBhdFxuICogYWxsLiBaZXJvIGVudHJpZXMgb3ZlciB0aGUgbmV4dCBOIGhvdXJzIG1lYW5zIG5vYm9keSBpcyBzY2hlZHVsZWQsIHdoaWNoIGlzXG4gKiB0aGUgZmFpbHVyZSB3b3J0aCBwYWdpbmcgYWJvdXQgYW5kIHRoZSBvbmUgYSByb3RhIG1haW50YWluZXIgYWN0dWFsbHkgbWFrZXMuXG4gKiBgdW5jb3ZlcmVkU2Vjb25kc2AgaXMgdGhlcmVmb3JlIHRoZSB3aG9sZSB3aW5kb3cgb3Igbm90aGluZywgYW5kIGl0IGlzIGFcbiAqIGRlbGliZXJhdGUgdW5kZXItcmVwb3J0IHJhdGhlciB0aGFuIGEgZ3Vlc3Mg4oCUIGEgcGFydGlhbCBob2xlIHJlYWRzIGFzIGNvdmVyZWRcbiAqIGhlcmUsIGFuZCBjbGFpbWluZyBvdGhlcndpc2Ugd291bGQgYmUgaW52ZW50aW5nIHByZWNpc2lvbi5cbiAqXG4gKiBga25vd246IGZhbHNlYCB3aGVuIHRoZXJlIGlzIG5vIEFQSSB0b2tlbiBvciB0aGUgY2FsbCBmYWlscywgYW5kIHRoZSBjYWxsZXJcbiAqIG11c3QgcHVibGlzaCBOT1RISU5HIGluIHRoYXQgY2FzZS4gQSBnYXVnZSBvZiAwIHdvdWxkIHNheSBcImZ1bGx5IGNvdmVyZWRcIixcbiAqIHdoaWNoIGlzIGV4YWN0bHkgdGhlIGxpZSB0aGlzIGNoZWNrIGV4aXN0cyB0byBwcmV2ZW50LlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZmV0Y2hSb3RhQ292ZXJhZ2UoY2ZnOiBPbkNhbGxJbmJvdW5kQ29uZmlnID0gb25DYWxsSW5ib3VuZENvbmZpZygpKTogUHJvbWlzZTxSb3RhQ292ZXJhZ2U+IHtcbiAgY29uc3Qgd2luZG93U2Vjb25kcyA9IGNmZy5sb29rYWhlYWRIb3VycyAqIDM2MDA7XG4gIGlmICghY2ZnLmFwaVRva2VuKSByZXR1cm4geyBrbm93bjogZmFsc2UsIGVudHJpZXM6IDAsIHVuY292ZXJlZFNlY29uZHM6IDAgfTtcblxuICBjb25zdCBzaW5jZSA9IG5ldyBEYXRlKCk7XG4gIGNvbnN0IHVudGlsID0gbmV3IERhdGUoc2luY2UuZ2V0VGltZSgpICsgd2luZG93U2Vjb25kcyAqIDEwMDApO1xuICBjb25zdCBwYXJhbXMgPSBuZXcgVVJMU2VhcmNoUGFyYW1zKHtcbiAgICBzaW5jZTogc2luY2UudG9JU09TdHJpbmcoKSxcbiAgICB1bnRpbDogdW50aWwudG9JU09TdHJpbmcoKSxcbiAgICBsaW1pdDogJzUwJyxcbiAgfSk7XG4gIGZvciAoY29uc3QgaWQgb2YgY2ZnLmVzY2FsYXRpb25Qb2xpY3lJZHMpIHBhcmFtcy5hcHBlbmQoJ2VzY2FsYXRpb25fcG9saWN5X2lkc1tdJywgaWQpO1xuXG4gIHRyeSB7XG4gICAgY29uc3QgcmVzcCA9IGF3YWl0IHNhZmVGZXRjaChgJHtjZmcuYXBpVXJsfS9vbmNhbGxzPyR7cGFyYW1zLnRvU3RyaW5nKCl9YCwge1xuICAgICAgbWV0aG9kOiAnR0VUJyxcbiAgICAgIGhlYWRlcnM6IHtcbiAgICAgICAgLy8gUGFnZXJEdXR5J3MgUkVTVCBzY2hlbWUuIGBBY2NlcHRgIHBpbnMgdGhlIEFQSSB2ZXJzaW9uOyB3aXRob3V0IGl0XG4gICAgICAgIC8vIHRoZSByZXNwb25zZSBzaGFwZSBpcyB3aGF0ZXZlciB0aGVpciBkZWZhdWx0IGhhcHBlbnMgdG8gYmUuXG4gICAgICAgIEF1dGhvcml6YXRpb246IGBUb2tlbiB0b2tlbj0ke2NmZy5hcGlUb2tlbn1gLFxuICAgICAgICBBY2NlcHQ6ICdhcHBsaWNhdGlvbi92bmQucGFnZXJkdXR5K2pzb247dmVyc2lvbj0yJyxcbiAgICAgIH0sXG4gICAgICB0aW1lb3V0TXM6IGNmZy50aW1lb3V0TXMsXG4gICAgfSk7XG4gICAgaWYgKHJlc3AucmVkaXJlY3RlZCB8fCAhcmVzcC5vaykge1xuICAgICAgbG9nZ2VyLndhcm4oJ0NvdWxkIG5vdCByZWFkIHRoZSBvbi1jYWxsIHJvdGEnLCB7IHN0YXR1czogcmVzcC5zdGF0dXMsIHJlZGlyZWN0ZWQ6IHJlc3AucmVkaXJlY3RlZCB9KTtcbiAgICAgIHJldHVybiB7IGtub3duOiBmYWxzZSwgZW50cmllczogMCwgdW5jb3ZlcmVkU2Vjb25kczogMCB9O1xuICAgIH1cbiAgICBjb25zdCBib2R5ID0gYXdhaXQgcmVzcC5qc29uKCkgYXMgeyBvbmNhbGxzPzogdW5rbm93bltdIH07XG4gICAgY29uc3QgZW50cmllcyA9IEFycmF5LmlzQXJyYXkoYm9keS5vbmNhbGxzKSA/IGJvZHkub25jYWxscy5sZW5ndGggOiAwO1xuICAgIHJldHVybiB7IGtub3duOiB0cnVlLCBlbnRyaWVzLCB1bmNvdmVyZWRTZWNvbmRzOiBlbnRyaWVzID4gMCA/IDAgOiB3aW5kb3dTZWNvbmRzIH07XG4gIH0gY2F0Y2ggKGVycikge1xuICAgIGxvZ2dlci53YXJuKCdDb3VsZCBub3QgcmVhY2ggdGhlIG9uLWNhbGwgcm90YSBBUEknLCB7IGVycm9yOiBlcnJvck1lc3NhZ2UoZXJyKSB9KTtcbiAgICByZXR1cm4geyBrbm93bjogZmFsc2UsIGVudHJpZXM6IDAsIHVuY292ZXJlZFNlY29uZHM6IDAgfTtcbiAgfVxufVxuXG4vKiogVGhlIHdlYmhvb2sgZXZlbnRzIHRoaXMgc3lzdGVtIGFjdHMgb24uIEV2ZXJ5dGhpbmcgZWxzZSBpcyBjb3VudGVkIGFuZCBkcm9wcGVkLiAqL1xuZXhwb3J0IHR5cGUgT25DYWxsV2ViaG9va0V2ZW50VHlwZSA9XG4gIHwgJ2luY2lkZW50LmFja25vd2xlZGdlZCdcbiAgfCAnaW5jaWRlbnQuZXNjYWxhdGVkJ1xuICB8ICdpbmNpZGVudC5yZXNvbHZlZCdcbiAgfCAnaW5jaWRlbnQudW5hY2tub3dsZWRnZWQnO1xuXG5leHBvcnQgaW50ZXJmYWNlIE9uQ2FsbFdlYmhvb2tFdmVudCB7XG4gIGV2ZW50VHlwZTogc3RyaW5nO1xuICAvKiogUHJvdmlkZXIgaW5jaWRlbnQgaWQg4oCUIHRoZSBqb2luIGJhY2sgdG8gd2hhdCB3ZSBzZW50LiAqL1xuICBpbmNpZGVudElkPzogc3RyaW5nO1xuICAvKiogT3VyIG93biBgZGVkdXBfa2V5YCwgd2hlbiB0aGUgcHJvdmlkZXIgZWNob2VzIGl0LiAqL1xuICBkZWR1cEtleT86IHN0cmluZztcbiAgLyoqIFdobyBhY3RlZCwgYXMgdGhlIHByb3ZpZGVyIGtub3dzIHRoZW0uIEFuIGlkIGFuZCBhbiBlbWFpbCwgbmV2ZXIgYSBzZXNzaW9uLiAqL1xuICBhZ2VudElkPzogc3RyaW5nO1xuICBhZ2VudFN1bW1hcnk/OiBzdHJpbmc7XG4gIC8qKiBXaGVuIHRoZSBpbmNpZGVudCB3YXMgY3JlYXRlZCwgZm9yIHRoZSBhY2stbGF0ZW5jeSBtZWFzdXJlbWVudC4gKi9cbiAgY3JlYXRlZEF0Pzogc3RyaW5nO1xuICBvY2N1cnJlZEF0Pzogc3RyaW5nO1xufVxuXG4vKipcbiAqIFJlZHVjZSBhIHYzIHdlYmhvb2sgYm9keSB0byB0aGUgZmllbGRzIHRoaXMgc3lzdGVtIHVzZXMuXG4gKlxuICogUmV0dXJucyBgbnVsbGAgZm9yIGFueXRoaW5nIHRoYXQgaXMgbm90IGEgcmVjb2duaXNlZCBpbmNpZGVudCBldmVudCwgd2hpY2hcbiAqIHRoZSBjYWxsZXIgQ09VTlRTIHJhdGhlciB0aGFuIGRyb3BzIHNpbGVudGx5IOKAlCBhbiB1bnJlY29nbmlzZWQgZXZlbnQgdHlwZSBpc1xuICogdXN1YWxseSBhIHN1YnNjcmlwdGlvbiBjb25maWd1cmVkIHdpZGVyIHRoYW4gdGhlIGhhbmRsZXIsIGFuZCBhIGNvdW50IGlzIGhvd1xuICogdGhhdCBiZWNvbWVzIHZpc2libGUgaW5zdGVhZCBvZiBiZWluZyBpbmZlcnJlZCBmcm9tIG5vdGhpbmcgaGFwcGVuaW5nLlxuICovXG5leHBvcnQgZnVuY3Rpb24gcGFyc2VPbkNhbGxXZWJob29rKGJvZHk6IHVua25vd24pOiBPbkNhbGxXZWJob29rRXZlbnQgfCBudWxsIHtcbiAgY29uc3QgZXZlbnQgPSAoYm9keSBhcyB7IGV2ZW50PzogUmVjb3JkPHN0cmluZywgdW5rbm93bj4gfSk/LmV2ZW50O1xuICBpZiAoIWV2ZW50IHx8IHR5cGVvZiBldmVudC5ldmVudF90eXBlICE9PSAnc3RyaW5nJykgcmV0dXJuIG51bGw7XG4gIGNvbnN0IGRhdGEgPSAoZXZlbnQuZGF0YSA/PyB7fSkgYXMgUmVjb3JkPHN0cmluZywgdW5rbm93bj47XG4gIGNvbnN0IGFnZW5jeSA9IChldmVudC5hZ2VudCA/PyB7fSkgYXMgUmVjb3JkPHN0cmluZywgdW5rbm93bj47XG4gIHJldHVybiB7XG4gICAgZXZlbnRUeXBlOiBldmVudC5ldmVudF90eXBlLFxuICAgIGluY2lkZW50SWQ6IHR5cGVvZiBkYXRhLmlkID09PSAnc3RyaW5nJyA/IGRhdGEuaWQgOiB1bmRlZmluZWQsXG4gICAgLy8gUGFnZXJEdXR5IGVjaG9lcyB0aGUgdHJpZ2dlcidzIGRlZHVwX2tleSBvbiB0aGUgaW5jaWRlbnQsIHdoaWNoIGlzIHRoZVxuICAgIC8vIG9ubHkgbGluayBiYWNrIHRvIFdISUNIIGNhc2Ugb3IgYWxlcnQgdGhpcyB3YXMg4oCUIHRoZWlyIGluY2lkZW50IGlkIGlzXG4gICAgLy8gdGhlaXJzIGFuZCBtZWFucyBub3RoaW5nIG9uIHRoaXMgc2lkZS5cbiAgICBkZWR1cEtleTogdHlwZW9mIGRhdGEuZGVkdXBfa2V5ID09PSAnc3RyaW5nJyA/IGRhdGEuZGVkdXBfa2V5IDogdW5kZWZpbmVkLFxuICAgIGFnZW50SWQ6IHR5cGVvZiBhZ2VuY3kuaWQgPT09ICdzdHJpbmcnID8gYWdlbmN5LmlkIDogdW5kZWZpbmVkLFxuICAgIGFnZW50U3VtbWFyeTogdHlwZW9mIGFnZW5jeS5zdW1tYXJ5ID09PSAnc3RyaW5nJyA/IGFnZW5jeS5zdW1tYXJ5IDogdW5kZWZpbmVkLFxuICAgIGNyZWF0ZWRBdDogdHlwZW9mIGRhdGEuY3JlYXRlZF9hdCA9PT0gJ3N0cmluZycgPyBkYXRhLmNyZWF0ZWRfYXQgOiB1bmRlZmluZWQsXG4gICAgb2NjdXJyZWRBdDogdHlwZW9mIGV2ZW50Lm9jY3VycmVkX2F0ID09PSAnc3RyaW5nJyA/IGV2ZW50Lm9jY3VycmVkX2F0IDogdW5kZWZpbmVkLFxuICB9O1xufVxuXG4vKipcbiAqIFNlY29uZHMgYmV0d2VlbiB0aGUgaW5jaWRlbnQgYmVpbmcgcmFpc2VkIGFuZCB0aGlzIGV2ZW50LlxuICpcbiAqIFJldHVybnMgYHVuZGVmaW5lZGAgcmF0aGVyIHRoYW4gMCB3aGVuIGVpdGhlciB0aW1lc3RhbXAgaXMgbWlzc2luZyBvciB0aGVcbiAqIGFyaXRobWV0aWMgaXMgbm9uc2Vuc2U6IGEgMCB3b3VsZCBsYW5kIGluIHRoZSBmaXJzdCBoaXN0b2dyYW0gYnVja2V0IGFuZCByZWFkXG4gKiBhcyBhbiBpbnN0YW50IGFja25vd2xlZGdlbWVudCwgd2hpY2ggaXMgdGhlIG1vc3QgZmxhdHRlcmluZyBwb3NzaWJsZSBsaWVcbiAqIGFib3V0IHJlc3BvbnNlIHRpbWUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBhY2tMYXRlbmN5U2Vjb25kcyhldmVudDogT25DYWxsV2ViaG9va0V2ZW50KTogbnVtYmVyIHwgdW5kZWZpbmVkIHtcbiAgaWYgKCFldmVudC5jcmVhdGVkQXQgfHwgIWV2ZW50Lm9jY3VycmVkQXQpIHJldHVybiB1bmRlZmluZWQ7XG4gIGNvbnN0IGZyb20gPSBuZXcgRGF0ZShldmVudC5jcmVhdGVkQXQpLmdldFRpbWUoKTtcbiAgY29uc3QgdG8gPSBuZXcgRGF0ZShldmVudC5vY2N1cnJlZEF0KS5nZXRUaW1lKCk7XG4gIGlmICghTnVtYmVyLmlzRmluaXRlKGZyb20pIHx8ICFOdW1iZXIuaXNGaW5pdGUodG8pIHx8IHRvIDwgZnJvbSkgcmV0dXJuIHVuZGVmaW5lZDtcbiAgcmV0dXJuICh0byAtIGZyb20pIC8gMTAwMDtcbn1cbiJdfQ==
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/** PagerDuty's own severity enum. Ours map straight onto it. */
|
|
2
|
+
export type PagerDutySeverity = 'critical' | 'error' | 'warning' | 'info';
|
|
3
|
+
/** The three follow-up actions a single `dedupKey` moves through. */
|
|
4
|
+
export type PagerDutyAction = 'trigger' | 'acknowledge' | 'resolve';
|
|
5
|
+
export interface PagerDutyEvent {
|
|
6
|
+
/**
|
|
7
|
+
* The key that ties a trigger to its later acknowledge/resolve. Stable and
|
|
8
|
+
* derived, never random: `case:<caseId>` for the per-case sweep,
|
|
9
|
+
* Alertmanager's own group fingerprint for the alert leg — so its grouping and
|
|
10
|
+
* ours cannot fight over the same incident.
|
|
11
|
+
*/
|
|
12
|
+
dedupKey: string;
|
|
13
|
+
action: PagerDutyAction;
|
|
14
|
+
/**
|
|
15
|
+
* WHICH leg raised this, as a metric dimension only — never sent to PagerDuty.
|
|
16
|
+
*
|
|
17
|
+
* The two legs have different failure modes and must be separable in one
|
|
18
|
+
* counter: `alertmanager` depends on Prometheus evaluating and Alertmanager
|
|
19
|
+
* delivering, `case` depends on this product's own sweep being alive. Collapsed
|
|
20
|
+
* into one series, a dead sweep is invisible for as long as the fleet alert
|
|
21
|
+
* still fires — which is precisely the window the per-case trigger exists to
|
|
22
|
+
* cover.
|
|
23
|
+
*/
|
|
24
|
+
trigger?: 'alertmanager' | 'case';
|
|
25
|
+
/** Required by the API on a `trigger`; ignored on resolve. */
|
|
26
|
+
summary?: string;
|
|
27
|
+
severity?: PagerDutySeverity;
|
|
28
|
+
/** Human-readable origin. Required on a `trigger`. */
|
|
29
|
+
source?: string;
|
|
30
|
+
component?: string;
|
|
31
|
+
/**
|
|
32
|
+
* PagerDuty's grouping dimension. We put the ORG ID here and nothing else —
|
|
33
|
+
* see the note on `customDetails`.
|
|
34
|
+
*/
|
|
35
|
+
group?: string;
|
|
36
|
+
class?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Free-form detail. IDS AND TIMESTAMPS ONLY — never case content.
|
|
39
|
+
*
|
|
40
|
+
* This is an authorization boundary, not a style preference. The on-call
|
|
41
|
+
* permission is system-org scoped and grants no tenant access at all
|
|
42
|
+
* (`isSystemOrgId` is a content-owner check, not a privilege gate), so putting
|
|
43
|
+
* a case subject or body here would route tenant content to a third party
|
|
44
|
+
* through a channel that deliberately has no right to read it.
|
|
45
|
+
*/
|
|
46
|
+
customDetails?: Record<string, string | number | boolean | null>;
|
|
47
|
+
}
|
|
48
|
+
export interface PagerDutyResult {
|
|
49
|
+
ok: boolean;
|
|
50
|
+
/** HTTP status when the request completed. */
|
|
51
|
+
status?: number;
|
|
52
|
+
/** Why it failed, for the log line — never surfaced to a tenant. */
|
|
53
|
+
error?: string;
|
|
54
|
+
/** True when paging is configured off, so a caller can tell "off" from "broke". */
|
|
55
|
+
skipped?: boolean;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Which provider the Events v2 calls go to.
|
|
59
|
+
*
|
|
60
|
+
* `pagerduty` pins the known endpoint. `custom` means "something else that
|
|
61
|
+
* implements this wire format" and REQUIRES `ONCALL_EVENTS_URL` — it does not
|
|
62
|
+
* fall back, because falling back is how a typo'd URL quietly pages the wrong
|
|
63
|
+
* account, or pages nobody while reporting success.
|
|
64
|
+
*
|
|
65
|
+
* WHY THERE IS NO `opsgenie` HERE, although the design notes proposed one:
|
|
66
|
+
* nothing documents Opsgenie accepting the Events v2 shape. Its own alert API
|
|
67
|
+
* takes `message`/`alias`/`priority`, not `summary`/`severity`/`dedup_key`, so
|
|
68
|
+
* an `opsgenie` value would name a provider this client cannot actually talk to
|
|
69
|
+
* — a config option that fails at 3am on the one call that had to work. A
|
|
70
|
+
* genuinely Events-v2-compatible vendor is reachable through `custom` with its
|
|
71
|
+
* endpoint stated, which is honest about who verified the compatibility.
|
|
72
|
+
*/
|
|
73
|
+
export type OnCallProvider = 'pagerduty' | 'custom';
|
|
74
|
+
export declare const PAGERDUTY_EVENTS_URL = "https://events.pagerduty.com/v2/enqueue";
|
|
75
|
+
export interface PagerDutyConfig {
|
|
76
|
+
/**
|
|
77
|
+
* Whether paging runs at all. OPT-IN, against this repo's usual opt-out grain
|
|
78
|
+
* (`SUPPORT_ENABLED`, `BILLING_DISCOUNTS_ENABLED`): a feature needing an
|
|
79
|
+
* external credential defaults off here, the way `BILLING_METERING_ENABLED`
|
|
80
|
+
* does, and a pager with no routing key would page nobody while reporting
|
|
81
|
+
* success.
|
|
82
|
+
*/
|
|
83
|
+
enabled: boolean;
|
|
84
|
+
/** Per-SERVICE integration key. NOT a REST API key — see docs/oncall-paging.md. */
|
|
85
|
+
routingKey: string;
|
|
86
|
+
provider: OnCallProvider;
|
|
87
|
+
/** Resolved from `provider` + `ONCALL_EVENTS_URL`. Empty means misconfigured. */
|
|
88
|
+
endpoint: string;
|
|
89
|
+
timeoutMs: number;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Resolve the endpoint from the provider and the override.
|
|
93
|
+
*
|
|
94
|
+
* Exported because the resolution has a failure case worth testing directly:
|
|
95
|
+
* `custom` with no URL returns EMPTY, and the caller treats that as
|
|
96
|
+
* unconfigured rather than silently reaching for PagerDuty's endpoint with
|
|
97
|
+
* somebody else's routing key.
|
|
98
|
+
*/
|
|
99
|
+
export declare function resolveEventsEndpoint(provider: OnCallProvider, override: string): string;
|
|
100
|
+
export declare function pagerDutyConfig(): PagerDutyConfig;
|
|
101
|
+
/**
|
|
102
|
+
* Send one event. Returns `{ ok: true }` only when PagerDuty ACCEPTED it.
|
|
103
|
+
*
|
|
104
|
+
* A 202 is an acceptance and not a delivery receipt — it says PagerDuty has the
|
|
105
|
+
* event, nothing about whether a phone rang. `oncall_pages_total{outcome}`
|
|
106
|
+
* records what we know, which is this and no more; the only honest evidence of
|
|
107
|
+
* coverage is a human acknowledging.
|
|
108
|
+
*/
|
|
109
|
+
export declare function sendPagerDutyEvent(event: PagerDutyEvent, cfg?: PagerDutyConfig): Promise<PagerDutyResult>;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* PagerDuty Events API v2 — the only thing in this repo that can wake a human.
|
|
5
|
+
*
|
|
6
|
+
* WHY THIS EXISTS. `SUPPORT_PREMIUM_ENABLED` ships on, and premium sells a first
|
|
7
|
+
* response "within 8 hours, any day" — the one response target in
|
|
8
|
+
* `SUPPORT_RESPONSE_TARGETS` with no business-hours exemption, so its clock runs
|
|
9
|
+
* through nights and weekends. Before this, the only thing watching it was
|
|
10
|
+
* `SupportPremiumFirstResponseAtRisk` routed to a Slack channel, which satisfies
|
|
11
|
+
* the routing and not the obligation: the alert's own description says "if no
|
|
12
|
+
* rota is awake, that is the cause".
|
|
13
|
+
*
|
|
14
|
+
* WHY NO SDK. The whole protocol is one HTTPS POST of JSON to a fixed endpoint.
|
|
15
|
+
* An SDK for that is a dependency for nothing in a repo that digest-pins,
|
|
16
|
+
* OSV-scans and enforces `minimumReleaseAge`.
|
|
17
|
+
*
|
|
18
|
+
* WHAT THE PROVIDER OWNS, AND WHY THAT DECIDED THE DESIGN. `dedupKey` plus
|
|
19
|
+
* `event_action` means PagerDuty holds the incident state, so three things this
|
|
20
|
+
* repo would otherwise have had to build are simply theirs:
|
|
21
|
+
*
|
|
22
|
+
* - DEDUPLICATION. Fifty premium cases breaching together — the normal shape
|
|
23
|
+
* after any outage — is one incident, not fifty pages. Fifty pages and no
|
|
24
|
+
* pages are the same thing.
|
|
25
|
+
* - RESOLUTION. `resolve` on the same key closes it, so a case that finally
|
|
26
|
+
* gets a first response stops paging without a local state machine.
|
|
27
|
+
* - CONTACT METHODS and their verification, the rota, and escalation.
|
|
28
|
+
*
|
|
29
|
+
* So there is no rota in this codebase on purpose. What stays here is the
|
|
30
|
+
* TRIGGER, because the product knows something PagerDuty cannot: a support case
|
|
31
|
+
* carries an exact `response_due_at`, which beats a p90 histogram that can only
|
|
32
|
+
* say the distribution already drifted.
|
|
33
|
+
*
|
|
34
|
+
* NEVER THROWS. Every caller is on an alerting path — a webhook handler or a
|
|
35
|
+
* sweep — and an exception there turns "could not page" into "crashed instead of
|
|
36
|
+
* paging". Failures come back as a typed result and are counted.
|
|
37
|
+
*/
|
|
38
|
+
import { envBool, envInt, envStr } from '../utils/env.js';
|
|
39
|
+
import { createLogger } from '../utils/logger.js';
|
|
40
|
+
import { emitCounter } from '../utils/metric-emitter.js';
|
|
41
|
+
import { errorMessage } from '../utils/response.js';
|
|
42
|
+
import { safeFetch } from '../utils/ssrf.js';
|
|
43
|
+
const logger = createLogger('pagerduty');
|
|
44
|
+
export const PAGERDUTY_EVENTS_URL = 'https://events.pagerduty.com/v2/enqueue';
|
|
45
|
+
/**
|
|
46
|
+
* Resolve the endpoint from the provider and the override.
|
|
47
|
+
*
|
|
48
|
+
* Exported because the resolution has a failure case worth testing directly:
|
|
49
|
+
* `custom` with no URL returns EMPTY, and the caller treats that as
|
|
50
|
+
* unconfigured rather than silently reaching for PagerDuty's endpoint with
|
|
51
|
+
* somebody else's routing key.
|
|
52
|
+
*/
|
|
53
|
+
export function resolveEventsEndpoint(provider, override) {
|
|
54
|
+
if (override)
|
|
55
|
+
return override;
|
|
56
|
+
return provider === 'pagerduty' ? PAGERDUTY_EVENTS_URL : '';
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Whether paging is BOTH asked for and coherent.
|
|
60
|
+
*
|
|
61
|
+
* ON-CALL REQUIRES SUPPORT. `ONCALL_ENABLED` exists to page about a support
|
|
62
|
+
* response target, so with `SUPPORT_ENABLED=false` there is no product to page
|
|
63
|
+
* about — the bundles are inactive, no case can be opened, and the only thing
|
|
64
|
+
* left is a deployment paying per seat for a pager wired to nothing.
|
|
65
|
+
*
|
|
66
|
+
* VALIDATED FOR THE ACTIVE FEATURE ONLY, which is the precedent that matters
|
|
67
|
+
* here: a bundle validator once crashed billing at boot by checking every
|
|
68
|
+
* bundle rather than every ACTIVE one. So this neither throws nor warns when
|
|
69
|
+
* paging is off — it is not a conflict until somebody asks for the thing. When
|
|
70
|
+
* they do, the prerequisite is reported once and paging stays off rather than
|
|
71
|
+
* half-on, because a pager that fires for a product nobody sells is the same
|
|
72
|
+
* failure as a pager with no key: it reports coverage of nothing.
|
|
73
|
+
*/
|
|
74
|
+
function onCallCoherentlyEnabled() {
|
|
75
|
+
if (!envBool('ONCALL_ENABLED', false))
|
|
76
|
+
return false;
|
|
77
|
+
if (envBool('SUPPORT_ENABLED', true))
|
|
78
|
+
return true;
|
|
79
|
+
logger.error('ONCALL_ENABLED=true with SUPPORT_ENABLED=false — paging stays OFF', {
|
|
80
|
+
why: 'on-call pages about a support response target; with support off there are no cases and no target',
|
|
81
|
+
});
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
export function pagerDutyConfig() {
|
|
85
|
+
const provider = envStr('ONCALL_PROVIDER', 'pagerduty') === 'custom' ? 'custom' : 'pagerduty';
|
|
86
|
+
return {
|
|
87
|
+
enabled: onCallCoherentlyEnabled(),
|
|
88
|
+
// Read from the environment rather than a mounted file: unlike the
|
|
89
|
+
// Alertmanager Slack URLs (api_url_file), this key is consumed by OUR
|
|
90
|
+
// process, which already receives its secrets as env from a Secret.
|
|
91
|
+
routingKey: envStr('PAGERDUTY_ROUTING_KEY', ''),
|
|
92
|
+
provider,
|
|
93
|
+
endpoint: resolveEventsEndpoint(provider, envStr('ONCALL_EVENTS_URL', '')),
|
|
94
|
+
timeoutMs: envInt('ONCALL_EVENTS_TIMEOUT_MS', 5000, { min: 1 }),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Send one event. Returns `{ ok: true }` only when PagerDuty ACCEPTED it.
|
|
99
|
+
*
|
|
100
|
+
* A 202 is an acceptance and not a delivery receipt — it says PagerDuty has the
|
|
101
|
+
* event, nothing about whether a phone rang. `oncall_pages_total{outcome}`
|
|
102
|
+
* records what we know, which is this and no more; the only honest evidence of
|
|
103
|
+
* coverage is a human acknowledging.
|
|
104
|
+
*/
|
|
105
|
+
export async function sendPagerDutyEvent(event, cfg = pagerDutyConfig()) {
|
|
106
|
+
const labels = { action: event.action, trigger: event.trigger ?? 'alertmanager' };
|
|
107
|
+
// Configured OFF is a SKIP, distinct from a failure. The Alertmanager leg
|
|
108
|
+
// relies on this: its route is always present (alertmanager.yml is asserted
|
|
109
|
+
// byte-identical across targets, so it cannot be toggled per deployment), and
|
|
110
|
+
// this endpoint accepting-and-dropping is what makes the knob work without a
|
|
111
|
+
// second config file and without a stream of notification failures.
|
|
112
|
+
if (!cfg.enabled) {
|
|
113
|
+
emitCounter('oncall_pages_total', { ...labels, outcome: 'disabled' });
|
|
114
|
+
return { ok: true, skipped: true };
|
|
115
|
+
}
|
|
116
|
+
if (!cfg.endpoint) {
|
|
117
|
+
// `ONCALL_PROVIDER=custom` with no `ONCALL_EVENTS_URL`. Counted under the
|
|
118
|
+
// same outcome as a missing key because it is the same class of failure —
|
|
119
|
+
// somebody asked for paging and the client has nowhere to send it — and
|
|
120
|
+
// because quietly reaching for PagerDuty's endpoint instead would send this
|
|
121
|
+
// deployment's routing key to a provider nobody chose.
|
|
122
|
+
emitCounter('oncall_pages_total', { ...labels, outcome: 'unconfigured' });
|
|
123
|
+
logger.error('ONCALL_PROVIDER=custom but ONCALL_EVENTS_URL is empty — nobody will be paged', {
|
|
124
|
+
dedupKey: event.dedupKey,
|
|
125
|
+
});
|
|
126
|
+
return { ok: false, error: 'no events endpoint' };
|
|
127
|
+
}
|
|
128
|
+
if (!cfg.routingKey) {
|
|
129
|
+
// Enabled with no key is the half-configured state the knob exists to
|
|
130
|
+
// prevent, and it is the one failure worth shouting about: somebody asked
|
|
131
|
+
// for paging and is not getting it.
|
|
132
|
+
emitCounter('oncall_pages_total', { ...labels, outcome: 'unconfigured' });
|
|
133
|
+
logger.error('ONCALL_ENABLED is true but PAGERDUTY_ROUTING_KEY is empty — nobody will be paged', {
|
|
134
|
+
dedupKey: event.dedupKey,
|
|
135
|
+
});
|
|
136
|
+
return { ok: false, error: 'no routing key' };
|
|
137
|
+
}
|
|
138
|
+
// `resolve` and `acknowledge` need only the key and the action; a trigger
|
|
139
|
+
// needs summary/severity/source. Built conditionally so a resolve cannot be
|
|
140
|
+
// rejected for a missing field it never required.
|
|
141
|
+
const body = {
|
|
142
|
+
routing_key: cfg.routingKey,
|
|
143
|
+
event_action: event.action,
|
|
144
|
+
dedup_key: event.dedupKey,
|
|
145
|
+
};
|
|
146
|
+
if (event.action === 'trigger') {
|
|
147
|
+
body.payload = {
|
|
148
|
+
summary: event.summary ?? 'Pipeline Builder alert',
|
|
149
|
+
severity: event.severity ?? 'critical',
|
|
150
|
+
source: event.source ?? 'pipeline-builder',
|
|
151
|
+
...(event.component ? { component: event.component } : {}),
|
|
152
|
+
...(event.group ? { group: event.group } : {}),
|
|
153
|
+
...(event.class ? { class: event.class } : {}),
|
|
154
|
+
...(event.customDetails ? { custom_details: event.customDetails } : {}),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const resp = await safeFetch(cfg.endpoint, {
|
|
159
|
+
method: 'POST',
|
|
160
|
+
headers: { 'Content-Type': 'application/json' },
|
|
161
|
+
body: JSON.stringify(body),
|
|
162
|
+
timeoutMs: cfg.timeoutMs,
|
|
163
|
+
});
|
|
164
|
+
// `safeFetch` refuses redirects rather than following them, so a 3xx arrives
|
|
165
|
+
// as `redirected` and is a FAILED send — never a pivot to an unvetted host
|
|
166
|
+
// and never recorded green.
|
|
167
|
+
if (resp.redirected) {
|
|
168
|
+
emitCounter('oncall_pages_total', { ...labels, outcome: 'redirected' });
|
|
169
|
+
logger.error('PagerDuty endpoint redirected — refused', { status: resp.status });
|
|
170
|
+
return { ok: false, status: resp.status, error: 'endpoint redirected (refused)' };
|
|
171
|
+
}
|
|
172
|
+
if (!resp.ok) {
|
|
173
|
+
emitCounter('oncall_pages_total', { ...labels, outcome: 'rejected' });
|
|
174
|
+
logger.error('PagerDuty rejected an event', { status: resp.status, dedupKey: event.dedupKey });
|
|
175
|
+
return { ok: false, status: resp.status, error: `status ${resp.status}` };
|
|
176
|
+
}
|
|
177
|
+
emitCounter('oncall_pages_total', { ...labels, outcome: 'accepted' });
|
|
178
|
+
return { ok: true, status: resp.status };
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
emitCounter('oncall_pages_total', { ...labels, outcome: 'error' });
|
|
182
|
+
logger.error('Could not reach PagerDuty', { error: errorMessage(err), dedupKey: event.dedupKey });
|
|
183
|
+
return { ok: false, error: errorMessage(err) };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGFnZXJkdXR5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NlcnZpY2VzL3BhZ2VyZHV0eS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBa0NHO0FBRUgsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDMUQsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQ2xELE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUN6RCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDcEQsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBRTdDLE1BQU0sTUFBTSxHQUFHLFlBQVksQ0FBQyxXQUFXLENBQUMsQ0FBQztBQWdGekMsTUFBTSxDQUFDLE1BQU0sb0JBQW9CLEdBQUcseUNBQXlDLENBQUM7QUFtQjlFOzs7Ozs7O0dBT0c7QUFDSCxNQUFNLFVBQVUscUJBQXFCLENBQUMsUUFBd0IsRUFBRSxRQUFnQjtJQUM5RSxJQUFJLFFBQVE7UUFBRSxPQUFPLFFBQVEsQ0FBQztJQUM5QixPQUFPLFFBQVEsS0FBSyxXQUFXLENBQUMsQ0FBQyxDQUFDLG9CQUFvQixDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7QUFDOUQsQ0FBQztBQUVEOzs7Ozs7Ozs7Ozs7Ozs7R0FlRztBQUNILFNBQVMsdUJBQXVCO0lBQzlCLElBQUksQ0FBQyxPQUFPLENBQUMsZ0JBQWdCLEVBQUUsS0FBSyxDQUFDO1FBQUUsT0FBTyxLQUFLLENBQUM7SUFDcEQsSUFBSSxPQUFPLENBQUMsaUJBQWlCLEVBQUUsSUFBSSxDQUFDO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFDbEQsTUFBTSxDQUFDLEtBQUssQ0FBQyxtRUFB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Q25FLE1BQU0sQ0FBQyxLQUFLLENBQUMsMkJBQTJCLEVBQUUsRUFBRSxLQUFLLEVBQUUsWUFBWSxDQUFDLEdBQUcsQ0FBQyxFQUFFLFFBQVEsRUFBRSxLQUFLLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQztRQUNsRyxPQUFPLEVBQUUsRUFBRSxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsWUFBWSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUM7SUFDakQsQ0FBQztBQUNILENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBQYWdlckR1dHkgRXZlbnRzIEFQSSB2MiDigJQgdGhlIG9ubHkgdGhpbmcgaW4gdGhpcyByZXBvIHRoYXQgY2FuIHdha2UgYSBodW1hbi5cbiAqXG4gKiBXSFkgVEhJUyBFWElTVFMuIGBTVVBQT1JUX1BSRU1JVU1fRU5BQkxFRGAgc2hpcHMgb24sIGFuZCBwcmVtaXVtIHNlbGxzIGEgZmlyc3RcbiAqIHJlc3BvbnNlIFwid2l0aGluIDggaG91cnMsIGFueSBkYXlcIiDigJQgdGhlIG9uZSByZXNwb25zZSB0YXJnZXQgaW5cbiAqIGBTVVBQT1JUX1JFU1BPTlNFX1RBUkdFVFNgIHdpdGggbm8gYnVzaW5lc3MtaG91cnMgZXhlbXB0aW9uLCBzbyBpdHMgY2xvY2sgcnVuc1xuICogdGhyb3VnaCBuaWdodHMgYW5kIHdlZWtlbmRzLiBCZWZvcmUgdGhpcywgdGhlIG9ubHkgdGhpbmcgd2F0Y2hpbmcgaXQgd2FzXG4gKiBgU3VwcG9ydFByZW1pdW1GaXJzdFJlc3BvbnNlQXRSaXNrYCByb3V0ZWQgdG8gYSBTbGFjayBjaGFubmVsLCB3aGljaCBzYXRpc2ZpZXNcbiAqIHRoZSByb3V0aW5nIGFuZCBub3QgdGhlIG9ibGlnYXRpb246IHRoZSBhbGVydCdzIG93biBkZXNjcmlwdGlvbiBzYXlzIFwiaWYgbm9cbiAqIHJvdGEgaXMgYXdha2UsIHRoYXQgaXMgdGhlIGNhdXNlXCIuXG4gKlxuICogV0hZIE5PIFNESy4gVGhlIHdob2xlIHByb3RvY29sIGlzIG9uZSBIVFRQUyBQT1NUIG9mIEpTT04gdG8gYSBmaXhlZCBlbmRwb2ludC5cbiAqIEFuIFNESyBmb3IgdGhhdCBpcyBhIGRlcGVuZGVuY3kgZm9yIG5vdGhpbmcgaW4gYSByZXBvIHRoYXQgZGlnZXN0LXBpbnMsXG4gKiBPU1Ytc2NhbnMgYW5kIGVuZm9yY2VzIGBtaW5pbXVtUmVsZWFzZUFnZWAuXG4gKlxuICogV0hBVCBUSEUgUFJPVklERVIgT1dOUywgQU5EIFdIWSBUSEFUIERFQ0lERUQgVEhFIERFU0lHTi4gYGRlZHVwS2V5YCBwbHVzXG4gKiBgZXZlbnRfYWN0aW9uYCBtZWFucyBQYWdlckR1dHkgaG9sZHMgdGhlIGluY2lkZW50IHN0YXRlLCBzbyB0aHJlZSB0aGluZ3MgdGhpc1xuICogcmVwbyB3b3VsZCBvdGhlcndpc2UgaGF2ZSBoYWQgdG8gYnVpbGQgYXJlIHNpbXBseSB0aGVpcnM6XG4gKlxuICogIC0gREVEVVBMSUNBVElPTi4gRmlmdHkgcHJlbWl1bSBjYXNlcyBicmVhY2hpbmcgdG9nZXRoZXIg4oCUIHRoZSBub3JtYWwgc2hhcGVcbiAqICAgIGFmdGVyIGFueSBvdXRhZ2Ug4oCUIGlzIG9uZSBpbmNpZGVudCwgbm90IGZpZnR5IHBhZ2VzLiBGaWZ0eSBwYWdlcyBhbmQgbm9cbiAqICAgIHBhZ2VzIGFyZSB0aGUgc2FtZSB0aGluZy5cbiAqICAtIFJFU09MVVRJT04uIGByZXNvbHZlYCBvbiB0aGUgc2FtZSBrZXkgY2xvc2VzIGl0LCBzbyBhIGNhc2UgdGhhdCBmaW5hbGx5XG4gKiAgICBnZXRzIGEgZmlyc3QgcmVzcG9uc2Ugc3RvcHMgcGFnaW5nIHdpdGhvdXQgYSBsb2NhbCBzdGF0ZSBtYWNoaW5lLlxuICogIC0gQ09OVEFDVCBNRVRIT0RTIGFuZCB0aGVpciB2ZXJpZmljYXRpb24sIHRoZSByb3RhLCBhbmQgZXNjYWxhdGlvbi5cbiAqXG4gKiBTbyB0aGVyZSBpcyBubyByb3RhIGluIHRoaXMgY29kZWJhc2Ugb24gcHVycG9zZS4gV2hhdCBzdGF5cyBoZXJlIGlzIHRoZVxuICogVFJJR0dFUiwgYmVjYXVzZSB0aGUgcHJvZHVjdCBrbm93cyBzb21ldGhpbmcgUGFnZXJEdXR5IGNhbm5vdDogYSBzdXBwb3J0IGNhc2VcbiAqIGNhcnJpZXMgYW4gZXhhY3QgYHJlc3BvbnNlX2R1ZV9hdGAsIHdoaWNoIGJlYXRzIGEgcDkwIGhpc3RvZ3JhbSB0aGF0IGNhbiBvbmx5XG4gKiBzYXkgdGhlIGRpc3RyaWJ1dGlvbiBhbHJlYWR5IGRyaWZ0ZWQuXG4gKlxuICogTkVWRVIgVEhST1dTLiBFdmVyeSBjYWxsZXIgaXMgb24gYW4gYWxlcnRpbmcgcGF0aCDigJQgYSB3ZWJob29rIGhhbmRsZXIgb3IgYVxuICogc3dlZXAg4oCUIGFuZCBhbiBleGNlcHRpb24gdGhlcmUgdHVybnMgXCJjb3VsZCBub3QgcGFnZVwiIGludG8gXCJjcmFzaGVkIGluc3RlYWQgb2ZcbiAqIHBhZ2luZ1wiLiBGYWlsdXJlcyBjb21lIGJhY2sgYXMgYSB0eXBlZCByZXN1bHQgYW5kIGFyZSBjb3VudGVkLlxuICovXG5cbmltcG9ydCB7IGVudkJvb2wsIGVudkludCwgZW52U3RyIH0gZnJvbSAnLi4vdXRpbHMvZW52LmpzJztcbmltcG9ydCB7IGNyZWF0ZUxvZ2dlciB9IGZyb20gJy4uL3V0aWxzL2xvZ2dlci5qcyc7XG5pbXBvcnQgeyBlbWl0Q291bnRlciB9IGZyb20gJy4uL3V0aWxzL21ldHJpYy1lbWl0dGVyLmpzJztcbmltcG9ydCB7IGVycm9yTWVzc2FnZSB9IGZyb20gJy4uL3V0aWxzL3Jlc3BvbnNlLmpzJztcbmltcG9ydCB7IHNhZmVGZXRjaCB9IGZyb20gJy4uL3V0aWxzL3NzcmYuanMnO1xuXG5jb25zdCBsb2dnZXIgPSBjcmVhdGVMb2dnZXIoJ3BhZ2VyZHV0eScpO1xuXG4vKiogUGFnZXJEdXR5J3Mgb3duIHNldmVyaXR5IGVudW0uIE91cnMgbWFwIHN0cmFpZ2h0IG9udG8gaXQuICovXG5leHBvcnQgdHlwZSBQYWdlckR1dHlTZXZlcml0eSA9ICdjcml0aWNhbCcgfCAnZXJyb3InIHwgJ3dhcm5pbmcnIHwgJ2luZm8nO1xuXG4vKiogVGhlIHRocmVlIGZvbGxvdy11cCBhY3Rpb25zIGEgc2luZ2xlIGBkZWR1cEtleWAgbW92ZXMgdGhyb3VnaC4gKi9cbmV4cG9ydCB0eXBlIFBhZ2VyRHV0eUFjdGlvbiA9ICd0cmlnZ2VyJyB8ICdhY2tub3dsZWRnZScgfCAncmVzb2x2ZSc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgUGFnZXJEdXR5RXZlbnQge1xuICAvKipcbiAgICogVGhlIGtleSB0aGF0IHRpZXMgYSB0cmlnZ2VyIHRvIGl0cyBsYXRlciBhY2tub3dsZWRnZS9yZXNvbHZlLiBTdGFibGUgYW5kXG4gICAqIGRlcml2ZWQsIG5ldmVyIHJhbmRvbTogYGNhc2U6PGNhc2VJZD5gIGZvciB0aGUgcGVyLWNhc2Ugc3dlZXAsXG4gICAqIEFsZXJ0bWFuYWdlcidzIG93biBncm91cCBmaW5nZXJwcmludCBmb3IgdGhlIGFsZXJ0IGxlZyDigJQgc28gaXRzIGdyb3VwaW5nIGFuZFxuICAgKiBvdXJzIGNhbm5vdCBmaWdodCBvdmVyIHRoZSBzYW1lIGluY2lkZW50LlxuICAgKi9cbiAgZGVkdXBLZXk6IHN0cmluZztcbiAgYWN0aW9uOiBQYWdlckR1dHlBY3Rpb247XG4gIC8qKlxuICAgKiBXSElDSCBsZWcgcmFpc2VkIHRoaXMsIGFzIGEgbWV0cmljIGRpbWVuc2lvbiBvbmx5IOKAlCBuZXZlciBzZW50IHRvIFBhZ2VyRHV0eS5cbiAgICpcbiAgICogVGhlIHR3byBsZWdzIGhhdmUgZGlmZmVyZW50IGZhaWx1cmUgbW9kZXMgYW5kIG11c3QgYmUgc2VwYXJhYmxlIGluIG9uZVxuICAgKiBjb3VudGVyOiBgYWxlcnRtYW5hZ2VyYCBkZXBlbmRzIG9uIFByb21ldGhldXMgZXZhbHVhdGluZyBhbmQgQWxlcnRtYW5hZ2VyXG4gICAqIGRlbGl2ZXJpbmcsIGBjYXNlYCBkZXBlbmRzIG9uIHRoaXMgcHJvZHVjdCdzIG93biBzd2VlcCBiZWluZyBhbGl2ZS4gQ29sbGFwc2VkXG4gICAqIGludG8gb25lIHNlcmllcywgYSBkZWFkIHN3ZWVwIGlzIGludmlzaWJsZSBmb3IgYXMgbG9uZyBhcyB0aGUgZmxlZXQgYWxlcnRcbiAgICogc3RpbGwgZmlyZXMg4oCUIHdoaWNoIGlzIHByZWNpc2VseSB0aGUgd2luZG93IHRoZSBwZXItY2FzZSB0cmlnZ2VyIGV4aXN0cyB0b1xuICAgKiBjb3Zlci5cbiAgICovXG4gIHRyaWdnZXI/OiAnYWxlcnRtYW5hZ2VyJyB8ICdjYXNlJztcbiAgLyoqIFJlcXVpcmVkIGJ5IHRoZSBBUEkgb24gYSBgdHJpZ2dlcmA7IGlnbm9yZWQgb24gcmVzb2x2ZS4gKi9cbiAgc3VtbWFyeT86IHN0cmluZztcbiAgc2V2ZXJpdHk/OiBQYWdlckR1dHlTZXZlcml0eTtcbiAgLyoqIEh1bWFuLXJlYWRhYmxlIG9yaWdpbi4gUmVxdWlyZWQgb24gYSBgdHJpZ2dlcmAuICovXG4gIHNvdXJjZT86IHN0cmluZztcbiAgY29tcG9uZW50Pzogc3RyaW5nO1xuICAvKipcbiAgICogUGFnZXJEdXR5J3MgZ3JvdXBpbmcgZGltZW5zaW9uLiBXZSBwdXQgdGhlIE9SRyBJRCBoZXJlIGFuZCBub3RoaW5nIGVsc2Ug4oCUXG4gICAqIHNlZSB0aGUgbm90ZSBvbiBgY3VzdG9tRGV0YWlsc2AuXG4gICAqL1xuICBncm91cD86IHN0cmluZztcbiAgY2xhc3M/OiBzdHJpbmc7XG4gIC8qKlxuICAgKiBGcmVlLWZvcm0gZGV0YWlsLiBJRFMgQU5EIFRJTUVTVEFNUFMgT05MWSDigJQgbmV2ZXIgY2FzZSBjb250ZW50LlxuICAgKlxuICAgKiBUaGlzIGlzIGFuIGF1dGhvcml6YXRpb24gYm91bmRhcnksIG5vdCBhIHN0eWxlIHByZWZlcmVuY2UuIFRoZSBvbi1jYWxsXG4gICAqIHBlcm1pc3Npb24gaXMgc3lzdGVtLW9yZyBzY29wZWQgYW5kIGdyYW50cyBubyB0ZW5hbnQgYWNjZXNzIGF0IGFsbFxuICAgKiAoYGlzU3lzdGVtT3JnSWRgIGlzIGEgY29udGVudC1vd25lciBjaGVjaywgbm90IGEgcHJpdmlsZWdlIGdhdGUpLCBzbyBwdXR0aW5nXG4gICAqIGEgY2FzZSBzdWJqZWN0IG9yIGJvZHkgaGVyZSB3b3VsZCByb3V0ZSB0ZW5hbnQgY29udGVudCB0byBhIHRoaXJkIHBhcnR5XG4gICAqIHRocm91Z2ggYSBjaGFubmVsIHRoYXQgZGVsaWJlcmF0ZWx5IGhhcyBubyByaWdodCB0byByZWFkIGl0LlxuICAgKi9cbiAgY3VzdG9tRGV0YWlscz86IFJlY29yZDxzdHJpbmcsIHN0cmluZyB8IG51bWJlciB8IGJvb2xlYW4gfCBudWxsPjtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBQYWdlckR1dHlSZXN1bHQge1xuICBvazogYm9vbGVhbjtcbiAgLyoqIEhUVFAgc3RhdHVzIHdoZW4gdGhlIHJlcXVlc3QgY29tcGxldGVkLiAqL1xuICBzdGF0dXM/OiBudW1iZXI7XG4gIC8qKiBXaHkgaXQgZmFpbGVkLCBmb3IgdGhlIGxvZyBsaW5lIOKAlCBuZXZlciBzdXJmYWNlZCB0byBhIHRlbmFudC4gKi9cbiAgZXJyb3I/OiBzdHJpbmc7XG4gIC8qKiBUcnVlIHdoZW4gcGFnaW5nIGlzIGNvbmZpZ3VyZWQgb2ZmLCBzbyBhIGNhbGxlciBjYW4gdGVsbCBcIm9mZlwiIGZyb20gXCJicm9rZVwiLiAqL1xuICBza2lwcGVkPzogYm9vbGVhbjtcbn1cblxuLyoqXG4gKiBXaGljaCBwcm92aWRlciB0aGUgRXZlbnRzIHYyIGNhbGxzIGdvIHRvLlxuICpcbiAqIGBwYWdlcmR1dHlgIHBpbnMgdGhlIGtub3duIGVuZHBvaW50LiBgY3VzdG9tYCBtZWFucyBcInNvbWV0aGluZyBlbHNlIHRoYXRcbiAqIGltcGxlbWVudHMgdGhpcyB3aXJlIGZvcm1hdFwiIGFuZCBSRVFVSVJFUyBgT05DQUxMX0VWRU5UU19VUkxgIOKAlCBpdCBkb2VzIG5vdFxuICogZmFsbCBiYWNrLCBiZWNhdXNlIGZhbGxpbmcgYmFjayBpcyBob3cgYSB0eXBvJ2QgVVJMIHF1aWV0bHkgcGFnZXMgdGhlIHdyb25nXG4gKiBhY2NvdW50LCBvciBwYWdlcyBub2JvZHkgd2hpbGUgcmVwb3J0aW5nIHN1Y2Nlc3MuXG4gKlxuICogV0hZIFRIRVJFIElTIE5PIGBvcHNnZW5pZWAgSEVSRSwgYWx0aG91Z2ggdGhlIGRlc2lnbiBub3RlcyBwcm9wb3NlZCBvbmU6XG4gKiBub3RoaW5nIGRvY3VtZW50cyBPcHNnZW5pZSBhY2NlcHRpbmcgdGhlIEV2ZW50cyB2MiBzaGFwZS4gSXRzIG93biBhbGVydCBBUElcbiAqIHRha2VzIGBtZXNzYWdlYC9gYWxpYXNgL2Bwcmlvcml0eWAsIG5vdCBgc3VtbWFyeWAvYHNldmVyaXR5YC9gZGVkdXBfa2V5YCwgc29cbiAqIGFuIGBvcHNnZW5pZWAgdmFsdWUgd291bGQgbmFtZSBhIHByb3ZpZGVyIHRoaXMgY2xpZW50IGNhbm5vdCBhY3R1YWxseSB0YWxrIHRvXG4gKiDigJQgYSBjb25maWcgb3B0aW9uIHRoYXQgZmFpbHMgYXQgM2FtIG9uIHRoZSBvbmUgY2FsbCB0aGF0IGhhZCB0byB3b3JrLiBBXG4gKiBnZW51aW5lbHkgRXZlbnRzLXYyLWNvbXBhdGlibGUgdmVuZG9yIGlzIHJlYWNoYWJsZSB0aHJvdWdoIGBjdXN0b21gIHdpdGggaXRzXG4gKiBlbmRwb2ludCBzdGF0ZWQsIHdoaWNoIGlzIGhvbmVzdCBhYm91dCB3aG8gdmVyaWZpZWQgdGhlIGNvbXBhdGliaWxpdHkuXG4gKi9cbmV4cG9ydCB0eXBlIE9uQ2FsbFByb3ZpZGVyID0gJ3BhZ2VyZHV0eScgfCAnY3VzdG9tJztcblxuZXhwb3J0IGNvbnN0IFBBR0VSRFVUWV9FVkVOVFNfVVJMID0gJ2h0dHBzOi8vZXZlbnRzLnBhZ2VyZHV0eS5jb20vdjIvZW5xdWV1ZSc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgUGFnZXJEdXR5Q29uZmlnIHtcbiAgLyoqXG4gICAqIFdoZXRoZXIgcGFnaW5nIHJ1bnMgYXQgYWxsLiBPUFQtSU4sIGFnYWluc3QgdGhpcyByZXBvJ3MgdXN1YWwgb3B0LW91dCBncmFpblxuICAgKiAoYFNVUFBPUlRfRU5BQkxFRGAsIGBCSUxMSU5HX0RJU0NPVU5UU19FTkFCTEVEYCk6IGEgZmVhdHVyZSBuZWVkaW5nIGFuXG4gICAqIGV4dGVybmFsIGNyZWRlbnRpYWwgZGVmYXVsdHMgb2ZmIGhlcmUsIHRoZSB3YXkgYEJJTExJTkdfTUVURVJJTkdfRU5BQkxFRGBcbiAgICogZG9lcywgYW5kIGEgcGFnZXIgd2l0aCBubyByb3V0aW5nIGtleSB3b3VsZCBwYWdlIG5vYm9keSB3aGlsZSByZXBvcnRpbmdcbiAgICogc3VjY2Vzcy5cbiAgICovXG4gIGVuYWJsZWQ6IGJvb2xlYW47XG4gIC8qKiBQZXItU0VSVklDRSBpbnRlZ3JhdGlvbiBrZXkuIE5PVCBhIFJFU1QgQVBJIGtleSDigJQgc2VlIGRvY3Mvb25jYWxsLXBhZ2luZy5tZC4gKi9cbiAgcm91dGluZ0tleTogc3RyaW5nO1xuICBwcm92aWRlcjogT25DYWxsUHJvdmlkZXI7XG4gIC8qKiBSZXNvbHZlZCBmcm9tIGBwcm92aWRlcmAgKyBgT05DQUxMX0VWRU5UU19VUkxgLiBFbXB0eSBtZWFucyBtaXNjb25maWd1cmVkLiAqL1xuICBlbmRwb2ludDogc3RyaW5nO1xuICB0aW1lb3V0TXM6IG51bWJlcjtcbn1cblxuLyoqXG4gKiBSZXNvbHZlIHRoZSBlbmRwb2ludCBmcm9tIHRoZSBwcm92aWRlciBhbmQgdGhlIG92ZXJyaWRlLlxuICpcbiAqIEV4cG9ydGVkIGJlY2F1c2UgdGhlIHJlc29sdXRpb24gaGFzIGEgZmFpbHVyZSBjYXNlIHdvcnRoIHRlc3RpbmcgZGlyZWN0bHk6XG4gKiBgY3VzdG9tYCB3aXRoIG5vIFVSTCByZXR1cm5zIEVNUFRZLCBhbmQgdGhlIGNhbGxlciB0cmVhdHMgdGhhdCBhc1xuICogdW5jb25maWd1cmVkIHJhdGhlciB0aGFuIHNpbGVudGx5IHJlYWNoaW5nIGZvciBQYWdlckR1dHkncyBlbmRwb2ludCB3aXRoXG4gKiBzb21lYm9keSBlbHNlJ3Mgcm91dGluZyBrZXkuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXNvbHZlRXZlbnRzRW5kcG9pbnQocHJvdmlkZXI6IE9uQ2FsbFByb3ZpZGVyLCBvdmVycmlkZTogc3RyaW5nKTogc3RyaW5nIHtcbiAgaWYgKG92ZXJyaWRlKSByZXR1cm4gb3ZlcnJpZGU7XG4gIHJldHVybiBwcm92aWRlciA9PT0gJ3BhZ2VyZHV0eScgPyBQQUdFUkRVVFlfRVZFTlRTX1VSTCA6ICcnO1xufVxuXG4vKipcbiAqIFdoZXRoZXIgcGFnaW5nIGlzIEJPVEggYXNrZWQgZm9yIGFuZCBjb2hlcmVudC5cbiAqXG4gKiBPTi1DQUxMIFJFUVVJUkVTIFNVUFBPUlQuIGBPTkNBTExfRU5BQkxFRGAgZXhpc3RzIHRvIHBhZ2UgYWJvdXQgYSBzdXBwb3J0XG4gKiByZXNwb25zZSB0YXJnZXQsIHNvIHdpdGggYFNVUFBPUlRfRU5BQkxFRD1mYWxzZWAgdGhlcmUgaXMgbm8gcHJvZHVjdCB0byBwYWdlXG4gKiBhYm91dCDigJQgdGhlIGJ1bmRsZXMgYXJlIGluYWN0aXZlLCBubyBjYXNlIGNhbiBiZSBvcGVuZWQsIGFuZCB0aGUgb25seSB0aGluZ1xuICogbGVmdCBpcyBhIGRlcGxveW1lbnQgcGF5aW5nIHBlciBzZWF0IGZvciBhIHBhZ2VyIHdpcmVkIHRvIG5vdGhpbmcuXG4gKlxuICogVkFMSURBVEVEIEZPUiBUSEUgQUNUSVZFIEZFQVRVUkUgT05MWSwgd2hpY2ggaXMgdGhlIHByZWNlZGVudCB0aGF0IG1hdHRlcnNcbiAqIGhlcmU6IGEgYnVuZGxlIHZhbGlkYXRvciBvbmNlIGNyYXNoZWQgYmlsbGluZyBhdCBib290IGJ5IGNoZWNraW5nIGV2ZXJ5XG4gKiBidW5kbGUgcmF0aGVyIHRoYW4gZXZlcnkgQUNUSVZFIG9uZS4gU28gdGhpcyBuZWl0aGVyIHRocm93cyBub3Igd2FybnMgd2hlblxuICogcGFnaW5nIGlzIG9mZiDigJQgaXQgaXMgbm90IGEgY29uZmxpY3QgdW50aWwgc29tZWJvZHkgYXNrcyBmb3IgdGhlIHRoaW5nLiBXaGVuXG4gKiB0aGV5IGRvLCB0aGUgcHJlcmVxdWlzaXRlIGlzIHJlcG9ydGVkIG9uY2UgYW5kIHBhZ2luZyBzdGF5cyBvZmYgcmF0aGVyIHRoYW5cbiAqIGhhbGYtb24sIGJlY2F1c2UgYSBwYWdlciB0aGF0IGZpcmVzIGZvciBhIHByb2R1Y3Qgbm9ib2R5IHNlbGxzIGlzIHRoZSBzYW1lXG4gKiBmYWlsdXJlIGFzIGEgcGFnZXIgd2l0aCBubyBrZXk6IGl0IHJlcG9ydHMgY292ZXJhZ2Ugb2Ygbm90aGluZy5cbiAqL1xuZnVuY3Rpb24gb25DYWxsQ29oZXJlbnRseUVuYWJsZWQoKTogYm9vbGVhbiB7XG4gIGlmICghZW52Qm9vbCgnT05DQUxMX0VOQUJMRUQnLCBmYWxzZSkpIHJldHVybiBmYWxzZTtcbiAgaWYgKGVudkJvb2woJ1NVUFBPUlRfRU5BQkxFRCcsIHRydWUpKSByZXR1cm4gdHJ1ZTtcbiAgbG9nZ2VyLmVycm9yKCdPTkNBTExfRU5BQkxFRD10cnVlIHdpdGggU1VQUE9SVF9FTkFCTEVEPWZhbHNlIOKAlCBwYWdpbmcgc3RheXMgT0ZGJywge1xuICAgIHdoeTogJ29uLWNhbGwgcGFnZXMgYWJvdXQgYSBzdXBwb3J0IHJlc3BvbnNlIHRhcmdldDsgd2l0aCBzdXBwb3J0IG9mZiB0aGVyZSBhcmUgbm8gY2FzZXMgYW5kIG5vIHRhcmdldCcsXG4gIH0pO1xuICByZXR1cm4gZmFsc2U7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBwYWdlckR1dHlDb25maWcoKTogUGFnZXJEdXR5Q29uZmlnIHtcbiAgY29uc3QgcHJvdmlkZXIgPSBlbnZTdHIoJ09OQ0FMTF9QUk9WSURFUicsICdwYWdlcmR1dHknKSA9PT0gJ2N1c3RvbScgPyAnY3VzdG9tJyA6ICdwYWdlcmR1dHknO1xuICByZXR1cm4ge1xuICAgIGVuYWJsZWQ6IG9uQ2FsbENvaGVyZW50bHlFbmFibGVkKCksXG4gICAgLy8gUmVhZCBmcm9tIHRoZSBlbnZpcm9ubWVudCByYXRoZXIgdGhhbiBhIG1vdW50ZWQgZmlsZTogdW5saWtlIHRoZVxuICAgIC8vIEFsZXJ0bWFuYWdlciBTbGFjayBVUkxzIChhcGlfdXJsX2ZpbGUpLCB0aGlzIGtleSBpcyBjb25zdW1lZCBieSBPVVJcbiAgICAvLyBwcm9jZXNzLCB3aGljaCBhbHJlYWR5IHJlY2VpdmVzIGl0cyBzZWNyZXRzIGFzIGVudiBmcm9tIGEgU2VjcmV0LlxuICAgIHJvdXRpbmdLZXk6IGVudlN0cignUEFHRVJEVVRZX1JPVVRJTkdfS0VZJywgJycpLFxuICAgIHByb3ZpZGVyLFxuICAgIGVuZHBvaW50OiByZXNvbHZlRXZlbnRzRW5kcG9pbnQocHJvdmlkZXIsIGVudlN0cignT05DQUxMX0VWRU5UU19VUkwnLCAnJykpLFxuICAgIHRpbWVvdXRNczogZW52SW50KCdPTkNBTExfRVZFTlRTX1RJTUVPVVRfTVMnLCA1MDAwLCB7IG1pbjogMSB9KSxcbiAgfTtcbn1cblxuLyoqXG4gKiBTZW5kIG9uZSBldmVudC4gUmV0dXJucyBgeyBvazogdHJ1ZSB9YCBvbmx5IHdoZW4gUGFnZXJEdXR5IEFDQ0VQVEVEIGl0LlxuICpcbiAqIEEgMjAyIGlzIGFuIGFjY2VwdGFuY2UgYW5kIG5vdCBhIGRlbGl2ZXJ5IHJlY2VpcHQg4oCUIGl0IHNheXMgUGFnZXJEdXR5IGhhcyB0aGVcbiAqIGV2ZW50LCBub3RoaW5nIGFib3V0IHdoZXRoZXIgYSBwaG9uZSByYW5nLiBgb25jYWxsX3BhZ2VzX3RvdGFse291dGNvbWV9YFxuICogcmVjb3JkcyB3aGF0IHdlIGtub3csIHdoaWNoIGlzIHRoaXMgYW5kIG5vIG1vcmU7IHRoZSBvbmx5IGhvbmVzdCBldmlkZW5jZSBvZlxuICogY292ZXJhZ2UgaXMgYSBodW1hbiBhY2tub3dsZWRnaW5nLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gc2VuZFBhZ2VyRHV0eUV2ZW50KFxuICBldmVudDogUGFnZXJEdXR5RXZlbnQsXG4gIGNmZzogUGFnZXJEdXR5Q29uZmlnID0gcGFnZXJEdXR5Q29uZmlnKCksXG4pOiBQcm9taXNlPFBhZ2VyRHV0eVJlc3VsdD4ge1xuICBjb25zdCBsYWJlbHMgPSB7IGFjdGlvbjogZXZlbnQuYWN0aW9uLCB0cmlnZ2VyOiBldmVudC50cmlnZ2VyID8/ICdhbGVydG1hbmFnZXInIH07XG5cbiAgLy8gQ29uZmlndXJlZCBPRkYgaXMgYSBTS0lQLCBkaXN0aW5jdCBmcm9tIGEgZmFpbHVyZS4gVGhlIEFsZXJ0bWFuYWdlciBsZWdcbiAgLy8gcmVsaWVzIG9uIHRoaXM6IGl0cyByb3V0ZSBpcyBhbHdheXMgcHJlc2VudCAoYWxlcnRtYW5hZ2VyLnltbCBpcyBhc3NlcnRlZFxuICAvLyBieXRlLWlkZW50aWNhbCBhY3Jvc3MgdGFyZ2V0cywgc28gaXQgY2Fubm90IGJlIHRvZ2dsZWQgcGVyIGRlcGxveW1lbnQpLCBhbmRcbiAgLy8gdGhpcyBlbmRwb2ludCBhY2NlcHRpbmctYW5kLWRyb3BwaW5nIGlzIHdoYXQgbWFrZXMgdGhlIGtub2Igd29yayB3aXRob3V0IGFcbiAgLy8gc2Vjb25kIGNvbmZpZyBmaWxlIGFuZCB3aXRob3V0IGEgc3RyZWFtIG9mIG5vdGlmaWNhdGlvbiBmYWlsdXJlcy5cbiAgaWYgKCFjZmcuZW5hYmxlZCkge1xuICAgIGVtaXRDb3VudGVyKCdvbmNhbGxfcGFnZXNfdG90YWwnLCB7IC4uLmxhYmVscywgb3V0Y29tZTogJ2Rpc2FibGVkJyB9KTtcbiAgICByZXR1cm4geyBvazogdHJ1ZSwgc2tpcHBlZDogdHJ1ZSB9O1xuICB9XG4gIGlmICghY2ZnLmVuZHBvaW50KSB7XG4gICAgLy8gYE9OQ0FMTF9QUk9WSURFUj1jdXN0b21gIHdpdGggbm8gYE9OQ0FMTF9FVkVOVFNfVVJMYC4gQ291bnRlZCB1bmRlciB0aGVcbiAgICAvLyBzYW1lIG91dGNvbWUgYXMgYSBtaXNzaW5nIGtleSBiZWNhdXNlIGl0IGlzIHRoZSBzYW1lIGNsYXNzIG9mIGZhaWx1cmUg4oCUXG4gICAgLy8gc29tZWJvZHkgYXNrZWQgZm9yIHBhZ2luZyBhbmQgdGhlIGNsaWVudCBoYXMgbm93aGVyZSB0byBzZW5kIGl0IOKAlCBhbmRcbiAgICAvLyBiZWNhdXNlIHF1aWV0bHkgcmVhY2hpbmcgZm9yIFBhZ2VyRHV0eSdzIGVuZHBvaW50IGluc3RlYWQgd291bGQgc2VuZCB0aGlzXG4gICAgLy8gZGVwbG95bWVudCdzIHJvdXRpbmcga2V5IHRvIGEgcHJvdmlkZXIgbm9ib2R5IGNob3NlLlxuICAgIGVtaXRDb3VudGVyKCdvbmNhbGxfcGFnZXNfdG90YWwnLCB7IC4uLmxhYmVscywgb3V0Y29tZTogJ3VuY29uZmlndXJlZCcgfSk7XG4gICAgbG9nZ2VyLmVycm9yKCdPTkNBTExfUFJPVklERVI9Y3VzdG9tIGJ1dCBPTkNBTExfRVZFTlRTX1VSTCBpcyBlbXB0eSDigJQgbm9ib2R5IHdpbGwgYmUgcGFnZWQnLCB7XG4gICAgICBkZWR1cEtleTogZXZlbnQuZGVkdXBLZXksXG4gICAgfSk7XG4gICAgcmV0dXJuIHsgb2s6IGZhbHNlLCBlcnJvcjogJ25vIGV2ZW50cyBlbmRwb2ludCcgfTtcbiAgfVxuICBpZiAoIWNmZy5yb3V0aW5nS2V5KSB7XG4gICAgLy8gRW5hYmxlZCB3aXRoIG5vIGtleSBpcyB0aGUgaGFsZi1jb25maWd1cmVkIHN0YXRlIHRoZSBrbm9iIGV4aXN0cyB0b1xuICAgIC8vIHByZXZlbnQsIGFuZCBpdCBpcyB0aGUgb25lIGZhaWx1cmUgd29ydGggc2hvdXRpbmcgYWJvdXQ6IHNvbWVib2R5IGFza2VkXG4gICAgLy8gZm9yIHBhZ2luZyBhbmQgaXMgbm90IGdldHRpbmcgaXQuXG4gICAgZW1pdENvdW50ZXIoJ29uY2FsbF9wYWdlc190b3RhbCcsIHsgLi4ubGFiZWxzLCBvdXRjb21lOiAndW5jb25maWd1cmVkJyB9KTtcbiAgICBsb2dnZXIuZXJyb3IoJ09OQ0FMTF9FTkFCTEVEIGlzIHRydWUgYnV0IFBBR0VSRFVUWV9ST1VUSU5HX0tFWSBpcyBlbXB0eSDigJQgbm9ib2R5IHdpbGwgYmUgcGFnZWQnLCB7XG4gICAgICBkZWR1cEtleTogZXZlbnQuZGVkdXBLZXksXG4gICAgfSk7XG4gICAgcmV0dXJuIHsgb2s6IGZhbHNlLCBlcnJvcjogJ25vIHJvdXRpbmcga2V5JyB9O1xuICB9XG5cbiAgLy8gYHJlc29sdmVgIGFuZCBgYWNrbm93bGVkZ2VgIG5lZWQgb25seSB0aGUga2V5IGFuZCB0aGUgYWN0aW9uOyBhIHRyaWdnZXJcbiAgLy8gbmVlZHMgc3VtbWFyeS9zZXZlcml0eS9zb3VyY2UuIEJ1aWx0IGNvbmRpdGlvbmFsbHkgc28gYSByZXNvbHZlIGNhbm5vdCBiZVxuICAvLyByZWplY3RlZCBmb3IgYSBtaXNzaW5nIGZpZWxkIGl0IG5ldmVyIHJlcXVpcmVkLlxuICBjb25zdCBib2R5OiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiA9IHtcbiAgICByb3V0aW5nX2tleTogY2ZnLnJvdXRpbmdLZXksXG4gICAgZXZlbnRfYWN0aW9uOiBldmVudC5hY3Rpb24sXG4gICAgZGVkdXBfa2V5OiBldmVudC5kZWR1cEtleSxcbiAgfTtcbiAgaWYgKGV2ZW50LmFjdGlvbiA9PT0gJ3RyaWdnZXInKSB7XG4gICAgYm9keS5wYXlsb2FkID0ge1xuICAgICAgc3VtbWFyeTogZXZlbnQuc3VtbWFyeSA/PyAnUGlwZWxpbmUgQnVpbGRlciBhbGVydCcsXG4gICAgICBzZXZlcml0eTogZXZlbnQuc2V2ZXJpdHkgPz8gJ2NyaXRpY2FsJyxcbiAgICAgIHNvdXJjZTogZXZlbnQuc291cmNlID8/ICdwaXBlbGluZS1idWlsZGVyJyxcbiAgICAgIC4uLihldmVudC5jb21wb25lbnQgPyB7IGNvbXBvbmVudDogZXZlbnQuY29tcG9uZW50IH0gOiB7fSksXG4gICAgICAuLi4oZXZlbnQuZ3JvdXAgPyB7IGdyb3VwOiBldmVudC5ncm91cCB9IDoge30pLFxuICAgICAgLi4uKGV2ZW50LmNsYXNzID8geyBjbGFzczogZXZlbnQuY2xhc3MgfSA6IHt9KSxcbiAgICAgIC4uLihldmVudC5jdXN0b21EZXRhaWxzID8geyBjdXN0b21fZGV0YWlsczogZXZlbnQuY3VzdG9tRGV0YWlscyB9IDoge30pLFxuICAgIH07XG4gIH1cblxuICB0cnkge1xuICAgIGNvbnN0IHJlc3AgPSBhd2FpdCBzYWZlRmV0Y2goY2ZnLmVuZHBvaW50LCB7XG4gICAgICBtZXRob2Q6ICdQT1NUJyxcbiAgICAgIGhlYWRlcnM6IHsgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9qc29uJyB9LFxuICAgICAgYm9keTogSlNPTi5zdHJpbmdpZnkoYm9keSksXG4gICAgICB0aW1lb3V0TXM6IGNmZy50aW1lb3V0TXMsXG4gICAgfSk7XG4gICAgLy8gYHNhZmVGZXRjaGAgcmVmdXNlcyByZWRpcmVjdHMgcmF0aGVyIHRoYW4gZm9sbG93aW5nIHRoZW0sIHNvIGEgM3h4IGFycml2ZXNcbiAgICAvLyBhcyBgcmVkaXJlY3RlZGAgYW5kIGlzIGEgRkFJTEVEIHNlbmQg4oCUIG5ldmVyIGEgcGl2b3QgdG8gYW4gdW52ZXR0ZWQgaG9zdFxuICAgIC8vIGFuZCBuZXZlciByZWNvcmRlZCBncmVlbi5cbiAgICBpZiAocmVzcC5yZWRpcmVjdGVkKSB7XG4gICAgICBlbWl0Q291bnRlcignb25jYWxsX3BhZ2VzX3RvdGFsJywgeyAuLi5sYWJlbHMsIG91dGNvbWU6ICdyZWRpcmVjdGVkJyB9KTtcbiAgICAgIGxvZ2dlci5lcnJvcignUGFnZXJEdXR5IGVuZHBvaW50IHJlZGlyZWN0ZWQg4oCUIHJlZnVzZWQnLCB7IHN0YXR1czogcmVzcC5zdGF0dXMgfSk7XG4gICAgICByZXR1cm4geyBvazogZmFsc2UsIHN0YXR1czogcmVzcC5zdGF0dXMsIGVycm9yOiAnZW5kcG9pbnQgcmVkaXJlY3RlZCAocmVmdXNlZCknIH07XG4gICAgfVxuICAgIGlmICghcmVzcC5vaykge1xuICAgICAgZW1pdENvdW50ZXIoJ29uY2FsbF9wYWdlc190b3RhbCcsIHsgLi4ubGFiZWxzLCBvdXRjb21lOiAncmVqZWN0ZWQnIH0pO1xuICAgICAgbG9nZ2VyLmVycm9yKCdQYWdlckR1dHkgcmVqZWN0ZWQgYW4gZXZlbnQnLCB7IHN0YXR1czogcmVzcC5zdGF0dXMsIGRlZHVwS2V5OiBldmVudC5kZWR1cEtleSB9KTtcbiAgICAgIHJldHVybiB7IG9rOiBmYWxzZSwgc3RhdHVzOiByZXNwLnN0YXR1cywgZXJyb3I6IGBzdGF0dXMgJHtyZXNwLnN0YXR1c31gIH07XG4gICAgfVxuICAgIGVtaXRDb3VudGVyKCdvbmNhbGxfcGFnZXNfdG90YWwnLCB7IC4uLmxhYmVscywgb3V0Y29tZTogJ2FjY2VwdGVkJyB9KTtcbiAgICByZXR1cm4geyBvazogdHJ1ZSwgc3RhdHVzOiByZXNwLnN0YXR1cyB9O1xuICB9IGNhdGNoIChlcnIpIHtcbiAgICBlbWl0Q291bnRlcignb25jYWxsX3BhZ2VzX3RvdGFsJywgeyAuLi5sYWJlbHMsIG91dGNvbWU6ICdlcnJvcicgfSk7XG4gICAgbG9nZ2VyLmVycm9yKCdDb3VsZCBub3QgcmVhY2ggUGFnZXJEdXR5JywgeyBlcnJvcjogZXJyb3JNZXNzYWdlKGVyciksIGRlZHVwS2V5OiBldmVudC5kZWR1cEtleSB9KTtcbiAgICByZXR1cm4geyBvazogZmFsc2UsIGVycm9yOiBlcnJvck1lc3NhZ2UoZXJyKSB9O1xuICB9XG59XG4iXX0=
|