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/capability.ts
CHANGED
|
@@ -1,62 +1,60 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { pickBy } from "ramda";
|
|
5
|
+
|
|
6
|
+
import { isWatchMode, modelToGroupVersionKind } from "./k8s/index";
|
|
5
7
|
import { GroupVersionKind } from "./k8s/types";
|
|
6
|
-
import
|
|
8
|
+
import Log from "./logger";
|
|
7
9
|
import {
|
|
8
|
-
BindToAction,
|
|
9
10
|
Binding,
|
|
10
11
|
BindingFilter,
|
|
11
12
|
BindingWithName,
|
|
12
|
-
CapabilityAction,
|
|
13
13
|
CapabilityCfg,
|
|
14
|
-
|
|
14
|
+
CapabilityExport,
|
|
15
15
|
Event,
|
|
16
16
|
GenericClass,
|
|
17
|
-
|
|
17
|
+
MutateAction,
|
|
18
|
+
MutateActionChain,
|
|
19
|
+
ValidateAction,
|
|
18
20
|
WhenSelector,
|
|
19
21
|
} from "./types";
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
24
|
* A capability is a unit of functionality that can be registered with the Pepr runtime.
|
|
23
25
|
*/
|
|
24
|
-
export class Capability implements
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
private _bindings: Binding[] = [];
|
|
33
|
-
|
|
34
|
-
get bindings(): Binding[] {
|
|
35
|
-
return this._bindings;
|
|
26
|
+
export class Capability implements CapabilityExport {
|
|
27
|
+
#name: string;
|
|
28
|
+
#description: string;
|
|
29
|
+
#namespaces?: string[] | undefined;
|
|
30
|
+
#bindings: Binding[] = [];
|
|
31
|
+
|
|
32
|
+
get bindings() {
|
|
33
|
+
return this.#bindings;
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
get name() {
|
|
39
|
-
return this
|
|
37
|
+
return this.#name;
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
get description() {
|
|
43
|
-
return this
|
|
41
|
+
return this.#description;
|
|
44
42
|
}
|
|
45
43
|
|
|
46
44
|
get namespaces() {
|
|
47
|
-
return this
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
get mutateOrValidate() {
|
|
51
|
-
return this._mutateOrValidate;
|
|
45
|
+
return this.#namespaces || [];
|
|
52
46
|
}
|
|
53
47
|
|
|
54
48
|
constructor(cfg: CapabilityCfg) {
|
|
55
|
-
this
|
|
56
|
-
this
|
|
57
|
-
this
|
|
58
|
-
|
|
59
|
-
|
|
49
|
+
this.#name = cfg.name;
|
|
50
|
+
this.#description = cfg.description;
|
|
51
|
+
this.#namespaces = cfg.namespaces;
|
|
52
|
+
|
|
53
|
+
// Bind When() to this instance
|
|
54
|
+
this.When = this.When.bind(this);
|
|
55
|
+
|
|
56
|
+
Log.info(`Capability ${this.#name} registered`);
|
|
57
|
+
Log.debug(cfg);
|
|
60
58
|
}
|
|
61
59
|
|
|
62
60
|
/**
|
|
@@ -68,7 +66,7 @@ export class Capability implements CapabilityCfg {
|
|
|
68
66
|
* @param kind if using a custom KubernetesObject not available in `a.*`, specify the GroupVersionKind
|
|
69
67
|
* @returns
|
|
70
68
|
*/
|
|
71
|
-
When
|
|
69
|
+
When<T extends GenericClass>(model: T, kind?: GroupVersionKind): WhenSelector<T> {
|
|
72
70
|
const matchedKind = modelToGroupVersionKind(model.name);
|
|
73
71
|
|
|
74
72
|
// If the kind is not specified and the model is not a KubernetesObject, throw an error
|
|
@@ -86,69 +84,83 @@ export class Capability implements CapabilityCfg {
|
|
|
86
84
|
labels: {},
|
|
87
85
|
annotations: {},
|
|
88
86
|
},
|
|
89
|
-
callback: () => undefined,
|
|
90
87
|
};
|
|
91
88
|
|
|
92
|
-
const
|
|
89
|
+
const bindings = this.#bindings;
|
|
90
|
+
const prefix = `${this.#name}: ${model.name}`;
|
|
91
|
+
const commonChain = { WithLabel, WithAnnotation, Mutate, Validate };
|
|
92
|
+
const isNotEmpty = (value: object) => Object.keys(value).length > 0;
|
|
93
|
+
const log = (message: string, cbString: string) => {
|
|
94
|
+
const filteredObj = pickBy(isNotEmpty, binding.filters);
|
|
93
95
|
|
|
94
|
-
|
|
96
|
+
Log.info(`${message} configured for ${binding.event}`, prefix);
|
|
97
|
+
Log.info(filteredObj, prefix);
|
|
98
|
+
Log.debug(cbString, prefix);
|
|
99
|
+
};
|
|
95
100
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
function Validate(validateCallback: ValidateAction<T>): void {
|
|
102
|
+
if (!isWatchMode) {
|
|
103
|
+
log("Validate Action", validateCallback.toString());
|
|
104
|
+
|
|
105
|
+
// Push the binding to the list of bindings for this capability as a new BindingAction
|
|
106
|
+
// with the callback function to preserve
|
|
107
|
+
bindings.push({
|
|
108
|
+
...binding,
|
|
109
|
+
isValidate: true,
|
|
110
|
+
validateCallback,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
105
114
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
115
|
+
function Mutate(mutateCallback: MutateAction<T>): MutateActionChain<T> {
|
|
116
|
+
if (!isWatchMode) {
|
|
117
|
+
log("Mutate Action", mutateCallback.toString());
|
|
109
118
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
119
|
+
// Push the binding to the list of bindings for this capability as a new BindingAction
|
|
120
|
+
// with the callback function to preserve
|
|
121
|
+
bindings.push({
|
|
122
|
+
...binding,
|
|
123
|
+
isMutate: true,
|
|
124
|
+
mutateCallback,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
113
127
|
|
|
114
|
-
|
|
115
|
-
|
|
128
|
+
// Now only allow adding actions to the same binding
|
|
129
|
+
return { Validate };
|
|
130
|
+
}
|
|
116
131
|
|
|
117
132
|
function InNamespace(...namespaces: string[]): BindingWithName<T> {
|
|
118
|
-
|
|
133
|
+
Log.debug(`Add namespaces filter ${namespaces}`, prefix);
|
|
119
134
|
binding.filters.namespaces.push(...namespaces);
|
|
120
|
-
return {
|
|
135
|
+
return { ...commonChain, WithName };
|
|
121
136
|
}
|
|
122
137
|
|
|
123
138
|
function WithName(name: string): BindingFilter<T> {
|
|
124
|
-
|
|
139
|
+
Log.debug(`Add name filter ${name}`, prefix);
|
|
125
140
|
binding.filters.name = name;
|
|
126
|
-
return
|
|
141
|
+
return commonChain;
|
|
127
142
|
}
|
|
128
143
|
|
|
129
144
|
function WithLabel(key: string, value = ""): BindingFilter<T> {
|
|
130
|
-
|
|
145
|
+
Log.debug(`Add label filter ${key}=${value}`, prefix);
|
|
131
146
|
binding.filters.labels[key] = value;
|
|
132
|
-
return
|
|
147
|
+
return commonChain;
|
|
133
148
|
}
|
|
134
149
|
|
|
135
|
-
|
|
136
|
-
|
|
150
|
+
function WithAnnotation(key: string, value = ""): BindingFilter<T> {
|
|
151
|
+
Log.debug(`Add annotation filter ${key}=${value}`, prefix);
|
|
137
152
|
binding.filters.annotations[key] = value;
|
|
138
|
-
return
|
|
139
|
-
}
|
|
153
|
+
return commonChain;
|
|
154
|
+
}
|
|
140
155
|
|
|
141
|
-
|
|
156
|
+
function bindEvent(event: Event) {
|
|
142
157
|
binding.event = event;
|
|
143
158
|
return {
|
|
159
|
+
...commonChain,
|
|
144
160
|
InNamespace,
|
|
145
|
-
Then,
|
|
146
|
-
ThenSet,
|
|
147
|
-
WithAnnotation,
|
|
148
|
-
WithLabel,
|
|
149
161
|
WithName,
|
|
150
162
|
};
|
|
151
|
-
}
|
|
163
|
+
}
|
|
152
164
|
|
|
153
165
|
return {
|
|
154
166
|
IsCreatedOrUpdated: () => bindEvent(Event.CreateOrUpdate),
|
|
@@ -156,5 +168,5 @@ export class Capability implements CapabilityCfg {
|
|
|
156
168
|
IsUpdated: () => bindEvent(Event.Update),
|
|
157
169
|
IsDeleted: () => bindEvent(Event.Delete),
|
|
158
170
|
};
|
|
159
|
-
}
|
|
171
|
+
}
|
|
160
172
|
}
|
package/src/lib/controller.ts
CHANGED
|
@@ -1,58 +1,73 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
3
|
|
|
4
|
-
import express from "express";
|
|
4
|
+
import express, { NextFunction } from "express";
|
|
5
5
|
import fs from "fs";
|
|
6
6
|
import https from "https";
|
|
7
7
|
|
|
8
8
|
import { Capability } from "./capability";
|
|
9
|
-
import {
|
|
9
|
+
import { isWatchMode } from "./k8s";
|
|
10
|
+
import { MutateResponse, Request, ValidateResponse } from "./k8s/types";
|
|
10
11
|
import Log from "./logger";
|
|
11
|
-
import { processor } from "./processor";
|
|
12
|
-
import { ModuleConfig } from "./types";
|
|
13
12
|
import { MetricsCollector } from "./metrics";
|
|
13
|
+
import { mutateProcessor } from "./mutate-processor";
|
|
14
|
+
import { ModuleConfig } from "./types";
|
|
15
|
+
import { validateProcessor } from "./validate-processor";
|
|
14
16
|
|
|
15
17
|
export class Controller {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
// Track whether the server is running
|
|
19
|
+
#running = false;
|
|
20
|
+
|
|
21
|
+
// Metrics collector
|
|
22
|
+
#metricsCollector = new MetricsCollector("pepr");
|
|
19
23
|
|
|
20
24
|
// The token used to authenticate requests
|
|
21
|
-
|
|
25
|
+
#token = "";
|
|
26
|
+
|
|
27
|
+
// The express app instance
|
|
28
|
+
readonly #app = express();
|
|
29
|
+
|
|
30
|
+
// Initialized with the constructor
|
|
31
|
+
readonly #config: ModuleConfig;
|
|
32
|
+
readonly #capabilities: Capability[];
|
|
33
|
+
readonly #beforeHook?: (req: Request) => void;
|
|
34
|
+
readonly #afterHook?: (res: MutateResponse) => void;
|
|
22
35
|
|
|
23
36
|
constructor(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
37
|
+
config: ModuleConfig,
|
|
38
|
+
capabilities: Capability[],
|
|
39
|
+
beforeHook?: (req: Request) => void,
|
|
40
|
+
afterHook?: (res: MutateResponse) => void,
|
|
28
41
|
) {
|
|
29
|
-
|
|
30
|
-
this
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
this.app.use(express.json({ limit: "2mb" }));
|
|
42
|
+
this.#config = config;
|
|
43
|
+
this.#capabilities = capabilities;
|
|
44
|
+
this.#beforeHook = beforeHook;
|
|
45
|
+
this.#afterHook = afterHook;
|
|
34
46
|
|
|
35
|
-
//
|
|
36
|
-
this.
|
|
47
|
+
// Bind public methods
|
|
48
|
+
this.startServer = this.startServer.bind(this);
|
|
37
49
|
|
|
38
|
-
//
|
|
39
|
-
this
|
|
50
|
+
// Middleware for logging requests
|
|
51
|
+
this.#app.use(Controller.#logger);
|
|
40
52
|
|
|
41
|
-
//
|
|
42
|
-
this
|
|
53
|
+
// Middleware for parsing JSON, limit to 2mb vs 100K for K8s compatibility
|
|
54
|
+
this.#app.use(express.json({ limit: "2mb" }));
|
|
43
55
|
|
|
44
56
|
if (beforeHook) {
|
|
45
|
-
|
|
57
|
+
Log.info(`Using beforeHook: ${beforeHook}`);
|
|
46
58
|
}
|
|
47
59
|
|
|
48
60
|
if (afterHook) {
|
|
49
|
-
|
|
61
|
+
Log.info(`Using afterHook: ${afterHook}`);
|
|
50
62
|
}
|
|
63
|
+
|
|
64
|
+
// Bind endpoints
|
|
65
|
+
this.#bindEndpoints();
|
|
51
66
|
}
|
|
52
67
|
|
|
53
68
|
/** Start the webhook server */
|
|
54
|
-
|
|
55
|
-
if (this
|
|
69
|
+
startServer(port: number) {
|
|
70
|
+
if (this.#running) {
|
|
56
71
|
throw new Error("Cannot start Pepr module: Pepr module was not instantiated with deferStart=true");
|
|
57
72
|
}
|
|
58
73
|
|
|
@@ -62,29 +77,32 @@ export class Controller {
|
|
|
62
77
|
cert: fs.readFileSync(process.env.SSL_CERT_PATH || "/etc/certs/tls.crt"),
|
|
63
78
|
};
|
|
64
79
|
|
|
65
|
-
// Get the API token
|
|
66
|
-
|
|
67
|
-
|
|
80
|
+
// Get the API token if not in watch mode
|
|
81
|
+
if (!isWatchMode) {
|
|
82
|
+
// Get the API token from the environment variable or the mounted secret
|
|
83
|
+
this.#token = process.env.PEPR_API_TOKEN || fs.readFileSync("/app/api-token/value").toString().trim();
|
|
84
|
+
Log.info(`Using API token: ${this.#token}`);
|
|
68
85
|
|
|
69
|
-
|
|
70
|
-
|
|
86
|
+
if (!this.#token) {
|
|
87
|
+
throw new Error("API token not found");
|
|
88
|
+
}
|
|
71
89
|
}
|
|
72
90
|
|
|
73
91
|
// Create HTTPS server
|
|
74
|
-
const server = https.createServer(options, this
|
|
92
|
+
const server = https.createServer(options, this.#app).listen(port);
|
|
75
93
|
|
|
76
94
|
// Handle server listening event
|
|
77
95
|
server.on("listening", () => {
|
|
78
|
-
|
|
96
|
+
Log.info(`Server listening on port ${port}`);
|
|
79
97
|
// Track that the server is running
|
|
80
|
-
this
|
|
98
|
+
this.#running = true;
|
|
81
99
|
});
|
|
82
100
|
|
|
83
101
|
// Handle EADDRINUSE errors
|
|
84
102
|
server.on("error", (e: { code: string }) => {
|
|
85
103
|
if (e.code === "EADDRINUSE") {
|
|
86
|
-
|
|
87
|
-
`Address in use, retrying in 2 seconds. If this persists, ensure ${port} is not in use, e.g. "lsof -i :${port}"
|
|
104
|
+
Log.warn(
|
|
105
|
+
`Address in use, retrying in 2 seconds. If this persists, ensure ${port} is not in use, e.g. "lsof -i :${port}"`,
|
|
88
106
|
);
|
|
89
107
|
setTimeout(() => {
|
|
90
108
|
server.close();
|
|
@@ -95,92 +113,174 @@ export class Controller {
|
|
|
95
113
|
|
|
96
114
|
// Listen for the SIGTERM signal and gracefully close the server
|
|
97
115
|
process.on("SIGTERM", () => {
|
|
98
|
-
|
|
116
|
+
Log.info("Received SIGTERM, closing server");
|
|
99
117
|
server.close(() => {
|
|
100
|
-
|
|
118
|
+
Log.info("Server closed");
|
|
101
119
|
process.exit(0);
|
|
102
120
|
});
|
|
103
121
|
});
|
|
104
|
-
}
|
|
122
|
+
}
|
|
105
123
|
|
|
106
|
-
|
|
107
|
-
|
|
124
|
+
#bindEndpoints = () => {
|
|
125
|
+
// Health check endpoint
|
|
126
|
+
this.#app.get("/healthz", Controller.#healthz);
|
|
108
127
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const elapsedTime = Date.now() - startTime;
|
|
112
|
-
const message = `[${now}] ${req.method} ${req.originalUrl} [${res.statusCode}] ${elapsedTime} ms\n`;
|
|
128
|
+
// Metrics endpoint
|
|
129
|
+
this.#app.get("/metrics", this.#metrics);
|
|
113
130
|
|
|
114
|
-
|
|
115
|
-
|
|
131
|
+
if (isWatchMode) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
116
134
|
|
|
117
|
-
|
|
135
|
+
// Require auth for webhook endpoints
|
|
136
|
+
this.#app.use(["/mutate/:token", "/validate/:token"], this.#validateToken);
|
|
137
|
+
|
|
138
|
+
// Mutate endpoint
|
|
139
|
+
this.#app.post("/mutate/:token", this.#admissionReq("Mutate"));
|
|
140
|
+
|
|
141
|
+
// Validate endpoint
|
|
142
|
+
this.#app.post("/validate/:token", this.#admissionReq("Validate"));
|
|
118
143
|
};
|
|
119
144
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
145
|
+
/**
|
|
146
|
+
* Validate the token in the request path
|
|
147
|
+
*
|
|
148
|
+
* @param req The incoming request
|
|
149
|
+
* @param res The outgoing response
|
|
150
|
+
* @param next The next middleware function
|
|
151
|
+
* @returns
|
|
152
|
+
*/
|
|
153
|
+
#validateToken = (req: express.Request, res: express.Response, next: NextFunction) => {
|
|
154
|
+
// Validate the token
|
|
155
|
+
const { token } = req.params;
|
|
156
|
+
if (token !== this.#token) {
|
|
157
|
+
const err = `Unauthorized: invalid token '${token.replace(/[^\w]/g, "_")}'`;
|
|
158
|
+
Log.warn(err);
|
|
159
|
+
res.status(401).send(err);
|
|
160
|
+
this.#metricsCollector.alert();
|
|
161
|
+
return;
|
|
126
162
|
}
|
|
163
|
+
|
|
164
|
+
// Token is valid, continue
|
|
165
|
+
next();
|
|
127
166
|
};
|
|
128
167
|
|
|
129
|
-
|
|
168
|
+
/**
|
|
169
|
+
* Metrics endpoint handler
|
|
170
|
+
*
|
|
171
|
+
* @param req the incoming request
|
|
172
|
+
* @param res the outgoing response
|
|
173
|
+
*/
|
|
174
|
+
#metrics = async (req: express.Request, res: express.Response) => {
|
|
130
175
|
try {
|
|
131
|
-
res.send(await this
|
|
176
|
+
res.send(await this.#metricsCollector.getMetrics());
|
|
132
177
|
} catch (err) {
|
|
133
|
-
|
|
178
|
+
Log.error(err);
|
|
134
179
|
res.status(500).send("Internal Server Error");
|
|
135
180
|
}
|
|
136
181
|
};
|
|
137
182
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
183
|
+
/**
|
|
184
|
+
* Admission request handler for both mutate and validate requests
|
|
185
|
+
*
|
|
186
|
+
* @param admissionKind the type of admission request
|
|
187
|
+
* @returns the request handler
|
|
188
|
+
*/
|
|
189
|
+
#admissionReq = (admissionKind: "Mutate" | "Validate") => {
|
|
190
|
+
// Create the admission request handler
|
|
191
|
+
return async (req: express.Request, res: express.Response) => {
|
|
192
|
+
// Start the metrics timer
|
|
193
|
+
const startTime = this.#metricsCollector.observeStart();
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
// Get the request from the body or create an empty request
|
|
197
|
+
const request: Request = req.body?.request || ({} as Request);
|
|
198
|
+
|
|
199
|
+
// Run the before hook if it exists
|
|
200
|
+
this.#beforeHook && this.#beforeHook(request || {});
|
|
201
|
+
|
|
202
|
+
// Setup identifiers for logging
|
|
203
|
+
const name = request?.name ? `/${request.name}` : "";
|
|
204
|
+
const namespace = request?.namespace || "";
|
|
205
|
+
const gvk = request?.kind || { group: "", version: "", kind: "" };
|
|
206
|
+
|
|
207
|
+
const reqMetadata = {
|
|
208
|
+
uid: request.uid,
|
|
209
|
+
namespace,
|
|
210
|
+
name,
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
Log.info({ ...reqMetadata, gvk, operation: request.operation, admissionKind }, "Incoming request");
|
|
214
|
+
Log.debug({ ...reqMetadata, request }, "Incoming request body");
|
|
215
|
+
|
|
216
|
+
// Process the request
|
|
217
|
+
let response: MutateResponse | ValidateResponse;
|
|
218
|
+
|
|
219
|
+
// Call mutate or validate based on the admission kind
|
|
220
|
+
if (admissionKind === "Mutate") {
|
|
221
|
+
response = await mutateProcessor(this.#config, this.#capabilities, request, reqMetadata);
|
|
222
|
+
} else {
|
|
223
|
+
response = await validateProcessor(this.#capabilities, request, reqMetadata);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Run the after hook if it exists
|
|
227
|
+
this.#afterHook && this.#afterHook(response);
|
|
228
|
+
|
|
229
|
+
// Log the response
|
|
230
|
+
Log.debug({ ...reqMetadata, response }, "Outgoing response");
|
|
231
|
+
|
|
232
|
+
// Send a no prob bob response
|
|
233
|
+
res.send({
|
|
234
|
+
apiVersion: "admission.k8s.io/v1",
|
|
235
|
+
kind: "AdmissionReview",
|
|
236
|
+
response,
|
|
237
|
+
});
|
|
238
|
+
this.#metricsCollector.observeEnd(startTime, admissionKind);
|
|
239
|
+
} catch (err) {
|
|
240
|
+
Log.error(err);
|
|
241
|
+
res.status(500).send("Internal Server Error");
|
|
242
|
+
this.#metricsCollector.error();
|
|
150
243
|
}
|
|
244
|
+
};
|
|
245
|
+
};
|
|
151
246
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
Log.info(`Mutate [${request.operation}] ${gvk.group}/${gvk.version}/${gvk.kind}`, prefix);
|
|
163
|
-
|
|
164
|
-
// Process the request
|
|
165
|
-
const response = await processor(this.config, this.capabilities, request, prefix);
|
|
166
|
-
|
|
167
|
-
// Run the after hook if it exists
|
|
168
|
-
this.afterHook && this.afterHook(response);
|
|
247
|
+
/**
|
|
248
|
+
* Middleware for logging requests
|
|
249
|
+
*
|
|
250
|
+
* @param req the incoming request
|
|
251
|
+
* @param res the outgoing response
|
|
252
|
+
* @param next the next middleware function
|
|
253
|
+
*/
|
|
254
|
+
static #logger(req: express.Request, res: express.Response, next: express.NextFunction) {
|
|
255
|
+
const startTime = Date.now();
|
|
169
256
|
|
|
170
|
-
|
|
171
|
-
|
|
257
|
+
res.on("finish", () => {
|
|
258
|
+
const elapsedTime = Date.now() - startTime;
|
|
259
|
+
const message = {
|
|
260
|
+
uid: req.body?.request?.uid,
|
|
261
|
+
method: req.method,
|
|
262
|
+
url: req.originalUrl,
|
|
263
|
+
status: res.statusCode,
|
|
264
|
+
duration: `${elapsedTime} ms`,
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
res.statusCode >= 300 ? Log.warn(message) : Log.info(message);
|
|
268
|
+
});
|
|
172
269
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
270
|
+
next();
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Health check endpoint handler
|
|
274
|
+
*
|
|
275
|
+
* @param req the incoming request
|
|
276
|
+
* @param res the outgoing response
|
|
277
|
+
*/
|
|
278
|
+
static #healthz(req: express.Request, res: express.Response) {
|
|
279
|
+
try {
|
|
280
|
+
res.send("OK");
|
|
180
281
|
} catch (err) {
|
|
181
|
-
|
|
282
|
+
Log.error(err);
|
|
182
283
|
res.status(500).send("Internal Server Error");
|
|
183
|
-
this.metricsCollector.error();
|
|
184
284
|
}
|
|
185
|
-
}
|
|
285
|
+
}
|
|
186
286
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
export const Errors = {
|
|
5
|
+
audit: "audit",
|
|
6
|
+
ignore: "ignore",
|
|
7
|
+
reject: "reject",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const ErrorList = Object.values(Errors);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Validate the error or throw an error
|
|
14
|
+
* @param error
|
|
15
|
+
*/
|
|
16
|
+
export function ValidateError(error = "") {
|
|
17
|
+
if (!ErrorList.includes(error)) {
|
|
18
|
+
throw new Error(`Invalid error: ${error}. Must be one of: ${ErrorList.join(", ")}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
package/src/lib/fetch.ts
CHANGED
|
@@ -30,7 +30,7 @@ export type FetchResponse<T> = {
|
|
|
30
30
|
export async function fetch<T>(url: URL | RequestInfo, init?: RequestInit): Promise<FetchResponse<T>> {
|
|
31
31
|
let data = undefined as unknown as T;
|
|
32
32
|
try {
|
|
33
|
-
logger.debug(`Fetching ${url}`);
|
|
33
|
+
logger.debug(init, `Fetching ${url}`);
|
|
34
34
|
|
|
35
35
|
const resp = await fetchRaw(url, init);
|
|
36
36
|
const contentType = resp.headers.get("content-type") || "";
|
package/src/lib/filter.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { Binding, Event } from "./types";
|
|
|
8
8
|
/**
|
|
9
9
|
* shouldSkipRequest determines if a request should be skipped based on the binding filters.
|
|
10
10
|
*
|
|
11
|
-
* @param binding the
|
|
11
|
+
* @param binding the action binding
|
|
12
12
|
* @param req the incoming request
|
|
13
13
|
* @returns
|
|
14
14
|
*/
|
|
@@ -20,8 +20,6 @@ export function shouldSkipRequest(binding: Binding, req: Request) {
|
|
|
20
20
|
const srcObject = operation === Operation.DELETE ? req.oldObject : req.object;
|
|
21
21
|
const { metadata } = srcObject || {};
|
|
22
22
|
|
|
23
|
-
console.log(metadata);
|
|
24
|
-
|
|
25
23
|
// Test for matching operation
|
|
26
24
|
if (!binding.event.includes(operation) && !binding.event.includes(Event.Any)) {
|
|
27
25
|
return true;
|
package/src/lib/k8s/index.ts
CHANGED
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
|
|
4
4
|
// Export kinds as a single object
|
|
5
5
|
import * as kind from "./upstream";
|
|
6
|
-
/** a is a collection of K8s types to be used within a
|
|
6
|
+
/** a is a collection of K8s types to be used within a action: `When(a.Configmap)` */
|
|
7
7
|
export { kind as a };
|
|
8
8
|
|
|
9
9
|
export { modelToGroupVersionKind, gvkMap, RegisterKind } from "./kinds";
|
|
10
10
|
|
|
11
|
+
// If the hostname is pepr-static-test-watcher-0, then we are running in watch mode
|
|
12
|
+
export const isWatchMode = process.env.PEPR_WATCH_MODE === "true";
|
|
13
|
+
|
|
11
14
|
export * from "./types";
|