pepr 0.43.0 → 0.44.0-nightly.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/package.json CHANGED
@@ -15,16 +15,16 @@
15
15
  "!src/**/*.test.ts",
16
16
  "!dist/**/*.test.d.ts*"
17
17
  ],
18
- "version": "0.43.0",
18
+ "version": "0.44.0-nightly.1",
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",
@@ -88,4 +88,4 @@
88
88
  "typescript": "^5.3.3",
89
89
  "uuid": "11.0.3"
90
90
  }
91
- }
91
+ }
@@ -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
+ opts.version ? (cfg.pepr.peprVersion = opts.version) : null;
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
+ image !== "" ? (assets.image = image) : null;
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
 
@@ -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/dev.ts CHANGED
@@ -11,6 +11,7 @@ import { buildModule, loadModule } from "./build";
11
11
  import { deployWebhook } from "../lib/assets/deploy";
12
12
  import { promises as fs } from "fs";
13
13
  import { validateCapabilityNames } from "../lib/helpers";
14
+
14
15
  export default function (program: RootCmd): void {
15
16
  program
16
17
  .command("dev")
@@ -42,6 +43,7 @@ export default function (program: RootCmd): void {
42
43
  description: cfg.description,
43
44
  },
44
45
  path,
46
+ [],
45
47
  opts.host,
46
48
  );
47
49
 
@@ -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;
@@ -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;
@@ -4,7 +4,7 @@
4
4
  import jsonPatch from "fast-json-patch";
5
5
  import { kind, KubernetesObject } from "kubernetes-fluent-client";
6
6
  import { clone } from "ramda";
7
-
7
+ import { MeasureWebhookTimeout } from "../telemetry/webhookTimeouts";
8
8
  import { Capability } from "../core/capability";
9
9
  import { shouldSkipRequest } from "../filter/filter";
10
10
  import { MutateResponse } from "../k8s";
@@ -15,7 +15,8 @@ import { PeprMutateRequest } from "../mutate-request";
15
15
  import { base64Encode, convertFromBase64Map, convertToBase64Map } from "../utils";
16
16
  import { OnError } from "../../cli/init/enums";
17
17
  import { resolveIgnoreNamespaces } from "../assets/webhooks";
18
-
18
+ import { Operation } from "fast-json-patch";
19
+ import { WebhookType } from "../enums";
19
20
  export interface Bindable {
20
21
  req: AdmissionRequest;
21
22
  config: ModuleConfig;
@@ -139,6 +140,8 @@ export async function mutateProcessor(
139
140
  req: AdmissionRequest,
140
141
  reqMetadata: Record<string, string>,
141
142
  ): Promise<MutateResponse> {
143
+ const webhookTimer = new MeasureWebhookTimeout(WebhookType.MUTATE);
144
+ webhookTimer.start(config.webhookTimeout);
142
145
  let response: MutateResponse = {
143
146
  uid: req.uid,
144
147
  warnings: [],
@@ -207,6 +210,14 @@ export async function mutateProcessor(
207
210
  // Compare the original request to the modified request to get the patches
208
211
  const patches = jsonPatch.compare(req.object, transformed);
209
212
 
213
+ updateResponsePatchAndWarnings(patches, response);
214
+
215
+ Log.debug({ ...reqMetadata, patches }, `Patches generated`);
216
+ webhookTimer.stop();
217
+ return response;
218
+ }
219
+
220
+ export function updateResponsePatchAndWarnings(patches: Operation[], response: MutateResponse): void {
210
221
  // Only add the patch if there are patches to apply
211
222
  if (patches.length > 0) {
212
223
  response.patchType = "JSONPatch";
@@ -219,8 +230,4 @@ export async function mutateProcessor(
219
230
  if (response.warnings && response.warnings.length < 1) {
220
231
  delete response.warnings;
221
232
  }
222
-
223
- Log.debug({ ...reqMetadata, patches }, `Patches generated`);
224
-
225
- return response;
226
233
  }
@@ -11,6 +11,8 @@ import { convertFromBase64Map } from "../utils";
11
11
  import { PeprValidateRequest } from "../validate-request";
12
12
  import { ModuleConfig } from "../core/module";
13
13
  import { resolveIgnoreNamespaces } from "../assets/webhooks";
14
+ import { MeasureWebhookTimeout } from "../telemetry/webhookTimeouts";
15
+ import { WebhookType } from "../enums";
14
16
 
15
17
  export async function processRequest(
16
18
  binding: Binding,
@@ -58,12 +60,13 @@ export async function validateProcessor(
58
60
  req: AdmissionRequest,
59
61
  reqMetadata: Record<string, string>,
60
62
  ): Promise<ValidateResponse[]> {
63
+ const webhookTimer = new MeasureWebhookTimeout(WebhookType.VALIDATE);
64
+ webhookTimer.start(config.webhookTimeout);
61
65
  const wrapped = new PeprValidateRequest(req);
62
66
  const response: ValidateResponse[] = [];
63
67
 
64
68
  // If the resource is a secret, decode the data
65
- const isSecret = req.kind.version === "v1" && req.kind.kind === "Secret";
66
- if (isSecret) {
69
+ if (req.kind.version === "v1" && req.kind.kind === "Secret") {
67
70
  convertFromBase64Map(wrapped.Raw as unknown as kind.Secret);
68
71
  }
69
72
 
@@ -94,6 +97,6 @@ export async function validateProcessor(
94
97
  response.push(resp);
95
98
  }
96
99
  }
97
-
100
+ webhookTimer.stop();
98
101
  return response;
99
102
  }
@@ -0,0 +1 @@
1
+ export const getNow = (): number => performance.now();
@@ -0,0 +1,34 @@
1
+ import { metricsCollector } from "./metrics";
2
+ import { getNow } from "./timeUtils";
3
+ import Log from "./logger";
4
+ import { WebhookType } from "../enums";
5
+ export class MeasureWebhookTimeout {
6
+ #startTime: number | null = null;
7
+ #webhookType: string;
8
+ timeout: number = 0;
9
+
10
+ constructor(webhookType: WebhookType) {
11
+ this.#webhookType = webhookType;
12
+ metricsCollector.addCounter(`${webhookType}_timeouts`, `Number of ${webhookType} webhook timeouts`);
13
+ }
14
+
15
+ start(timeout: number = 10): void {
16
+ this.#startTime = getNow();
17
+ this.timeout = timeout;
18
+ Log.info(`Starting timer at ${this.#startTime}`);
19
+ }
20
+
21
+ stop(): void {
22
+ if (this.#startTime === null) {
23
+ throw new Error("Timer was not started before calling stop.");
24
+ }
25
+
26
+ const elapsedTime = getNow() - this.#startTime;
27
+ Log.info(`Webhook ${this.#startTime} took ${elapsedTime}ms`);
28
+ this.#startTime = null;
29
+
30
+ if (elapsedTime > this.timeout) {
31
+ metricsCollector.incCounter(`${this.#webhookType}_timeouts`);
32
+ }
33
+ }
34
+ }