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.
Files changed (88) hide show
  1. package/CODE_OF_CONDUCT.md +83 -0
  2. package/CONTRIBUTING.md +70 -0
  3. package/README.md +28 -30
  4. package/dist/cli.js +644 -679
  5. package/dist/controller.js +13 -81
  6. package/dist/lib/assets/deploy.d.ts +3 -0
  7. package/dist/lib/assets/deploy.d.ts.map +1 -0
  8. package/dist/lib/assets/index.d.ts +18 -0
  9. package/dist/lib/assets/index.d.ts.map +1 -0
  10. package/dist/lib/assets/loader.d.ts +14 -0
  11. package/dist/lib/assets/loader.d.ts.map +1 -0
  12. package/dist/lib/assets/networking.d.ts +6 -0
  13. package/dist/lib/assets/networking.d.ts.map +1 -0
  14. package/dist/lib/assets/pods.d.ts +8 -0
  15. package/dist/lib/assets/pods.d.ts.map +1 -0
  16. package/dist/lib/assets/rbac.d.ts +11 -0
  17. package/dist/lib/assets/rbac.d.ts.map +1 -0
  18. package/dist/lib/assets/webhooks.d.ts +6 -0
  19. package/dist/lib/assets/webhooks.d.ts.map +1 -0
  20. package/dist/lib/assets/yaml.d.ts +4 -0
  21. package/dist/lib/assets/yaml.d.ts.map +1 -0
  22. package/dist/lib/capability.d.ts +1 -3
  23. package/dist/lib/capability.d.ts.map +1 -1
  24. package/dist/lib/controller.d.ts +45 -10
  25. package/dist/lib/controller.d.ts.map +1 -1
  26. package/dist/lib/filter.d.ts +1 -1
  27. package/dist/lib/filter.d.ts.map +1 -1
  28. package/dist/lib/k8s/index.d.ts +2 -1
  29. package/dist/lib/k8s/index.d.ts.map +1 -1
  30. package/dist/lib/k8s/kinds.d.ts.map +1 -1
  31. package/dist/lib/k8s/types.d.ts +13 -13
  32. package/dist/lib/k8s/types.d.ts.map +1 -1
  33. package/dist/lib/k8s/upstream.d.ts +2 -2
  34. package/dist/lib/k8s/upstream.d.ts.map +1 -1
  35. package/dist/lib/logger.d.ts +8 -54
  36. package/dist/lib/logger.d.ts.map +1 -1
  37. package/dist/lib/metrics.d.ts +11 -4
  38. package/dist/lib/metrics.d.ts.map +1 -1
  39. package/dist/lib/module.d.ts +2 -2
  40. package/dist/lib/module.d.ts.map +1 -1
  41. package/dist/lib/mutate-processor.d.ts +5 -0
  42. package/dist/lib/mutate-processor.d.ts.map +1 -0
  43. package/dist/lib/{request.d.ts → mutate-request.d.ts} +5 -5
  44. package/dist/lib/mutate-request.d.ts.map +1 -0
  45. package/dist/lib/types.d.ts +45 -46
  46. package/dist/lib/types.d.ts.map +1 -1
  47. package/dist/lib/validate-processor.d.ts +4 -0
  48. package/dist/lib/validate-processor.d.ts.map +1 -0
  49. package/dist/lib/validate-request.d.ts +54 -0
  50. package/dist/lib/validate-request.d.ts.map +1 -0
  51. package/dist/lib.d.ts +3 -2
  52. package/dist/lib.d.ts.map +1 -1
  53. package/dist/lib.js +502 -302
  54. package/dist/lib.js.map +4 -4
  55. package/package.json +15 -12
  56. package/src/cli.ts +2 -11
  57. package/src/lib/assets/deploy.ts +179 -0
  58. package/src/lib/assets/index.ts +46 -0
  59. package/src/lib/assets/loader.ts +49 -0
  60. package/src/lib/assets/networking.ts +58 -0
  61. package/src/lib/assets/pods.ts +148 -0
  62. package/src/lib/assets/rbac.ts +57 -0
  63. package/src/lib/assets/webhooks.ts +139 -0
  64. package/src/lib/assets/yaml.ts +75 -0
  65. package/src/lib/capability.ts +54 -44
  66. package/src/lib/controller.ts +171 -89
  67. package/src/lib/fetch.ts +1 -1
  68. package/src/lib/filter.ts +1 -3
  69. package/src/lib/k8s/index.ts +4 -1
  70. package/src/lib/k8s/kinds.ts +40 -0
  71. package/src/lib/k8s/types.ts +16 -14
  72. package/src/lib/k8s/upstream.ts +5 -1
  73. package/src/lib/logger.ts +14 -125
  74. package/src/lib/metrics.ts +67 -23
  75. package/src/lib/module.ts +13 -11
  76. package/src/lib/{processor.ts → mutate-processor.ts} +37 -28
  77. package/src/lib/{request.ts → mutate-request.ts} +4 -4
  78. package/src/lib/types.ts +51 -51
  79. package/src/lib/validate-processor.ts +68 -0
  80. package/src/lib/validate-request.ts +94 -0
  81. package/src/lib.ts +4 -2
  82. package/src/runtime/controller.ts +1 -1
  83. package/dist/lib/k8s/webhook.d.ts +0 -37
  84. package/dist/lib/k8s/webhook.d.ts.map +0 -1
  85. package/dist/lib/processor.d.ts +0 -5
  86. package/dist/lib/processor.d.ts.map +0 -1
  87. package/dist/lib/request.d.ts.map +0 -1
  88. package/src/lib/k8s/webhook.ts +0 -643
@@ -0,0 +1,75 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+
4
+ import { dumpYaml } from "@kubernetes/client-node";
5
+ import crypto from "crypto";
6
+ import { promises as fs } from "fs";
7
+
8
+ import { Assets } from ".";
9
+ import { apiTokenSecret, service, tlsSecret } from "./networking";
10
+ import { deployment, moduleSecret, namespace } from "./pods";
11
+ import { clusterRole, clusterRoleBinding, serviceAccount } from "./rbac";
12
+ import { webhookConfig } from "./webhooks";
13
+
14
+ export function zarfYaml({ name, image, config }: Assets, path: string) {
15
+ const zarfCfg = {
16
+ kind: "ZarfPackageConfig",
17
+ metadata: {
18
+ name,
19
+ description: `Pepr Module: ${config.description}`,
20
+ url: "https://github.com/defenseunicorns/pepr",
21
+ version: `${config.appVersion || "0.0.1"}`,
22
+ },
23
+ components: [
24
+ {
25
+ name: "module",
26
+ required: true,
27
+ manifests: [
28
+ {
29
+ name: "module",
30
+ namespace: "pepr-system",
31
+ files: [path],
32
+ },
33
+ ],
34
+ images: [image],
35
+ },
36
+ ],
37
+ };
38
+
39
+ return dumpYaml(zarfCfg, { noRefs: true });
40
+ }
41
+
42
+ export async function allYaml(assets: Assets) {
43
+ const { name, tls, apiToken, path } = assets;
44
+
45
+ const code = await fs.readFile(path);
46
+
47
+ // Generate a hash of the code
48
+ const hash = crypto.createHash("sha256").update(code).digest("hex");
49
+
50
+ const mutateWebhook = await webhookConfig(assets, "mutate");
51
+ const validateWebhook = await webhookConfig(assets, "validate");
52
+
53
+ const resources = [
54
+ namespace,
55
+ clusterRole(name),
56
+ clusterRoleBinding(name),
57
+ serviceAccount(name),
58
+ apiTokenSecret(name, apiToken),
59
+ tlsSecret(name, tls),
60
+ deployment(assets, hash),
61
+ service(name),
62
+ moduleSecret(name, code, hash),
63
+ ];
64
+
65
+ if (mutateWebhook) {
66
+ resources.push(mutateWebhook);
67
+ }
68
+
69
+ if (validateWebhook) {
70
+ resources.push(validateWebhook);
71
+ }
72
+
73
+ // Convert the resources to a single YAML string
74
+ return resources.map(r => dumpYaml(r, { noRefs: true })).join("---\n");
75
+ }
@@ -1,20 +1,21 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
3
 
4
- import { modelToGroupVersionKind } from "./k8s/index";
4
+ import { pickBy } from "ramda";
5
+
6
+ import { isWatchMode, modelToGroupVersionKind } from "./k8s/index";
5
7
  import { GroupVersionKind } from "./k8s/types";
6
- import logger from "./logger";
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
- DeepPartial,
15
14
  Event,
16
15
  GenericClass,
17
- HookPhase,
16
+ MutateAction,
17
+ MutateActionChain,
18
+ ValidateAction,
18
19
  WhenSelector,
19
20
  } from "./types";
20
21
 
@@ -26,9 +27,6 @@ export class Capability implements CapabilityCfg {
26
27
  private _description: string;
27
28
  private _namespaces?: string[] | undefined;
28
29
 
29
- // Currently everything is considered a mutation
30
- private _mutateOrValidate = HookPhase.mutate;
31
-
32
30
  private _bindings: Binding[] = [];
33
31
 
34
32
  get bindings(): Binding[] {
@@ -47,16 +45,13 @@ export class Capability implements CapabilityCfg {
47
45
  return this._namespaces || [];
48
46
  }
49
47
 
50
- get mutateOrValidate() {
51
- return this._mutateOrValidate;
52
- }
53
-
54
48
  constructor(cfg: CapabilityCfg) {
55
49
  this._name = cfg.name;
56
50
  this._description = cfg.description;
57
51
  this._namespaces = cfg.namespaces;
58
- logger.info(`Capability ${this._name} registered`);
59
- logger.debug(cfg);
52
+
53
+ Log.info(`Capability ${this._name} registered`);
54
+ Log.debug(cfg);
60
55
  }
61
56
 
62
57
  /**
@@ -86,66 +81,81 @@ export class Capability implements CapabilityCfg {
86
81
  labels: {},
87
82
  annotations: {},
88
83
  },
89
- callback: () => undefined,
90
84
  };
91
85
 
92
86
  const prefix = `${this._name}: ${model.name}`;
93
87
 
94
- logger.info(`Binding created`, prefix);
88
+ const isNotEmpty = (value: object) => Object.keys(value).length > 0;
89
+ const log = (message: string, cbString: string) => {
90
+ const filteredObj = pickBy(isNotEmpty, binding.filters);
95
91
 
96
- const Then = (cb: CapabilityAction<T>): BindToAction<T> => {
97
- logger.info(`Binding action created`, prefix);
98
- logger.debug(cb.toString(), prefix);
99
- // Push the binding to the list of bindings for this capability as a new BindingAction
100
- // with the callback function to preserve
101
- this._bindings.push({
102
- ...binding,
103
- callback: cb,
104
- });
92
+ Log.info(`${message} configured for ${binding.event}`, prefix);
93
+ Log.info(filteredObj, prefix);
94
+ Log.debug(cbString, prefix);
95
+ };
105
96
 
106
- // Now only allow adding actions to the same binding
107
- return { Then };
97
+ const Validate = (validateCallback: ValidateAction<T>): void => {
98
+ if (!isWatchMode) {
99
+ log("Validate Action", validateCallback.toString());
100
+
101
+ // Push the binding to the list of bindings for this capability as a new BindingAction
102
+ // with the callback function to preserve
103
+ this._bindings.push({
104
+ ...binding,
105
+ isValidate: true,
106
+ validateCallback,
107
+ });
108
+ }
108
109
  };
109
110
 
110
- const ThenSet = (merge: DeepPartial<InstanceType<T>>): BindToAction<T> => {
111
- // Add the new action to the binding
112
- Then(req => req.Merge(merge));
111
+ const Mutate = (mutateCallback: MutateAction<T>): MutateActionChain<T> => {
112
+ if (!isWatchMode) {
113
+ log("Mutate Action", mutateCallback.toString());
114
+
115
+ // Push the binding to the list of bindings for this capability as a new BindingAction
116
+ // with the callback function to preserve
117
+ this._bindings.push({
118
+ ...binding,
119
+ isMutate: true,
120
+ mutateCallback,
121
+ });
122
+ }
113
123
 
114
- return { Then };
124
+ // Now only allow adding actions to the same binding
125
+ return { Validate };
115
126
  };
116
127
 
117
128
  function InNamespace(...namespaces: string[]): BindingWithName<T> {
118
- logger.debug(`Add namespaces filter ${namespaces}`, prefix);
129
+ Log.debug(`Add namespaces filter ${namespaces}`, prefix);
119
130
  binding.filters.namespaces.push(...namespaces);
120
- return { WithLabel, WithAnnotation, WithName, Then, ThenSet };
131
+ return { ...commonChain, WithName };
121
132
  }
122
133
 
123
134
  function WithName(name: string): BindingFilter<T> {
124
- logger.debug(`Add name filter ${name}`, prefix);
135
+ Log.debug(`Add name filter ${name}`, prefix);
125
136
  binding.filters.name = name;
126
- return { WithLabel, WithAnnotation, Then, ThenSet };
137
+ return commonChain;
127
138
  }
128
139
 
129
140
  function WithLabel(key: string, value = ""): BindingFilter<T> {
130
- logger.debug(`Add label filter ${key}=${value}`, prefix);
141
+ Log.debug(`Add label filter ${key}=${value}`, prefix);
131
142
  binding.filters.labels[key] = value;
132
- return { WithLabel, WithAnnotation, Then, ThenSet };
143
+ return commonChain;
133
144
  }
134
145
 
135
146
  const WithAnnotation = (key: string, value = ""): BindingFilter<T> => {
136
- logger.debug(`Add annotation filter ${key}=${value}`, prefix);
147
+ Log.debug(`Add annotation filter ${key}=${value}`, prefix);
137
148
  binding.filters.annotations[key] = value;
138
- return { WithLabel, WithAnnotation, Then, ThenSet };
149
+ return commonChain;
139
150
  };
140
151
 
152
+ const commonChain = { WithLabel, WithAnnotation, Mutate, Validate };
153
+
141
154
  const bindEvent = (event: Event) => {
142
155
  binding.event = event;
143
156
  return {
157
+ ...commonChain,
144
158
  InNamespace,
145
- Then,
146
- ThenSet,
147
- WithAnnotation,
148
- WithLabel,
149
159
  WithName,
150
160
  };
151
161
  };
@@ -1,58 +1,75 @@
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 { Request, Response } from "./k8s/types";
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
- private readonly app = express();
17
- private running = false;
18
+ private readonly _app = express();
19
+ private _running = false;
18
20
  private metricsCollector = new MetricsCollector("pepr");
19
21
 
20
22
  // The token used to authenticate requests
21
- private token = "";
23
+ private _token = "";
22
24
 
23
25
  constructor(
24
- private readonly config: ModuleConfig,
25
- private readonly capabilities: Capability[],
26
- private readonly beforeHook?: (req: Request) => void,
27
- private readonly afterHook?: (res: Response) => void
26
+ private readonly _config: ModuleConfig,
27
+ private readonly _capabilities: Capability[],
28
+ private readonly _beforeHook?: (req: Request) => void,
29
+ private readonly _afterHook?: (res: MutateResponse) => void,
28
30
  ) {
29
31
  // Middleware for logging requests
30
- this.app.use(this.logger);
32
+ this._app.use(this.logger);
31
33
 
32
34
  // Middleware for parsing JSON, limit to 2mb vs 100K for K8s compatibility
33
- this.app.use(express.json({ limit: "2mb" }));
35
+ this._app.use(express.json({ limit: "2mb" }));
36
+
37
+ if (_beforeHook) {
38
+ Log.info(`Using beforeHook: ${_beforeHook}`);
39
+ }
40
+
41
+ if (_afterHook) {
42
+ Log.info(`Using afterHook: ${_afterHook}`);
43
+ }
44
+
45
+ // Bind endpoints
46
+ this.bindEndpoints();
47
+ }
34
48
 
49
+ private bindEndpoints = () => {
35
50
  // Health check endpoint
36
- this.app.get("/healthz", this.healthz);
51
+ this._app.get("/healthz", this.healthz);
37
52
 
38
53
  // Metrics endpoint
39
- this.app.get("/metrics", this.metrics);
40
-
41
- // Mutate endpoint
42
- this.app.post("/mutate/:token", this.mutate);
54
+ this._app.get("/metrics", this.metrics);
43
55
 
44
- if (beforeHook) {
45
- console.info(`Using beforeHook: ${beforeHook}`);
56
+ if (isWatchMode) {
57
+ return;
46
58
  }
47
59
 
48
- if (afterHook) {
49
- console.info(`Using afterHook: ${afterHook}`);
50
- }
51
- }
60
+ // Require auth for webhook endpoints
61
+ this._app.use(["/mutate/:token", "/validate/:token"], this.validateToken);
62
+
63
+ // Mutate endpoint
64
+ this._app.post("/mutate/:token", this.admissionReq("Mutate"));
65
+
66
+ // Validate endpoint
67
+ this._app.post("/validate/:token", this.admissionReq("Validate"));
68
+ };
52
69
 
53
70
  /** Start the webhook server */
54
71
  public startServer = (port: number) => {
55
- if (this.running) {
72
+ if (this._running) {
56
73
  throw new Error("Cannot start Pepr module: Pepr module was not instantiated with deferStart=true");
57
74
  }
58
75
 
@@ -62,29 +79,32 @@ export class Controller {
62
79
  cert: fs.readFileSync(process.env.SSL_CERT_PATH || "/etc/certs/tls.crt"),
63
80
  };
64
81
 
65
- // Get the API token from the environment variable or the mounted secret
66
- this.token = process.env.PEPR_API_TOKEN || fs.readFileSync("/app/api-token/value").toString().trim();
67
- console.info(`Using API token: ${this.token}`);
82
+ // Get the API token if not in watch mode
83
+ if (!isWatchMode) {
84
+ // Get the API token from the environment variable or the mounted secret
85
+ this._token = process.env.PEPR_API_TOKEN || fs.readFileSync("/app/api-token/value").toString().trim();
86
+ Log.info(`Using API token: ${this._token}`);
68
87
 
69
- if (!this.token) {
70
- throw new Error("API token not found");
88
+ if (!this._token) {
89
+ throw new Error("API token not found");
90
+ }
71
91
  }
72
92
 
73
93
  // Create HTTPS server
74
- const server = https.createServer(options, this.app).listen(port);
94
+ const server = https.createServer(options, this._app).listen(port);
75
95
 
76
96
  // Handle server listening event
77
97
  server.on("listening", () => {
78
- console.log(`Server listening on port ${port}`);
98
+ Log.info(`Server listening on port ${port}`);
79
99
  // Track that the server is running
80
- this.running = true;
100
+ this._running = true;
81
101
  });
82
102
 
83
103
  // Handle EADDRINUSE errors
84
104
  server.on("error", (e: { code: string }) => {
85
105
  if (e.code === "EADDRINUSE") {
86
- console.log(
87
- `Address in use, retrying in 2 seconds. If this persists, ensure ${port} is not in use, e.g. "lsof -i :${port}"`
106
+ Log.warn(
107
+ `Address in use, retrying in 2 seconds. If this persists, ensure ${port} is not in use, e.g. "lsof -i :${port}"`,
88
108
  );
89
109
  setTimeout(() => {
90
110
  server.close();
@@ -95,92 +115,154 @@ export class Controller {
95
115
 
96
116
  // Listen for the SIGTERM signal and gracefully close the server
97
117
  process.on("SIGTERM", () => {
98
- console.log("Received SIGTERM, closing server");
118
+ Log.info("Received SIGTERM, closing server");
99
119
  server.close(() => {
100
- console.log("Server closed");
120
+ Log.info("Server closed");
101
121
  process.exit(0);
102
122
  });
103
123
  });
104
124
  };
105
125
 
126
+ /**
127
+ * Middleware for logging requests
128
+ *
129
+ * @param req the incoming request
130
+ * @param res the outgoing response
131
+ * @param next the next middleware function
132
+ */
106
133
  private logger = (req: express.Request, res: express.Response, next: express.NextFunction) => {
107
134
  const startTime = Date.now();
108
135
 
109
136
  res.on("finish", () => {
110
- const now = new Date().toISOString();
111
137
  const elapsedTime = Date.now() - startTime;
112
- const message = `[${now}] ${req.method} ${req.originalUrl} [${res.statusCode}] ${elapsedTime} ms\n`;
113
-
114
- res.statusCode >= 400 ? console.error(message) : console.info(message);
138
+ const message = {
139
+ uid: req.body?.request?.uid,
140
+ method: req.method,
141
+ url: req.originalUrl,
142
+ status: res.statusCode,
143
+ duration: `${elapsedTime} ms`,
144
+ };
145
+
146
+ res.statusCode >= 300 ? Log.warn(message) : Log.info(message);
115
147
  });
116
148
 
117
149
  next();
118
150
  };
119
151
 
152
+ /**
153
+ * Validate the token in the request path
154
+ *
155
+ * @param req The incoming request
156
+ * @param res The outgoing response
157
+ * @param next The next middleware function
158
+ * @returns
159
+ */
160
+ private validateToken = (req: express.Request, res: express.Response, next: NextFunction) => {
161
+ // Validate the token
162
+ const { token } = req.params;
163
+ if (token !== this._token) {
164
+ const err = `Unauthorized: invalid token '${token.replace(/[^\w]/g, "_")}'`;
165
+ Log.warn(err);
166
+ res.status(401).send(err);
167
+ this.metricsCollector.alert();
168
+ return;
169
+ }
170
+
171
+ // Token is valid, continue
172
+ next();
173
+ };
174
+
175
+ /**
176
+ * Health check endpoint handler
177
+ *
178
+ * @param req the incoming request
179
+ * @param res the outgoing response
180
+ */
120
181
  private healthz = (req: express.Request, res: express.Response) => {
121
182
  try {
122
183
  res.send("OK");
123
184
  } catch (err) {
124
- console.error(err);
185
+ Log.error(err);
125
186
  res.status(500).send("Internal Server Error");
126
187
  }
127
188
  };
128
189
 
190
+ /**
191
+ * Metrics endpoint handler
192
+ *
193
+ * @param req the incoming request
194
+ * @param res the outgoing response
195
+ */
129
196
  private metrics = async (req: express.Request, res: express.Response) => {
130
197
  try {
131
198
  res.send(await this.metricsCollector.getMetrics());
132
199
  } catch (err) {
133
- console.error(err);
200
+ Log.error(err);
134
201
  res.status(500).send("Internal Server Error");
135
202
  }
136
203
  };
137
204
 
138
- private mutate = async (req: express.Request, res: express.Response) => {
139
- const startTime = this.metricsCollector.observeStart();
140
-
141
- try {
142
- // Validate the token
143
- const { token } = req.params;
144
- if (token !== this.token) {
145
- const err = `Unauthorized: invalid token '${token.replace(/[^\w]/g, "_")}'`;
146
- console.warn(err);
147
- res.status(401).send(err);
148
- this.metricsCollector.alert();
149
- return;
205
+ /**
206
+ * Admission request handler for both mutate and validate requests
207
+ *
208
+ * @param admissionKind the type of admission request
209
+ * @returns the request handler
210
+ */
211
+ private admissionReq = (admissionKind: "Mutate" | "Validate") => {
212
+ // Create the admission request handler
213
+ return async (req: express.Request, res: express.Response) => {
214
+ // Start the metrics timer
215
+ const startTime = this.metricsCollector.observeStart();
216
+
217
+ try {
218
+ // Get the request from the body or create an empty request
219
+ const request: Request = req.body?.request || ({} as Request);
220
+
221
+ // Run the before hook if it exists
222
+ this._beforeHook && this._beforeHook(request || {});
223
+
224
+ // Setup identifiers for logging
225
+ const name = request?.name ? `/${request.name}` : "";
226
+ const namespace = request?.namespace || "";
227
+ const gvk = request?.kind || { group: "", version: "", kind: "" };
228
+
229
+ const reqMetadata = {
230
+ uid: request.uid,
231
+ namespace,
232
+ name,
233
+ };
234
+
235
+ Log.info({ ...reqMetadata, gvk, operation: request.operation, admissionKind }, "Incoming request");
236
+ Log.debug({ ...reqMetadata, request }, "Incoming request body");
237
+
238
+ // Process the request
239
+ let response: MutateResponse | ValidateResponse;
240
+
241
+ // Call mutate or validate based on the admission kind
242
+ if (admissionKind === "Mutate") {
243
+ response = await mutateProcessor(this._config, this._capabilities, request, reqMetadata);
244
+ } else {
245
+ response = await validateProcessor(this._capabilities, request, reqMetadata);
246
+ }
247
+
248
+ // Run the after hook if it exists
249
+ this._afterHook && this._afterHook(response);
250
+
251
+ // Log the response
252
+ Log.debug({ ...reqMetadata, response }, "Outgoing response");
253
+
254
+ // Send a no prob bob response
255
+ res.send({
256
+ apiVersion: "admission.k8s.io/v1",
257
+ kind: "AdmissionReview",
258
+ response,
259
+ });
260
+ this.metricsCollector.observeEnd(startTime, admissionKind);
261
+ } catch (err) {
262
+ Log.error(err);
263
+ res.status(500).send("Internal Server Error");
264
+ this.metricsCollector.error();
150
265
  }
151
-
152
- const request: Request = req.body?.request || ({} as Request);
153
-
154
- // Run the before hook if it exists
155
- this.beforeHook && this.beforeHook(request || {});
156
-
157
- const name = request?.name ? `/${request.name}` : "";
158
- const namespace = request?.namespace || "";
159
- const gvk = request?.kind || { group: "", version: "", kind: "" };
160
- const prefix = `${request.uid} ${namespace}${name}`;
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);
169
-
170
- // Log the response
171
- Log.debug(response, prefix);
172
-
173
- // Send a no prob bob response
174
- res.send({
175
- apiVersion: "admission.k8s.io/v1",
176
- kind: "AdmissionReview",
177
- response,
178
- });
179
- this.metricsCollector.observeEnd(startTime);
180
- } catch (err) {
181
- console.error(err);
182
- res.status(500).send("Internal Server Error");
183
- this.metricsCollector.error();
184
- }
266
+ };
185
267
  };
186
268
  }
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 capability action binding
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;
@@ -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 CapabilityAction: `When(a.Configmap)` */
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";
@@ -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.