pepr 0.40.1 → 0.42.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/README.md +11 -5
- package/dist/cli/build.d.ts +1 -0
- package/dist/cli/build.d.ts.map +1 -1
- package/dist/cli/build.helpers.d.ts +66 -0
- package/dist/cli/build.helpers.d.ts.map +1 -1
- package/dist/cli/deploy.d.ts.map +1 -1
- package/dist/cli/init/templates.d.ts +2 -2
- package/dist/cli/monitor.d.ts +23 -0
- package/dist/cli/monitor.d.ts.map +1 -1
- package/dist/cli.js +536 -429
- package/dist/controller.js +52 -27
- package/dist/lib/assets/destroy.d.ts.map +1 -1
- package/dist/lib/assets/helm.d.ts +1 -1
- package/dist/lib/assets/helm.d.ts.map +1 -1
- package/dist/lib/assets/index.d.ts.map +1 -1
- package/dist/lib/assets/pods.d.ts +5 -19
- package/dist/lib/assets/pods.d.ts.map +1 -1
- package/dist/lib/assets/webhooks.d.ts.map +1 -1
- package/dist/lib/assets/yaml.d.ts.map +1 -1
- package/dist/lib/capability.d.ts.map +1 -1
- package/dist/lib/controller/index.d.ts.map +1 -1
- package/dist/lib/controller/index.util.d.ts +10 -0
- package/dist/lib/controller/index.util.d.ts.map +1 -0
- package/dist/lib/controller/store.d.ts +0 -1
- package/dist/lib/controller/store.d.ts.map +1 -1
- package/dist/lib/controller/storeCache.d.ts +1 -0
- package/dist/lib/controller/storeCache.d.ts.map +1 -1
- package/dist/lib/deploymentChecks.d.ts +3 -0
- package/dist/lib/deploymentChecks.d.ts.map +1 -0
- package/dist/lib/enums.d.ts +5 -5
- package/dist/lib/enums.d.ts.map +1 -1
- package/dist/lib/filesystemService.d.ts +2 -0
- package/dist/lib/filesystemService.d.ts.map +1 -0
- package/dist/lib/filter/adjudicators/adjudicators.d.ts +73 -0
- package/dist/lib/filter/adjudicators/adjudicators.d.ts.map +1 -0
- package/dist/lib/filter/adjudicators/defaultTestObjects.d.ts +7 -0
- package/dist/lib/filter/adjudicators/defaultTestObjects.d.ts.map +1 -0
- package/dist/lib/helpers.d.ts +1 -4
- package/dist/lib/helpers.d.ts.map +1 -1
- package/dist/lib/mutate-request.d.ts +2 -2
- package/dist/lib/mutate-request.d.ts.map +1 -1
- package/dist/lib/queue.d.ts.map +1 -1
- package/dist/lib/schedule.d.ts.map +1 -1
- package/dist/lib/storage.d.ts +5 -5
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/{logger.d.ts → telemetry/logger.d.ts} +1 -1
- package/dist/lib/telemetry/logger.d.ts.map +1 -0
- package/dist/lib/{metrics.d.ts → telemetry/metrics.d.ts} +3 -1
- package/dist/lib/telemetry/metrics.d.ts.map +1 -0
- package/dist/lib/types.d.ts +10 -9
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/validate-processor.d.ts +4 -1
- package/dist/lib/validate-processor.d.ts.map +1 -1
- package/dist/lib/watch-processor.d.ts.map +1 -1
- package/dist/lib.d.ts +1 -1
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +283 -231
- package/dist/lib.js.map +4 -4
- package/dist/sdk/sdk.d.ts +3 -4
- package/dist/sdk/sdk.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/cli/build.helpers.ts +180 -0
- package/src/cli/build.ts +85 -132
- package/src/cli/deploy.ts +2 -1
- package/src/cli/init/templates.ts +1 -1
- package/src/cli/monitor.ts +108 -65
- package/src/lib/assets/deploy.ts +7 -7
- package/src/lib/assets/destroy.ts +2 -2
- package/src/lib/assets/helm.ts +6 -6
- package/src/lib/assets/index.ts +110 -89
- package/src/lib/assets/pods.ts +10 -5
- package/src/lib/assets/webhooks.ts +3 -3
- package/src/lib/assets/yaml.ts +12 -9
- package/src/lib/capability.ts +29 -19
- package/src/lib/controller/index.ts +41 -69
- package/src/lib/controller/index.util.ts +47 -0
- package/src/lib/controller/store.ts +24 -11
- package/src/lib/controller/storeCache.ts +11 -2
- package/src/lib/deploymentChecks.ts +43 -0
- package/src/lib/enums.ts +5 -5
- package/src/lib/filesystemService.ts +16 -0
- package/src/lib/filter/{adjudicators.ts → adjudicators/adjudicators.ts} +67 -35
- package/src/lib/filter/adjudicators/defaultTestObjects.ts +46 -0
- package/src/lib/filter/filter.ts +1 -1
- package/src/lib/finalizer.ts +1 -1
- package/src/lib/helpers.ts +31 -88
- package/src/lib/mutate-processor.ts +1 -1
- package/src/lib/mutate-request.ts +11 -11
- package/src/lib/queue.ts +13 -5
- package/src/lib/schedule.ts +8 -8
- package/src/lib/storage.ts +48 -39
- package/src/lib/{logger.ts → telemetry/logger.ts} +1 -1
- package/src/lib/{metrics.ts → telemetry/metrics.ts} +18 -17
- package/src/lib/types.ts +12 -9
- package/src/lib/utils.ts +6 -6
- package/src/lib/validate-processor.ts +48 -40
- package/src/lib/watch-processor.ts +19 -15
- package/src/lib.ts +1 -1
- package/src/runtime/controller.ts +1 -1
- package/src/sdk/cosign.ts +4 -4
- package/src/sdk/sdk.ts +6 -9
- package/src/templates/capabilities/hello-pepr.ts +19 -9
- package/dist/lib/filter/adjudicators.d.ts +0 -69
- package/dist/lib/filter/adjudicators.d.ts.map +0 -1
- package/dist/lib/logger.d.ts.map +0 -1
- package/dist/lib/metrics.d.ts.map +0 -1
package/src/lib/storage.ts
CHANGED
|
@@ -12,15 +12,20 @@ export type Unsubscribe = () => void;
|
|
|
12
12
|
const MAX_WAIT_TIME = 15000;
|
|
13
13
|
const STORE_VERSION_PREFIX = "v2";
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
interface WaitRecord {
|
|
16
|
+
timeout?: ReturnType<typeof setTimeout>;
|
|
17
|
+
unsubscribe?: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function v2StoreKey(key: string): string {
|
|
16
21
|
return `${STORE_VERSION_PREFIX}-${pointer.escape(key)}`;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
|
-
export function v2UnescapedStoreKey(key: string) {
|
|
24
|
+
export function v2UnescapedStoreKey(key: string): string {
|
|
20
25
|
return `${STORE_VERSION_PREFIX}-${key}`;
|
|
21
26
|
}
|
|
22
27
|
|
|
23
|
-
export function stripV2Prefix(key: string) {
|
|
28
|
+
export function stripV2Prefix(key: string): string {
|
|
24
29
|
return key.replace(/^v2-/, "");
|
|
25
30
|
}
|
|
26
31
|
export interface PeprStore {
|
|
@@ -58,13 +63,13 @@ export interface PeprStore {
|
|
|
58
63
|
* Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously.
|
|
59
64
|
* Resolves when the key/value show up in the store.
|
|
60
65
|
*/
|
|
61
|
-
setItemAndWait(key: string, value: string): Promise<
|
|
66
|
+
setItemAndWait(key: string, value: string): Promise<string>;
|
|
62
67
|
|
|
63
68
|
/**
|
|
64
69
|
* Remove the value of the key.
|
|
65
70
|
* Resolves when the key does not show up in the store.
|
|
66
71
|
*/
|
|
67
|
-
removeItemAndWait(key: string): Promise<
|
|
72
|
+
removeItemAndWait(key: string): Promise<string>;
|
|
68
73
|
}
|
|
69
74
|
|
|
70
75
|
/**
|
|
@@ -80,11 +85,11 @@ export class Storage implements PeprStore {
|
|
|
80
85
|
#subscriberId = 0;
|
|
81
86
|
#readyHandlers: DataReceiver[] = [];
|
|
82
87
|
|
|
83
|
-
registerSender = (send: DataSender) => {
|
|
88
|
+
registerSender = (send: DataSender): void => {
|
|
84
89
|
this.#send = send;
|
|
85
90
|
};
|
|
86
91
|
|
|
87
|
-
receive = (data: DataStore) => {
|
|
92
|
+
receive = (data: DataStore): void => {
|
|
88
93
|
this.#store = data || {};
|
|
89
94
|
|
|
90
95
|
this.#onReady();
|
|
@@ -96,7 +101,7 @@ export class Storage implements PeprStore {
|
|
|
96
101
|
}
|
|
97
102
|
};
|
|
98
103
|
|
|
99
|
-
getItem = (key: string) => {
|
|
104
|
+
getItem = (key: string): string | null => {
|
|
100
105
|
const result = this.#store[v2UnescapedStoreKey(key)] || null;
|
|
101
106
|
if (result !== null && typeof result !== "function" && typeof result !== "object") {
|
|
102
107
|
return result;
|
|
@@ -104,7 +109,7 @@ export class Storage implements PeprStore {
|
|
|
104
109
|
return null;
|
|
105
110
|
};
|
|
106
111
|
|
|
107
|
-
clear = () => {
|
|
112
|
+
clear = (): void => {
|
|
108
113
|
Object.keys(this.#store).length > 0 &&
|
|
109
114
|
this.#dispatchUpdate(
|
|
110
115
|
"remove",
|
|
@@ -112,11 +117,11 @@ export class Storage implements PeprStore {
|
|
|
112
117
|
);
|
|
113
118
|
};
|
|
114
119
|
|
|
115
|
-
removeItem = (key: string) => {
|
|
120
|
+
removeItem = (key: string): void => {
|
|
116
121
|
this.#dispatchUpdate("remove", [v2StoreKey(key)]);
|
|
117
122
|
};
|
|
118
123
|
|
|
119
|
-
setItem = (key: string, value: string) => {
|
|
124
|
+
setItem = (key: string, value: string): void => {
|
|
120
125
|
this.#dispatchUpdate("add", [v2StoreKey(key)], value);
|
|
121
126
|
};
|
|
122
127
|
|
|
@@ -128,22 +133,24 @@ export class Storage implements PeprStore {
|
|
|
128
133
|
* @param value - The value of the key
|
|
129
134
|
* @returns
|
|
130
135
|
*/
|
|
131
|
-
setItemAndWait = (key: string, value: string) => {
|
|
136
|
+
setItemAndWait = (key: string, value: string): Promise<string> => {
|
|
132
137
|
this.#dispatchUpdate("add", [v2StoreKey(key)], value);
|
|
138
|
+
const record: WaitRecord = {};
|
|
133
139
|
|
|
134
|
-
return new Promise<
|
|
135
|
-
|
|
140
|
+
return new Promise<string>((resolve, reject) => {
|
|
141
|
+
// If promise has not resolved before MAX_WAIT_TIME reject
|
|
142
|
+
record.timeout = setTimeout(() => {
|
|
143
|
+
record.unsubscribe!();
|
|
144
|
+
return reject(`MAX_WAIT_TIME elapsed: Key ${key} not seen in ${MAX_WAIT_TIME / 1000}s`);
|
|
145
|
+
}, MAX_WAIT_TIME);
|
|
146
|
+
|
|
147
|
+
record.unsubscribe = this.subscribe(data => {
|
|
136
148
|
if (data[`${v2UnescapedStoreKey(key)}`] === value) {
|
|
137
|
-
unsubscribe();
|
|
138
|
-
|
|
149
|
+
record.unsubscribe!();
|
|
150
|
+
clearTimeout(record.timeout);
|
|
151
|
+
resolve("ok");
|
|
139
152
|
}
|
|
140
153
|
});
|
|
141
|
-
|
|
142
|
-
// If promise has not resolved before MAX_WAIT_TIME reject
|
|
143
|
-
setTimeout(() => {
|
|
144
|
-
unsubscribe();
|
|
145
|
-
return reject();
|
|
146
|
-
}, MAX_WAIT_TIME);
|
|
147
154
|
});
|
|
148
155
|
};
|
|
149
156
|
|
|
@@ -154,31 +161,33 @@ export class Storage implements PeprStore {
|
|
|
154
161
|
* @param key - The key to add into the store
|
|
155
162
|
* @returns
|
|
156
163
|
*/
|
|
157
|
-
removeItemAndWait = (key: string) => {
|
|
164
|
+
removeItemAndWait = (key: string): Promise<string> => {
|
|
158
165
|
this.#dispatchUpdate("remove", [v2StoreKey(key)]);
|
|
159
|
-
|
|
160
|
-
|
|
166
|
+
const record: WaitRecord = {};
|
|
167
|
+
return new Promise<string>((resolve, reject) => {
|
|
168
|
+
// If promise has not resolved before MAX_WAIT_TIME reject
|
|
169
|
+
record.timeout = setTimeout(() => {
|
|
170
|
+
record.unsubscribe!();
|
|
171
|
+
return reject(`MAX_WAIT_TIME elapsed: Key ${key} still seen after ${MAX_WAIT_TIME / 1000}s`);
|
|
172
|
+
}, MAX_WAIT_TIME);
|
|
173
|
+
|
|
174
|
+
record.unsubscribe = this.subscribe(data => {
|
|
161
175
|
if (!Object.hasOwn(data, `${v2UnescapedStoreKey(key)}`)) {
|
|
162
|
-
unsubscribe();
|
|
163
|
-
|
|
176
|
+
record.unsubscribe!();
|
|
177
|
+
clearTimeout(record.timeout);
|
|
178
|
+
resolve("ok");
|
|
164
179
|
}
|
|
165
180
|
});
|
|
166
|
-
|
|
167
|
-
// If promise has not resolved before MAX_WAIT_TIME reject
|
|
168
|
-
setTimeout(() => {
|
|
169
|
-
unsubscribe();
|
|
170
|
-
return reject();
|
|
171
|
-
}, MAX_WAIT_TIME);
|
|
172
181
|
});
|
|
173
182
|
};
|
|
174
183
|
|
|
175
|
-
subscribe = (subscriber: DataReceiver) => {
|
|
184
|
+
subscribe = (subscriber: DataReceiver): (() => void) => {
|
|
176
185
|
const idx = this.#subscriberId++;
|
|
177
186
|
this.#subscribers[idx] = subscriber;
|
|
178
187
|
return () => this.unsubscribe(idx);
|
|
179
188
|
};
|
|
180
189
|
|
|
181
|
-
onReady = (callback: DataReceiver) => {
|
|
190
|
+
onReady = (callback: DataReceiver): void => {
|
|
182
191
|
this.#readyHandlers.push(callback);
|
|
183
192
|
};
|
|
184
193
|
|
|
@@ -186,18 +195,18 @@ export class Storage implements PeprStore {
|
|
|
186
195
|
* Remove a subscriber from the list of subscribers.
|
|
187
196
|
* @param idx - The index of the subscriber to remove.
|
|
188
197
|
*/
|
|
189
|
-
unsubscribe = (idx: number) => {
|
|
198
|
+
unsubscribe = (idx: number): void => {
|
|
190
199
|
delete this.#subscribers[idx];
|
|
191
200
|
};
|
|
192
201
|
|
|
193
|
-
#onReady = () => {
|
|
202
|
+
#onReady = (): void => {
|
|
194
203
|
// Notify all ready handlers with a clone of the store
|
|
195
204
|
for (const handler of this.#readyHandlers) {
|
|
196
205
|
handler(clone(this.#store));
|
|
197
206
|
}
|
|
198
207
|
|
|
199
208
|
// Make this a noop so that it can't be called again
|
|
200
|
-
this.#onReady = () => {};
|
|
209
|
+
this.#onReady = (): void => {};
|
|
201
210
|
};
|
|
202
211
|
|
|
203
212
|
/**
|
|
@@ -206,7 +215,7 @@ export class Storage implements PeprStore {
|
|
|
206
215
|
* @param keys - The keys to update.
|
|
207
216
|
* @param [value] - The new value.
|
|
208
217
|
*/
|
|
209
|
-
#dispatchUpdate = (op: DataOp, keys: string[], value?: string) => {
|
|
218
|
+
#dispatchUpdate = (op: DataOp, keys: string[], value?: string): void => {
|
|
210
219
|
this.#send(op, keys, value);
|
|
211
220
|
};
|
|
212
221
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { Operation } from "fast-json-patch";
|
|
5
5
|
import { pino, stdTimeFunctions } from "pino";
|
|
6
|
-
import { Store } from "
|
|
6
|
+
import { Store } from "../k8s";
|
|
7
7
|
|
|
8
8
|
const isPrettyLog = process.env.PEPR_PRETTY_LOGS === "true";
|
|
9
9
|
const redactedValue = "**redacted**";
|
|
@@ -9,6 +9,7 @@ import Log from "./logger";
|
|
|
9
9
|
|
|
10
10
|
const loggingPrefix = "MetricsCollector";
|
|
11
11
|
|
|
12
|
+
type MetricsCollectorInstance = InstanceType<typeof MetricsCollector>;
|
|
12
13
|
interface MetricNames {
|
|
13
14
|
errors: string;
|
|
14
15
|
alerts: string;
|
|
@@ -60,7 +61,7 @@ export class MetricsCollector {
|
|
|
60
61
|
this.addGauge(this.#metricNames.resyncFailureCount, "Number of failures per resync operation", ["count"]);
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
#getMetricName = (name: string) => `${this.#prefix}_${name}`;
|
|
64
|
+
#getMetricName = (name: string): string => `${this.#prefix}_${name}`;
|
|
64
65
|
|
|
65
66
|
#addMetric = <T extends Counter<string> | Gauge<string> | Summary<string>>(
|
|
66
67
|
collection: Map<string, T>,
|
|
@@ -68,7 +69,7 @@ export class MetricsCollector {
|
|
|
68
69
|
name: string,
|
|
69
70
|
help: string,
|
|
70
71
|
labelNames?: string[],
|
|
71
|
-
) => {
|
|
72
|
+
): void => {
|
|
72
73
|
if (collection.has(this.#getMetricName(name))) {
|
|
73
74
|
Log.debug(`Metric for ${name} already exists`, loggingPrefix);
|
|
74
75
|
return;
|
|
@@ -84,42 +85,42 @@ export class MetricsCollector {
|
|
|
84
85
|
collection.set(this.#getMetricName(name), metric);
|
|
85
86
|
};
|
|
86
87
|
|
|
87
|
-
addCounter = (name: string, help: string) => {
|
|
88
|
+
addCounter = (name: string, help: string): void => {
|
|
88
89
|
this.#addMetric(this.#counters, promClient.Counter, name, help, []);
|
|
89
90
|
};
|
|
90
91
|
|
|
91
|
-
addSummary = (name: string, help: string) => {
|
|
92
|
+
addSummary = (name: string, help: string): void => {
|
|
92
93
|
this.#addMetric(this.#summaries, promClient.Summary, name, help, []);
|
|
93
94
|
};
|
|
94
95
|
|
|
95
|
-
addGauge = (name: string, help: string, labelNames?: string[]) => {
|
|
96
|
+
addGauge = (name: string, help: string, labelNames?: string[]): void => {
|
|
96
97
|
this.#addMetric(this.#gauges, promClient.Gauge, name, help, labelNames);
|
|
97
98
|
};
|
|
98
99
|
|
|
99
|
-
incCounter = (name: string) => {
|
|
100
|
+
incCounter = (name: string): void => {
|
|
100
101
|
this.#counters.get(this.#getMetricName(name))?.inc();
|
|
101
102
|
};
|
|
102
103
|
|
|
103
|
-
incGauge = (name: string, labels?: Record<string, string>, value: number = 1) => {
|
|
104
|
+
incGauge = (name: string, labels?: Record<string, string>, value: number = 1): void => {
|
|
104
105
|
this.#gauges.get(this.#getMetricName(name))?.inc(labels || {}, value);
|
|
105
106
|
};
|
|
106
107
|
|
|
107
108
|
/**
|
|
108
109
|
* Increments the error counter.
|
|
109
110
|
*/
|
|
110
|
-
error = () => this.incCounter(this.#metricNames.errors);
|
|
111
|
+
error = (): void => this.incCounter(this.#metricNames.errors);
|
|
111
112
|
|
|
112
113
|
/**
|
|
113
114
|
* Increments the alerts counter.
|
|
114
115
|
*/
|
|
115
|
-
alert = () => this.incCounter(this.#metricNames.alerts);
|
|
116
|
+
alert = (): void => this.incCounter(this.#metricNames.alerts);
|
|
116
117
|
|
|
117
118
|
/**
|
|
118
119
|
* Observes the duration since the provided start time and updates the summary.
|
|
119
120
|
* @param startTime - The start time.
|
|
120
121
|
* @param name - The metrics summary to increment.
|
|
121
122
|
*/
|
|
122
|
-
observeEnd = (startTime: number, name: string = this.#metricNames.mutate) => {
|
|
123
|
+
observeEnd = (startTime: number, name: string = this.#metricNames.mutate): void => {
|
|
123
124
|
this.#summaries.get(this.#getMetricName(name))?.observe(performance.now() - startTime);
|
|
124
125
|
};
|
|
125
126
|
|
|
@@ -127,13 +128,13 @@ export class MetricsCollector {
|
|
|
127
128
|
* Fetches the current metrics from the registry.
|
|
128
129
|
* @returns The metrics.
|
|
129
130
|
*/
|
|
130
|
-
getMetrics = () => this.#registry.metrics();
|
|
131
|
+
getMetrics = (): Promise<string> => this.#registry.metrics();
|
|
131
132
|
|
|
132
133
|
/**
|
|
133
134
|
* Returns the current timestamp from performance.now() method. Useful for start timing an operation.
|
|
134
135
|
* @returns The timestamp.
|
|
135
136
|
*/
|
|
136
|
-
static observeStart() {
|
|
137
|
+
static observeStart(): number {
|
|
137
138
|
return performance.now();
|
|
138
139
|
}
|
|
139
140
|
|
|
@@ -141,7 +142,7 @@ export class MetricsCollector {
|
|
|
141
142
|
* Increments the cache miss gauge for a given label.
|
|
142
143
|
* @param label - The label for the cache miss.
|
|
143
144
|
*/
|
|
144
|
-
incCacheMiss = (window: string) => {
|
|
145
|
+
incCacheMiss = (window: string): void => {
|
|
145
146
|
this.incGauge(this.#metricNames.cacheMiss, { window });
|
|
146
147
|
};
|
|
147
148
|
|
|
@@ -149,7 +150,7 @@ export class MetricsCollector {
|
|
|
149
150
|
* Increments the retry count gauge.
|
|
150
151
|
* @param count - The count to increment by.
|
|
151
152
|
*/
|
|
152
|
-
incRetryCount = (count: string) => {
|
|
153
|
+
incRetryCount = (count: string): void => {
|
|
153
154
|
this.incGauge(this.#metricNames.resyncFailureCount, { count });
|
|
154
155
|
};
|
|
155
156
|
|
|
@@ -157,7 +158,7 @@ export class MetricsCollector {
|
|
|
157
158
|
* Initializes the cache miss gauge for a given label.
|
|
158
159
|
* @param label - The label for the cache miss.
|
|
159
160
|
*/
|
|
160
|
-
initCacheMissWindow = (window: string) => {
|
|
161
|
+
initCacheMissWindow = (window: string): void => {
|
|
161
162
|
this.#rollCacheMissWindows();
|
|
162
163
|
this.#gauges.get(this.#getMetricName(this.#metricNames.cacheMiss))?.set({ window }, 0);
|
|
163
164
|
this.#cacheMissWindows.set(window, 0);
|
|
@@ -166,7 +167,7 @@ export class MetricsCollector {
|
|
|
166
167
|
/**
|
|
167
168
|
* Manages the size of the cache miss gauge map.
|
|
168
169
|
*/
|
|
169
|
-
#rollCacheMissWindows = () => {
|
|
170
|
+
#rollCacheMissWindows = (): void => {
|
|
170
171
|
const maxCacheMissWindows = process.env.PEPR_MAX_CACHE_MISS_WINDOWS
|
|
171
172
|
? parseInt(process.env.PEPR_MAX_CACHE_MISS_WINDOWS, 10)
|
|
172
173
|
: undefined;
|
|
@@ -181,4 +182,4 @@ export class MetricsCollector {
|
|
|
181
182
|
};
|
|
182
183
|
}
|
|
183
184
|
|
|
184
|
-
export const metricsCollector = new MetricsCollector("pepr");
|
|
185
|
+
export const metricsCollector: MetricsCollectorInstance = new MetricsCollector("pepr");
|
package/src/lib/types.ts
CHANGED
|
@@ -70,6 +70,17 @@ export interface RegExpFilter {
|
|
|
70
70
|
obj: RegExp;
|
|
71
71
|
source: string;
|
|
72
72
|
}
|
|
73
|
+
|
|
74
|
+
export type Filters = {
|
|
75
|
+
annotations: Record<string, string>;
|
|
76
|
+
deletionTimestamp: boolean;
|
|
77
|
+
labels: Record<string, string>;
|
|
78
|
+
name: string;
|
|
79
|
+
namespaces: string[];
|
|
80
|
+
regexName: string;
|
|
81
|
+
regexNamespaces: string[];
|
|
82
|
+
};
|
|
83
|
+
|
|
73
84
|
export type Binding = {
|
|
74
85
|
event: Event;
|
|
75
86
|
isMutate?: boolean;
|
|
@@ -79,15 +90,7 @@ export type Binding = {
|
|
|
79
90
|
isFinalize?: boolean;
|
|
80
91
|
readonly model: GenericClass;
|
|
81
92
|
readonly kind: GroupVersionKind;
|
|
82
|
-
readonly filters:
|
|
83
|
-
name: string;
|
|
84
|
-
regexName: string;
|
|
85
|
-
namespaces: string[];
|
|
86
|
-
regexNamespaces: string[];
|
|
87
|
-
labels: Record<string, string>;
|
|
88
|
-
annotations: Record<string, string>;
|
|
89
|
-
deletionTimestamp: boolean;
|
|
90
|
-
};
|
|
93
|
+
readonly filters: Filters;
|
|
91
94
|
alias?: string;
|
|
92
95
|
readonly mutateCallback?: MutateAction<GenericClass, InstanceType<GenericClass>>;
|
|
93
96
|
readonly validateCallback?: ValidateAction<GenericClass, InstanceType<GenericClass>>;
|
package/src/lib/utils.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
3
|
|
|
4
|
-
import Log from "./logger";
|
|
4
|
+
import Log from "./telemetry/logger";
|
|
5
5
|
|
|
6
6
|
/** Test if a string is ascii or not */
|
|
7
|
-
export const isAscii = /^[\s\x20-\x7E]*$/;
|
|
7
|
+
export const isAscii: RegExp = /^[\s\x20-\x7E]*$/;
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Encode all ascii values in a map to base64
|
|
11
11
|
* @param obj The object to encode
|
|
12
12
|
* @param skip A list of keys to skip encoding
|
|
13
13
|
*/
|
|
14
|
-
export function convertToBase64Map(obj: { data?: Record<string, string> }, skip: string[]) {
|
|
14
|
+
export function convertToBase64Map(obj: { data?: Record<string, string> }, skip: string[]): void {
|
|
15
15
|
obj.data = obj.data ?? {};
|
|
16
16
|
for (const key in obj.data) {
|
|
17
17
|
const value = obj.data[key];
|
|
@@ -25,7 +25,7 @@ export function convertToBase64Map(obj: { data?: Record<string, string> }, skip:
|
|
|
25
25
|
* @param obj The object to decode
|
|
26
26
|
* @returns A list of keys that were skipped
|
|
27
27
|
*/
|
|
28
|
-
export function convertFromBase64Map(obj: { data?: Record<string, string> }) {
|
|
28
|
+
export function convertFromBase64Map(obj: { data?: Record<string, string> }): string[] {
|
|
29
29
|
const skip: string[] = [];
|
|
30
30
|
|
|
31
31
|
obj.data = obj.data ?? {};
|
|
@@ -47,11 +47,11 @@ export function convertFromBase64Map(obj: { data?: Record<string, string> }) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/** Decode a base64 string */
|
|
50
|
-
export function base64Decode(data: string) {
|
|
50
|
+
export function base64Decode(data: string): string {
|
|
51
51
|
return Buffer.from(data, "base64").toString("utf-8");
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/** Encode a string to base64 */
|
|
55
|
-
export function base64Encode(data: string) {
|
|
55
|
+
export function base64Encode(data: string): string {
|
|
56
56
|
return Buffer.from(data).toString("base64");
|
|
57
57
|
}
|
|
@@ -1,17 +1,56 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
3
|
|
|
4
|
-
import { kind } from "kubernetes-fluent-client";
|
|
5
|
-
|
|
4
|
+
import { kind, KubernetesObject } from "kubernetes-fluent-client";
|
|
6
5
|
import { Capability } from "./capability";
|
|
7
6
|
import { shouldSkipRequest } from "./filter/filter";
|
|
8
7
|
import { ValidateResponse } from "./k8s";
|
|
9
|
-
import { AdmissionRequest } from "./types";
|
|
10
|
-
import Log from "./logger";
|
|
8
|
+
import { AdmissionRequest, Binding } from "./types";
|
|
9
|
+
import Log from "./telemetry/logger";
|
|
11
10
|
import { convertFromBase64Map } from "./utils";
|
|
12
11
|
import { PeprValidateRequest } from "./validate-request";
|
|
13
12
|
import { ModuleConfig } from "./module";
|
|
14
13
|
|
|
14
|
+
export async function processRequest(
|
|
15
|
+
binding: Binding,
|
|
16
|
+
actionMetadata: Record<string, string>,
|
|
17
|
+
peprValidateRequest: PeprValidateRequest<KubernetesObject>,
|
|
18
|
+
): Promise<ValidateResponse> {
|
|
19
|
+
const label = binding.validateCallback!.name;
|
|
20
|
+
Log.info(actionMetadata, `Processing validation action (${label})`);
|
|
21
|
+
|
|
22
|
+
const valResp: ValidateResponse = {
|
|
23
|
+
uid: peprValidateRequest.Request.uid,
|
|
24
|
+
allowed: true, // Assume it's allowed until a validation check fails
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
// Run the validation callback, if it fails set allowed to false
|
|
29
|
+
const callbackResp = await binding.validateCallback!(peprValidateRequest);
|
|
30
|
+
valResp.allowed = callbackResp.allowed;
|
|
31
|
+
|
|
32
|
+
// If the validation callback returned a status code or message, set it in the Response
|
|
33
|
+
if (callbackResp.statusCode || callbackResp.statusMessage) {
|
|
34
|
+
valResp.status = {
|
|
35
|
+
code: callbackResp.statusCode || 400,
|
|
36
|
+
message: callbackResp.statusMessage || `Validation failed for ${name}`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
Log.info(actionMetadata, `Validation action complete (${label}): ${callbackResp.allowed ? "allowed" : "denied"}`);
|
|
41
|
+
return valResp;
|
|
42
|
+
} catch (e) {
|
|
43
|
+
// If any validation throws an error, note the failure in the Response
|
|
44
|
+
Log.error(actionMetadata, `Action failed: ${JSON.stringify(e)}`);
|
|
45
|
+
valResp.allowed = false;
|
|
46
|
+
valResp.status = {
|
|
47
|
+
code: 500,
|
|
48
|
+
message: `Action failed with error: ${JSON.stringify(e)}`,
|
|
49
|
+
};
|
|
50
|
+
return valResp;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
15
54
|
export async function validateProcessor(
|
|
16
55
|
config: ModuleConfig,
|
|
17
56
|
capabilities: Capability[],
|
|
@@ -32,52 +71,21 @@ export async function validateProcessor(
|
|
|
32
71
|
for (const { name, bindings, namespaces } of capabilities) {
|
|
33
72
|
const actionMetadata = { ...reqMetadata, name };
|
|
34
73
|
|
|
35
|
-
for (const
|
|
74
|
+
for (const binding of bindings) {
|
|
36
75
|
// Skip this action if it's not a validation action
|
|
37
|
-
if (!
|
|
76
|
+
if (!binding.validateCallback) {
|
|
38
77
|
continue;
|
|
39
78
|
}
|
|
40
79
|
|
|
41
|
-
const localResponse: ValidateResponse = {
|
|
42
|
-
uid: req.uid,
|
|
43
|
-
allowed: true, // Assume it's allowed until a validation check fails
|
|
44
|
-
};
|
|
45
|
-
|
|
46
80
|
// Continue to the next action without doing anything if this one should be skipped
|
|
47
|
-
const shouldSkip = shouldSkipRequest(
|
|
81
|
+
const shouldSkip = shouldSkipRequest(binding, req, namespaces, config?.alwaysIgnore?.namespaces);
|
|
48
82
|
if (shouldSkip !== "") {
|
|
49
83
|
Log.debug(shouldSkip);
|
|
50
84
|
continue;
|
|
51
85
|
}
|
|
52
86
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
// Run the validation callback, if it fails set allowed to false
|
|
58
|
-
const resp = await action.validateCallback(wrapped);
|
|
59
|
-
localResponse.allowed = resp.allowed;
|
|
60
|
-
|
|
61
|
-
// If the validation callback returned a status code or message, set it in the Response
|
|
62
|
-
if (resp.statusCode || resp.statusMessage) {
|
|
63
|
-
localResponse.status = {
|
|
64
|
-
code: resp.statusCode || 400,
|
|
65
|
-
message: resp.statusMessage || `Validation failed for ${name}`,
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
Log.info(actionMetadata, `Validation action complete (${label}): ${resp.allowed ? "allowed" : "denied"}`);
|
|
70
|
-
} catch (e) {
|
|
71
|
-
// If any validation throws an error, note the failure in the Response
|
|
72
|
-
Log.error(actionMetadata, `Action failed: ${JSON.stringify(e)}`);
|
|
73
|
-
localResponse.allowed = false;
|
|
74
|
-
localResponse.status = {
|
|
75
|
-
code: 500,
|
|
76
|
-
message: `Action failed with error: ${JSON.stringify(e)}`,
|
|
77
|
-
};
|
|
78
|
-
return [localResponse];
|
|
79
|
-
}
|
|
80
|
-
response.push(localResponse);
|
|
87
|
+
const resp = await processRequest(binding, actionMetadata, wrapped);
|
|
88
|
+
response.push(resp);
|
|
81
89
|
}
|
|
82
90
|
}
|
|
83
91
|
|
|
@@ -5,11 +5,11 @@ import { WatchPhase } from "kubernetes-fluent-client/dist/fluent/types";
|
|
|
5
5
|
import { Capability } from "./capability";
|
|
6
6
|
import { filterNoMatchReason } from "./helpers";
|
|
7
7
|
import { removeFinalizer } from "./finalizer";
|
|
8
|
-
import Log from "./logger";
|
|
8
|
+
import Log from "./telemetry/logger";
|
|
9
9
|
import { Queue } from "./queue";
|
|
10
10
|
import { Binding } from "./types";
|
|
11
11
|
import { Event } from "./enums";
|
|
12
|
-
import { metricsCollector } from "./metrics";
|
|
12
|
+
import { metricsCollector } from "./telemetry/metrics";
|
|
13
13
|
|
|
14
14
|
// stores Queue instances
|
|
15
15
|
const queues: Record<string, Queue<KubernetesObject>> = {};
|
|
@@ -20,7 +20,7 @@ const queues: Record<string, Queue<KubernetesObject>> = {};
|
|
|
20
20
|
* @param obj The object to derive a key from
|
|
21
21
|
* @returns The key to a Queue in the list of queues
|
|
22
22
|
*/
|
|
23
|
-
export function queueKey(obj: KubernetesObject) {
|
|
23
|
+
export function queueKey(obj: KubernetesObject): string {
|
|
24
24
|
const options = ["kind", "kindNs", "kindNsName", "global"];
|
|
25
25
|
const d3fault = "kind";
|
|
26
26
|
|
|
@@ -40,7 +40,7 @@ export function queueKey(obj: KubernetesObject) {
|
|
|
40
40
|
return lookup[strat];
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
export function getOrCreateQueue(obj: KubernetesObject) {
|
|
43
|
+
export function getOrCreateQueue(obj: KubernetesObject): Queue<KubernetesObject> {
|
|
44
44
|
const key = queueKey(obj);
|
|
45
45
|
if (!queues[key]) {
|
|
46
46
|
queues[key] = new Queue<KubernetesObject>(key);
|
|
@@ -62,11 +62,11 @@ const watchCfg: WatchCfg = {
|
|
|
62
62
|
|
|
63
63
|
// Map the event to the watch phase
|
|
64
64
|
const eventToPhaseMap = {
|
|
65
|
-
[Event.
|
|
66
|
-
[Event.
|
|
67
|
-
[Event.
|
|
68
|
-
[Event.
|
|
69
|
-
[Event.
|
|
65
|
+
[Event.CREATE]: [WatchPhase.Added],
|
|
66
|
+
[Event.UPDATE]: [WatchPhase.Modified],
|
|
67
|
+
[Event.CREATE_OR_UPDATE]: [WatchPhase.Added, WatchPhase.Modified],
|
|
68
|
+
[Event.DELETE]: [WatchPhase.Deleted],
|
|
69
|
+
[Event.ANY]: [WatchPhase.Added, WatchPhase.Modified, WatchPhase.Deleted],
|
|
70
70
|
};
|
|
71
71
|
|
|
72
72
|
/**
|
|
@@ -74,7 +74,7 @@ const eventToPhaseMap = {
|
|
|
74
74
|
*
|
|
75
75
|
* @param capabilities The capabilities to load watches for
|
|
76
76
|
*/
|
|
77
|
-
export function setupWatch(capabilities: Capability[], ignoredNamespaces?: string[]) {
|
|
77
|
+
export function setupWatch(capabilities: Capability[], ignoredNamespaces?: string[]): void {
|
|
78
78
|
capabilities.map(capability =>
|
|
79
79
|
capability.bindings
|
|
80
80
|
.filter(binding => binding.isWatch)
|
|
@@ -88,14 +88,18 @@ export function setupWatch(capabilities: Capability[], ignoredNamespaces?: strin
|
|
|
88
88
|
* @param binding the binding to watch
|
|
89
89
|
* @param capabilityNamespaces list of namespaces to filter on
|
|
90
90
|
*/
|
|
91
|
-
async function runBinding(
|
|
91
|
+
async function runBinding(
|
|
92
|
+
binding: Binding,
|
|
93
|
+
capabilityNamespaces: string[],
|
|
94
|
+
ignoredNamespaces?: string[],
|
|
95
|
+
): Promise<void> {
|
|
92
96
|
// Get the phases to match, fallback to any
|
|
93
|
-
const phaseMatch: WatchPhase[] = eventToPhaseMap[binding.event] || eventToPhaseMap[Event.
|
|
97
|
+
const phaseMatch: WatchPhase[] = eventToPhaseMap[binding.event] || eventToPhaseMap[Event.ANY];
|
|
94
98
|
|
|
95
99
|
// The watch callback is run when an object is received or dequeued
|
|
96
100
|
Log.debug({ watchCfg }, "Effective WatchConfig");
|
|
97
101
|
|
|
98
|
-
const watchCallback = async (kubernetesObject: KubernetesObject, phase: WatchPhase) => {
|
|
102
|
+
const watchCallback = async (kubernetesObject: KubernetesObject, phase: WatchPhase): Promise<void> => {
|
|
99
103
|
// First, filter the object based on the phase
|
|
100
104
|
if (phaseMatch.includes(phase)) {
|
|
101
105
|
try {
|
|
@@ -117,7 +121,7 @@ async function runBinding(binding: Binding, capabilityNamespaces: string[], igno
|
|
|
117
121
|
}
|
|
118
122
|
};
|
|
119
123
|
|
|
120
|
-
const handleFinalizerRemoval = async (kubernetesObject: KubernetesObject) => {
|
|
124
|
+
const handleFinalizerRemoval = async (kubernetesObject: KubernetesObject): Promise<void> => {
|
|
121
125
|
if (!kubernetesObject.metadata?.deletionTimestamp) {
|
|
122
126
|
return;
|
|
123
127
|
}
|
|
@@ -191,7 +195,7 @@ async function runBinding(binding: Binding, capabilityNamespaces: string[], igno
|
|
|
191
195
|
}
|
|
192
196
|
}
|
|
193
197
|
|
|
194
|
-
export function logEvent(event: WatchEvent, message: string = "", obj?: KubernetesObject) {
|
|
198
|
+
export function logEvent(event: WatchEvent, message: string = "", obj?: KubernetesObject): void {
|
|
195
199
|
const logMessage = `Watch event ${event} received${message ? `. ${message}.` : "."}`;
|
|
196
200
|
if (obj) {
|
|
197
201
|
Log.debug(obj, logMessage);
|
package/src/lib.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { K8s, RegisterKind, kind as a, fetch, fetchStatus, kind } from "kubernet
|
|
|
2
2
|
import * as R from "ramda";
|
|
3
3
|
|
|
4
4
|
import { Capability } from "./lib/capability";
|
|
5
|
-
import Log from "./lib/logger";
|
|
5
|
+
import Log from "./lib/telemetry/logger";
|
|
6
6
|
import { PeprModule } from "./lib/module";
|
|
7
7
|
import { PeprMutateRequest } from "./lib/mutate-request";
|
|
8
8
|
import * as PeprUtils from "./lib/utils";
|
|
@@ -8,7 +8,7 @@ import crypto from "crypto";
|
|
|
8
8
|
import fs from "fs";
|
|
9
9
|
import { gunzipSync } from "zlib";
|
|
10
10
|
import { K8s, kind } from "kubernetes-fluent-client";
|
|
11
|
-
import Log from "../lib/logger";
|
|
11
|
+
import Log from "../lib/telemetry/logger";
|
|
12
12
|
import { packageJSON } from "../templates/data.json";
|
|
13
13
|
import { peprStoreCRD } from "../lib/assets/store";
|
|
14
14
|
import { validateHash } from "../lib/helpers";
|