pepr 0.12.2 → 0.13.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/CODE_OF_CONDUCT.md +83 -0
- package/CONTRIBUTING.md +70 -0
- package/README.md +28 -30
- package/dist/cli.js +644 -679
- package/dist/controller.js +13 -81
- package/dist/lib/assets/deploy.d.ts +3 -0
- package/dist/lib/assets/deploy.d.ts.map +1 -0
- package/dist/lib/assets/index.d.ts +18 -0
- package/dist/lib/assets/index.d.ts.map +1 -0
- package/dist/lib/assets/loader.d.ts +14 -0
- package/dist/lib/assets/loader.d.ts.map +1 -0
- package/dist/lib/assets/networking.d.ts +6 -0
- package/dist/lib/assets/networking.d.ts.map +1 -0
- package/dist/lib/assets/pods.d.ts +8 -0
- package/dist/lib/assets/pods.d.ts.map +1 -0
- package/dist/lib/assets/rbac.d.ts +11 -0
- package/dist/lib/assets/rbac.d.ts.map +1 -0
- package/dist/lib/assets/webhooks.d.ts +6 -0
- package/dist/lib/assets/webhooks.d.ts.map +1 -0
- package/dist/lib/assets/yaml.d.ts +4 -0
- package/dist/lib/assets/yaml.d.ts.map +1 -0
- package/dist/lib/capability.d.ts +1 -3
- package/dist/lib/capability.d.ts.map +1 -1
- package/dist/lib/controller.d.ts +45 -10
- package/dist/lib/controller.d.ts.map +1 -1
- package/dist/lib/filter.d.ts +1 -1
- package/dist/lib/filter.d.ts.map +1 -1
- package/dist/lib/k8s/index.d.ts +2 -1
- package/dist/lib/k8s/index.d.ts.map +1 -1
- package/dist/lib/k8s/kinds.d.ts.map +1 -1
- package/dist/lib/k8s/types.d.ts +13 -13
- package/dist/lib/k8s/types.d.ts.map +1 -1
- package/dist/lib/k8s/upstream.d.ts +2 -2
- package/dist/lib/k8s/upstream.d.ts.map +1 -1
- package/dist/lib/logger.d.ts +8 -54
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/metrics.d.ts +11 -4
- package/dist/lib/metrics.d.ts.map +1 -1
- package/dist/lib/module.d.ts +2 -2
- package/dist/lib/module.d.ts.map +1 -1
- package/dist/lib/mutate-processor.d.ts +5 -0
- package/dist/lib/mutate-processor.d.ts.map +1 -0
- package/dist/lib/{request.d.ts → mutate-request.d.ts} +5 -5
- package/dist/lib/mutate-request.d.ts.map +1 -0
- package/dist/lib/types.d.ts +45 -46
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/validate-processor.d.ts +4 -0
- package/dist/lib/validate-processor.d.ts.map +1 -0
- package/dist/lib/validate-request.d.ts +54 -0
- package/dist/lib/validate-request.d.ts.map +1 -0
- package/dist/lib.d.ts +3 -2
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +502 -302
- package/dist/lib.js.map +4 -4
- package/package.json +15 -12
- package/src/cli.ts +2 -11
- package/src/lib/assets/deploy.ts +179 -0
- package/src/lib/assets/index.ts +46 -0
- package/src/lib/assets/loader.ts +49 -0
- package/src/lib/assets/networking.ts +58 -0
- package/src/lib/assets/pods.ts +148 -0
- package/src/lib/assets/rbac.ts +57 -0
- package/src/lib/assets/webhooks.ts +139 -0
- package/src/lib/assets/yaml.ts +75 -0
- package/src/lib/capability.ts +54 -44
- package/src/lib/controller.ts +171 -89
- package/src/lib/fetch.ts +1 -1
- package/src/lib/filter.ts +1 -3
- package/src/lib/k8s/index.ts +4 -1
- package/src/lib/k8s/kinds.ts +40 -0
- package/src/lib/k8s/types.ts +16 -14
- package/src/lib/k8s/upstream.ts +5 -1
- package/src/lib/logger.ts +14 -125
- package/src/lib/metrics.ts +67 -23
- package/src/lib/module.ts +13 -11
- package/src/lib/{processor.ts → mutate-processor.ts} +37 -28
- package/src/lib/{request.ts → mutate-request.ts} +4 -4
- package/src/lib/types.ts +51 -51
- package/src/lib/validate-processor.ts +68 -0
- package/src/lib/validate-request.ts +94 -0
- package/src/lib.ts +4 -2
- package/src/runtime/controller.ts +1 -1
- package/dist/lib/k8s/webhook.d.ts +0 -37
- package/dist/lib/k8s/webhook.d.ts.map +0 -1
- package/dist/lib/processor.d.ts +0 -5
- package/dist/lib/processor.d.ts.map +0 -1
- package/dist/lib/request.d.ts.map +0 -1
- package/src/lib/k8s/webhook.ts +0 -643
package/src/lib/k8s/types.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { KubernetesListObject, KubernetesObject, V1ObjectMeta } from "@kubernetes/client-node";
|
|
5
|
+
|
|
6
|
+
export { KubernetesListObject, KubernetesObject };
|
|
5
7
|
|
|
6
8
|
export enum Operation {
|
|
7
9
|
CREATE = "CREATE",
|
|
@@ -10,18 +12,6 @@ export enum Operation {
|
|
|
10
12
|
CONNECT = "CONNECT",
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
export interface KubernetesObject {
|
|
14
|
-
apiVersion?: string;
|
|
15
|
-
kind?: string;
|
|
16
|
-
metadata?: V1ObjectMeta;
|
|
17
|
-
}
|
|
18
|
-
export interface KubernetesListObject<T extends KubernetesObject> {
|
|
19
|
-
apiVersion?: string;
|
|
20
|
-
kind?: string;
|
|
21
|
-
metadata?: V1ListMeta;
|
|
22
|
-
items: T[];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
15
|
/**
|
|
26
16
|
* GenericKind is a generic Kubernetes object that can be used to represent any Kubernetes object
|
|
27
17
|
* that is not explicitly supported by Pepr. This can be used on its own or as a base class for
|
|
@@ -138,7 +128,7 @@ export interface Request<T = KubernetesObject> {
|
|
|
138
128
|
readonly options?: any;
|
|
139
129
|
}
|
|
140
130
|
|
|
141
|
-
export interface
|
|
131
|
+
export interface MutateResponse {
|
|
142
132
|
/** UID is an identifier for the individual request/response. This must be copied over from the corresponding AdmissionRequest. */
|
|
143
133
|
uid: string;
|
|
144
134
|
|
|
@@ -163,6 +153,18 @@ export interface Response {
|
|
|
163
153
|
warnings?: string[];
|
|
164
154
|
}
|
|
165
155
|
|
|
156
|
+
export interface ValidateResponse extends MutateResponse {
|
|
157
|
+
/** Status contains extra details into why an admission request was denied. This field IS NOT consulted in any way if "Allowed" is "true". */
|
|
158
|
+
status?: {
|
|
159
|
+
/** A machine-readable description of why this operation is in the
|
|
160
|
+
"Failure" status. If this value is empty there is no information available. */
|
|
161
|
+
code: number;
|
|
162
|
+
|
|
163
|
+
/** A human-readable description of the status of this operation. */
|
|
164
|
+
message: string;
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
166
168
|
export type WebhookIgnore = {
|
|
167
169
|
/**
|
|
168
170
|
* List of Kubernetes namespaces to always ignore.
|
package/src/lib/k8s/upstream.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
3
|
|
|
4
|
-
/** a is a collection of K8s types to be used within
|
|
4
|
+
/** a is a collection of K8s types to be used within an action: `When(a.Configmap)` */
|
|
5
5
|
export {
|
|
6
6
|
V1APIService as APIService,
|
|
7
7
|
V1CertificateSigningRequest as CertificateSigningRequest,
|
|
8
|
+
V1ClusterRole as ClusterRole,
|
|
9
|
+
V1ClusterRoleBinding as ClusterRoleBinding,
|
|
8
10
|
V1ConfigMap as ConfigMap,
|
|
9
11
|
V1ControllerRevision as ControllerRevision,
|
|
10
12
|
V1CronJob as CronJob,
|
|
@@ -32,6 +34,8 @@ export {
|
|
|
32
34
|
V1ReplicaSet as ReplicaSet,
|
|
33
35
|
V1ReplicationController as ReplicationController,
|
|
34
36
|
V1ResourceQuota as ResourceQuota,
|
|
37
|
+
V1Role as Role,
|
|
38
|
+
V1RoleBinding as RoleBinding,
|
|
35
39
|
V1RuntimeClass as RuntimeClass,
|
|
36
40
|
V1Secret as Secret,
|
|
37
41
|
V1SelfSubjectAccessReview as SelfSubjectAccessReview,
|
package/src/lib/logger.ts
CHANGED
|
@@ -1,136 +1,25 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
* Enumeration representing different logging levels.
|
|
6
|
-
*/
|
|
7
|
-
export enum LogLevel {
|
|
8
|
-
debug = 0,
|
|
9
|
-
info = 1,
|
|
10
|
-
warn = 2,
|
|
11
|
-
error = 3,
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
enum ConsoleColors {
|
|
15
|
-
Reset = "\x1b[0m",
|
|
16
|
-
Bright = "\x1b[1m",
|
|
17
|
-
Dim = "\x1b[2m",
|
|
18
|
-
Underscore = "\x1b[4m",
|
|
19
|
-
Blink = "\x1b[5m",
|
|
20
|
-
Reverse = "\x1b[7m",
|
|
21
|
-
Hidden = "\x1b[8m",
|
|
22
|
-
|
|
23
|
-
FgBlack = "\x1b[30m",
|
|
24
|
-
FgRed = "\x1b[31m",
|
|
25
|
-
FgGreen = "\x1b[32m",
|
|
26
|
-
FgYellow = "\x1b[33m",
|
|
27
|
-
FgBlue = "\x1b[34m",
|
|
28
|
-
FgMagenta = "\x1b[35m",
|
|
29
|
-
FgCyan = "\x1b[36m",
|
|
30
|
-
FgWhite = "\x1b[37m",
|
|
31
|
-
|
|
32
|
-
BgBlack = "\x1b[40m",
|
|
33
|
-
BgRed = "\x1b[41m",
|
|
34
|
-
BgGreen = "\x1b[42m",
|
|
35
|
-
BgYellow = "\x1b[43m",
|
|
36
|
-
BgBlue = "\x1b[44m",
|
|
37
|
-
BgMagenta = "\x1b[45m",
|
|
38
|
-
BgCyan = "\x1b[46m",
|
|
39
|
-
BgWhite = "\x1b[47m",
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Simple logger class that logs messages at different log levels.
|
|
44
|
-
*/
|
|
45
|
-
export class Logger {
|
|
46
|
-
private _logLevel: LogLevel;
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Create a new logger instance.
|
|
50
|
-
* @param logLevel - The minimum log level to log messages for.
|
|
51
|
-
*/
|
|
52
|
-
constructor(logLevel: LogLevel) {
|
|
53
|
-
this._logLevel = logLevel;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Change the log level of the logger.
|
|
58
|
-
* @param logLevel - The log level to log the message at.
|
|
59
|
-
*/
|
|
60
|
-
public SetLogLevel(logLevel: string): void {
|
|
61
|
-
this._logLevel = LogLevel[logLevel as keyof typeof LogLevel];
|
|
62
|
-
this.debug(`Log level set to ${logLevel}`);
|
|
63
|
-
}
|
|
4
|
+
import { pino } from "pino";
|
|
64
5
|
|
|
65
|
-
|
|
66
|
-
* Log a debug message.
|
|
67
|
-
* @param message - The message to log.
|
|
68
|
-
*/
|
|
69
|
-
public debug<T>(message: T, prefix?: string): void {
|
|
70
|
-
this.log(LogLevel.debug, message, prefix);
|
|
71
|
-
}
|
|
6
|
+
const isPrettyLog = process.env.PEPR_PRETTY_LOGS === "true";
|
|
72
7
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
8
|
+
const pretty = {
|
|
9
|
+
target: "pino-pretty",
|
|
10
|
+
options: {
|
|
11
|
+
colorize: true,
|
|
12
|
+
},
|
|
13
|
+
};
|
|
80
14
|
|
|
81
|
-
|
|
82
|
-
* Log a warning message.
|
|
83
|
-
* @param message - The message to log.
|
|
84
|
-
*/
|
|
85
|
-
public warn<T>(message: T, prefix?: string): void {
|
|
86
|
-
this.log(LogLevel.warn, message, prefix);
|
|
87
|
-
}
|
|
15
|
+
const transport = isPrettyLog ? pretty : undefined;
|
|
88
16
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
*/
|
|
93
|
-
public error<T>(message: T, prefix?: string): void {
|
|
94
|
-
this.log(LogLevel.error, message, prefix);
|
|
95
|
-
}
|
|
17
|
+
const Log = pino({
|
|
18
|
+
transport,
|
|
19
|
+
});
|
|
96
20
|
|
|
97
|
-
/**
|
|
98
|
-
* Log a message at the specified log level.
|
|
99
|
-
* @param logLevel - The log level of the message.
|
|
100
|
-
* @param message - The message to log.
|
|
101
|
-
*/
|
|
102
|
-
private log<T>(logLevel: LogLevel, message: T, callerPrefix = ""): void {
|
|
103
|
-
const color = {
|
|
104
|
-
[LogLevel.debug]: ConsoleColors.FgBlack,
|
|
105
|
-
[LogLevel.info]: ConsoleColors.FgCyan,
|
|
106
|
-
[LogLevel.warn]: ConsoleColors.FgYellow,
|
|
107
|
-
[LogLevel.error]: ConsoleColors.FgRed,
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
if (logLevel >= this._logLevel) {
|
|
111
|
-
// Prefix the message with the colored log level.
|
|
112
|
-
let prefix = "[" + LogLevel[logLevel] + "]\t" + callerPrefix;
|
|
113
|
-
|
|
114
|
-
prefix = this.colorize(prefix, color[logLevel]);
|
|
115
|
-
|
|
116
|
-
// If the message is not a string, use the debug method to log the object.
|
|
117
|
-
if (typeof message !== "string") {
|
|
118
|
-
console.log(prefix);
|
|
119
|
-
console.debug("%o", message);
|
|
120
|
-
} else {
|
|
121
|
-
console.log(prefix + "\t" + message);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
private colorize(text: string, color: ConsoleColors): string {
|
|
127
|
-
return color + text + ConsoleColors.Reset;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/** Log is an instance of Logger used to generate log entries. */
|
|
132
|
-
const Log = new Logger(LogLevel.info);
|
|
133
21
|
if (process.env.LOG_LEVEL) {
|
|
134
|
-
Log.
|
|
22
|
+
Log.level = process.env.LOG_LEVEL;
|
|
135
23
|
}
|
|
24
|
+
|
|
136
25
|
export default Log;
|
package/src/lib/metrics.ts
CHANGED
|
@@ -1,56 +1,99 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
3
|
|
|
4
|
-
import promClient from "prom-client";
|
|
4
|
+
import promClient, { Counter, Summary, Registry } from "prom-client";
|
|
5
5
|
import { performance } from "perf_hooks";
|
|
6
|
+
import Log from "./logger";
|
|
7
|
+
|
|
8
|
+
const loggingPrefix = "MetricsCollector";
|
|
9
|
+
|
|
10
|
+
interface MetricNames {
|
|
11
|
+
errors: string;
|
|
12
|
+
alerts: string;
|
|
13
|
+
mutate: string;
|
|
14
|
+
validate: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface MetricArgs {
|
|
18
|
+
name: string;
|
|
19
|
+
help: string;
|
|
20
|
+
registers: Registry[];
|
|
21
|
+
}
|
|
6
22
|
|
|
7
23
|
/**
|
|
8
24
|
* MetricsCollector class handles metrics collection using prom-client and performance hooks.
|
|
9
25
|
*/
|
|
10
26
|
export class MetricsCollector {
|
|
11
|
-
private _registry:
|
|
12
|
-
private
|
|
13
|
-
private
|
|
14
|
-
private
|
|
27
|
+
private _registry: Registry;
|
|
28
|
+
private _counters: Map<string, Counter<string>> = new Map();
|
|
29
|
+
private _summaries: Map<string, Summary<string>> = new Map();
|
|
30
|
+
private _prefix: string;
|
|
31
|
+
|
|
32
|
+
private _metricNames: MetricNames = {
|
|
33
|
+
errors: "errors",
|
|
34
|
+
alerts: "alerts",
|
|
35
|
+
mutate: "Mutate",
|
|
36
|
+
validate: "Validate",
|
|
37
|
+
};
|
|
15
38
|
|
|
16
39
|
/**
|
|
17
40
|
* Creates a MetricsCollector instance with prefixed metrics.
|
|
18
41
|
* @param {string} [prefix='pepr'] - The prefix for the metric names.
|
|
19
42
|
*/
|
|
20
43
|
constructor(prefix = "pepr") {
|
|
21
|
-
this._registry = new
|
|
44
|
+
this._registry = new Registry();
|
|
45
|
+
this._prefix = prefix;
|
|
46
|
+
this.addCounter(this._metricNames.errors, "Mutation/Validate errors encountered");
|
|
47
|
+
this.addCounter(this._metricNames.alerts, "Mutation/Validate bad api token received");
|
|
48
|
+
this.addSummary(this._metricNames.mutate, "Mutation operation summary");
|
|
49
|
+
this.addSummary(this._metricNames.validate, "Validation operation summary");
|
|
50
|
+
}
|
|
51
|
+
private getMetricName(name: string) {
|
|
52
|
+
return `${this._prefix}_${name}`;
|
|
53
|
+
}
|
|
22
54
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
55
|
+
private addMetric<T extends Counter<string> | Summary<string>>(
|
|
56
|
+
collection: Map<string, T>,
|
|
57
|
+
MetricType: new (args: MetricArgs) => T,
|
|
58
|
+
name: string,
|
|
59
|
+
help: string,
|
|
60
|
+
) {
|
|
61
|
+
if (collection.has(this.getMetricName(name))) {
|
|
62
|
+
Log.debug(`Metric for ${name} already exists`, loggingPrefix);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const metric = new MetricType({
|
|
66
|
+
name: this.getMetricName(name),
|
|
67
|
+
help,
|
|
26
68
|
registers: [this._registry],
|
|
27
69
|
});
|
|
70
|
+
collection.set(this.getMetricName(name), metric);
|
|
71
|
+
}
|
|
28
72
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
registers: [this._registry],
|
|
33
|
-
});
|
|
73
|
+
addCounter(name: string, help: string) {
|
|
74
|
+
this.addMetric(this._counters, promClient.Counter, name, help);
|
|
75
|
+
}
|
|
34
76
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
77
|
+
addSummary(name: string, help: string) {
|
|
78
|
+
this.addMetric(this._summaries, promClient.Summary, name, help);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
incCounter(name: string) {
|
|
82
|
+
this._counters.get(this.getMetricName(name))?.inc();
|
|
40
83
|
}
|
|
41
84
|
|
|
42
85
|
/**
|
|
43
86
|
* Increments the error counter.
|
|
44
87
|
*/
|
|
45
88
|
error() {
|
|
46
|
-
this.
|
|
89
|
+
this.incCounter(this._metricNames.errors);
|
|
47
90
|
}
|
|
48
91
|
|
|
49
92
|
/**
|
|
50
93
|
* Increments the alerts counter.
|
|
51
94
|
*/
|
|
52
95
|
alert() {
|
|
53
|
-
this.
|
|
96
|
+
this.incCounter(this._metricNames.alerts);
|
|
54
97
|
}
|
|
55
98
|
|
|
56
99
|
/**
|
|
@@ -64,9 +107,10 @@ export class MetricsCollector {
|
|
|
64
107
|
/**
|
|
65
108
|
* Observes the duration since the provided start time and updates the summary.
|
|
66
109
|
* @param {number} startTime - The start time.
|
|
110
|
+
* @param {string} name - The metrics summary to increment.
|
|
67
111
|
*/
|
|
68
|
-
observeEnd(startTime: number) {
|
|
69
|
-
this.
|
|
112
|
+
observeEnd(startTime: number, name: string = this._metricNames.mutate) {
|
|
113
|
+
this._summaries.get(this.getMetricName(name))?.observe(performance.now() - startTime);
|
|
70
114
|
}
|
|
71
115
|
|
|
72
116
|
/**
|
package/src/lib/module.ts
CHANGED
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { clone } from "ramda";
|
|
5
5
|
|
|
6
6
|
import { Capability } from "./capability";
|
|
7
7
|
import { Controller } from "./controller";
|
|
8
|
-
import { Request,
|
|
8
|
+
import { MutateResponse, Request, ValidateResponse } from "./k8s/types";
|
|
9
9
|
import { ModuleConfig } from "./types";
|
|
10
10
|
|
|
11
|
-
const alwaysIgnore = {
|
|
12
|
-
namespaces: ["kube-system", "pepr-system"],
|
|
13
|
-
labels: [{ "pepr.dev": "ignore" }],
|
|
14
|
-
};
|
|
15
|
-
|
|
16
11
|
export type PackageJSON = {
|
|
17
12
|
description: string;
|
|
18
13
|
pepr: ModuleConfig;
|
|
@@ -25,7 +20,7 @@ export type PeprModuleOptions = {
|
|
|
25
20
|
beforeHook?: (req: Request) => void;
|
|
26
21
|
|
|
27
22
|
/** A user-defined callback to post-process or intercept a Pepr response just before it is returned to K8s */
|
|
28
|
-
afterHook?: (res:
|
|
23
|
+
afterHook?: (res: MutateResponse | ValidateResponse) => void;
|
|
29
24
|
};
|
|
30
25
|
|
|
31
26
|
export class PeprModule {
|
|
@@ -39,12 +34,19 @@ export class PeprModule {
|
|
|
39
34
|
* @param _deferStart (optional) If set to `true`, the Pepr runtime will not be started automatically. This can be used to start the Pepr runtime manually with `start()`.
|
|
40
35
|
*/
|
|
41
36
|
constructor({ description, pepr }: PackageJSON, capabilities: Capability[] = [], opts: PeprModuleOptions = {}) {
|
|
42
|
-
const config: ModuleConfig =
|
|
37
|
+
const config: ModuleConfig = clone(pepr);
|
|
43
38
|
config.description = description;
|
|
44
39
|
|
|
40
|
+
// Need to validate at runtime since TS gets sad about parsing the package.json
|
|
41
|
+
const validOnErrors = ["ignore", "warn", "fail"];
|
|
42
|
+
if (!validOnErrors.includes(config.onError || "")) {
|
|
43
|
+
throw new Error(`Invalid onErrors value: ${config.onError}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
45
46
|
// Handle build mode
|
|
46
|
-
if (process.env.PEPR_MODE === "build") {
|
|
47
|
-
|
|
47
|
+
if (process.env.PEPR_MODE === "build" && process.send) {
|
|
48
|
+
// Send capability map to parent process
|
|
49
|
+
process.send(capabilities);
|
|
48
50
|
return;
|
|
49
51
|
}
|
|
50
52
|
|
|
@@ -5,28 +5,28 @@ import jsonPatch from "fast-json-patch";
|
|
|
5
5
|
|
|
6
6
|
import { Capability } from "./capability";
|
|
7
7
|
import { shouldSkipRequest } from "./filter";
|
|
8
|
-
import {
|
|
8
|
+
import { MutateResponse, Request } from "./k8s/types";
|
|
9
9
|
import { Secret } from "./k8s/upstream";
|
|
10
10
|
import Log from "./logger";
|
|
11
|
-
import {
|
|
11
|
+
import { PeprMutateRequest } from "./mutate-request";
|
|
12
12
|
import { ModuleConfig } from "./types";
|
|
13
|
-
import { convertFromBase64Map, convertToBase64Map } from "./utils";
|
|
13
|
+
import { base64Encode, convertFromBase64Map, convertToBase64Map } from "./utils";
|
|
14
14
|
|
|
15
|
-
export async function
|
|
15
|
+
export async function mutateProcessor(
|
|
16
16
|
config: ModuleConfig,
|
|
17
17
|
capabilities: Capability[],
|
|
18
18
|
req: Request,
|
|
19
|
-
|
|
20
|
-
): Promise<
|
|
21
|
-
const wrapped = new
|
|
22
|
-
const response:
|
|
19
|
+
reqMetadata: Record<string, string>,
|
|
20
|
+
): Promise<MutateResponse> {
|
|
21
|
+
const wrapped = new PeprMutateRequest(req);
|
|
22
|
+
const response: MutateResponse = {
|
|
23
23
|
uid: req.uid,
|
|
24
24
|
warnings: [],
|
|
25
25
|
allowed: false,
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
// Track whether any capability matched the request
|
|
29
|
-
let
|
|
29
|
+
let matchedAction = false;
|
|
30
30
|
|
|
31
31
|
// Track data fields that should be skipped during decoding
|
|
32
32
|
let skipDecode: string[] = [];
|
|
@@ -37,21 +37,26 @@ export async function processor(
|
|
|
37
37
|
skipDecode = convertFromBase64Map(wrapped.Raw as unknown as Secret);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
Log.info(`Processing request
|
|
40
|
+
Log.info(reqMetadata, `Processing request`);
|
|
41
41
|
|
|
42
42
|
for (const { name, bindings } of capabilities) {
|
|
43
|
-
const
|
|
43
|
+
const actionMetadata = { ...reqMetadata, name };
|
|
44
44
|
|
|
45
45
|
for (const action of bindings) {
|
|
46
|
+
// Skip this action if it's not a mutate action
|
|
47
|
+
if (!action.mutateCallback) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
46
51
|
// Continue to the next action without doing anything if this one should be skipped
|
|
47
52
|
if (shouldSkipRequest(action, req)) {
|
|
48
53
|
continue;
|
|
49
54
|
}
|
|
50
55
|
|
|
51
|
-
const label = action.
|
|
52
|
-
Log.info(`Processing matched action ${label}
|
|
56
|
+
const label = action.mutateCallback.name;
|
|
57
|
+
Log.info(actionMetadata, `Processing matched action ${label}`);
|
|
53
58
|
|
|
54
|
-
|
|
59
|
+
matchedAction = true;
|
|
55
60
|
|
|
56
61
|
// Add annotations to the request to indicate that the capability started processing
|
|
57
62
|
// this will allow tracking of failed mutations that were permitted to continue
|
|
@@ -71,25 +76,29 @@ export async function processor(
|
|
|
71
76
|
|
|
72
77
|
try {
|
|
73
78
|
// Run the action
|
|
74
|
-
await action.
|
|
79
|
+
await action.mutateCallback(wrapped);
|
|
75
80
|
|
|
76
|
-
Log.info(`Action succeeded
|
|
81
|
+
Log.info(actionMetadata, `Action succeeded`);
|
|
77
82
|
|
|
78
83
|
// Add annotations to the request to indicate that the capability succeeded
|
|
79
84
|
updateStatus("succeeded");
|
|
80
85
|
} catch (e) {
|
|
86
|
+
Log.warn(actionMetadata, `Action failed: ${e}`);
|
|
87
|
+
updateStatus("warning");
|
|
88
|
+
|
|
81
89
|
// Annoying ts false positive
|
|
82
90
|
response.warnings = response.warnings || [];
|
|
83
91
|
response.warnings.push(`Action failed: ${e}`);
|
|
84
92
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
switch (config.onError) {
|
|
94
|
+
case "reject":
|
|
95
|
+
Log.error(actionMetadata, `Action failed: ${e}`);
|
|
96
|
+
response.result = "Pepr module configured to reject on error";
|
|
97
|
+
return response;
|
|
98
|
+
|
|
99
|
+
case "audit":
|
|
100
|
+
// @todo: implement audit logging
|
|
101
|
+
break;
|
|
93
102
|
}
|
|
94
103
|
}
|
|
95
104
|
}
|
|
@@ -99,8 +108,8 @@ export async function processor(
|
|
|
99
108
|
response.allowed = true;
|
|
100
109
|
|
|
101
110
|
// If no capability matched the request, exit early
|
|
102
|
-
if (!
|
|
103
|
-
Log.info(`No matching
|
|
111
|
+
if (!matchedAction) {
|
|
112
|
+
Log.info(reqMetadata, `No matching actions found`);
|
|
104
113
|
return response;
|
|
105
114
|
}
|
|
106
115
|
|
|
@@ -124,7 +133,7 @@ export async function processor(
|
|
|
124
133
|
response.patchType = "JSONPatch";
|
|
125
134
|
// Webhook must be base64-encoded
|
|
126
135
|
// https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#response
|
|
127
|
-
response.patch =
|
|
136
|
+
response.patch = base64Encode(JSON.stringify(patches));
|
|
128
137
|
}
|
|
129
138
|
|
|
130
139
|
// Remove the warnings array if it's empty
|
|
@@ -132,7 +141,7 @@ export async function processor(
|
|
|
132
141
|
delete response.warnings;
|
|
133
142
|
}
|
|
134
143
|
|
|
135
|
-
Log.debug(patches,
|
|
144
|
+
Log.debug({ ...reqMetadata, patches }, `Patches generated`);
|
|
136
145
|
|
|
137
146
|
return response;
|
|
138
147
|
}
|
|
@@ -10,7 +10,7 @@ import { DeepPartial } from "./types";
|
|
|
10
10
|
* The RequestWrapper class provides methods to modify Kubernetes objects in the context
|
|
11
11
|
* of a mutating webhook request.
|
|
12
12
|
*/
|
|
13
|
-
export class
|
|
13
|
+
export class PeprMutateRequest<T extends KubernetesObject> {
|
|
14
14
|
public Raw: T;
|
|
15
15
|
|
|
16
16
|
get PermitSideEffects() {
|
|
@@ -42,7 +42,7 @@ export class PeprRequest<T extends KubernetesObject> {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
|
-
* Creates a new instance of the
|
|
45
|
+
* Creates a new instance of the action class.
|
|
46
46
|
* @param input - The request object containing the Kubernetes resource to modify.
|
|
47
47
|
*/
|
|
48
48
|
constructor(private _input: Request<T>) {
|
|
@@ -72,7 +72,7 @@ export class PeprRequest<T extends KubernetesObject> {
|
|
|
72
72
|
* Updates a label on the Kubernetes resource.
|
|
73
73
|
* @param key - The key of the label to update.
|
|
74
74
|
* @param value - The value of the label.
|
|
75
|
-
* @returns The current
|
|
75
|
+
* @returns The current action instance for method chaining.
|
|
76
76
|
*/
|
|
77
77
|
SetLabel(key: string, value: string) {
|
|
78
78
|
const ref = this.Raw;
|
|
@@ -88,7 +88,7 @@ export class PeprRequest<T extends KubernetesObject> {
|
|
|
88
88
|
* Updates an annotation on the Kubernetes resource.
|
|
89
89
|
* @param key - The key of the annotation to update.
|
|
90
90
|
* @param value - The value of the annotation.
|
|
91
|
-
* @returns The current
|
|
91
|
+
* @returns The current action instance for method chaining.
|
|
92
92
|
*/
|
|
93
93
|
SetAnnotation(key: string, value: string) {
|
|
94
94
|
const ref = this.Raw;
|