pepr 0.12.2 → 0.13.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/CODE_OF_CONDUCT.md +83 -0
- package/CONTRIBUTING.md +70 -0
- package/README.md +28 -30
- package/dist/cli.js +666 -692
- 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 +17 -0
- package/dist/lib/assets/index.d.ts.map +1 -0
- package/dist/lib/assets/loader.d.ts +8 -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 +4 -9
- package/dist/lib/capability.d.ts.map +1 -1
- package/dist/lib/controller.d.ts +4 -15
- package/dist/lib/controller.d.ts.map +1 -1
- package/dist/lib/errors.d.ts +12 -0
- package/dist/lib/errors.d.ts.map +1 -0
- 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 +18 -14
- 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 +10 -9
- package/dist/lib/metrics.d.ts.map +1 -1
- package/dist/lib/module.d.ts +4 -4
- 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} +7 -7
- package/dist/lib/mutate-request.d.ts.map +1 -0
- package/dist/lib/types.d.ts +48 -55
- 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 +610 -354
- package/dist/lib.js.map +4 -4
- package/jest.config.json +4 -0
- package/journey/before.ts +21 -0
- package/journey/k8s.ts +81 -0
- package/journey/pepr-build.ts +69 -0
- package/journey/pepr-deploy.ts +133 -0
- package/journey/pepr-dev.ts +155 -0
- package/journey/pepr-format.ts +13 -0
- package/journey/pepr-init.ts +12 -0
- package/package.json +29 -27
- package/src/cli.ts +2 -11
- package/src/lib/assets/deploy.ts +179 -0
- package/src/lib/assets/index.ts +53 -0
- package/src/lib/assets/loader.ts +41 -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 +80 -68
- package/src/lib/controller.ts +199 -99
- package/src/lib/errors.ts +20 -0
- 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 +21 -15
- package/src/lib/k8s/upstream.ts +5 -1
- package/src/lib/logger.ts +14 -125
- package/src/lib/metrics.ts +86 -29
- package/src/lib/module.ts +32 -16
- package/src/lib/{processor.ts → mutate-processor.ts} +39 -28
- package/src/lib/{request.ts → mutate-request.ts} +26 -13
- package/src/lib/types.ts +54 -60
- package/src/lib/validate-processor.ts +76 -0
- package/src/lib/validate-request.ts +106 -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/kinds.ts
CHANGED
|
@@ -5,6 +5,46 @@ import { GenericClass } from "../types";
|
|
|
5
5
|
import { GroupVersionKind } from "./types";
|
|
6
6
|
|
|
7
7
|
export const gvkMap: Record<string, GroupVersionKind> = {
|
|
8
|
+
/**
|
|
9
|
+
* Represents a K8s ClusterRole resource.
|
|
10
|
+
* ClusterRole is a set of permissions that can be bound to a user or group in a cluster-wide scope.
|
|
11
|
+
* @see {@link https://kubernetes.io/docs/reference/access-authn-authz/rbac/#role-and-clusterrole}
|
|
12
|
+
*/
|
|
13
|
+
V1ClusterRole: {
|
|
14
|
+
kind: "ClusterRole",
|
|
15
|
+
version: "v1",
|
|
16
|
+
group: "rbac.authorization.k8s.io",
|
|
17
|
+
},
|
|
18
|
+
/**
|
|
19
|
+
* Represents a K8s ClusterRoleBinding resource.
|
|
20
|
+
* ClusterRoleBinding binds a ClusterRole to a user or group in a cluster-wide scope.
|
|
21
|
+
* @see {@link https://kubernetes.io/docs/reference/access-authn-authz/rbac/#rolebinding-and-clusterrolebinding}
|
|
22
|
+
*/
|
|
23
|
+
V1ClusterRoleBinding: {
|
|
24
|
+
kind: "ClusterRoleBinding",
|
|
25
|
+
version: "v1",
|
|
26
|
+
group: "rbac.authorization.k8s.io",
|
|
27
|
+
},
|
|
28
|
+
/**
|
|
29
|
+
* Represents a K8s Role resource.
|
|
30
|
+
* Role is a set of permissions that can be bound to a user or group in a namespace scope.
|
|
31
|
+
* @see {@link https://kubernetes.io/docs/reference/access-authn-authz/rbac/#role-and-clusterrole}
|
|
32
|
+
*/
|
|
33
|
+
V1Role: {
|
|
34
|
+
kind: "Role",
|
|
35
|
+
version: "v1",
|
|
36
|
+
group: "rbac.authorization.k8s.io",
|
|
37
|
+
},
|
|
38
|
+
/**
|
|
39
|
+
* Represents a K8s RoleBinding resource.
|
|
40
|
+
* RoleBinding binds a Role to a user or group in a namespace scope.
|
|
41
|
+
* @see {@link https://kubernetes.io/docs/reference/access-authn-authz/rbac/#rolebinding-and-clusterrolebinding}
|
|
42
|
+
*/
|
|
43
|
+
V1RoleBinding: {
|
|
44
|
+
kind: "RoleBinding",
|
|
45
|
+
version: "v1",
|
|
46
|
+
group: "rbac.authorization.k8s.io",
|
|
47
|
+
},
|
|
8
48
|
/**
|
|
9
49
|
* Represents a K8s ConfigMap resource.
|
|
10
50
|
* ConfigMap holds configuration data for pods to consume.
|
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
|
|
|
@@ -154,7 +144,11 @@ export interface Response {
|
|
|
154
144
|
/** The type of Patch. Currently we only allow "JSONPatch". */
|
|
155
145
|
patchType?: "JSONPatch";
|
|
156
146
|
|
|
157
|
-
/**
|
|
147
|
+
/**
|
|
148
|
+
* AuditAnnotations is an unstructured key value map set by remote admission controller (e.g. error=image-blacklisted).
|
|
149
|
+
*
|
|
150
|
+
* See https://kubernetes.io/docs/reference/labels-annotations-taints/audit-annotations/ for more information
|
|
151
|
+
*/
|
|
158
152
|
auditAnnotations?: {
|
|
159
153
|
[key: string]: string;
|
|
160
154
|
};
|
|
@@ -163,6 +157,18 @@ export interface Response {
|
|
|
163
157
|
warnings?: string[];
|
|
164
158
|
}
|
|
165
159
|
|
|
160
|
+
export interface ValidateResponse extends MutateResponse {
|
|
161
|
+
/** Status contains extra details into why an admission request was denied. This field IS NOT consulted in any way if "Allowed" is "true". */
|
|
162
|
+
status?: {
|
|
163
|
+
/** A machine-readable description of why this operation is in the
|
|
164
|
+
"Failure" status. If this value is empty there is no information available. */
|
|
165
|
+
code: number;
|
|
166
|
+
|
|
167
|
+
/** A human-readable description of the status of this operation. */
|
|
168
|
+
message: string;
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
166
172
|
export type WebhookIgnore = {
|
|
167
173
|
/**
|
|
168
174
|
* 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,61 +1,117 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/* eslint-disable class-methods-use-this */
|
|
5
|
+
|
|
5
6
|
import { performance } from "perf_hooks";
|
|
7
|
+
import promClient, { Counter, Registry, Summary } from "prom-client";
|
|
8
|
+
import Log from "./logger";
|
|
9
|
+
|
|
10
|
+
const loggingPrefix = "MetricsCollector";
|
|
11
|
+
|
|
12
|
+
interface MetricNames {
|
|
13
|
+
errors: string;
|
|
14
|
+
alerts: string;
|
|
15
|
+
mutate: string;
|
|
16
|
+
validate: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface MetricArgs {
|
|
20
|
+
name: string;
|
|
21
|
+
help: string;
|
|
22
|
+
registers: Registry[];
|
|
23
|
+
}
|
|
6
24
|
|
|
7
25
|
/**
|
|
8
26
|
* MetricsCollector class handles metrics collection using prom-client and performance hooks.
|
|
9
27
|
*/
|
|
10
28
|
export class MetricsCollector {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
29
|
+
#registry: Registry;
|
|
30
|
+
#counters: Map<string, Counter<string>> = new Map();
|
|
31
|
+
#summaries: Map<string, Summary<string>> = new Map();
|
|
32
|
+
#prefix: string;
|
|
33
|
+
|
|
34
|
+
#metricNames: MetricNames = {
|
|
35
|
+
errors: "errors",
|
|
36
|
+
alerts: "alerts",
|
|
37
|
+
mutate: "Mutate",
|
|
38
|
+
validate: "Validate",
|
|
39
|
+
};
|
|
15
40
|
|
|
16
41
|
/**
|
|
17
42
|
* Creates a MetricsCollector instance with prefixed metrics.
|
|
18
|
-
* @param
|
|
43
|
+
* @param [prefix='pepr'] - The prefix for the metric names.
|
|
19
44
|
*/
|
|
20
45
|
constructor(prefix = "pepr") {
|
|
21
|
-
this
|
|
46
|
+
this.#registry = new Registry();
|
|
47
|
+
this.#prefix = prefix;
|
|
48
|
+
this.addCounter(this.#metricNames.errors, "Mutation/Validate errors encountered");
|
|
49
|
+
this.addCounter(this.#metricNames.alerts, "Mutation/Validate bad api token received");
|
|
50
|
+
this.addSummary(this.#metricNames.mutate, "Mutation operation summary");
|
|
51
|
+
this.addSummary(this.#metricNames.validate, "Validation operation summary");
|
|
52
|
+
}
|
|
22
53
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
registers: [this._registry],
|
|
27
|
-
});
|
|
54
|
+
#getMetricName(name: string) {
|
|
55
|
+
return `${this.#prefix}_${name}`;
|
|
56
|
+
}
|
|
28
57
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
58
|
+
#addMetric<T extends Counter<string> | Summary<string>>(
|
|
59
|
+
collection: Map<string, T>,
|
|
60
|
+
MetricType: new (args: MetricArgs) => T,
|
|
61
|
+
name: string,
|
|
62
|
+
help: string,
|
|
63
|
+
) {
|
|
64
|
+
if (collection.has(this.#getMetricName(name))) {
|
|
65
|
+
Log.debug(`Metric for ${name} already exists`, loggingPrefix);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Bind public methods
|
|
70
|
+
this.incCounter = this.incCounter.bind(this);
|
|
71
|
+
this.error = this.error.bind(this);
|
|
72
|
+
this.alert = this.alert.bind(this);
|
|
73
|
+
this.observeStart = this.observeStart.bind(this);
|
|
74
|
+
this.observeEnd = this.observeEnd.bind(this);
|
|
75
|
+
this.getMetrics = this.getMetrics.bind(this);
|
|
34
76
|
|
|
35
|
-
|
|
36
|
-
name:
|
|
37
|
-
help
|
|
38
|
-
registers: [this
|
|
77
|
+
const metric = new MetricType({
|
|
78
|
+
name: this.#getMetricName(name),
|
|
79
|
+
help,
|
|
80
|
+
registers: [this.#registry],
|
|
39
81
|
});
|
|
82
|
+
|
|
83
|
+
collection.set(this.#getMetricName(name), metric);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
addCounter(name: string, help: string) {
|
|
87
|
+
this.#addMetric(this.#counters, promClient.Counter, name, help);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
addSummary(name: string, help: string) {
|
|
91
|
+
this.#addMetric(this.#summaries, promClient.Summary, name, help);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
incCounter(name: string) {
|
|
95
|
+
this.#counters.get(this.#getMetricName(name))?.inc();
|
|
40
96
|
}
|
|
41
97
|
|
|
42
98
|
/**
|
|
43
99
|
* Increments the error counter.
|
|
44
100
|
*/
|
|
45
101
|
error() {
|
|
46
|
-
this.
|
|
102
|
+
this.incCounter(this.#metricNames.errors);
|
|
47
103
|
}
|
|
48
104
|
|
|
49
105
|
/**
|
|
50
106
|
* Increments the alerts counter.
|
|
51
107
|
*/
|
|
52
108
|
alert() {
|
|
53
|
-
this.
|
|
109
|
+
this.incCounter(this.#metricNames.alerts);
|
|
54
110
|
}
|
|
55
111
|
|
|
56
112
|
/**
|
|
57
113
|
* Returns the current timestamp from performance.now() method. Useful for start timing an operation.
|
|
58
|
-
* @returns
|
|
114
|
+
* @returns The timestamp.
|
|
59
115
|
*/
|
|
60
116
|
observeStart() {
|
|
61
117
|
return performance.now();
|
|
@@ -63,17 +119,18 @@ export class MetricsCollector {
|
|
|
63
119
|
|
|
64
120
|
/**
|
|
65
121
|
* Observes the duration since the provided start time and updates the summary.
|
|
66
|
-
* @param
|
|
122
|
+
* @param startTime - The start time.
|
|
123
|
+
* @param name - The metrics summary to increment.
|
|
67
124
|
*/
|
|
68
|
-
observeEnd(startTime: number) {
|
|
69
|
-
this.
|
|
125
|
+
observeEnd(startTime: number, name: string = this.#metricNames.mutate) {
|
|
126
|
+
this.#summaries.get(this.#getMetricName(name))?.observe(performance.now() - startTime);
|
|
70
127
|
}
|
|
71
128
|
|
|
72
129
|
/**
|
|
73
130
|
* Fetches the current metrics from the registry.
|
|
74
|
-
* @returns
|
|
131
|
+
* @returns The metrics.
|
|
75
132
|
*/
|
|
76
133
|
async getMetrics() {
|
|
77
|
-
return this.
|
|
134
|
+
return this.#registry.metrics();
|
|
78
135
|
}
|
|
79
136
|
}
|
package/src/lib/module.ts
CHANGED
|
@@ -1,17 +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 {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
const alwaysIgnore = {
|
|
12
|
-
namespaces: ["kube-system", "pepr-system"],
|
|
13
|
-
labels: [{ "pepr.dev": "ignore" }],
|
|
14
|
-
};
|
|
8
|
+
import { ValidateError } from "./errors";
|
|
9
|
+
import { MutateResponse, Request, ValidateResponse } from "./k8s/types";
|
|
10
|
+
import { CapabilityExport, ModuleConfig } from "./types";
|
|
15
11
|
|
|
16
12
|
export type PackageJSON = {
|
|
17
13
|
description: string;
|
|
@@ -25,30 +21,50 @@ export type PeprModuleOptions = {
|
|
|
25
21
|
beforeHook?: (req: Request) => void;
|
|
26
22
|
|
|
27
23
|
/** A user-defined callback to post-process or intercept a Pepr response just before it is returned to K8s */
|
|
28
|
-
afterHook?: (res:
|
|
24
|
+
afterHook?: (res: MutateResponse | ValidateResponse) => void;
|
|
29
25
|
};
|
|
30
26
|
|
|
31
27
|
export class PeprModule {
|
|
32
|
-
|
|
28
|
+
#controller!: Controller;
|
|
33
29
|
|
|
34
30
|
/**
|
|
35
31
|
* Create a new Pepr runtime
|
|
36
32
|
*
|
|
37
33
|
* @param config The configuration for the Pepr runtime
|
|
38
34
|
* @param capabilities The capabilities to be loaded into the Pepr runtime
|
|
39
|
-
* @param
|
|
35
|
+
* @param opts Options for the Pepr runtime
|
|
40
36
|
*/
|
|
41
37
|
constructor({ description, pepr }: PackageJSON, capabilities: Capability[] = [], opts: PeprModuleOptions = {}) {
|
|
42
|
-
const config: ModuleConfig =
|
|
38
|
+
const config: ModuleConfig = clone(pepr);
|
|
43
39
|
config.description = description;
|
|
44
40
|
|
|
41
|
+
// Need to validate at runtime since TS gets sad about parsing the package.json
|
|
42
|
+
ValidateError(config.onError);
|
|
43
|
+
|
|
44
|
+
// Bind public methods
|
|
45
|
+
this.start = this.start.bind(this);
|
|
46
|
+
|
|
45
47
|
// Handle build mode
|
|
46
|
-
if (process.env.PEPR_MODE === "build") {
|
|
47
|
-
|
|
48
|
+
if (process.env.PEPR_MODE === "build" && process.send) {
|
|
49
|
+
const exportedCapabilities: CapabilityExport[] = [];
|
|
50
|
+
|
|
51
|
+
// Send capability map to parent process
|
|
52
|
+
for (const capability of capabilities) {
|
|
53
|
+
// Convert the capability to a capability config
|
|
54
|
+
exportedCapabilities.push({
|
|
55
|
+
name: capability.name,
|
|
56
|
+
description: capability.description,
|
|
57
|
+
namespaces: capability.namespaces,
|
|
58
|
+
bindings: capability.bindings,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
process.send(exportedCapabilities);
|
|
63
|
+
|
|
48
64
|
return;
|
|
49
65
|
}
|
|
50
66
|
|
|
51
|
-
this
|
|
67
|
+
this.#controller = new Controller(config, capabilities, opts.beforeHook, opts.afterHook);
|
|
52
68
|
|
|
53
69
|
// Stop processing if deferStart is set to true
|
|
54
70
|
if (opts.deferStart) {
|
|
@@ -65,6 +81,6 @@ export class PeprModule {
|
|
|
65
81
|
* @param port
|
|
66
82
|
*/
|
|
67
83
|
start(port = 3000) {
|
|
68
|
-
this.
|
|
84
|
+
this.#controller.startServer(port);
|
|
69
85
|
}
|
|
70
86
|
}
|
|
@@ -4,29 +4,30 @@
|
|
|
4
4
|
import jsonPatch from "fast-json-patch";
|
|
5
5
|
|
|
6
6
|
import { Capability } from "./capability";
|
|
7
|
+
import { Errors } from "./errors";
|
|
7
8
|
import { shouldSkipRequest } from "./filter";
|
|
8
|
-
import {
|
|
9
|
+
import { MutateResponse, Request } from "./k8s/types";
|
|
9
10
|
import { Secret } from "./k8s/upstream";
|
|
10
11
|
import Log from "./logger";
|
|
11
|
-
import {
|
|
12
|
+
import { PeprMutateRequest } from "./mutate-request";
|
|
12
13
|
import { ModuleConfig } from "./types";
|
|
13
|
-
import { convertFromBase64Map, convertToBase64Map } from "./utils";
|
|
14
|
+
import { base64Encode, convertFromBase64Map, convertToBase64Map } from "./utils";
|
|
14
15
|
|
|
15
|
-
export async function
|
|
16
|
+
export async function mutateProcessor(
|
|
16
17
|
config: ModuleConfig,
|
|
17
18
|
capabilities: Capability[],
|
|
18
19
|
req: Request,
|
|
19
|
-
|
|
20
|
-
): Promise<
|
|
21
|
-
const wrapped = new
|
|
22
|
-
const response:
|
|
20
|
+
reqMetadata: Record<string, string>,
|
|
21
|
+
): Promise<MutateResponse> {
|
|
22
|
+
const wrapped = new PeprMutateRequest(req);
|
|
23
|
+
const response: MutateResponse = {
|
|
23
24
|
uid: req.uid,
|
|
24
25
|
warnings: [],
|
|
25
26
|
allowed: false,
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
// Track whether any capability matched the request
|
|
29
|
-
let
|
|
30
|
+
let matchedAction = false;
|
|
30
31
|
|
|
31
32
|
// Track data fields that should be skipped during decoding
|
|
32
33
|
let skipDecode: string[] = [];
|
|
@@ -37,21 +38,26 @@ export async function processor(
|
|
|
37
38
|
skipDecode = convertFromBase64Map(wrapped.Raw as unknown as Secret);
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
Log.info(`Processing request
|
|
41
|
+
Log.info(reqMetadata, `Processing request`);
|
|
41
42
|
|
|
42
43
|
for (const { name, bindings } of capabilities) {
|
|
43
|
-
const
|
|
44
|
+
const actionMetadata = { ...reqMetadata, name };
|
|
44
45
|
|
|
45
46
|
for (const action of bindings) {
|
|
47
|
+
// Skip this action if it's not a mutate action
|
|
48
|
+
if (!action.mutateCallback) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
46
52
|
// Continue to the next action without doing anything if this one should be skipped
|
|
47
53
|
if (shouldSkipRequest(action, req)) {
|
|
48
54
|
continue;
|
|
49
55
|
}
|
|
50
56
|
|
|
51
|
-
const label = action.
|
|
52
|
-
Log.info(`Processing matched action ${label}
|
|
57
|
+
const label = action.mutateCallback.name;
|
|
58
|
+
Log.info(actionMetadata, `Processing matched action ${label}`);
|
|
53
59
|
|
|
54
|
-
|
|
60
|
+
matchedAction = true;
|
|
55
61
|
|
|
56
62
|
// Add annotations to the request to indicate that the capability started processing
|
|
57
63
|
// this will allow tracking of failed mutations that were permitted to continue
|
|
@@ -71,25 +77,30 @@ export async function processor(
|
|
|
71
77
|
|
|
72
78
|
try {
|
|
73
79
|
// Run the action
|
|
74
|
-
await action.
|
|
80
|
+
await action.mutateCallback(wrapped);
|
|
75
81
|
|
|
76
|
-
Log.info(`Action succeeded
|
|
82
|
+
Log.info(actionMetadata, `Action succeeded`);
|
|
77
83
|
|
|
78
84
|
// Add annotations to the request to indicate that the capability succeeded
|
|
79
85
|
updateStatus("succeeded");
|
|
80
86
|
} catch (e) {
|
|
87
|
+
Log.warn(actionMetadata, `Action failed: ${e}`);
|
|
88
|
+
updateStatus("warning");
|
|
89
|
+
|
|
81
90
|
// Annoying ts false positive
|
|
82
91
|
response.warnings = response.warnings || [];
|
|
83
92
|
response.warnings.push(`Action failed: ${e}`);
|
|
84
93
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
switch (config.onError) {
|
|
95
|
+
case Errors.reject:
|
|
96
|
+
Log.error(actionMetadata, `Action failed: ${e}`);
|
|
97
|
+
response.result = "Pepr module configured to reject on error";
|
|
98
|
+
return response;
|
|
99
|
+
|
|
100
|
+
case Errors.audit:
|
|
101
|
+
response.auditAnnotations = response.auditAnnotations || {};
|
|
102
|
+
response.auditAnnotations[Date.now()] = e;
|
|
103
|
+
break;
|
|
93
104
|
}
|
|
94
105
|
}
|
|
95
106
|
}
|
|
@@ -99,8 +110,8 @@ export async function processor(
|
|
|
99
110
|
response.allowed = true;
|
|
100
111
|
|
|
101
112
|
// If no capability matched the request, exit early
|
|
102
|
-
if (!
|
|
103
|
-
Log.info(`No matching
|
|
113
|
+
if (!matchedAction) {
|
|
114
|
+
Log.info(reqMetadata, `No matching actions found`);
|
|
104
115
|
return response;
|
|
105
116
|
}
|
|
106
117
|
|
|
@@ -124,7 +135,7 @@ export async function processor(
|
|
|
124
135
|
response.patchType = "JSONPatch";
|
|
125
136
|
// Webhook must be base64-encoded
|
|
126
137
|
// https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#response
|
|
127
|
-
response.patch =
|
|
138
|
+
response.patch = base64Encode(JSON.stringify(patches));
|
|
128
139
|
}
|
|
129
140
|
|
|
130
141
|
// Remove the warnings array if it's empty
|
|
@@ -132,7 +143,7 @@ export async function processor(
|
|
|
132
143
|
delete response.warnings;
|
|
133
144
|
}
|
|
134
145
|
|
|
135
|
-
Log.debug(patches,
|
|
146
|
+
Log.debug({ ...reqMetadata, patches }, `Patches generated`);
|
|
136
147
|
|
|
137
148
|
return response;
|
|
138
149
|
}
|