pepr 0.40.1 → 0.42.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 (78) hide show
  1. package/README.md +11 -5
  2. package/dist/cli/build.d.ts.map +1 -1
  3. package/dist/cli/deploy.d.ts.map +1 -1
  4. package/dist/cli/init/templates.d.ts +2 -2
  5. package/dist/cli.js +228 -179
  6. package/dist/controller.js +52 -27
  7. package/dist/lib/assets/index.d.ts.map +1 -1
  8. package/dist/lib/capability.d.ts.map +1 -1
  9. package/dist/lib/controller/index.d.ts.map +1 -1
  10. package/dist/lib/controller/index.util.d.ts +10 -0
  11. package/dist/lib/controller/index.util.d.ts.map +1 -0
  12. package/dist/lib/controller/store.d.ts +1 -1
  13. package/dist/lib/deploymentChecks.d.ts +3 -0
  14. package/dist/lib/deploymentChecks.d.ts.map +1 -0
  15. package/dist/lib/enums.d.ts +5 -5
  16. package/dist/lib/enums.d.ts.map +1 -1
  17. package/dist/lib/filesystemService.d.ts +2 -0
  18. package/dist/lib/filesystemService.d.ts.map +1 -0
  19. package/dist/lib/filter/adjudicators/adjudicators.d.ts +73 -0
  20. package/dist/lib/filter/adjudicators/adjudicators.d.ts.map +1 -0
  21. package/dist/lib/filter/adjudicators/defaultTestObjects.d.ts +7 -0
  22. package/dist/lib/filter/adjudicators/defaultTestObjects.d.ts.map +1 -0
  23. package/dist/lib/helpers.d.ts +1 -4
  24. package/dist/lib/helpers.d.ts.map +1 -1
  25. package/dist/lib/schedule.d.ts.map +1 -1
  26. package/dist/lib/storage.d.ts +1 -1
  27. package/dist/lib/storage.d.ts.map +1 -1
  28. package/dist/lib/{logger.d.ts → telemetry/logger.d.ts} +1 -1
  29. package/dist/lib/telemetry/logger.d.ts.map +1 -0
  30. package/dist/lib/{metrics.d.ts → telemetry/metrics.d.ts} +3 -1
  31. package/dist/lib/telemetry/metrics.d.ts.map +1 -0
  32. package/dist/lib/types.d.ts +10 -9
  33. package/dist/lib/types.d.ts.map +1 -1
  34. package/dist/lib.d.ts +1 -1
  35. package/dist/lib.d.ts.map +1 -1
  36. package/dist/lib.js +151 -126
  37. package/dist/lib.js.map +4 -4
  38. package/dist/sdk/sdk.d.ts +3 -4
  39. package/dist/sdk/sdk.d.ts.map +1 -1
  40. package/package.json +5 -5
  41. package/src/cli/build.ts +2 -1
  42. package/src/cli/deploy.ts +2 -1
  43. package/src/cli/init/templates.ts +1 -1
  44. package/src/lib/assets/deploy.ts +1 -1
  45. package/src/lib/assets/destroy.ts +1 -1
  46. package/src/lib/assets/index.ts +102 -81
  47. package/src/lib/assets/webhooks.ts +2 -2
  48. package/src/lib/capability.ts +8 -9
  49. package/src/lib/controller/index.ts +32 -62
  50. package/src/lib/controller/index.util.ts +47 -0
  51. package/src/lib/controller/store.ts +2 -2
  52. package/src/lib/controller/storeCache.ts +1 -1
  53. package/src/lib/deploymentChecks.ts +43 -0
  54. package/src/lib/enums.ts +5 -5
  55. package/src/lib/filesystemService.ts +16 -0
  56. package/src/lib/filter/{adjudicators.ts → adjudicators/adjudicators.ts} +67 -35
  57. package/src/lib/filter/adjudicators/defaultTestObjects.ts +46 -0
  58. package/src/lib/filter/filter.ts +1 -1
  59. package/src/lib/finalizer.ts +1 -1
  60. package/src/lib/helpers.ts +31 -88
  61. package/src/lib/mutate-processor.ts +1 -1
  62. package/src/lib/queue.ts +1 -1
  63. package/src/lib/schedule.ts +8 -8
  64. package/src/lib/storage.ts +17 -17
  65. package/src/lib/{logger.ts → telemetry/logger.ts} +1 -1
  66. package/src/lib/{metrics.ts → telemetry/metrics.ts} +18 -17
  67. package/src/lib/types.ts +12 -9
  68. package/src/lib/utils.ts +1 -1
  69. package/src/lib/validate-processor.ts +1 -1
  70. package/src/lib/watch-processor.ts +8 -8
  71. package/src/lib.ts +1 -1
  72. package/src/runtime/controller.ts +1 -1
  73. package/src/sdk/sdk.ts +6 -9
  74. package/src/templates/capabilities/hello-pepr.ts +19 -9
  75. package/dist/lib/filter/adjudicators.d.ts +0 -69
  76. package/dist/lib/filter/adjudicators.d.ts.map +0 -1
  77. package/dist/lib/logger.d.ts.map +0 -1
  78. package/dist/lib/metrics.d.ts.map +0 -1
package/dist/sdk/sdk.d.ts CHANGED
@@ -1,15 +1,14 @@
1
1
  import { PeprValidateRequest } from "../lib/validate-request";
2
2
  import { PeprMutateRequest } from "../lib/mutate-request";
3
- import { V1OwnerReference } from "@kubernetes/client-node";
4
- import { GenericKind } from "kubernetes-fluent-client";
5
- import { kind } from "kubernetes-fluent-client";
3
+ import { V1OwnerReference, V1Container } from "@kubernetes/client-node";
4
+ import { GenericKind, kind } from "kubernetes-fluent-client";
6
5
  /**
7
6
  * Returns all containers in a pod
8
7
  * @param request the request/pod to get the containers from
9
8
  * @param containerType the type of container to get
10
9
  * @returns the list of containers in the pod
11
10
  */
12
- export declare function containers(request: PeprValidateRequest<kind.Pod> | PeprMutateRequest<kind.Pod>, containerType?: "containers" | "initContainers" | "ephemeralContainers"): import("@kubernetes/client-node").V1Container[];
11
+ export declare function containers(request: PeprValidateRequest<kind.Pod> | PeprMutateRequest<kind.Pod>, containerType?: "containers" | "initContainers" | "ephemeralContainers"): V1Container[];
13
12
  /**
14
13
  * Write a K8s event for a CRD
15
14
  *
@@ -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,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAO,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAGrD;;;;;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,mDAgBxE;AAED;;;;;;;;;GASG;AACH,wBAAsB,UAAU,CAC9B,EAAE,EAAE,WAAW,EACf,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,EAC9B,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,kBAAkB,EAAE,MAAM,EAC1B,iBAAiB,EAAE,MAAM,iBAwB1B;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,UAYhD"}
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,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,kBAAkB,EAAE,MAAM,EAC1B,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,IAAI,CAAC,CAqBf;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"}
package/package.json CHANGED
@@ -15,7 +15,7 @@
15
15
  "!src/**/*.test.ts",
16
16
  "!dist/**/*.test.d.ts*"
17
17
  ],
18
- "version": "0.40.1",
18
+ "version": "0.42.0",
19
19
  "main": "dist/lib.js",
20
20
  "types": "dist/lib.d.ts",
21
21
  "scripts": {
@@ -41,12 +41,12 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@types/ramda": "0.30.2",
44
- "express": "4.21.1",
44
+ "express": "4.21.2",
45
45
  "fast-json-patch": "3.1.1",
46
46
  "follow-redirects": "1.15.9",
47
47
  "http-status-codes": "^2.3.0",
48
48
  "json-pointer": "^0.6.2",
49
- "kubernetes-fluent-client": "3.3.4",
49
+ "kubernetes-fluent-client": "3.3.7",
50
50
  "pino": "9.5.0",
51
51
  "pino-pretty": "13.0.0",
52
52
  "prom-client": "15.1.3",
@@ -69,8 +69,8 @@
69
69
  "husky": "^9.1.6",
70
70
  "jest": "29.7.0",
71
71
  "js-yaml": "^4.1.0",
72
- "nock": "^13.5.4",
73
- "ts-jest": "29.2.5"
72
+ "ts-jest": "29.2.5",
73
+ "undici": "^7.0.1"
74
74
  },
75
75
  "peerDependencies": {
76
76
  "@types/prompts": "2.4.9",
package/src/cli/build.ts CHANGED
@@ -11,9 +11,10 @@ import { dependencies, version } from "./init/templates";
11
11
  import { RootCmd } from "./root";
12
12
  import { peprFormat } from "./format";
13
13
  import { Option } from "commander";
14
- import { createDirectoryIfNotExists, validateCapabilityNames, parseTimeout } from "../lib/helpers";
14
+ import { validateCapabilityNames, parseTimeout } from "../lib/helpers";
15
15
  import { sanitizeResourceName } from "../sdk/sdk";
16
16
  import { determineRbacMode } from "./build.helpers";
17
+ import { createDirectoryIfNotExists } from "../lib/filesystemService";
17
18
  const peprTS = "pepr.ts";
18
19
  let outputDir: string = "dist";
19
20
  export type Reloader = (opts: BuildResult<BuildOptions>) => void | Promise<void>;
package/src/cli/deploy.ts CHANGED
@@ -6,10 +6,11 @@ import prompt from "prompts";
6
6
  import { Assets } from "../lib/assets";
7
7
  import { buildModule } from "./build";
8
8
  import { RootCmd } from "./root";
9
- import { validateCapabilityNames, namespaceDeploymentsReady } from "../lib/helpers";
9
+ import { validateCapabilityNames } from "../lib/helpers";
10
10
  import { ImagePullSecret } from "../lib/types";
11
11
  import { sanitizeName } from "./init/utils";
12
12
  import { deployImagePullSecret } from "../lib/assets/deploy";
13
+ import { namespaceDeploymentsReady } from "../lib/deploymentChecks";
13
14
 
14
15
  export default function (program: RootCmd) {
15
16
  program
@@ -58,7 +58,7 @@ export function genPkgJSON(opts: InitOptions, pgkVerOverride?: string) {
58
58
  },
59
59
  dependencies: {
60
60
  pepr: pgkVerOverride || version,
61
- nock: "13.5.4",
61
+ undici: "^7.0.1",
62
62
  },
63
63
  devDependencies: {
64
64
  typescript,
@@ -7,7 +7,7 @@ import { K8s, kind } from "kubernetes-fluent-client";
7
7
  import { V1PolicyRule as PolicyRule } from "@kubernetes/client-node";
8
8
 
9
9
  import { Assets } from ".";
10
- import Log from "../logger";
10
+ import Log from "../telemetry/logger";
11
11
  import { apiTokenSecret, service, tlsSecret, watcherService } from "./networking";
12
12
  import { deployment, moduleSecret, namespace, watcher } from "./pods";
13
13
  import { clusterRole, clusterRoleBinding, serviceAccount, storeRole, storeRoleBinding } from "./rbac";
@@ -3,7 +3,7 @@
3
3
 
4
4
  import { K8s, kind } from "kubernetes-fluent-client";
5
5
 
6
- import Log from "../logger";
6
+ import Log from "../telemetry/logger";
7
7
  import { peprStoreCRD } from "./store";
8
8
 
9
9
  export async function destroyModule(name: string) {
@@ -3,6 +3,7 @@
3
3
 
4
4
  import crypto from "crypto";
5
5
  import { dumpYaml } from "@kubernetes/client-node";
6
+ import { kind } from "kubernetes-fluent-client";
6
7
  import { ModuleConfig } from "../module";
7
8
  import { TLSOut, genTLS } from "../tls";
8
9
  import { CapabilityExport } from "../types";
@@ -11,7 +12,7 @@ import { deploy } from "./deploy";
11
12
  import { loadCapabilities } from "./loader";
12
13
  import { allYaml, zarfYaml, overridesFile, zarfYamlChart } from "./yaml";
13
14
  import { namespaceComplianceValidator, replaceString } from "../helpers";
14
- import { createDirectoryIfNotExists, dedent } from "../helpers";
15
+ import { dedent } from "../helpers";
15
16
  import { resolve } from "path";
16
17
  import {
17
18
  chartYaml,
@@ -27,6 +28,69 @@ import { apiTokenSecret, service, tlsSecret, watcherService } from "./networking
27
28
  import { watcher, moduleSecret } from "./pods";
28
29
 
29
30
  import { clusterRoleBinding, serviceAccount, storeRole, storeRoleBinding } from "./rbac";
31
+ import { createDirectoryIfNotExists } from "../filesystemService";
32
+
33
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
+ function toYaml(obj: any): string {
35
+ return dumpYaml(obj, { noRefs: true });
36
+ }
37
+
38
+ function createWebhookYaml(
39
+ assets: Assets,
40
+ webhookConfiguration: kind.MutatingWebhookConfiguration | kind.ValidatingWebhookConfiguration,
41
+ ): string {
42
+ const yaml = toYaml(webhookConfiguration);
43
+ return replaceString(
44
+ replaceString(
45
+ replaceString(yaml, assets.name, "{{ .Values.uuid }}"),
46
+ assets.config.onError === "reject" ? "Fail" : "Ignore",
47
+ "{{ .Values.admission.failurePolicy }}",
48
+ ),
49
+ `${assets.config.webhookTimeout}` || "10",
50
+ "{{ .Values.admission.webhookTimeout }}",
51
+ );
52
+ }
53
+
54
+ function helmLayout(basePath: string, unique: string) {
55
+ const helm: Record<string, Record<string, string>> = {
56
+ dirs: {
57
+ chart: resolve(`${basePath}/${unique}-chart`),
58
+ },
59
+ files: {},
60
+ };
61
+
62
+ helm.dirs = {
63
+ ...helm.dirs,
64
+ charts: `${helm.dirs.chart}/charts`,
65
+ tmpls: `${helm.dirs.chart}/templates`,
66
+ };
67
+
68
+ helm.files = {
69
+ ...helm.files,
70
+ valuesYaml: `${helm.dirs.chart}/values.yaml`,
71
+ chartYaml: `${helm.dirs.chart}/Chart.yaml`,
72
+ namespaceYaml: `${helm.dirs.tmpls}/namespace.yaml`,
73
+ watcherServiceYaml: `${helm.dirs.tmpls}/watcher-service.yaml`,
74
+ admissionServiceYaml: `${helm.dirs.tmpls}/admission-service.yaml`,
75
+ mutationWebhookYaml: `${helm.dirs.tmpls}/mutation-webhook.yaml`,
76
+ validationWebhookYaml: `${helm.dirs.tmpls}/validation-webhook.yaml`,
77
+ admissionDeploymentYaml: `${helm.dirs.tmpls}/admission-deployment.yaml`,
78
+ admissionServiceMonitorYaml: `${helm.dirs.tmpls}/admission-service-monitor.yaml`,
79
+ watcherDeploymentYaml: `${helm.dirs.tmpls}/watcher-deployment.yaml`,
80
+ watcherServiceMonitorYaml: `${helm.dirs.tmpls}/watcher-service-monitor.yaml`,
81
+ tlsSecretYaml: `${helm.dirs.tmpls}/tls-secret.yaml`,
82
+ apiTokenSecretYaml: `${helm.dirs.tmpls}/api-token-secret.yaml`,
83
+ moduleSecretYaml: `${helm.dirs.tmpls}/module-secret.yaml`,
84
+ storeRoleYaml: `${helm.dirs.tmpls}/store-role.yaml`,
85
+ storeRoleBindingYaml: `${helm.dirs.tmpls}/store-role-binding.yaml`,
86
+ clusterRoleYaml: `${helm.dirs.tmpls}/cluster-role.yaml`,
87
+ clusterRoleBindingYaml: `${helm.dirs.tmpls}/cluster-role-binding.yaml`,
88
+ serviceAccountYaml: `${helm.dirs.tmpls}/service-account.yaml`,
89
+ };
90
+
91
+ return helm;
92
+ }
93
+
30
94
  export class Assets {
31
95
  readonly name: string;
32
96
  readonly tls: TLSOut;
@@ -78,102 +142,59 @@ export class Assets {
78
142
  return allYaml(this, imagePullSecret);
79
143
  };
80
144
 
145
+ /* eslint max-statements: ["warn", 21] */
81
146
  generateHelmChart = async (basePath: string) => {
82
- const CHART_DIR = `${basePath}/${this.config.uuid}-chart`;
83
- const CHAR_TEMPLATES_DIR = `${CHART_DIR}/templates`;
84
- const valuesPath = resolve(CHART_DIR, `values.yaml`);
85
- const chartPath = resolve(CHART_DIR, `Chart.yaml`);
86
- const nsPath = resolve(CHAR_TEMPLATES_DIR, `namespace.yaml`);
87
- const watcherSVCPath = resolve(CHAR_TEMPLATES_DIR, `watcher-service.yaml`);
88
- const admissionSVCPath = resolve(CHAR_TEMPLATES_DIR, `admission-service.yaml`);
89
- const mutationWebhookPath = resolve(CHAR_TEMPLATES_DIR, `mutation-webhook.yaml`);
90
- const validationWebhookPath = resolve(CHAR_TEMPLATES_DIR, `validation-webhook.yaml`);
91
- const admissionDeployPath = resolve(CHAR_TEMPLATES_DIR, `admission-deployment.yaml`);
92
- const admissionServiceMonitorPath = resolve(CHAR_TEMPLATES_DIR, `admission-service-monitor.yaml`);
93
- const watcherDeployPath = resolve(CHAR_TEMPLATES_DIR, `watcher-deployment.yaml`);
94
- const watcherServiceMonitorPath = resolve(CHAR_TEMPLATES_DIR, `watcher-service-monitor.yaml`);
95
- const tlsSecretPath = resolve(CHAR_TEMPLATES_DIR, `tls-secret.yaml`);
96
- const apiTokenSecretPath = resolve(CHAR_TEMPLATES_DIR, `api-token-secret.yaml`);
97
- const moduleSecretPath = resolve(CHAR_TEMPLATES_DIR, `module-secret.yaml`);
98
- const storeRolePath = resolve(CHAR_TEMPLATES_DIR, `store-role.yaml`);
99
- const storeRoleBindingPath = resolve(CHAR_TEMPLATES_DIR, `store-role-binding.yaml`);
100
- const clusterRolePath = resolve(CHAR_TEMPLATES_DIR, `cluster-role.yaml`);
101
- const clusterRoleBindingPath = resolve(CHAR_TEMPLATES_DIR, `cluster-role-binding.yaml`);
102
- const serviceAccountPath = resolve(CHAR_TEMPLATES_DIR, `service-account.yaml`);
103
-
104
- // create helm chart
105
- try {
106
- // create chart dir
107
- await createDirectoryIfNotExists(CHART_DIR);
108
-
109
- // create charts dir
110
- await createDirectoryIfNotExists(`${CHART_DIR}/charts`);
111
-
112
- // create templates dir
113
- await createDirectoryIfNotExists(`${CHAR_TEMPLATES_DIR}`);
147
+ const helm = helmLayout(basePath, this.config.uuid);
114
148
 
115
- // create values file
116
- await overridesFile(this, valuesPath);
117
-
118
- // create the chart.yaml
119
- await fs.writeFile(chartPath, dedent(chartYaml(this.config.uuid, this.config.description || "")));
120
-
121
- // create the namespace.yaml in templates
122
- await fs.writeFile(nsPath, dedent(nsTemplate()));
149
+ try {
150
+ await Promise.all(
151
+ Object.values(helm.dirs)
152
+ .sort((l, r) => l.split("/").length - r.split("/").length)
153
+ .map(async dir => await createDirectoryIfNotExists(dir)),
154
+ );
123
155
 
124
156
  const code = await fs.readFile(this.path);
125
157
 
126
- await fs.writeFile(watcherSVCPath, dumpYaml(watcherService(this.name), { noRefs: true }));
127
- await fs.writeFile(admissionSVCPath, dumpYaml(service(this.name), { noRefs: true }));
128
- await fs.writeFile(tlsSecretPath, dumpYaml(tlsSecret(this.name, this.tls), { noRefs: true }));
129
- await fs.writeFile(apiTokenSecretPath, dumpYaml(apiTokenSecret(this.name, this.apiToken), { noRefs: true }));
130
- await fs.writeFile(moduleSecretPath, dumpYaml(moduleSecret(this.name, code, this.hash), { noRefs: true }));
131
- await fs.writeFile(storeRolePath, dumpYaml(storeRole(this.name), { noRefs: true }));
132
- await fs.writeFile(storeRoleBindingPath, dumpYaml(storeRoleBinding(this.name), { noRefs: true }));
133
- await fs.writeFile(clusterRolePath, dedent(clusterRoleTemplate()));
134
- await fs.writeFile(clusterRoleBindingPath, dumpYaml(clusterRoleBinding(this.name), { noRefs: true }));
135
- await fs.writeFile(serviceAccountPath, dumpYaml(serviceAccount(this.name), { noRefs: true }));
136
-
137
- const mutateWebhook = await webhookConfig(this, "mutate", this.config.webhookTimeout);
138
- const validateWebhook = await webhookConfig(this, "validate", this.config.webhookTimeout);
139
- const watchDeployment = watcher(this, this.hash, this.buildTimestamp);
158
+ const pairs: [string, () => string][] = [
159
+ [helm.files.chartYaml, () => dedent(chartYaml(this.config.uuid, this.config.description || ""))],
160
+ [helm.files.namespaceYaml, () => dedent(nsTemplate())],
161
+ [helm.files.watcherServiceYaml, () => toYaml(watcherService(this.name))],
162
+ [helm.files.admissionServiceYaml, () => toYaml(service(this.name))],
163
+ [helm.files.tlsSecretYaml, () => toYaml(tlsSecret(this.name, this.tls))],
164
+ [helm.files.apiTokenSecretYaml, () => toYaml(apiTokenSecret(this.name, this.apiToken))],
165
+ [helm.files.storeRoleYaml, () => toYaml(storeRole(this.name))],
166
+ [helm.files.storeRoleBindingYaml, () => toYaml(storeRoleBinding(this.name))],
167
+ [helm.files.clusterRoleYaml, () => dedent(clusterRoleTemplate())],
168
+ [helm.files.clusterRoleBindingYaml, () => toYaml(clusterRoleBinding(this.name))],
169
+ [helm.files.serviceAccountYaml, () => toYaml(serviceAccount(this.name))],
170
+ [helm.files.moduleSecretYaml, () => toYaml(moduleSecret(this.name, code, this.hash))],
171
+ ];
172
+ await Promise.all(pairs.map(async ([file, content]) => await fs.writeFile(file, content())));
173
+
174
+ await overridesFile(this, helm.files.valuesYaml);
175
+
176
+ const [mutateWebhook, validateWebhook] = await Promise.all([
177
+ webhookConfig(this, "mutate", this.config.webhookTimeout),
178
+ webhookConfig(this, "validate", this.config.webhookTimeout),
179
+ ]);
140
180
 
141
181
  if (validateWebhook || mutateWebhook) {
142
- await fs.writeFile(admissionDeployPath, dedent(admissionDeployTemplate(this.buildTimestamp)));
143
- await fs.writeFile(admissionServiceMonitorPath, dedent(serviceMonitorTemplate("admission")));
182
+ await fs.writeFile(helm.files.admissionDeploymentYaml, dedent(admissionDeployTemplate(this.buildTimestamp)));
183
+ await fs.writeFile(helm.files.admissionServiceMonitorYaml, dedent(serviceMonitorTemplate("admission")));
144
184
  }
145
185
 
146
186
  if (mutateWebhook) {
147
- const yamlMutateWebhook = dumpYaml(mutateWebhook, { noRefs: true });
148
- const mutateWebhookTemplate = replaceString(
149
- replaceString(
150
- replaceString(yamlMutateWebhook, this.name, "{{ .Values.uuid }}"),
151
- this.config.onError === "reject" ? "Fail" : "Ignore",
152
- "{{ .Values.admission.failurePolicy }}",
153
- ),
154
- `${this.config.webhookTimeout}` || "10",
155
- "{{ .Values.admission.webhookTimeout }}",
156
- );
157
- await fs.writeFile(mutationWebhookPath, mutateWebhookTemplate);
187
+ await fs.writeFile(helm.files.mutationWebhookYaml, createWebhookYaml(this, mutateWebhook));
158
188
  }
159
189
 
160
190
  if (validateWebhook) {
161
- const yamlValidateWebhook = dumpYaml(validateWebhook, { noRefs: true });
162
- const validateWebhookTemplate = replaceString(
163
- replaceString(
164
- replaceString(yamlValidateWebhook, this.name, "{{ .Values.uuid }}"),
165
- this.config.onError === "reject" ? "Fail" : "Ignore",
166
- "{{ .Values.admission.failurePolicy }}",
167
- ),
168
- `${this.config.webhookTimeout}` || "10",
169
- "{{ .Values.admission.webhookTimeout }}",
170
- );
171
- await fs.writeFile(validationWebhookPath, validateWebhookTemplate);
191
+ await fs.writeFile(helm.files.validationWebhookYaml, createWebhookYaml(this, validateWebhook));
172
192
  }
173
193
 
194
+ const watchDeployment = watcher(this, this.hash, this.buildTimestamp);
174
195
  if (watchDeployment) {
175
- await fs.writeFile(watcherDeployPath, dedent(watcherDeployTemplate(this.buildTimestamp)));
176
- await fs.writeFile(watcherServiceMonitorPath, dedent(serviceMonitorTemplate("watcher")));
196
+ await fs.writeFile(helm.files.watcherDeploymentYaml, dedent(watcherDeployTemplate(this.buildTimestamp)));
197
+ await fs.writeFile(helm.files.watcherServiceMonitorYaml, dedent(serviceMonitorTemplate("watcher")));
177
198
  }
178
199
  } catch (err) {
179
200
  console.error(`Error generating helm chart: ${err.message}`);
@@ -44,8 +44,8 @@ export async function generateWebhookRules(assets: Assets, isMutateWebhook: bool
44
44
  const operations: string[] = [];
45
45
 
46
46
  // CreateOrUpdate is a Pepr-specific event that is translated to Create and Update
47
- if (event === Event.CreateOrUpdate) {
48
- operations.push(Event.Create, Event.Update);
47
+ if (event === Event.CREATE_OR_UPDATE) {
48
+ operations.push(Event.CREATE, Event.UPDATE);
49
49
  } else {
50
50
  operations.push(event);
51
51
  }
@@ -1,10 +1,9 @@
1
- /* eslint-disable max-statements */
2
1
  // SPDX-License-Identifier: Apache-2.0
3
2
  // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
4
3
 
5
4
  import { GenericClass, GroupVersionKind, modelToGroupVersionKind } from "kubernetes-fluent-client";
6
5
  import { pickBy } from "ramda";
7
- import Log from "./logger";
6
+ import Log from "./telemetry/logger";
8
7
  import { isBuildMode, isDevMode, isWatchMode } from "./module";
9
8
  import { PeprStore, Storage } from "./storage";
10
9
  import { OnSchedule, Schedule } from "./schedule";
@@ -193,7 +192,7 @@ export class Capability implements CapabilityExport {
193
192
  model,
194
193
  // If the kind is not specified, use the matched kind from the model
195
194
  kind: kind || matchedKind,
196
- event: Event.Any,
195
+ event: Event.ANY,
197
196
  filters: {
198
197
  name: "",
199
198
  namespaces: [],
@@ -317,7 +316,7 @@ export class Capability implements CapabilityExport {
317
316
  ...binding,
318
317
  isMutate: true,
319
318
  isFinalize: true,
320
- event: Event.Any,
319
+ event: Event.ANY,
321
320
  mutateCallback: addFinalizer,
322
321
  };
323
322
  bindings.push(mutateBinding);
@@ -329,7 +328,7 @@ export class Capability implements CapabilityExport {
329
328
  ...binding,
330
329
  isWatch: true,
331
330
  isFinalize: true,
332
- event: Event.Update,
331
+ event: Event.UPDATE,
333
332
  finalizeCallback: async (update: InstanceType<T>, logger = aliasLogger) => {
334
333
  Log.info(`Executing finalize action with alias: ${binding.alias || "no alias provided"}`);
335
334
  return await finalizeCallback(update, logger);
@@ -401,10 +400,10 @@ export class Capability implements CapabilityExport {
401
400
  }
402
401
 
403
402
  return {
404
- IsCreatedOrUpdated: () => bindEvent(Event.CreateOrUpdate),
405
- IsCreated: () => bindEvent(Event.Create),
406
- IsUpdated: () => bindEvent(Event.Update),
407
- IsDeleted: () => bindEvent(Event.Delete),
403
+ IsCreatedOrUpdated: () => bindEvent(Event.CREATE_OR_UPDATE),
404
+ IsCreated: () => bindEvent(Event.CREATE),
405
+ IsUpdated: () => bindEvent(Event.UPDATE),
406
+ IsDeleted: () => bindEvent(Event.DELETE),
408
407
  };
409
408
  };
410
409
  }
@@ -7,17 +7,19 @@ import https from "https";
7
7
 
8
8
  import { Capability } from "../capability";
9
9
  import { MutateResponse, ValidateResponse } from "../k8s";
10
- import Log from "../logger";
11
- import { metricsCollector, MetricsCollector } from "../metrics";
10
+ import Log from "../telemetry/logger";
11
+ import { metricsCollector, MetricsCollector } from "../telemetry/metrics";
12
12
  import { ModuleConfig, isWatchMode } from "../module";
13
13
  import { mutateProcessor } from "../mutate-processor";
14
14
  import { validateProcessor } from "../validate-processor";
15
15
  import { StoreController } from "./store";
16
- import { ResponseItem, AdmissionRequest } from "../types";
16
+ import { AdmissionRequest } from "../types";
17
+ import { karForMutate, karForValidate, KubeAdmissionReview } from "./index.util";
17
18
 
18
19
  if (!process.env.PEPR_NODE_WARNINGS) {
19
20
  process.removeAllListeners("warning");
20
21
  }
22
+
21
23
  export class Controller {
22
24
  // Track whether the server is running
23
25
  #running = false;
@@ -183,6 +185,8 @@ export class Controller {
183
185
  */
184
186
  #metrics = async (req: express.Request, res: express.Response) => {
185
187
  try {
188
+ // https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#basic-info
189
+ res.set("Content-Type", "text/plain; version=0.0.4");
186
190
  res.send(await this.#metricsCollector.getMetrics());
187
191
  } catch (err) {
188
192
  Log.error(err, `Error getting metrics`);
@@ -206,77 +210,38 @@ export class Controller {
206
210
  // Get the request from the body or create an empty request
207
211
  const request: AdmissionRequest = req.body?.request || ({} as AdmissionRequest);
208
212
 
209
- // Run the before hook if it exists
210
- this.#beforeHook && this.#beforeHook(request || {});
211
-
212
- // Setup identifiers for logging
213
- const name = request?.name ? `/${request.name}` : "";
214
- const namespace = request?.namespace || "";
215
- const gvk = request?.kind || { group: "", version: "", kind: "" };
216
-
217
- const reqMetadata = {
218
- uid: request.uid,
219
- namespace,
220
- name,
213
+ const { name, namespace, gvk } = {
214
+ name: request?.name ? `/${request.name}` : "",
215
+ namespace: request?.namespace || "",
216
+ gvk: request?.kind || { group: "", version: "", kind: "" },
221
217
  };
222
218
 
219
+ const reqMetadata = { uid: request.uid, namespace, name };
223
220
  Log.info({ ...reqMetadata, gvk, operation: request.operation, admissionKind }, "Incoming request");
224
221
  Log.debug({ ...reqMetadata, request }, "Incoming request body");
225
222
 
226
- // Process the request
227
- let response: MutateResponse | ValidateResponse[];
223
+ // Run the before hook if it exists
224
+ this.#beforeHook && this.#beforeHook(request || {});
228
225
 
229
- // Call mutate or validate based on the admission kind
230
- if (admissionKind === "Mutate") {
231
- response = await mutateProcessor(this.#config, this.#capabilities, request, reqMetadata);
232
- } else {
233
- response = await validateProcessor(this.#config, this.#capabilities, request, reqMetadata);
234
- }
226
+ // Process the request
227
+ const response: MutateResponse | ValidateResponse[] =
228
+ admissionKind === "Mutate"
229
+ ? await mutateProcessor(this.#config, this.#capabilities, request, reqMetadata)
230
+ : await validateProcessor(this.#config, this.#capabilities, request, reqMetadata);
235
231
 
236
232
  // Run the after hook if it exists
237
- const responseList: ValidateResponse[] | MutateResponse[] = Array.isArray(response) ? response : [response];
238
- responseList.map(res => {
233
+ [response].flat().map(res => {
239
234
  this.#afterHook && this.#afterHook(res);
240
- // Log the response
241
235
  Log.info({ ...reqMetadata, res }, "Check response");
242
236
  });
243
237
 
244
- let kubeAdmissionResponse: ValidateResponse[] | MutateResponse | ResponseItem;
245
-
246
- if (admissionKind === "Mutate") {
247
- kubeAdmissionResponse = response;
248
- Log.debug({ ...reqMetadata, response }, "Outgoing response");
249
- res.send({
250
- apiVersion: "admission.k8s.io/v1",
251
- kind: "AdmissionReview",
252
- response: kubeAdmissionResponse,
253
- });
254
- } else {
255
- kubeAdmissionResponse =
256
- responseList.length === 0
257
- ? {
258
- uid: request.uid,
259
- allowed: true,
260
- status: { message: "no in-scope validations -- allowed!" },
261
- }
262
- : {
263
- uid: responseList[0].uid,
264
- allowed: responseList.filter(r => !r.allowed).length === 0,
265
- status: {
266
- message: (responseList as ValidateResponse[])
267
- .filter(rl => !rl.allowed)
268
- .map(curr => curr.status?.message)
269
- .join("; "),
270
- },
271
- };
272
- res.send({
273
- apiVersion: "admission.k8s.io/v1",
274
- kind: "AdmissionReview",
275
- response: kubeAdmissionResponse,
276
- });
277
- }
278
-
279
- Log.debug({ ...reqMetadata, kubeAdmissionResponse }, "Outgoing response");
238
+ const kar: KubeAdmissionReview =
239
+ admissionKind === "Mutate"
240
+ ? karForMutate(response as MutateResponse)
241
+ : karForValidate(request, response as ValidateResponse[]);
242
+
243
+ Log.debug({ ...reqMetadata, kubeAdmissionResponse: kar.response }, "Outgoing response");
244
+ res.send(kar);
280
245
 
281
246
  this.#metricsCollector.observeEnd(startTime, admissionKind);
282
247
  } catch (err) {
@@ -298,6 +263,11 @@ export class Controller {
298
263
  const startTime = Date.now();
299
264
 
300
265
  res.on("finish", () => {
266
+ const excludedRoutes = ["/healthz", "/metrics"];
267
+ if (excludedRoutes.includes(req.originalUrl)) {
268
+ return;
269
+ }
270
+
301
271
  const elapsedTime = Date.now() - startTime;
302
272
  const message = {
303
273
  uid: req.body?.request?.uid,
@@ -0,0 +1,47 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+
4
+ import { MutateResponse, ValidateResponse } from "../k8s";
5
+ import { ResponseItem, AdmissionRequest } from "../types";
6
+
7
+ export interface KubeAdmissionReview {
8
+ apiVersion: string;
9
+ kind: string;
10
+ response: ValidateResponse[] | MutateResponse | ResponseItem;
11
+ }
12
+
13
+ export function karForMutate(mr: MutateResponse): KubeAdmissionReview {
14
+ return {
15
+ apiVersion: "admission.k8s.io/v1",
16
+ kind: "AdmissionReview",
17
+ response: mr,
18
+ };
19
+ }
20
+
21
+ export function karForValidate(ar: AdmissionRequest, vr: ValidateResponse[]): KubeAdmissionReview {
22
+ const isAllowed = vr.filter(r => !r.allowed).length === 0;
23
+
24
+ const resp: ValidateResponse =
25
+ vr.length === 0
26
+ ? {
27
+ uid: ar.uid,
28
+ allowed: true,
29
+ status: { code: 200, message: "no in-scope validations -- allowed!" },
30
+ }
31
+ : {
32
+ uid: vr[0].uid,
33
+ allowed: isAllowed,
34
+ status: {
35
+ code: isAllowed ? 200 : 422,
36
+ message: vr
37
+ .filter(rl => !rl.allowed)
38
+ .map(curr => curr.status?.message)
39
+ .join("; "),
40
+ },
41
+ };
42
+ return {
43
+ apiVersion: "admission.k8s.io/v1",
44
+ kind: "AdmissionReview",
45
+ response: resp,
46
+ };
47
+ }
@@ -7,12 +7,12 @@ import { startsWith } from "ramda";
7
7
 
8
8
  import { Capability } from "../capability";
9
9
  import { Store } from "../k8s";
10
- import Log, { redactedPatch, redactedStore } from "../logger";
10
+ import Log, { redactedPatch, redactedStore } from "../telemetry/logger";
11
11
  import { DataOp, DataSender, DataStore, Storage } from "../storage";
12
12
  import { fillStoreCache, sendUpdatesAndFlushCache } from "./storeCache";
13
13
 
14
14
  const namespace = "pepr-system";
15
- export const debounceBackoff = 5000;
15
+ export const debounceBackoff = 1000;
16
16
 
17
17
  export class StoreController {
18
18
  #name: string;
@@ -1,5 +1,5 @@
1
1
  import { DataOp } from "../storage";
2
- import Log from "../logger";
2
+ import Log from "../telemetry/logger";
3
3
  import { K8s } from "kubernetes-fluent-client";
4
4
  import { Store } from "../k8s";
5
5
  import { StatusCodes } from "http-status-codes";