pepr 0.6.0 → 0.7.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/dist/cli.js +138 -85
- package/dist/controller.js +1 -1
- package/dist/lib/capability.d.ts.map +1 -1
- package/dist/lib/controller.d.ts.map +1 -1
- package/dist/lib/filter.d.ts.map +1 -1
- package/dist/lib/k8s/kinds.d.ts.map +1 -1
- package/dist/lib/k8s/types.d.ts +2 -0
- package/dist/lib/k8s/types.d.ts.map +1 -1
- package/dist/lib/k8s/webhook.d.ts +5 -4
- package/dist/lib/k8s/webhook.d.ts.map +1 -1
- package/dist/lib/module.d.ts.map +1 -1
- package/dist/lib/processor.d.ts +1 -1
- package/dist/lib/processor.d.ts.map +1 -1
- package/dist/lib/request.d.ts +0 -6
- package/dist/lib/request.d.ts.map +1 -1
- package/dist/lib/types.d.ts +3 -2
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/utils.d.ts +23 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib.d.ts +2 -1
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +77 -32
- package/dist/lib.js.map +4 -4
- package/package.json +2 -2
- package/src/lib/capability.ts +1 -0
- package/src/lib/controller.ts +21 -16
- package/src/lib/filter.ts +2 -2
- package/src/lib/k8s/kinds.ts +2 -0
- package/src/lib/k8s/types.ts +2 -0
- package/src/lib/k8s/webhook.ts +120 -26
- package/src/lib/module.ts +7 -1
- package/src/lib/processor.ts +40 -14
- package/src/lib/request.ts +0 -20
- package/src/lib/types.ts +2 -1
- package/src/lib/utils.ts +55 -0
- package/src/lib.ts +2 -0
package/src/lib/k8s/webhook.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
-
AdmissionregistrationV1Api,
|
|
6
|
-
AdmissionregistrationV1WebhookClientConfig,
|
|
5
|
+
AdmissionregistrationV1Api as AdmissionRegV1API,
|
|
6
|
+
AdmissionregistrationV1WebhookClientConfig as AdmissionRegnV1WebhookClientCfg,
|
|
7
7
|
AppsV1Api,
|
|
8
8
|
CoreV1Api,
|
|
9
9
|
HttpError,
|
|
@@ -17,15 +17,20 @@ import {
|
|
|
17
17
|
V1MutatingWebhookConfiguration,
|
|
18
18
|
V1Namespace,
|
|
19
19
|
V1NetworkPolicy,
|
|
20
|
+
V1RuleWithOperations,
|
|
20
21
|
V1Secret,
|
|
21
22
|
V1Service,
|
|
22
23
|
V1ServiceAccount,
|
|
23
24
|
dumpYaml,
|
|
24
25
|
} from "@kubernetes/client-node";
|
|
26
|
+
import { fork } from "child_process";
|
|
25
27
|
import crypto from "crypto";
|
|
28
|
+
import { promises as fs } from "fs";
|
|
29
|
+
import { equals, uniqWith } from "ramda";
|
|
26
30
|
import { gzipSync } from "zlib";
|
|
31
|
+
|
|
27
32
|
import Log from "../logger";
|
|
28
|
-
import { ModuleConfig } from "../types";
|
|
33
|
+
import { Binding, Event, HookPhase, ModuleConfig } from "../types";
|
|
29
34
|
import { TLSOut, genTLS } from "./tls";
|
|
30
35
|
|
|
31
36
|
const peprIgnore: V1LabelSelectorRequirement = {
|
|
@@ -132,7 +137,95 @@ export class Webhook {
|
|
|
132
137
|
};
|
|
133
138
|
}
|
|
134
139
|
|
|
135
|
-
|
|
140
|
+
generateWebhookRules(path: string): Promise<V1RuleWithOperations[]> {
|
|
141
|
+
return new Promise((resolve, reject) => {
|
|
142
|
+
const rules: V1RuleWithOperations[] = [];
|
|
143
|
+
|
|
144
|
+
// Add a default rule that allows all resources as a fallback
|
|
145
|
+
const defaultRule = {
|
|
146
|
+
apiGroups: ["*"],
|
|
147
|
+
apiVersions: ["*"],
|
|
148
|
+
operations: ["CREATE", "UPDATE", "DELETE"],
|
|
149
|
+
resources: ["*/*"],
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Fork is needed with the PEPR_MODE env var to ensure the module is loaded in build mode and will send back the capabilities
|
|
153
|
+
const program = fork(path, {
|
|
154
|
+
env: {
|
|
155
|
+
...process.env,
|
|
156
|
+
LOG_LEVEL: "warn",
|
|
157
|
+
PEPR_MODE: "build",
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// We are receiving javscript so the private fields are now public
|
|
162
|
+
interface ModuleCapabilities {
|
|
163
|
+
capabilities: {
|
|
164
|
+
_name: string;
|
|
165
|
+
_description: string;
|
|
166
|
+
_namespaces: string[];
|
|
167
|
+
_mutateOrValidate: HookPhase;
|
|
168
|
+
_bindings: Binding[];
|
|
169
|
+
}[];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Wait for the module to send back the capabilities
|
|
173
|
+
program.on("message", message => {
|
|
174
|
+
// Cast the message to the ModuleCapabilities type
|
|
175
|
+
const { capabilities } = message.valueOf() as ModuleCapabilities;
|
|
176
|
+
|
|
177
|
+
for (const capability of capabilities) {
|
|
178
|
+
Log.info(`Module ${this.config.uuid} has capability: ${capability._name}`);
|
|
179
|
+
|
|
180
|
+
const { _bindings } = capability;
|
|
181
|
+
|
|
182
|
+
// Read the bindings and generate the rules
|
|
183
|
+
for (const binding of _bindings) {
|
|
184
|
+
const { event, kind } = binding;
|
|
185
|
+
|
|
186
|
+
const operations: string[] = [];
|
|
187
|
+
|
|
188
|
+
// CreateOrUpdate is a Pepr-specific event that is translated to Create and Update
|
|
189
|
+
if (event === Event.CreateOrUpdate) {
|
|
190
|
+
operations.push(Event.Create, Event.Update);
|
|
191
|
+
} else {
|
|
192
|
+
operations.push(event);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Use the plural property if it exists, otherwise use lowercase kind + s
|
|
196
|
+
const resource = kind.plural || `${kind.kind.toLowerCase()}s`;
|
|
197
|
+
|
|
198
|
+
rules.push({
|
|
199
|
+
apiGroups: [kind.group],
|
|
200
|
+
apiVersions: [kind.version || "*"],
|
|
201
|
+
operations,
|
|
202
|
+
resources: [resource],
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
program.on("exit", code => {
|
|
209
|
+
if (code !== 0) {
|
|
210
|
+
reject(new Error(`Child process exited with code ${code}`));
|
|
211
|
+
} else {
|
|
212
|
+
// If there are no rules, add a catch-all
|
|
213
|
+
if (rules.length < 1) {
|
|
214
|
+
resolve([defaultRule]);
|
|
215
|
+
} else {
|
|
216
|
+
const reducedRules = uniqWith(equals, rules);
|
|
217
|
+
resolve(reducedRules);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
program.on("error", error => {
|
|
223
|
+
reject(error);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async mutatingWebhook(path: string, timeoutSeconds = 10): Promise<V1MutatingWebhookConfiguration> {
|
|
136
229
|
const { name } = this;
|
|
137
230
|
const ignore = [peprIgnore];
|
|
138
231
|
|
|
@@ -145,7 +238,7 @@ export class Webhook {
|
|
|
145
238
|
});
|
|
146
239
|
}
|
|
147
240
|
|
|
148
|
-
const clientConfig:
|
|
241
|
+
const clientConfig: AdmissionRegnV1WebhookClientCfg = {
|
|
149
242
|
caBundle: this._tls.ca,
|
|
150
243
|
};
|
|
151
244
|
|
|
@@ -161,6 +254,8 @@ export class Webhook {
|
|
|
161
254
|
};
|
|
162
255
|
}
|
|
163
256
|
|
|
257
|
+
const rules = await this.generateWebhookRules(path);
|
|
258
|
+
|
|
164
259
|
return {
|
|
165
260
|
apiVersion: "admissionregistration.k8s.io/v1",
|
|
166
261
|
kind: "MutatingWebhookConfiguration",
|
|
@@ -179,15 +274,7 @@ export class Webhook {
|
|
|
179
274
|
objectSelector: {
|
|
180
275
|
matchExpressions: ignore,
|
|
181
276
|
},
|
|
182
|
-
|
|
183
|
-
rules: [
|
|
184
|
-
{
|
|
185
|
-
apiGroups: ["*"],
|
|
186
|
-
apiVersions: ["*"],
|
|
187
|
-
operations: ["CREATE", "UPDATE", "DELETE"],
|
|
188
|
-
resources: ["*/*"],
|
|
189
|
-
},
|
|
190
|
-
],
|
|
277
|
+
rules,
|
|
191
278
|
// @todo: track side effects state
|
|
192
279
|
sideEffects: "None",
|
|
193
280
|
},
|
|
@@ -390,10 +477,14 @@ export class Webhook {
|
|
|
390
477
|
return dumpYaml(zarfCfg, { noRefs: true });
|
|
391
478
|
}
|
|
392
479
|
|
|
393
|
-
allYaml(
|
|
480
|
+
async allYaml(path: string) {
|
|
481
|
+
const code = await fs.readFile(path);
|
|
482
|
+
|
|
394
483
|
// Generate a hash of the code
|
|
395
484
|
const hash = crypto.createHash("sha256").update(code).digest("hex");
|
|
396
485
|
|
|
486
|
+
const webhook = await this.mutatingWebhook(path);
|
|
487
|
+
|
|
397
488
|
const resources = [
|
|
398
489
|
this.namespace(),
|
|
399
490
|
this.networkPolicy(),
|
|
@@ -401,7 +492,7 @@ export class Webhook {
|
|
|
401
492
|
this.clusterRoleBinding(),
|
|
402
493
|
this.serviceAccount(),
|
|
403
494
|
this.tlsSecret(),
|
|
404
|
-
|
|
495
|
+
webhook,
|
|
405
496
|
this.deployment(hash),
|
|
406
497
|
this.service(),
|
|
407
498
|
this.moduleSecret(code, hash),
|
|
@@ -411,7 +502,7 @@ export class Webhook {
|
|
|
411
502
|
return resources.map(r => dumpYaml(r, { noRefs: true })).join("---\n");
|
|
412
503
|
}
|
|
413
504
|
|
|
414
|
-
async deploy(
|
|
505
|
+
async deploy(path: string, webhookTimeout?: number) {
|
|
415
506
|
Log.info("Establishing connection to Kubernetes");
|
|
416
507
|
|
|
417
508
|
const namespace = "pepr-system";
|
|
@@ -421,10 +512,7 @@ export class Webhook {
|
|
|
421
512
|
kubeConfig.loadFromDefault();
|
|
422
513
|
|
|
423
514
|
const coreV1Api = kubeConfig.makeApiClient(CoreV1Api);
|
|
424
|
-
const
|
|
425
|
-
const appsApi = kubeConfig.makeApiClient(AppsV1Api);
|
|
426
|
-
const admissionApi = kubeConfig.makeApiClient(AdmissionregistrationV1Api);
|
|
427
|
-
const networkApi = kubeConfig.makeApiClient(NetworkingV1Api);
|
|
515
|
+
const admissionApi = kubeConfig.makeApiClient(AdmissionRegV1API);
|
|
428
516
|
|
|
429
517
|
const ns = this.namespace();
|
|
430
518
|
try {
|
|
@@ -436,7 +524,7 @@ export class Webhook {
|
|
|
436
524
|
await coreV1Api.createNamespace(ns);
|
|
437
525
|
}
|
|
438
526
|
|
|
439
|
-
const wh = this.mutatingWebhook(webhookTimeout);
|
|
527
|
+
const wh = await this.mutatingWebhook(path, webhookTimeout);
|
|
440
528
|
try {
|
|
441
529
|
Log.info("Creating mutating webhook");
|
|
442
530
|
await admissionApi.createMutatingWebhookConfiguration(wh);
|
|
@@ -452,20 +540,26 @@ export class Webhook {
|
|
|
452
540
|
return;
|
|
453
541
|
}
|
|
454
542
|
|
|
455
|
-
if (!
|
|
543
|
+
if (!path) {
|
|
456
544
|
throw new Error("No code provided");
|
|
457
545
|
}
|
|
458
546
|
|
|
547
|
+
const code = await fs.readFile(path);
|
|
548
|
+
|
|
459
549
|
const hash = crypto.createHash("sha256").update(code).digest("hex");
|
|
460
550
|
|
|
461
|
-
const
|
|
551
|
+
const appsApi = kubeConfig.makeApiClient(AppsV1Api);
|
|
552
|
+
const rbacApi = kubeConfig.makeApiClient(RbacAuthorizationV1Api);
|
|
553
|
+
const networkApi = kubeConfig.makeApiClient(NetworkingV1Api);
|
|
554
|
+
|
|
555
|
+
const networkPolicy = this.networkPolicy();
|
|
462
556
|
try {
|
|
463
557
|
Log.info("Checking for network policy");
|
|
464
|
-
await networkApi.readNamespacedNetworkPolicy(
|
|
558
|
+
await networkApi.readNamespacedNetworkPolicy(networkPolicy.metadata?.name ?? "", namespace);
|
|
465
559
|
} catch (e) {
|
|
466
560
|
Log.debug(e instanceof HttpError ? e.body : e);
|
|
467
561
|
Log.info("Creating network policy");
|
|
468
|
-
await networkApi.createNamespacedNetworkPolicy(namespace,
|
|
562
|
+
await networkApi.createNamespacedNetworkPolicy(namespace, networkPolicy);
|
|
469
563
|
}
|
|
470
564
|
|
|
471
565
|
const crb = this.clusterRoleBinding();
|
package/src/lib/module.ts
CHANGED
|
@@ -29,7 +29,7 @@ export type PeprModuleOptions = {
|
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
export class PeprModule {
|
|
32
|
-
private _controller
|
|
32
|
+
private _controller!: Controller;
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* Create a new Pepr runtime
|
|
@@ -42,6 +42,12 @@ export class PeprModule {
|
|
|
42
42
|
const config: ModuleConfig = mergeDeepWith(concat, pepr, alwaysIgnore);
|
|
43
43
|
config.description = description;
|
|
44
44
|
|
|
45
|
+
// Handle build mode
|
|
46
|
+
if (process.env.PEPR_MODE === "build") {
|
|
47
|
+
process.send?.({ capabilities });
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
45
51
|
this._controller = new Controller(config, capabilities, opts.beforeHook, opts.afterHook);
|
|
46
52
|
|
|
47
53
|
// Stop processing if deferStart is set to true
|
package/src/lib/processor.ts
CHANGED
|
@@ -7,11 +7,17 @@ import { Capability } from "./capability";
|
|
|
7
7
|
import { shouldSkipRequest } from "./filter";
|
|
8
8
|
import { Request, Response } from "./k8s/types";
|
|
9
9
|
import { Secret } from "./k8s/upstream";
|
|
10
|
-
import
|
|
11
|
-
import { PeprRequest
|
|
10
|
+
import Log from "./logger";
|
|
11
|
+
import { PeprRequest } from "./request";
|
|
12
12
|
import { ModuleConfig } from "./types";
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
import { convertFromBase64Map, convertToBase64Map } from "./utils";
|
|
14
|
+
|
|
15
|
+
export async function processor(
|
|
16
|
+
config: ModuleConfig,
|
|
17
|
+
capabilities: Capability[],
|
|
18
|
+
req: Request,
|
|
19
|
+
parentPrefix: string
|
|
20
|
+
): Promise<Response> {
|
|
15
21
|
const wrapped = new PeprRequest(req);
|
|
16
22
|
const response: Response = {
|
|
17
23
|
uid: req.uid,
|
|
@@ -19,11 +25,22 @@ export async function processor(config: ModuleConfig, capabilities: Capability[]
|
|
|
19
25
|
allowed: false,
|
|
20
26
|
};
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
// Track whether any capability matched the request
|
|
29
|
+
let matchedCapabilityAction = false;
|
|
30
|
+
|
|
31
|
+
// Track data fields that should be skipped during decoding
|
|
32
|
+
let skipDecode: string[] = [];
|
|
33
|
+
|
|
34
|
+
// If the resource is a secret, decode the data
|
|
35
|
+
const isSecret = req.kind.version == "v1" && req.kind.kind == "Secret";
|
|
36
|
+
if (isSecret) {
|
|
37
|
+
skipDecode = convertFromBase64Map(wrapped.Raw as unknown as Secret);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
Log.info(`Processing request`, parentPrefix);
|
|
23
41
|
|
|
24
42
|
for (const { name, bindings } of capabilities) {
|
|
25
|
-
const prefix = `${
|
|
26
|
-
logger.info(`Processing capability ${name}`, prefix);
|
|
43
|
+
const prefix = `${parentPrefix} ${name}:`;
|
|
27
44
|
|
|
28
45
|
for (const action of bindings) {
|
|
29
46
|
// Continue to the next action without doing anything if this one should be skipped
|
|
@@ -31,7 +48,10 @@ export async function processor(config: ModuleConfig, capabilities: Capability[]
|
|
|
31
48
|
continue;
|
|
32
49
|
}
|
|
33
50
|
|
|
34
|
-
|
|
51
|
+
const label = action.callback.name;
|
|
52
|
+
Log.info(`Processing matched action ${label}`, prefix);
|
|
53
|
+
|
|
54
|
+
matchedCapabilityAction = true;
|
|
35
55
|
|
|
36
56
|
// Add annotations to the request to indicate that the capability started processing
|
|
37
57
|
// this will allow tracking of failed mutations that were permitted to continue
|
|
@@ -53,7 +73,7 @@ export async function processor(config: ModuleConfig, capabilities: Capability[]
|
|
|
53
73
|
// Run the action
|
|
54
74
|
await action.callback(wrapped);
|
|
55
75
|
|
|
56
|
-
|
|
76
|
+
Log.info(`Action succeeded`, prefix);
|
|
57
77
|
|
|
58
78
|
// Add annotations to the request to indicate that the capability succeeded
|
|
59
79
|
updateStatus("succeeded");
|
|
@@ -64,11 +84,11 @@ export async function processor(config: ModuleConfig, capabilities: Capability[]
|
|
|
64
84
|
|
|
65
85
|
// If errors are not allowed, note the failure in the Response
|
|
66
86
|
if (config.onError) {
|
|
67
|
-
|
|
87
|
+
Log.error(`Action failed: ${e}`, prefix);
|
|
68
88
|
response.result = "Pepr module configured to reject on error";
|
|
69
89
|
return response;
|
|
70
90
|
} else {
|
|
71
|
-
|
|
91
|
+
Log.warn(`Action failed: ${e}`, prefix);
|
|
72
92
|
updateStatus("warning");
|
|
73
93
|
}
|
|
74
94
|
}
|
|
@@ -78,11 +98,17 @@ export async function processor(config: ModuleConfig, capabilities: Capability[]
|
|
|
78
98
|
// If we've made it this far, the request is allowed
|
|
79
99
|
response.allowed = true;
|
|
80
100
|
|
|
101
|
+
// If no capability matched the request, exit early
|
|
102
|
+
if (!matchedCapabilityAction) {
|
|
103
|
+
Log.info(`No matching capability action found`, parentPrefix);
|
|
104
|
+
return response;
|
|
105
|
+
}
|
|
106
|
+
|
|
81
107
|
const transformed = wrapped.Raw;
|
|
82
108
|
|
|
83
109
|
// Post-process the Secret requests to convert it back to the original format
|
|
84
|
-
if (
|
|
85
|
-
convertToBase64Map(transformed as unknown as Secret);
|
|
110
|
+
if (isSecret) {
|
|
111
|
+
convertToBase64Map(transformed as unknown as Secret, skipDecode);
|
|
86
112
|
}
|
|
87
113
|
|
|
88
114
|
// Compare the original request to the modified request to get the patches
|
|
@@ -101,7 +127,7 @@ export async function processor(config: ModuleConfig, capabilities: Capability[]
|
|
|
101
127
|
delete response.warnings;
|
|
102
128
|
}
|
|
103
129
|
|
|
104
|
-
|
|
130
|
+
Log.debug(patches, parentPrefix);
|
|
105
131
|
|
|
106
132
|
return response;
|
|
107
133
|
}
|
package/src/lib/request.ts
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
import { clone, mergeDeepRight } from "ramda";
|
|
5
5
|
|
|
6
6
|
import { KubernetesObject, Request } from "./k8s/types";
|
|
7
|
-
import { Secret } from "./k8s/upstream";
|
|
8
7
|
import { DeepPartial } from "./types";
|
|
9
8
|
|
|
10
9
|
/**
|
|
@@ -49,11 +48,6 @@ export class PeprRequest<T extends KubernetesObject> {
|
|
|
49
48
|
constructor(private _input: Request<T>) {
|
|
50
49
|
// Deep clone the object to prevent mutation of the original object
|
|
51
50
|
this.Raw = clone(_input.object);
|
|
52
|
-
|
|
53
|
-
// If the resource is a secret, decode the data
|
|
54
|
-
if (_input.kind.version == "v1" && _input.kind.kind == "Secret") {
|
|
55
|
-
convertFromBase64Map(this.Raw as unknown as Secret);
|
|
56
|
-
}
|
|
57
51
|
}
|
|
58
52
|
|
|
59
53
|
/**
|
|
@@ -141,17 +135,3 @@ export class PeprRequest<T extends KubernetesObject> {
|
|
|
141
135
|
return this.Raw?.metadata?.annotations?.[key] !== undefined;
|
|
142
136
|
}
|
|
143
137
|
}
|
|
144
|
-
|
|
145
|
-
export function convertToBase64Map(obj: { data?: Record<string, string> }) {
|
|
146
|
-
obj.data = obj.data ?? {};
|
|
147
|
-
for (const key in obj.data) {
|
|
148
|
-
obj.data[key] = Buffer.from(obj.data[key]).toString("base64");
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export function convertFromBase64Map(obj: { data?: Record<string, string> }) {
|
|
153
|
-
obj.data = obj.data ?? {};
|
|
154
|
-
for (const key in obj.data) {
|
|
155
|
-
obj.data[key] = Buffer.from(obj.data[key], "base64").toString("utf-8");
|
|
156
|
-
}
|
|
157
|
-
}
|
package/src/lib/types.ts
CHANGED
|
@@ -44,6 +44,7 @@ export enum Event {
|
|
|
44
44
|
Update = "UPDATE",
|
|
45
45
|
Delete = "DELETE",
|
|
46
46
|
CreateOrUpdate = "CREATEORUPDATE",
|
|
47
|
+
Any = "*",
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
export interface CapabilityCfg {
|
|
@@ -123,7 +124,7 @@ export type WhenSelector<T extends GenericClass> = {
|
|
|
123
124
|
};
|
|
124
125
|
|
|
125
126
|
export type Binding = {
|
|
126
|
-
event
|
|
127
|
+
event: Event;
|
|
127
128
|
readonly kind: GroupVersionKind;
|
|
128
129
|
readonly filters: {
|
|
129
130
|
name: string;
|
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
import Log from "./logger";
|
|
5
|
+
|
|
6
|
+
/** Test if a string is ascii or not */
|
|
7
|
+
export const isAscii = /^[\s\x20-\x7E]*$/;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Encode all ascii values in a map to base64
|
|
11
|
+
* @param obj The object to encode
|
|
12
|
+
* @param skip A list of keys to skip encoding
|
|
13
|
+
*/
|
|
14
|
+
export function convertToBase64Map(obj: { data?: Record<string, string> }, skip: string[]) {
|
|
15
|
+
obj.data = obj.data ?? {};
|
|
16
|
+
for (const key in obj.data) {
|
|
17
|
+
const value = obj.data[key];
|
|
18
|
+
// Only encode ascii values
|
|
19
|
+
obj.data[key] = skip.includes(key) ? value : base64Encode(value);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Decode all ascii values in a map from base64 to utf-8
|
|
25
|
+
* @param obj The object to decode
|
|
26
|
+
* @returns A list of keys that were skipped
|
|
27
|
+
*/
|
|
28
|
+
export function convertFromBase64Map(obj: { data?: Record<string, string> }) {
|
|
29
|
+
const skip: string[] = [];
|
|
30
|
+
|
|
31
|
+
obj.data = obj.data ?? {};
|
|
32
|
+
for (const key in obj.data) {
|
|
33
|
+
const decoded = base64Decode(obj.data[key]);
|
|
34
|
+
if (isAscii.test(decoded)) {
|
|
35
|
+
// Only decode ascii values
|
|
36
|
+
obj.data[key] = decoded;
|
|
37
|
+
} else {
|
|
38
|
+
skip.push(key);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
Log.debug(`Non-ascii data detected in keys: ${skip}, skipping automatic base64 decoding`);
|
|
43
|
+
|
|
44
|
+
return skip;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Decode a base64 string */
|
|
48
|
+
export function base64Decode(data: string) {
|
|
49
|
+
return Buffer.from(data, "base64").toString("utf-8");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Encode a string to base64 */
|
|
53
|
+
export function base64Encode(data: string) {
|
|
54
|
+
return Buffer.from(data).toString("base64");
|
|
55
|
+
}
|
package/src/lib.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { RegisterKind, a } from "./lib/k8s/index";
|
|
|
7
7
|
import Log from "./lib/logger";
|
|
8
8
|
import { PeprModule } from "./lib/module";
|
|
9
9
|
import { PeprRequest } from "./lib/request";
|
|
10
|
+
import * as PeprUtils from "./lib/utils";
|
|
10
11
|
|
|
11
12
|
// Import type information for external packages
|
|
12
13
|
import type * as KubernetesClientNode from "@kubernetes/client-node";
|
|
@@ -17,6 +18,7 @@ export {
|
|
|
17
18
|
/** PeprModule is used to setup a complete Pepr Module: `new PeprModule(cfg, {...capabilities})` */
|
|
18
19
|
PeprModule,
|
|
19
20
|
PeprRequest,
|
|
21
|
+
PeprUtils,
|
|
20
22
|
RegisterKind,
|
|
21
23
|
Capability,
|
|
22
24
|
Log,
|