pepr 0.49.0 → 0.50.0-nightly.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 (66) hide show
  1. package/dist/cli/crd/generate.d.ts.map +1 -1
  2. package/dist/cli/crd/messages.d.ts +11 -0
  3. package/dist/cli/crd/messages.d.ts.map +1 -0
  4. package/dist/cli/deploy.d.ts.map +1 -1
  5. package/dist/cli/docs/cli.helper.d.ts +12 -0
  6. package/dist/cli/docs/cli.helper.d.ts.map +1 -0
  7. package/dist/cli/docs/markdown.helper.d.ts +8 -0
  8. package/dist/cli/docs/markdown.helper.d.ts.map +1 -0
  9. package/dist/cli/format/format.helpers.d.ts.map +1 -0
  10. package/dist/cli/{format.d.ts → format/index.d.ts} +2 -2
  11. package/dist/cli/format/index.d.ts.map +1 -0
  12. package/dist/cli/init/templates.d.ts +12 -18
  13. package/dist/cli/init/templates.d.ts.map +1 -1
  14. package/dist/cli/update/index.d.ts +3 -0
  15. package/dist/cli/update/index.d.ts.map +1 -0
  16. package/dist/cli.js +321 -231
  17. package/dist/controller.js +1 -1
  18. package/dist/lib/assets/assets.d.ts.map +1 -1
  19. package/dist/lib/assets/helm.d.ts.map +1 -1
  20. package/dist/lib/assets/ignoredNamespaces.d.ts +2 -0
  21. package/dist/lib/assets/ignoredNamespaces.d.ts.map +1 -0
  22. package/dist/lib/assets/webhooks.d.ts +0 -1
  23. package/dist/lib/assets/webhooks.d.ts.map +1 -1
  24. package/dist/lib/assets/yaml/overridesFile.d.ts.map +1 -1
  25. package/dist/lib/core/module.d.ts.map +1 -1
  26. package/dist/lib/helpers.d.ts +1 -1
  27. package/dist/lib/helpers.d.ts.map +1 -1
  28. package/dist/lib/processors/mutate-processor.d.ts.map +1 -1
  29. package/dist/lib/processors/validate-processor.d.ts.map +1 -1
  30. package/dist/lib/types.d.ts +8 -0
  31. package/dist/lib/types.d.ts.map +1 -1
  32. package/dist/lib.js +86 -83
  33. package/dist/lib.js.map +4 -4
  34. package/dist/sdk/sdk.d.ts +2 -0
  35. package/dist/sdk/sdk.d.ts.map +1 -1
  36. package/package.json +12 -10
  37. package/src/cli/build.ts +1 -1
  38. package/src/cli/crd/generate.ts +8 -7
  39. package/src/cli/crd/messages.ts +15 -0
  40. package/src/cli/deploy.ts +12 -2
  41. package/src/cli/{format.ts → format/index.ts} +2 -2
  42. package/src/cli/init/templates.ts +29 -3
  43. package/src/cli/{update.ts → update/index.ts} +30 -3
  44. package/src/lib/assets/assets.ts +7 -0
  45. package/src/lib/assets/helm.ts +28 -1
  46. package/src/lib/assets/ignoredNamespaces.ts +17 -0
  47. package/src/lib/assets/webhooks.ts +6 -17
  48. package/src/lib/assets/yaml/overridesFile.ts +7 -2
  49. package/src/lib/controller/createHooks.ts +1 -1
  50. package/src/lib/core/module.ts +3 -1
  51. package/src/lib/helpers.ts +16 -6
  52. package/src/lib/processors/mutate-processor.ts +6 -2
  53. package/src/lib/processors/validate-processor.ts +9 -3
  54. package/src/lib/types.ts +8 -0
  55. package/src/sdk/sdk.ts +10 -7
  56. package/src/templates/capabilities/hello-pepr.ts +1 -1
  57. package/src/templates/eslint.config.mjs +45 -0
  58. package/src/templates/package.json +10 -0
  59. package/dist/cli/format.d.ts.map +0 -1
  60. package/dist/cli/format.helpers.d.ts.map +0 -1
  61. package/dist/cli/update.d.ts +0 -3
  62. package/dist/cli/update.d.ts.map +0 -1
  63. package/src/templates/.eslintrc.json +0 -6
  64. package/src/templates/.eslintrc.template.json +0 -18
  65. /package/dist/cli/{format.helpers.d.ts → format/format.helpers.d.ts} +0 -0
  66. /package/src/cli/{format.helpers.ts → format/format.helpers.ts} +0 -0
package/dist/sdk/sdk.d.ts CHANGED
@@ -38,6 +38,8 @@ export declare function getOwnerRefFrom(customResource: GenericKind, blockOwnerD
38
38
  *
39
39
  * @param name the name of the resource to sanitize
40
40
  * @returns the sanitized resource name
41
+ *
42
+ * https://kubernetes.io/docs/concepts/overview/working-with-objects/names/
41
43
  */
42
44
  export declare function sanitizeResourceName(name: string): string;
43
45
  //# sourceMappingURL=sdk.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/sdk/sdk.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,EAAE,WAAW,EAAO,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAElE;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,EACpE,aAAa,CAAC,EAAE,YAAY,GAAG,gBAAgB,GAAG,qBAAqB,GACtE,WAAW,EAAE,CAef;AAED;;;;;;;;;GASG;AAEH,wBAAsB,UAAU,CAC9B,EAAE,EAAE,WAAW,EACf,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,EAC9B,OAAO,EAAE;IACP,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;CAC3B,GACA,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,cAAc,EAAE,WAAW,EAC3B,kBAAkB,CAAC,EAAE,OAAO,EAC5B,UAAU,CAAC,EAAE,OAAO,GACnB,gBAAgB,EAAE,CAcpB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAYzD"}
1
+ {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/sdk/sdk.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,EAAE,WAAW,EAAO,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAElE;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,EACpE,aAAa,CAAC,EAAE,YAAY,GAAG,gBAAgB,GAAG,qBAAqB,GACtE,WAAW,EAAE,CAef;AAED;;;;;;;;;GASG;AAEH,wBAAsB,UAAU,CAC9B,EAAE,EAAE,WAAW,EACf,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,EAC9B,OAAO,EAAE;IACP,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;CAC3B,GACA,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,cAAc,EAAE,WAAW,EAC3B,kBAAkB,CAAC,EAAE,OAAO,EAC5B,UAAU,CAAC,EAAE,OAAO,GACnB,gBAAgB,EAAE,CAcpB;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAazD"}
package/package.json CHANGED
@@ -14,9 +14,10 @@
14
14
  "/src",
15
15
  "!src/**/*.test.ts",
16
16
  "!src/fixtures/**",
17
- "!dist/**/*.test.d.ts*"
17
+ "!dist/**/*.test.d.ts*",
18
+ "!src/cli/docs/**"
18
19
  ],
19
- "version": "0.49.0",
20
+ "version": "0.50.0-nightly.0",
20
21
  "main": "dist/lib.js",
21
22
  "types": "dist/lib.d.ts",
22
23
  "scripts": {
@@ -29,6 +30,7 @@
29
30
  "set:version": "node scripts/set-version.js",
30
31
  "test": "npm run test:unit && npm run test:journey && npm run test:journey-wasm",
31
32
  "test:artifacts": "npm run build && jest src/build-artifact.test.ts",
33
+ "test:docs": "jest --verbose src/cli/docs/*.test.ts",
32
34
  "test:integration": "npm run test:integration:prep && npm run test:integration:run",
33
35
  "test:integration:prep": "./integration/prep.sh",
34
36
  "test:integration:run": "jest --maxWorkers=4 integration",
@@ -42,9 +44,9 @@
42
44
  "test:journey:run-wasm": "jest --detectOpenHandles journey/entrypoint-wasm.test.ts",
43
45
  "test:journey:unicorn": "npm run test:journey:k3d && npm run test:journey:image:unicorn && npm run test:journey:run",
44
46
  "test:journey:upgrade": "npm run test:journey:k3d && npm run test:journey:image && jest --detectOpenHandles journey/pepr-upgrade.test.ts",
45
- "test:unit": "npm run gen-data-json && jest src --coverage --detectOpenHandles --coverageDirectory=./coverage --testPathIgnorePatterns='build-artifact.test.ts'",
46
- "format:check": "eslint src && prettier --config .prettierrc src --check",
47
- "format:fix": "eslint src --fix && prettier --config .prettierrc src --write",
47
+ "test:unit": "npm run gen-data-json && jest src --coverage --detectOpenHandles --coverageDirectory=./coverage --testPathIgnorePatterns=\"build-artifact.test.ts|src/cli/docs/.*\\.test\\.ts\"",
48
+ "format:check": "eslint --ignore-pattern src/templates/eslint.config.mjs src && prettier --config .prettierrc src --check",
49
+ "format:fix": "eslint --fix --ignore-pattern src/templates/eslint.config.mjs src && prettier --config .prettierrc src --write",
48
50
  "prepare": "if [ \"$NODE_ENV\" != 'production' ]; then husky; fi"
49
51
  },
50
52
  "dependencies": {
@@ -54,7 +56,7 @@
54
56
  "heredoc": "^1.3.1",
55
57
  "http-status-codes": "^2.3.0",
56
58
  "json-pointer": "^0.6.2",
57
- "kubernetes-fluent-client": "3.5.1",
59
+ "kubernetes-fluent-client": "3.5.3",
58
60
  "pino": "9.6.0",
59
61
  "pino-pretty": "13.0.0",
60
62
  "prom-client": "15.1.3",
@@ -63,8 +65,8 @@
63
65
  "ts-morph": "^25.0.1"
64
66
  },
65
67
  "devDependencies": {
66
- "@commitlint/cli": "19.8.0",
67
- "@commitlint/config-conventional": "19.8.0",
68
+ "@commitlint/cli": "19.8.1",
69
+ "@commitlint/config-conventional": "19.8.1",
68
70
  "@fast-check/jest": "^2.0.1",
69
71
  "@jest/globals": "29.7.0",
70
72
  "@types/eslint": "9.6.1",
@@ -91,11 +93,11 @@
91
93
  "@typescript-eslint/parser": "8.23.0",
92
94
  "commander": "13.1.0",
93
95
  "esbuild": "0.25.0",
94
- "eslint": "8.57.0",
96
+ "eslint": "^9.26.0",
95
97
  "node-forge": "1.3.1",
96
98
  "prettier": "3.4.2",
97
99
  "prompts": "2.4.2",
98
100
  "typescript": "5.7.3",
99
101
  "uuid": "11.0.5"
100
102
  }
101
- }
103
+ }
package/src/cli/build.ts CHANGED
@@ -71,7 +71,7 @@ export default function (program: RootCmd): void {
71
71
  .option("-e, --entry-point [file]", "Specify the entry point file to build with.", peprTS)
72
72
  .option(
73
73
  "-n, --no-embed",
74
- "Disables embedding of deployment files into output module. Useful when creating library modules intended solely for reuse/distribution via NPM.",
74
+ "Disables embedding of deployment files into output module. Useful when creating library modules intended solely for reuse/distribution via NPM.",
75
75
  )
76
76
  .addOption(
77
77
  new Option(
@@ -4,7 +4,7 @@
4
4
  import { Command } from "commander";
5
5
  import fs from "fs";
6
6
  import path from "path";
7
- import { stringify as toYAML } from "yaml";
7
+ import { stringify } from "yaml";
8
8
  import {
9
9
  Project,
10
10
  InterfaceDeclaration,
@@ -17,6 +17,7 @@ import {
17
17
  import { createDirectoryIfNotExists } from "../../lib/filesystemService";
18
18
  import { kind as k } from "kubernetes-fluent-client";
19
19
  import { V1JSONSchemaProps } from "@kubernetes/client-node";
20
+ import { WarningMessages, ErrorMessages } from "./messages";
20
21
 
21
22
  export default new Command("generate")
22
23
  .description("Generate CRD manifests from TypeScript definitions")
@@ -82,13 +83,13 @@ export function processSourceFile(
82
83
  const { kind, fqdn, scope, plural, shortNames } = extractCRDDetails(content, sourceFile);
83
84
 
84
85
  if (!kind) {
85
- console.warn(`Skipping ${sourceFile.getBaseName()}: missing '// Kind: <KindName>' comment`);
86
+ console.warn(WarningMessages.MISSING_KIND_COMMENT(sourceFile.getBaseName()));
86
87
  return;
87
88
  }
88
89
 
89
90
  const spec = sourceFile.getInterface(`${kind}Spec`);
90
91
  if (!spec) {
91
- console.warn(`Skipping ${sourceFile.getBaseName()}: missing interface ${kind}Spec`);
92
+ console.warn(WarningMessages.MISSING_INTERFACE(sourceFile.getBaseName(), kind));
92
93
  return;
93
94
  }
94
95
 
@@ -108,7 +109,7 @@ export function processSourceFile(
108
109
  });
109
110
 
110
111
  const outPath = path.join(outputDir, `${kind.toLowerCase()}.yaml`);
111
- fs.writeFileSync(outPath, toYAML(crd), "utf8");
112
+ fs.writeFileSync(outPath, stringify(crd), "utf8");
112
113
  console.log(`✔ Created ${outPath}`);
113
114
  }
114
115
 
@@ -126,7 +127,7 @@ export function extractDetails(sourceFile: SourceFile): {
126
127
  } {
127
128
  const decl = sourceFile.getVariableDeclaration("details");
128
129
  if (!decl) {
129
- throw new Error(`Missing 'details' variable declaration.`);
130
+ throw new Error(ErrorMessages.MISSING_DETAILS);
130
131
  }
131
132
 
132
133
  const init = decl.getInitializerIfKindOrThrow(SyntaxKind.ObjectLiteralExpression);
@@ -135,7 +136,7 @@ export function extractDetails(sourceFile: SourceFile): {
135
136
  const prop = init.getProperty(key);
136
137
  const value = prop?.getFirstChildByKind(SyntaxKind.StringLiteral)?.getLiteralText();
137
138
  if (!value) {
138
- throw new Error(`Missing or invalid value for required key: '${key}'`);
139
+ throw new Error(ErrorMessages.MISSING_OR_INVALID_KEY(key));
139
140
  }
140
141
  return value;
141
142
  };
@@ -149,7 +150,7 @@ export function extractDetails(sourceFile: SourceFile): {
149
150
  };
150
151
  }
151
152
 
152
- throw new Error(`'scope' must be either "Cluster" or "Namespaced", got "${scope}"`);
153
+ throw new Error(ErrorMessages.INVALID_SCOPE(scope));
153
154
  }
154
155
 
155
156
  export function getJsDocDescription(node: Node): string {
@@ -0,0 +1,15 @@
1
+ export const ErrorMessages = {
2
+ MISSING_DETAILS: "Missing 'details' variable declaration.",
3
+ INVALID_SCOPE: (scope: string): string =>
4
+ `'scope' must be either "Cluster" or "Namespaced", got "${scope}"`,
5
+ MISSING_OR_INVALID_KEY: (key: string): string =>
6
+ `Missing or invalid value for required key: '${key}'`,
7
+ };
8
+
9
+ export const WarningMessages = {
10
+ MISSING_DETAILS: "Missing 'details' variable declaration.",
11
+ MISSING_KIND_COMMENT: (fileName: string): string =>
12
+ `Skipping ${fileName}: missing '// Kind: <KindName>' comment`,
13
+ MISSING_INTERFACE: (fileName: string, kind: string): string =>
14
+ `Skipping ${fileName}: missing interface ${kind}Spec`,
15
+ };
package/src/cli/deploy.ts CHANGED
@@ -11,7 +11,8 @@ import { deployImagePullSecret, deployWebhook } from "../lib/assets/deploy";
11
11
  import { namespaceDeploymentsReady } from "../lib/deploymentChecks";
12
12
  import { sanitizeName } from "./init/utils";
13
13
  import { validateCapabilityNames } from "../lib/helpers";
14
-
14
+ import { namespaceComplianceValidator } from "../lib/helpers";
15
+ import { loadCapabilities } from "../lib/assets/loader";
15
16
  export interface ImagePullSecretDetails {
16
17
  pullSecret?: string;
17
18
  dockerServer?: string;
@@ -106,7 +107,16 @@ async function buildAndDeployModule(image: string, force: boolean): Promise<void
106
107
  [],
107
108
  );
108
109
  webhook.image = image ?? webhook.image;
109
-
110
+ const capabilities = await loadCapabilities(webhook.path);
111
+ for (const capability of capabilities) {
112
+ namespaceComplianceValidator(capability, webhook.alwaysIgnore?.namespaces);
113
+ namespaceComplianceValidator(
114
+ capability,
115
+ webhook.config.admission?.alwaysIgnore?.namespaces,
116
+ false,
117
+ );
118
+ namespaceComplianceValidator(capability, webhook.config.watch?.alwaysIgnore?.namespaces, true);
119
+ }
110
120
  try {
111
121
  await webhook.deploy(deployWebhook, force, builtModule.cfg.pepr.webhookTimeout ?? 10);
112
122
 
@@ -4,13 +4,13 @@
4
4
  import { ESLint } from "eslint";
5
5
  import { formatWithPrettier } from "./format.helpers";
6
6
 
7
- import { RootCmd } from "./root";
7
+ import { RootCmd } from "../root";
8
8
 
9
9
  export default function (program: RootCmd): void {
10
10
  program
11
11
  .command("format")
12
12
  .description("Lint and format this Pepr module")
13
- .option("-v, --validate-only", "Do not modify files, only validate formatting")
13
+ .option("-v, --validate-only", "Do not modify files, only validate formatting.")
14
14
  .action(async opts => {
15
15
  const success = await peprFormat(opts.validateOnly);
16
16
 
@@ -4,8 +4,9 @@
4
4
  import { dumpYaml } from "@kubernetes/client-node";
5
5
  import { inspect } from "util";
6
6
  import { v4 as uuidv4 } from "uuid";
7
+ import { readFileSync } from "fs";
8
+ import path from "path";
7
9
 
8
- import eslintJSON from "../../templates/.eslintrc.template.json";
9
10
  import peprSnippetsJSON from "../../templates/pepr.code-snippets.json";
10
11
  import prettierJSON from "../../templates/.prettierrc.json";
11
12
  import samplesJSON from "../../templates/capabilities/hello-pepr.samples.json";
@@ -33,6 +34,8 @@ export type peprPackageJSON = {
33
34
  webhookTimeout: number;
34
35
  customLabels: CustomLabels;
35
36
  alwaysIgnore: { namespaces: string[] };
37
+ admission: { alwaysIgnore: { namespaces: string[] } };
38
+ watch: { alwaysIgnore: { namespaces: string[] } };
36
39
  includedFiles: string[];
37
40
  env: object;
38
41
  rbac?: PolicyRule[];
@@ -79,6 +82,16 @@ export function genPkgJSON(opts: InitOptions, pgkVerOverride?: string): peprPack
79
82
  alwaysIgnore: {
80
83
  namespaces: [],
81
84
  },
85
+ admission: {
86
+ alwaysIgnore: {
87
+ namespaces: [],
88
+ },
89
+ },
90
+ watch: {
91
+ alwaysIgnore: {
92
+ namespaces: [],
93
+ },
94
+ },
82
95
  includedFiles: [],
83
96
  env: pgkVerOverride ? testEnv : {},
84
97
  },
@@ -147,6 +160,19 @@ export const prettier = {
147
160
  };
148
161
 
149
162
  export const eslint = {
150
- path: ".eslintrc.json",
151
- data: eslintJSON,
163
+ path: "eslint.config.mjs",
164
+ data: readFileSync(
165
+ path.resolve(
166
+ ((): string => {
167
+ const fullPath = __dirname;
168
+ const lengthOfSuffix = "pepr/".length;
169
+ // Find the last occurrence of "pepr/"
170
+ const lastPeprIndex = fullPath.lastIndexOf("pepr/");
171
+ // Return the path up to and including the last "pepr/"
172
+ return fullPath.substring(0, lastPeprIndex + lengthOfSuffix);
173
+ })(),
174
+ "src/templates/eslint.config.mjs",
175
+ ),
176
+ "utf-8",
177
+ ),
152
178
  };
@@ -13,9 +13,9 @@ import {
13
13
  samplesYaml,
14
14
  snippet,
15
15
  tsConfig,
16
- } from "./init/templates";
17
- import { write } from "./init/utils";
18
- import { RootCmd } from "./root";
16
+ } from "../init/templates";
17
+ import { write } from "../init/utils";
18
+ import { RootCmd } from "../root";
19
19
 
20
20
  export default function (program: RootCmd): void {
21
21
  program
@@ -41,6 +41,33 @@ export default function (program: RootCmd): void {
41
41
  console.log("Updating the Pepr module...");
42
42
 
43
43
  try {
44
+ // Check if eslint v8 is a project dependency and warn about future upgrade
45
+ let packageLockContent = "";
46
+ let foundPackageLock = false;
47
+
48
+ try {
49
+ // Try to find package-lock.json in the current directory
50
+ if (fs.existsSync("./package-lock.json")) {
51
+ packageLockContent = fs.readFileSync("./package-lock.json", "utf-8");
52
+ foundPackageLock = true;
53
+ }
54
+ } catch {
55
+ // Ignore errors and continue with installation
56
+ }
57
+
58
+ // If we found the package-lock.json and could read it, check for eslint v8
59
+ if (foundPackageLock && packageLockContent) {
60
+ // Look for eslint version 8.x.x pattern in the file content
61
+ if (
62
+ packageLockContent.indexOf('"eslint":') >= 0 &&
63
+ packageLockContent.match(/"eslint":\s*"[~^]?8\.[0-9]+\.[0-9]+"/)
64
+ ) {
65
+ console.warn(
66
+ "\nWarning: This Pepr module uses ESLint v8. Pepr will be upgraded to use v9 in a future release.\nSee eslint@9.0.0 release notes for more details: https://eslint.org/blog/2024/04/eslint-v9.0.0-released/",
67
+ );
68
+ }
69
+ }
70
+
44
71
  // Update Pepr for the module
45
72
  execSync("npm install pepr@latest", {
46
73
  stdio: "inherit",
@@ -101,7 +101,14 @@ export class Assets {
101
101
  this.capabilities = await loadCapabilities(this.path);
102
102
  // give error if namespaces are not respected
103
103
  for (const capability of this.capabilities) {
104
+ // until deployment, Pepr does not distinguish between watch and admission
104
105
  namespaceComplianceValidator(capability, this.alwaysIgnore?.namespaces);
106
+ namespaceComplianceValidator(
107
+ capability,
108
+ this.config.admission?.alwaysIgnore?.namespaces,
109
+ false,
110
+ );
111
+ namespaceComplianceValidator(capability, this.config.watch?.alwaysIgnore?.namespaces, true);
105
112
  }
106
113
 
107
114
  const code = await fs.readFile(this.path);
@@ -94,7 +94,13 @@ export function watcherDeployTemplate(buildTimestamp: string): string {
94
94
  terminationGracePeriodSeconds: {{ .Values.watcher.terminationGracePeriodSeconds }}
95
95
  serviceAccountName: {{ .Values.uuid }}
96
96
  securityContext:
97
- {{- toYaml .Values.admission.securityContext | nindent 8 }}
97
+ {{- toYaml .Values.watcher.securityContext | nindent 8 }}
98
+ nodeSelector:
99
+ {{- toYaml .Values.watcher.nodeSelector | nindent 8 }}
100
+ tolerations:
101
+ {{- toYaml .Values.watcher.tolerations | nindent 8 }}
102
+ affinity:
103
+ {{- toYaml .Values.watcher.affinity | nindent 8 }}
98
104
  containers:
99
105
  - name: watcher
100
106
  image: {{ .Values.watcher.image }}
@@ -179,6 +185,27 @@ export function admissionDeployTemplate(buildTimestamp: string): string {
179
185
  app: {{ .Values.uuid }}
180
186
  pepr.dev/controller: admission
181
187
  spec:
188
+ {{- if or .Values.admission.antiAffinity .Values.admission.affinity }}
189
+ affinity:
190
+ {{- if .Values.admission.antiAffinity }}
191
+ podAntiAffinity:
192
+ requiredDuringSchedulingIgnoredDuringExecution:
193
+ - labelSelector:
194
+ matchExpressions:
195
+ - key: pepr.dev/controller
196
+ operator: In
197
+ values:
198
+ - admission
199
+ topologyKey: "kubernetes.io/hostname"
200
+ {{- end }}
201
+ {{- if .Values.admission.affinity }}
202
+ {{- toYaml .Values.admission.affinity | nindent 8 }}
203
+ {{- end }}
204
+ {{- end }}
205
+ nodeSelector:
206
+ {{- toYaml .Values.admission.nodeSelector | nindent 8 }}
207
+ tolerations:
208
+ {{- toYaml .Values.admission.tolerations | nindent 8 }}
182
209
  terminationGracePeriodSeconds: {{ .Values.admission.terminationGracePeriodSeconds }}
183
210
  priorityClassName: system-node-critical
184
211
  serviceAccountName: {{ .Values.uuid }}
@@ -0,0 +1,17 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+
4
+ export function resolveIgnoreNamespaces(ignoredNSConfig: string[] = []): string[] {
5
+ const ignoredNSEnv = process.env.PEPR_ADDITIONAL_IGNORED_NAMESPACES;
6
+ if (!ignoredNSEnv) {
7
+ return ignoredNSConfig;
8
+ }
9
+
10
+ const namespaces = ignoredNSEnv.split(",").map(ns => ns.trim());
11
+
12
+ // add alwaysIgnore.namespaces to the list
13
+ if (ignoredNSConfig) {
14
+ namespaces.push(...ignoredNSConfig);
15
+ }
16
+ return namespaces.filter(ns => ns.length > 0);
17
+ }
@@ -8,7 +8,7 @@ import {
8
8
  } from "@kubernetes/client-node";
9
9
  import { kind } from "kubernetes-fluent-client";
10
10
  import { concat, equals, uniqWith } from "ramda";
11
-
11
+ import { resolveIgnoreNamespaces } from "./ignoredNamespaces";
12
12
  import { Assets } from "./assets";
13
13
  import { Event, WebhookType } from "../enums";
14
14
  import { Binding } from "../types";
@@ -42,21 +42,6 @@ export const validateRule = (
42
42
  return ruleObject;
43
43
  };
44
44
 
45
- export function resolveIgnoreNamespaces(ignoredNSConfig: string[] = []): string[] {
46
- const ignoredNSEnv = process.env.PEPR_ADDITIONAL_IGNORED_NAMESPACES;
47
- if (!ignoredNSEnv) {
48
- return ignoredNSConfig;
49
- }
50
-
51
- const namespaces = ignoredNSEnv.split(",").map(ns => ns.trim());
52
-
53
- // add alwaysIgnore.namespaces to the list
54
- if (ignoredNSConfig) {
55
- namespaces.push(...ignoredNSConfig);
56
- }
57
- return namespaces.filter(ns => ns.length > 0);
58
- }
59
-
60
45
  export async function generateWebhookRules(
61
46
  assets: Assets,
62
47
  isMutateWebhook: boolean,
@@ -84,7 +69,11 @@ export async function webhookConfigGenerator(
84
69
  const { name, tls, config, apiPath, host } = assets;
85
70
  const ignoreNS = concat(
86
71
  peprIgnoreNamespaces,
87
- resolveIgnoreNamespaces(config?.alwaysIgnore?.namespaces),
72
+ resolveIgnoreNamespaces(
73
+ config?.alwaysIgnore?.namespaces?.length
74
+ ? config?.alwaysIgnore?.namespaces
75
+ : config?.admission?.alwaysIgnore?.namespaces,
76
+ ),
88
77
  );
89
78
 
90
79
  // Add any namespaces to ignore
@@ -3,7 +3,7 @@ import { CapabilityExport, ModuleConfig } from "../../types";
3
3
  import { dumpYaml } from "@kubernetes/client-node";
4
4
  import { clusterRole } from "../rbac";
5
5
  import { promises as fs } from "fs";
6
-
6
+ import { resolveIgnoreNamespaces } from "../ignoredNamespaces";
7
7
  export type ChartOverrides = {
8
8
  apiPath: string;
9
9
  capabilities: CapabilityExport[];
@@ -23,7 +23,11 @@ export async function overridesFile(
23
23
 
24
24
  const overrides = {
25
25
  imagePullSecrets,
26
- additionalIgnoredNamespaces: [],
26
+ additionalIgnoredNamespaces: resolveIgnoreNamespaces(
27
+ config?.alwaysIgnore?.namespaces?.length
28
+ ? config?.alwaysIgnore?.namespaces
29
+ : config?.admission?.alwaysIgnore?.namespaces,
30
+ ),
27
31
  rbac: rbacOverrides,
28
32
  secrets: {
29
33
  apiPath: Buffer.from(apiPath).toString("base64"),
@@ -37,6 +41,7 @@ export async function overridesFile(
37
41
  },
38
42
  uuid: name,
39
43
  admission: {
44
+ antiAffinity: false,
40
45
  terminationGracePeriodSeconds: 5,
41
46
  failurePolicy: config.onError === "reject" ? "Fail" : "Ignore",
42
47
  webhookTimeout: config.webhookTimeout,
@@ -1,5 +1,5 @@
1
1
  import { ControllerHooks } from ".";
2
- import { resolveIgnoreNamespaces } from "../assets/webhooks";
2
+ import { resolveIgnoreNamespaces } from "../assets/ignoredNamespaces";
3
3
  import { Capability } from "../core/capability";
4
4
  import { isWatchMode, isDevMode } from "../core/envChecks";
5
5
  import { setupWatch } from "../processors/watch-processor";
@@ -60,7 +60,9 @@ export class PeprModule {
60
60
  const controllerHooks = createControllerHooks(
61
61
  opts,
62
62
  capabilities,
63
- pepr?.alwaysIgnore?.namespaces,
63
+ pepr?.alwaysIgnore?.namespaces?.length
64
+ ? pepr.alwaysIgnore.namespaces
65
+ : config?.watch?.alwaysIgnore?.namespaces,
64
66
  );
65
67
 
66
68
  this.#controller = new Controller(config, capabilities, controllerHooks);
@@ -138,20 +138,30 @@ export function generateWatchNamespaceError(
138
138
  export function namespaceComplianceValidator(
139
139
  capability: CapabilityExport,
140
140
  ignoredNamespaces?: string[],
141
+ watch?: boolean,
141
142
  ): void {
142
143
  const { namespaces: capabilityNamespaces, bindings, name } = capability;
143
- const bindingNamespaces: string[] = bindings.flatMap(
144
- (binding: Binding) => binding.filters.namespaces,
144
+
145
+ const shouldInclude = (binding: Binding): boolean => {
146
+ if (watch === true) return !!binding.isWatch;
147
+ if (watch === false) return !!binding.isMutate;
148
+ return true;
149
+ };
150
+
151
+ const bindingNamespaces: string[] = bindings.flatMap(binding =>
152
+ shouldInclude(binding) ? binding.filters.namespaces || [] : [],
145
153
  );
146
- const bindingRegexNamespaces: string[] = bindings.flatMap(
147
- (binding: Binding) => binding.filters.regexNamespaces || [],
154
+
155
+ const bindingRegexNamespaces: string[] = bindings.flatMap(binding =>
156
+ shouldInclude(binding) ? binding.filters.regexNamespaces || [] : [],
148
157
  );
149
158
 
150
159
  const namespaceError = generateWatchNamespaceError(
151
- ignoredNamespaces ? ignoredNamespaces : [],
160
+ ignoredNamespaces ?? [],
152
161
  bindingNamespaces,
153
- capabilityNamespaces ? capabilityNamespaces : [],
162
+ capabilityNamespaces ?? [],
154
163
  );
164
+
155
165
  if (namespaceError !== "") {
156
166
  throw new Error(
157
167
  `Error in ${name} capability. A binding violates namespace rules. Please check ignoredNamespaces and capability namespaces: ${namespaceError}`,
@@ -13,7 +13,7 @@ import { ModuleConfig } from "../types";
13
13
  import { PeprMutateRequest } from "../mutate-request";
14
14
  import { base64Encode } from "../utils";
15
15
  import { OnError } from "../../cli/init/enums";
16
- import { resolveIgnoreNamespaces } from "../assets/webhooks";
16
+ import { resolveIgnoreNamespaces } from "../assets/ignoredNamespaces";
17
17
  import { Operation } from "fast-json-patch";
18
18
  import { WebhookType } from "../enums";
19
19
 
@@ -149,7 +149,11 @@ export async function mutateProcessor(
149
149
  bind.binding,
150
150
  bind.req,
151
151
  bind.namespaces,
152
- resolveIgnoreNamespaces(bind.config?.alwaysIgnore?.namespaces),
152
+ resolveIgnoreNamespaces(
153
+ bind?.config?.alwaysIgnore?.namespaces?.length
154
+ ? bind.config?.alwaysIgnore?.namespaces
155
+ : bind.config?.admission?.alwaysIgnore?.namespaces,
156
+ ),
153
157
  );
154
158
  if (shouldSkip !== "") {
155
159
  Log.debug(shouldSkip);
@@ -10,7 +10,7 @@ import Log from "../telemetry/logger";
10
10
  import { convertFromBase64Map } from "../utils";
11
11
  import { PeprValidateRequest } from "../validate-request";
12
12
  import { ModuleConfig } from "../types";
13
- import { resolveIgnoreNamespaces } from "../assets/webhooks";
13
+ import { resolveIgnoreNamespaces } from "../assets/ignoredNamespaces";
14
14
  import { MeasureWebhookTimeout } from "../telemetry/webhookTimeouts";
15
15
  import { WebhookType } from "../enums";
16
16
  import { AdmissionRequest } from "../common-types";
@@ -37,7 +37,9 @@ export async function processRequest(
37
37
  if (callbackResp.statusCode || callbackResp.statusMessage) {
38
38
  valResp.status = {
39
39
  code: callbackResp.statusCode || 400,
40
- message: callbackResp.statusMessage || `Validation failed for ${name}`,
40
+ message:
41
+ callbackResp.statusMessage ||
42
+ `Validation failed for ${peprValidateRequest.Request.kind.kind.toLowerCase()}/${peprValidateRequest.Request.name}${peprValidateRequest.Request.namespace ? ` in ${peprValidateRequest.Request.namespace} namespace.` : ""}`,
41
43
  };
42
44
  }
43
45
 
@@ -95,7 +97,11 @@ export async function validateProcessor(
95
97
  binding,
96
98
  req,
97
99
  namespaces,
98
- resolveIgnoreNamespaces(config?.alwaysIgnore?.namespaces),
100
+ resolveIgnoreNamespaces(
101
+ config?.alwaysIgnore?.namespaces?.length
102
+ ? config?.alwaysIgnore?.namespaces
103
+ : config?.admission?.alwaysIgnore?.namespaces,
104
+ ),
99
105
  );
100
106
  if (shouldSkip !== "") {
101
107
  Log.debug(shouldSkip);
package/src/lib/types.ts CHANGED
@@ -292,6 +292,14 @@ export type ModuleConfig = {
292
292
  uuid: string;
293
293
  /** Configure global exclusions that will never be processed by Pepr. */
294
294
  alwaysIgnore: WebhookIgnore;
295
+ /** admission specific ignore */
296
+ admission?: {
297
+ alwaysIgnore: WebhookIgnore;
298
+ };
299
+ /** watch specific ignore */
300
+ watch?: {
301
+ alwaysIgnore: WebhookIgnore;
302
+ };
295
303
  } & Partial<ModuleConfigOptions>;
296
304
 
297
305
  export type PackageJSON = {
package/src/sdk/sdk.ts CHANGED
@@ -109,17 +109,20 @@ export function getOwnerRefFrom(
109
109
  *
110
110
  * @param name the name of the resource to sanitize
111
111
  * @returns the sanitized resource name
112
+ *
113
+ * https://kubernetes.io/docs/concepts/overview/working-with-objects/names/
112
114
  */
113
115
  export function sanitizeResourceName(name: string): string {
114
116
  return (
115
117
  name
116
- // The name must be lowercase
117
118
  .toLowerCase()
118
- // Replace sequences of non-alphanumeric characters with a single '-'
119
- .replace(/[^a-z0-9]+/g, "-")
120
- // Truncate the name to 250 characters
121
- .slice(0, 250)
122
- // Remove leading and trailing non-letter characters
123
- .replace(/^[^a-z]+|[^a-z]+$/g, "")
119
+ // Replace invalid characters (anything not a-z, 0-9, or '-') with '-'
120
+ .replace(/[^a-z0-9-]+/g, "-")
121
+ // Trim to 63 characters (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#rfc-1035-label-names)
122
+ .slice(0, 63)
123
+ // Remove leading non-alphanumeric characters
124
+ .replace(/^[^a-z0-9]+/, "")
125
+ // Remove trailing non-alphanumeric characters
126
+ .replace(/[^a-z0-9]+$/, "")
124
127
  );
125
128
  }
@@ -211,7 +211,7 @@ When(a.ConfigMap)
211
211
  When(a.ConfigMap).IsCreated().WithName("example-4").Mutate(example4Cb);
212
212
 
213
213
  // This function uses the complete type definition, but is not required.
214
- function example4Cb(cm: PeprMutateRequest<a.ConfigMap>) {
214
+ function example4Cb(cm: PeprMutateRequest<a.ConfigMap>): void {
215
215
  cm.SetLabel("pepr.dev/first", "true");
216
216
  cm.SetLabel("pepr.dev/second", "true");
217
217
  cm.SetLabel("pepr.dev/third", "true");