pepr 0.44.0 → 0.45.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 (42) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/build.d.ts +2 -2
  3. package/dist/cli/build.d.ts.map +1 -1
  4. package/dist/cli/build.helpers.d.ts +13 -7
  5. package/dist/cli/build.helpers.d.ts.map +1 -1
  6. package/dist/cli/deploy.d.ts.map +1 -1
  7. package/dist/cli/init/templates.d.ts +7 -7
  8. package/dist/cli/init/templates.d.ts.map +1 -1
  9. package/dist/cli.js +119 -99
  10. package/dist/controller.js +1 -1
  11. package/dist/lib/assets/rbac.d.ts.map +1 -1
  12. package/dist/lib/assets/webhooks.d.ts +2 -0
  13. package/dist/lib/assets/webhooks.d.ts.map +1 -1
  14. package/dist/lib/assets/yaml/overridesFile.d.ts +1 -3
  15. package/dist/lib/assets/yaml/overridesFile.d.ts.map +1 -1
  16. package/dist/lib/controller/index.d.ts +6 -1
  17. package/dist/lib/controller/index.d.ts.map +1 -1
  18. package/dist/lib/core/module.d.ts +23 -20
  19. package/dist/lib/core/module.d.ts.map +1 -1
  20. package/dist/lib/core/storage.d.ts.map +1 -1
  21. package/dist/lib/processors/watch-processor.d.ts.map +1 -1
  22. package/dist/lib/telemetry/metrics.d.ts.map +1 -1
  23. package/dist/lib.js +42 -24
  24. package/dist/lib.js.map +3 -3
  25. package/package.json +14 -10
  26. package/src/cli/build.helpers.ts +28 -13
  27. package/src/cli/build.ts +64 -64
  28. package/src/cli/deploy.ts +32 -26
  29. package/src/cli/init/index.ts +2 -2
  30. package/src/cli/init/templates.ts +6 -5
  31. package/src/cli/init/walkthrough.ts +1 -1
  32. package/src/lib/assets/rbac.ts +1 -2
  33. package/src/lib/assets/webhooks.ts +1 -1
  34. package/src/lib/assets/yaml/overridesFile.ts +3 -5
  35. package/src/lib/controller/index.ts +17 -10
  36. package/src/lib/core/module.ts +41 -31
  37. package/src/lib/core/schedule.ts +2 -2
  38. package/src/lib/core/storage.ts +2 -1
  39. package/src/lib/processors/mutate-processor.ts +1 -1
  40. package/src/lib/processors/watch-processor.ts +5 -3
  41. package/src/lib/telemetry/metrics.ts +4 -6
  42. package/src/sdk/heredoc.ts +2 -2
package/package.json CHANGED
@@ -15,16 +15,16 @@
15
15
  "!src/**/*.test.ts",
16
16
  "!dist/**/*.test.d.ts*"
17
17
  ],
18
- "version": "0.44.0",
18
+ "version": "0.45.0",
19
19
  "main": "dist/lib.js",
20
20
  "types": "dist/lib.d.ts",
21
21
  "scripts": {
22
22
  "ci": "npm ci",
23
23
  "gen-data-json": "node hack/build-template-data.js",
24
24
  "prebuild": "rm -fr dist/* && npm run gen-data-json",
25
- "version": "node scripts/set-version.js",
26
25
  "build": "tsc && node build.mjs && npm pack",
27
26
  "build:image": "npm run build && docker buildx build --output type=docker --tag pepr:dev .",
27
+ "set:version": "node scripts/set-version.js",
28
28
  "test": "npm run test:unit && npm run test:journey",
29
29
  "test:unit": "npm run gen-data-json && jest src --coverage --detectOpenHandles --coverageDirectory=./coverage --testPathIgnorePatterns='cosign.e2e.test.ts'",
30
30
  "test:integration": "npm run test:integration:prep && npm run test:integration:run",
@@ -57,8 +57,8 @@
57
57
  "sigstore": "3.0.0"
58
58
  },
59
59
  "devDependencies": {
60
- "@commitlint/cli": "19.6.1",
61
- "@commitlint/config-conventional": "19.6.0",
60
+ "@commitlint/cli": "19.7.1",
61
+ "@commitlint/config-conventional": "19.7.1",
62
62
  "@fast-check/jest": "^2.0.1",
63
63
  "@jest/globals": "29.7.0",
64
64
  "@types/eslint": "9.6.1",
@@ -72,20 +72,24 @@
72
72
  "husky": "^9.1.6",
73
73
  "jest": "29.7.0",
74
74
  "js-yaml": "^4.1.0",
75
+ "shellcheck": "^3.0.0",
75
76
  "ts-jest": "29.2.5",
76
77
  "undici": "^7.0.1"
77
78
  },
79
+ "overrides": {
80
+ "glob": "^9.0.0"
81
+ },
78
82
  "peerDependencies": {
79
- "@typescript-eslint/eslint-plugin": "7.18.0",
80
- "@typescript-eslint/parser": "7.18.0",
81
83
  "@types/prompts": "2.4.9",
84
+ "@typescript-eslint/eslint-plugin": "8.23.0",
85
+ "@typescript-eslint/parser": "8.23.0",
86
+ "commander": "13.1.0",
87
+ "esbuild": "0.24.2",
82
88
  "eslint": "8.57.0",
83
- "commander": "12.1.0",
84
- "esbuild": "0.24.0",
85
89
  "node-forge": "1.3.1",
86
90
  "prettier": "3.4.2",
87
91
  "prompts": "2.4.2",
88
- "typescript": "^5.3.3",
89
- "uuid": "11.0.3"
92
+ "typescript": "5.7.3",
93
+ "uuid": "11.0.5"
90
94
  }
91
95
  }
@@ -12,6 +12,34 @@ import { generateAllYaml } from "../lib/assets/yaml/generateAllYaml";
12
12
  import { webhookConfigGenerator } from "../lib/assets/webhooks";
13
13
  import { generateZarfYamlGeneric } from "../lib/assets/yaml/generateZarfYaml";
14
14
 
15
+ interface ImageOptions {
16
+ customImage?: string;
17
+ registryInfo?: string;
18
+ peprVersion?: string;
19
+ registry?: string;
20
+ }
21
+ /**
22
+ * Assign image string
23
+ * @param imageOptions CLI options for image
24
+ * @returns image string
25
+ */
26
+ export function assignImage(imageOptions: ImageOptions): string {
27
+ const { customImage, registryInfo, peprVersion, registry } = imageOptions;
28
+ if (customImage) {
29
+ return customImage;
30
+ }
31
+
32
+ if (registryInfo) {
33
+ return `${registryInfo}/custom-pepr-controller:${peprVersion}`;
34
+ }
35
+
36
+ if (registry) {
37
+ return checkIronBankImage(registry, "", peprVersion!);
38
+ }
39
+
40
+ return "";
41
+ }
42
+
15
43
  export type Reloader = (opts: BuildResult<BuildOptions>) => void | Promise<void>;
16
44
  /**
17
45
  * Determine the RBAC mode based on the CLI options and the module's config
@@ -114,19 +142,6 @@ export async function handleCustomImageBuild(
114
142
  }
115
143
  }
116
144
 
117
- /**
118
- * Disables embedding of deployment files into output module
119
- * @param embed
120
- * @param path
121
- * @returns
122
- */
123
- export function handleEmbedding(embed: boolean, path: string): void {
124
- if (!embed) {
125
- console.info(`✅ Module built successfully at ${path}`);
126
- return;
127
- }
128
- }
129
-
130
145
  /**
131
146
  * Check if the capability names are valid
132
147
  * @param capabilities The capabilities to check
package/src/cli/build.ts CHANGED
@@ -11,23 +11,21 @@ import { RootCmd } from "./root";
11
11
  import { Option } from "commander";
12
12
  import { parseTimeout } from "../lib/helpers";
13
13
  import { peprFormat } from "./format";
14
+ import { ModuleConfig } from "../lib/core/module";
14
15
  import {
15
16
  watchForChanges,
16
17
  determineRbacMode,
17
- handleEmbedding,
18
+ assignImage,
18
19
  handleCustomOutputDir,
19
20
  handleValidCapabilityNames,
20
21
  handleCustomImageBuild,
21
- checkIronBankImage,
22
22
  validImagePullSecret,
23
23
  generateYamlAndWriteToDisk,
24
24
  } from "./build.helpers";
25
- import { ModuleConfig } from "../lib/core/module";
26
25
 
27
26
  const peprTS = "pepr.ts";
28
27
  let outputDir: string = "dist";
29
28
  export type Reloader = (opts: BuildResult<BuildOptions>) => void | Promise<void>;
30
-
31
29
  export type PeprNestedFields = Pick<
32
30
  ModuleConfig,
33
31
  | "uuid"
@@ -64,7 +62,7 @@ type BuildModuleReturn = {
64
62
  path: string;
65
63
  cfg: PeprConfig;
66
64
  uuid: string;
67
- } | void;
65
+ };
68
66
 
69
67
  export default function (program: RootCmd): void {
70
68
  program
@@ -134,68 +132,70 @@ export default function (program: RootCmd): void {
134
132
 
135
133
  // Build the module
136
134
  const buildModuleResult = await buildModule(undefined, opts.entryPoint, opts.embed);
137
- if (buildModuleResult?.cfg && buildModuleResult.path && buildModuleResult.uuid) {
138
- const { cfg, path, uuid } = buildModuleResult;
139
- // Files to include in controller image for WASM support
140
- const { includedFiles } = cfg.pepr;
141
-
142
- let image = opts.customImage || "";
143
-
144
- // Check if there is a custom timeout defined
145
- if (opts.timeout !== undefined) {
146
- cfg.pepr.webhookTimeout = opts.timeout;
147
- }
148
-
149
- if (opts.registryInfo !== undefined) {
150
- console.info(`Including ${includedFiles.length} files in controller image.`);
151
-
152
- // for journey test to make sure the image is built
153
- image = `${opts.registryInfo}/custom-pepr-controller:${cfg.pepr.peprVersion}`;
154
-
155
- // only actually build/push if there are files to include
156
- await handleCustomImageBuild(includedFiles, cfg.pepr.peprVersion, cfg.description, image);
157
- }
158
-
159
- // If building without embedding, exit after building
160
- handleEmbedding(opts.embed, path);
161
-
162
- // set the image version if provided
163
- opts.version ? (cfg.pepr.peprVersion = opts.version) : null;
164
-
165
- // Generate a secret for the module
166
- const assets = new Assets(
167
- {
168
- ...cfg.pepr,
169
- appVersion: cfg.version,
170
- description: cfg.description,
171
- alwaysIgnore: {
172
- namespaces: cfg.pepr.alwaysIgnore?.namespaces,
173
- },
174
- // Can override the rbacMode with the CLI option
175
- rbacMode: determineRbacMode(opts, cfg),
176
- },
177
- path,
178
- opts.withPullSecret === "" ? [] : [opts.withPullSecret],
179
- );
180
135
 
181
- // If registry is set to Iron Bank, use Iron Bank image
182
- image = checkIronBankImage(opts.registry, image, cfg.pepr.peprVersion);
136
+ const { cfg, path, uuid } = buildModuleResult!;
137
+ const image = assignImage({
138
+ customImage: opts.customImage,
139
+ registryInfo: opts.registryInfo,
140
+ peprVersion: cfg.pepr.peprVersion,
141
+ registry: opts.registry,
142
+ });
143
+
144
+ // Check if there is a custom timeout defined
145
+ if (opts.timeout !== undefined) {
146
+ cfg.pepr.webhookTimeout = opts.timeout;
147
+ }
183
148
 
184
- // if image is a custom image, use that instead of the default
185
- image !== "" ? (assets.image = image) : null;
149
+ if (opts.registryInfo !== undefined) {
150
+ console.info(`Including ${cfg.pepr.includedFiles.length} files in controller image.`);
151
+ // for journey test to make sure the image is built
186
152
 
187
- // Ensure imagePullSecret is valid
188
- validImagePullSecret(opts.withPullSecret);
153
+ // only actually build/push if there are files to include
154
+ await handleCustomImageBuild(
155
+ cfg.pepr.includedFiles,
156
+ cfg.pepr.peprVersion,
157
+ cfg.description,
158
+ image,
159
+ );
160
+ }
189
161
 
190
- handleValidCapabilityNames(assets.capabilities);
191
- await generateYamlAndWriteToDisk({
192
- uuid,
193
- outputDir,
194
- imagePullSecret: opts.withPullSecret,
195
- zarf: opts.zarf,
196
- assets,
197
- });
162
+ // If building without embedding, exit after building
163
+ if (!opts.embed) {
164
+ console.info(`✅ Module built successfully at ${path}`);
165
+ return;
198
166
  }
167
+ // set the image version if provided
168
+ if (opts.version) cfg.pepr.peprVersion = opts.version;
169
+
170
+ // Generate a secret for the module
171
+ const assets = new Assets(
172
+ {
173
+ ...cfg.pepr,
174
+ appVersion: cfg.version,
175
+ description: cfg.description,
176
+ alwaysIgnore: {
177
+ namespaces: cfg.pepr.alwaysIgnore?.namespaces,
178
+ },
179
+ // Can override the rbacMode with the CLI option
180
+ rbacMode: determineRbacMode(opts, cfg),
181
+ },
182
+ path,
183
+ opts.withPullSecret === "" ? [] : [opts.withPullSecret],
184
+ );
185
+
186
+ if (image !== "") assets.image = image;
187
+
188
+ // Ensure imagePullSecret is valid
189
+ validImagePullSecret(opts.withPullSecret);
190
+
191
+ handleValidCapabilityNames(assets.capabilities);
192
+ await generateYamlAndWriteToDisk({
193
+ uuid,
194
+ outputDir,
195
+ imagePullSecret: opts.withPullSecret,
196
+ zarf: opts.zarf,
197
+ assets,
198
+ });
199
199
  });
200
200
  }
201
201
 
@@ -218,7 +218,7 @@ export async function loadModule(entryPoint = peprTS): Promise<LoadModuleReturn>
218
218
  try {
219
219
  await fs.access(cfgPath);
220
220
  await fs.access(entryPointPath);
221
- } catch (e) {
221
+ } catch {
222
222
  console.error(
223
223
  `Could not find ${cfgPath} or ${entryPointPath} in the current directory. Please run this command from the root of your module's directory.`,
224
224
  );
@@ -253,7 +253,7 @@ export async function buildModule(
253
253
  reloader?: Reloader,
254
254
  entryPoint = peprTS,
255
255
  embed = true,
256
- ): Promise<BuildModuleReturn> {
256
+ ): Promise<BuildModuleReturn | void> {
257
257
  try {
258
258
  const { cfg, modulePath, path, uuid } = await loadModule(entryPoint);
259
259
 
package/src/cli/deploy.ts CHANGED
@@ -93,6 +93,35 @@ export async function getUserConfirmation(opts: { confirm: boolean }): Promise<b
93
93
  return confirm.confirm ? true : false;
94
94
  }
95
95
 
96
+ async function buildAndDeployModule(image: string, force: boolean): Promise<void> {
97
+ const builtModule = await buildModule();
98
+ if (!builtModule) {
99
+ return;
100
+ }
101
+
102
+ // Generate a secret for the module
103
+ const webhook = new Assets(
104
+ { ...builtModule.cfg.pepr, description: builtModule.cfg.description },
105
+ builtModule.path,
106
+ [],
107
+ );
108
+ webhook.image = image ?? webhook.image;
109
+
110
+ try {
111
+ await webhook.deploy(deployWebhook, force, builtModule.cfg.pepr.webhookTimeout ?? 10);
112
+
113
+ // wait for capabilities to be loaded and test names
114
+ validateCapabilityNames(webhook.capabilities);
115
+
116
+ // Wait for the pepr-system resources to be fully up
117
+ await namespaceDeploymentsReady();
118
+ console.info(`✅ Module deployed successfully`);
119
+ } catch (e) {
120
+ console.error(`Error deploying module:`, e);
121
+ process.exit(1);
122
+ }
123
+ }
124
+
96
125
  export default function (program: RootCmd): void {
97
126
  program
98
127
  .command("deploy")
@@ -117,33 +146,10 @@ export default function (program: RootCmd): void {
117
146
  return;
118
147
  }
119
148
 
120
- (await getUserConfirmation(opts)) || process.exit(0);
121
-
122
- const builtModule = await buildModule();
123
- if (!builtModule) {
124
- return;
149
+ if (!(await getUserConfirmation(opts))) {
150
+ process.exit(0);
125
151
  }
126
152
 
127
- // Generate a secret for the module
128
- const webhook = new Assets(
129
- { ...builtModule.cfg.pepr, description: builtModule.cfg.description },
130
- builtModule.path,
131
- [],
132
- );
133
- webhook.image = opts.image ?? webhook.image;
134
-
135
- try {
136
- await webhook.deploy(deployWebhook, opts.force, builtModule.cfg.pepr.webhookTimeout ?? 10);
137
-
138
- // wait for capabilities to be loaded and test names
139
- validateCapabilityNames(webhook.capabilities);
140
-
141
- // Wait for the pepr-system resources to be fully up
142
- await namespaceDeploymentsReady();
143
- console.info(`✅ Module deployed successfully`);
144
- } catch (e) {
145
- console.error(`Error deploying module:`, e);
146
- process.exit(1);
147
- }
153
+ await buildAndDeployModule(opts.image, opts.force);
148
154
  });
149
155
  }
@@ -107,7 +107,7 @@ const doPostInitActions = (dirName: string): void => {
107
107
  execSync("code .", {
108
108
  stdio: "inherit",
109
109
  });
110
- } catch (e) {
111
- // vscode not found, do nothing
110
+ } catch {
111
+ console.warn("VSCode was not found, IDE will not automatically open.");
112
112
  }
113
113
  };
@@ -6,16 +6,17 @@ import { inspect } from "util";
6
6
  import { v4 as uuidv4, v5 as uuidv5 } from "uuid";
7
7
 
8
8
  import eslintJSON from "../../templates/.eslintrc.template.json";
9
+ import peprSnippetsJSON from "../../templates/pepr.code-snippets.json";
9
10
  import prettierJSON from "../../templates/.prettierrc.json";
10
11
  import samplesJSON from "../../templates/capabilities/hello-pepr.samples.json";
11
- import { gitIgnore, helloPeprTS, packageJSON, peprTS, readmeMd } from "../../templates/data.json";
12
- import peprSnippetsJSON from "../../templates/pepr.code-snippets.json";
13
12
  import settingsJSON from "../../templates/settings.json";
14
13
  import tsConfigJSON from "../../templates/tsconfig.module.json";
15
- import { sanitizeName } from "./utils";
14
+ import { CustomLabels } from "../../lib/core/module";
16
15
  import { InitOptions } from "../types";
17
- import { V1PolicyRule as PolicyRule } from "@kubernetes/client-node";
18
16
  import { OnError, RbacMode } from "./enums";
17
+ import { V1PolicyRule as PolicyRule } from "@kubernetes/client-node";
18
+ import { gitIgnore, helloPeprTS, packageJSON, peprTS, readmeMd } from "../../templates/data.json";
19
+ import { sanitizeName } from "./utils";
19
20
 
20
21
  export const { dependencies, devDependencies, peerDependencies, scripts, version } = packageJSON;
21
22
 
@@ -30,7 +31,7 @@ type peprPackageJSON = {
30
31
  uuid: string;
31
32
  onError: OnError;
32
33
  webhookTimeout: number;
33
- customLabels: { namespace: Record<string, string> };
34
+ customLabels: CustomLabels;
34
35
  alwaysIgnore: { namespaces: string[] };
35
36
  includedFiles: string[];
36
37
  env: object;
@@ -38,7 +38,7 @@ export async function setName(name?: string): Promise<Answers<string>> {
38
38
  await fs.access(name, fs.constants.F_OK);
39
39
 
40
40
  return "A directory with this name already exists";
41
- } catch (e) {
41
+ } catch {
42
42
  return val.length > 2 || "The name must be at least 3 characters long";
43
43
  }
44
44
  },
@@ -24,8 +24,7 @@ export function clusterRole(
24
24
  const rbacMap = createRBACMap(capabilities);
25
25
  // Generate scoped rules from rbacMap
26
26
  const scopedRules = Object.keys(rbacMap).map(key => {
27
- let group: string;
28
- key.split("/").length < 3 ? (group = "") : (group = key.split("/")[0]);
27
+ const group: string = key.split("/").length < 3 ? "" : key.split("/")[0];
29
28
 
30
29
  return {
31
30
  apiGroups: [group],
@@ -15,7 +15,7 @@ import { Binding } from "../types";
15
15
 
16
16
  export const peprIgnoreNamespaces: string[] = ["kube-system", "pepr-system"];
17
17
 
18
- const validateRule = (binding: Binding, isMutateWebhook: boolean): V1RuleWithOperations | undefined => {
18
+ export const validateRule = (binding: Binding, isMutateWebhook: boolean): V1RuleWithOperations | undefined => {
19
19
  const { event, kind, isMutate, isValidate } = binding;
20
20
 
21
21
  // Skip invalid bindings based on webhook type
@@ -5,17 +5,15 @@ import { dumpYaml } from "@kubernetes/client-node";
5
5
  import { clusterRole } from "../rbac";
6
6
  import { promises as fs } from "fs";
7
7
 
8
- type CommonOverrideValues = {
8
+ type ChartOverrides = {
9
9
  apiToken: string;
10
10
  capabilities: CapabilityExport[];
11
11
  config: ModuleConfig;
12
12
  hash: string;
13
13
  name: string;
14
- };
15
-
16
- type ChartOverrides = CommonOverrideValues & {
17
14
  image: string;
18
15
  };
16
+
19
17
  // Helm Chart overrides file (values.yaml) generated from assets
20
18
  export async function overridesFile(
21
19
  { hash, name, image, config, apiToken, capabilities }: ChartOverrides,
@@ -34,7 +32,7 @@ export async function overridesFile(
34
32
  hash,
35
33
  namespace: {
36
34
  annotations: {},
37
- labels: {
35
+ labels: config.customLabels?.namespace ?? {
38
36
  "pepr.dev": "",
39
37
  },
40
38
  },
@@ -16,6 +16,12 @@ import { StoreController } from "./store";
16
16
  import { AdmissionRequest } from "../types";
17
17
  import { karForMutate, karForValidate, KubeAdmissionReview } from "./index.util";
18
18
 
19
+ export interface ControllerHooks {
20
+ beforeHook?: (req: AdmissionRequest) => void;
21
+ afterHook?: (res: MutateResponse | ValidateResponse) => void;
22
+ onReady?: () => void;
23
+ }
24
+
19
25
  if (!process.env.PEPR_NODE_WARNINGS) {
20
26
  process.removeAllListeners("warning");
21
27
  }
@@ -39,20 +45,17 @@ export class Controller {
39
45
  readonly #beforeHook?: (req: AdmissionRequest) => void;
40
46
  readonly #afterHook?: (res: MutateResponse | ValidateResponse) => void;
41
47
 
42
- constructor(
43
- config: ModuleConfig,
44
- capabilities: Capability[],
45
- beforeHook?: (req: AdmissionRequest) => void,
46
- afterHook?: (res: MutateResponse | ValidateResponse) => void,
47
- onReady?: () => void,
48
- ) {
48
+ constructor(config: ModuleConfig, capabilities: Capability[], hooks: ControllerHooks = {}) {
49
+ const { beforeHook, afterHook, onReady } = hooks;
49
50
  this.#config = config;
50
51
  this.#capabilities = capabilities;
51
52
 
52
53
  // Initialize the Pepr store for each capability
53
54
  new StoreController(capabilities, `pepr-${config.uuid}-store`, () => {
54
55
  this.#bindEndpoints();
55
- onReady && onReady();
56
+ if (typeof onReady === "function") {
57
+ onReady();
58
+ }
56
59
  Log.info("✅ Controller startup complete");
57
60
  // Initialize the schedule store for each capability
58
61
  new StoreController(capabilities, `pepr-${config.uuid}-schedule`, () => {
@@ -223,7 +226,9 @@ export class Controller {
223
226
  Log.debug({ ...reqMetadata, request }, "Incoming request body");
224
227
 
225
228
  // Run the before hook if it exists
226
- this.#beforeHook && this.#beforeHook(request || {});
229
+ if (typeof this.#beforeHook === "function") {
230
+ this.#beforeHook(request || {});
231
+ }
227
232
 
228
233
  // Process the request
229
234
  const response: MutateResponse | ValidateResponse[] =
@@ -233,7 +238,9 @@ export class Controller {
233
238
 
234
239
  // Run the after hook if it exists
235
240
  [response].flat().map(res => {
236
- this.#afterHook && this.#afterHook(res);
241
+ if (typeof this.#afterHook === "function") {
242
+ this.#afterHook(res);
243
+ }
237
244
  Log.info({ ...reqMetadata, res }, "Check response");
238
245
  });
239
246
 
@@ -2,7 +2,7 @@
2
2
  // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
3
  import { clone } from "ramda";
4
4
  import { Capability } from "./capability";
5
- import { Controller } from "../controller";
5
+ import { Controller, ControllerHooks } from "../controller";
6
6
  import { ValidateError } from "../errors";
7
7
  import { MutateResponse, ValidateResponse, WebhookIgnore } from "../k8s";
8
8
  import { CapabilityExport, AdmissionRequest } from "../types";
@@ -12,37 +12,41 @@ import { V1PolicyRule as PolicyRule } from "@kubernetes/client-node";
12
12
  import { resolveIgnoreNamespaces } from "../assets/webhooks";
13
13
 
14
14
  /** Custom Labels Type for package.json */
15
- export interface CustomLabels {
16
- namespace?: Record<string, string>;
17
- }
18
- /** Global configuration for the Pepr runtime. */
19
- export type ModuleConfig = {
15
+
16
+ export type CustomLabels = { namespace: Record<string, string> } | Record<string, never>;
17
+
18
+ /** Configuration that MAY be set a Pepr module's package.json. */
19
+ export type ModuleConfigOptions = {
20
20
  /** The Pepr version this module uses */
21
- peprVersion?: string;
21
+ peprVersion: string;
22
22
  /** The user-defined version of the module */
23
- appVersion?: string;
24
- /** A unique identifier for this Pepr module. This is automatically generated by Pepr. */
25
- uuid: string;
23
+ appVersion: string;
26
24
  /** A description of the Pepr module and what it does. */
27
- description?: string;
25
+ description: string;
28
26
  /** The webhookTimeout */
29
- webhookTimeout?: number;
27
+ webhookTimeout: number;
30
28
  /** Reject K8s resource AdmissionRequests on error. */
31
- onError?: string;
32
- /** Configure global exclusions that will never be processed by Pepr. */
33
- alwaysIgnore: WebhookIgnore;
29
+ onError: string;
34
30
  /** Define the log level for the in-cluster controllers */
35
- logLevel?: string;
31
+ logLevel: string;
36
32
  /** Propagate env variables to in-cluster controllers */
37
- env?: Record<string, string>;
38
- /** Custom Labels for Kubernetes Objects */
39
- customLabels?: CustomLabels;
33
+ env: Record<string, string>;
40
34
  /** Custom RBAC rules */
41
- rbac?: PolicyRule[];
35
+ rbac: PolicyRule[];
42
36
  /** The RBAC mode; if "scoped", generates scoped rules, otherwise uses wildcard rules. */
43
- rbacMode?: string;
37
+ rbacMode: string;
38
+ /** Custom Labels for Kubernetes Objects */
39
+ customLabels: CustomLabels;
44
40
  };
45
41
 
42
+ /** Global configuration for the Pepr runtime. */
43
+ export type ModuleConfig = {
44
+ /** A unique identifier for this Pepr module. This is automatically generated by Pepr. */
45
+ uuid: string;
46
+ /** Configure global exclusions that will never be processed by Pepr. */
47
+ alwaysIgnore: WebhookIgnore;
48
+ } & Partial<ModuleConfigOptions>;
49
+
46
50
  export type PackageJSON = {
47
51
  description: string;
48
52
  pepr: ModuleConfig;
@@ -110,17 +114,23 @@ export class PeprModule {
110
114
  return;
111
115
  }
112
116
 
113
- this.#controller = new Controller(config, capabilities, opts.beforeHook, opts.afterHook, () => {
114
- // Wait for the controller to be ready before setting up watches
115
- if (isWatchMode() || isDevMode()) {
116
- try {
117
- setupWatch(capabilities, resolveIgnoreNamespaces(pepr?.alwaysIgnore?.namespaces));
118
- } catch (e) {
119
- Log.error(e, "Error setting up watch");
120
- process.exit(1);
117
+ const controllerHooks: ControllerHooks = {
118
+ beforeHook: opts.beforeHook,
119
+ afterHook: opts.afterHook,
120
+ onReady: (): void => {
121
+ // Wait for the controller to be ready before setting up watches
122
+ if (isWatchMode() || isDevMode()) {
123
+ try {
124
+ setupWatch(capabilities, resolveIgnoreNamespaces(pepr?.alwaysIgnore?.namespaces));
125
+ } catch (e) {
126
+ Log.error(e, "Error setting up watch");
127
+ process.exit(1);
128
+ }
121
129
  }
122
- }
123
- });
130
+ },
131
+ };
132
+
133
+ this.#controller = new Controller(config, capabilities, controllerHooks);
124
134
 
125
135
  // Stop processing if deferStart is set to true
126
136
  if (opts.deferStart) {
@@ -91,7 +91,7 @@ export class OnSchedule implements Schedule {
91
91
  lastTimestamp: new Date(),
92
92
  name: this.name,
93
93
  };
94
- this.store && this.store.setItem(this.name, JSON.stringify(schedule));
94
+ if (this.store) this.store.setItem(this.name, JSON.stringify(schedule));
95
95
  }
96
96
 
97
97
  /**
@@ -170,6 +170,6 @@ export class OnSchedule implements Schedule {
170
170
  clearInterval(this.intervalId);
171
171
  this.intervalId = null;
172
172
  }
173
- this.store && this.store.removeItem(this.name);
173
+ if (this.store) this.store.removeItem(this.name);
174
174
  }
175
175
  }
@@ -110,11 +110,12 @@ export class Storage implements PeprStore {
110
110
  };
111
111
 
112
112
  clear = (): void => {
113
- Object.keys(this.#store).length > 0 &&
113
+ if (Object.keys(this.#store).length > 0) {
114
114
  this.#dispatchUpdate(
115
115
  "remove",
116
116
  Object.keys(this.#store).map(key => pointer.escape(key)),
117
117
  );
118
+ }
118
119
  };
119
120
 
120
121
  removeItem = (key: string): void => {