pepr 0.40.1 → 0.42.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.
Files changed (107) hide show
  1. package/README.md +11 -5
  2. package/dist/cli/build.d.ts +1 -0
  3. package/dist/cli/build.d.ts.map +1 -1
  4. package/dist/cli/build.helpers.d.ts +66 -0
  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 +2 -2
  8. package/dist/cli/monitor.d.ts +23 -0
  9. package/dist/cli/monitor.d.ts.map +1 -1
  10. package/dist/cli.js +536 -429
  11. package/dist/controller.js +52 -27
  12. package/dist/lib/assets/destroy.d.ts.map +1 -1
  13. package/dist/lib/assets/helm.d.ts +1 -1
  14. package/dist/lib/assets/helm.d.ts.map +1 -1
  15. package/dist/lib/assets/index.d.ts.map +1 -1
  16. package/dist/lib/assets/pods.d.ts +5 -19
  17. package/dist/lib/assets/pods.d.ts.map +1 -1
  18. package/dist/lib/assets/webhooks.d.ts.map +1 -1
  19. package/dist/lib/assets/yaml.d.ts.map +1 -1
  20. package/dist/lib/capability.d.ts.map +1 -1
  21. package/dist/lib/controller/index.d.ts.map +1 -1
  22. package/dist/lib/controller/index.util.d.ts +10 -0
  23. package/dist/lib/controller/index.util.d.ts.map +1 -0
  24. package/dist/lib/controller/store.d.ts +0 -1
  25. package/dist/lib/controller/store.d.ts.map +1 -1
  26. package/dist/lib/controller/storeCache.d.ts +1 -0
  27. package/dist/lib/controller/storeCache.d.ts.map +1 -1
  28. package/dist/lib/deploymentChecks.d.ts +3 -0
  29. package/dist/lib/deploymentChecks.d.ts.map +1 -0
  30. package/dist/lib/enums.d.ts +5 -5
  31. package/dist/lib/enums.d.ts.map +1 -1
  32. package/dist/lib/filesystemService.d.ts +2 -0
  33. package/dist/lib/filesystemService.d.ts.map +1 -0
  34. package/dist/lib/filter/adjudicators/adjudicators.d.ts +73 -0
  35. package/dist/lib/filter/adjudicators/adjudicators.d.ts.map +1 -0
  36. package/dist/lib/filter/adjudicators/defaultTestObjects.d.ts +7 -0
  37. package/dist/lib/filter/adjudicators/defaultTestObjects.d.ts.map +1 -0
  38. package/dist/lib/helpers.d.ts +1 -4
  39. package/dist/lib/helpers.d.ts.map +1 -1
  40. package/dist/lib/mutate-request.d.ts +2 -2
  41. package/dist/lib/mutate-request.d.ts.map +1 -1
  42. package/dist/lib/queue.d.ts.map +1 -1
  43. package/dist/lib/schedule.d.ts.map +1 -1
  44. package/dist/lib/storage.d.ts +5 -5
  45. package/dist/lib/storage.d.ts.map +1 -1
  46. package/dist/lib/{logger.d.ts → telemetry/logger.d.ts} +1 -1
  47. package/dist/lib/telemetry/logger.d.ts.map +1 -0
  48. package/dist/lib/{metrics.d.ts → telemetry/metrics.d.ts} +3 -1
  49. package/dist/lib/telemetry/metrics.d.ts.map +1 -0
  50. package/dist/lib/types.d.ts +10 -9
  51. package/dist/lib/types.d.ts.map +1 -1
  52. package/dist/lib/utils.d.ts.map +1 -1
  53. package/dist/lib/validate-processor.d.ts +4 -1
  54. package/dist/lib/validate-processor.d.ts.map +1 -1
  55. package/dist/lib/watch-processor.d.ts.map +1 -1
  56. package/dist/lib.d.ts +1 -1
  57. package/dist/lib.d.ts.map +1 -1
  58. package/dist/lib.js +283 -231
  59. package/dist/lib.js.map +4 -4
  60. package/dist/sdk/sdk.d.ts +3 -4
  61. package/dist/sdk/sdk.d.ts.map +1 -1
  62. package/package.json +5 -5
  63. package/src/cli/build.helpers.ts +180 -0
  64. package/src/cli/build.ts +85 -132
  65. package/src/cli/deploy.ts +2 -1
  66. package/src/cli/init/templates.ts +1 -1
  67. package/src/cli/monitor.ts +108 -65
  68. package/src/lib/assets/deploy.ts +7 -7
  69. package/src/lib/assets/destroy.ts +2 -2
  70. package/src/lib/assets/helm.ts +6 -6
  71. package/src/lib/assets/index.ts +110 -89
  72. package/src/lib/assets/pods.ts +10 -5
  73. package/src/lib/assets/webhooks.ts +3 -3
  74. package/src/lib/assets/yaml.ts +12 -9
  75. package/src/lib/capability.ts +29 -19
  76. package/src/lib/controller/index.ts +41 -69
  77. package/src/lib/controller/index.util.ts +47 -0
  78. package/src/lib/controller/store.ts +24 -11
  79. package/src/lib/controller/storeCache.ts +11 -2
  80. package/src/lib/deploymentChecks.ts +43 -0
  81. package/src/lib/enums.ts +5 -5
  82. package/src/lib/filesystemService.ts +16 -0
  83. package/src/lib/filter/{adjudicators.ts → adjudicators/adjudicators.ts} +67 -35
  84. package/src/lib/filter/adjudicators/defaultTestObjects.ts +46 -0
  85. package/src/lib/filter/filter.ts +1 -1
  86. package/src/lib/finalizer.ts +1 -1
  87. package/src/lib/helpers.ts +31 -88
  88. package/src/lib/mutate-processor.ts +1 -1
  89. package/src/lib/mutate-request.ts +11 -11
  90. package/src/lib/queue.ts +13 -5
  91. package/src/lib/schedule.ts +8 -8
  92. package/src/lib/storage.ts +48 -39
  93. package/src/lib/{logger.ts → telemetry/logger.ts} +1 -1
  94. package/src/lib/{metrics.ts → telemetry/metrics.ts} +18 -17
  95. package/src/lib/types.ts +12 -9
  96. package/src/lib/utils.ts +6 -6
  97. package/src/lib/validate-processor.ts +48 -40
  98. package/src/lib/watch-processor.ts +19 -15
  99. package/src/lib.ts +1 -1
  100. package/src/runtime/controller.ts +1 -1
  101. package/src/sdk/cosign.ts +4 -4
  102. package/src/sdk/sdk.ts +6 -9
  103. package/src/templates/capabilities/hello-pepr.ts +19 -9
  104. package/dist/lib/filter/adjudicators.d.ts +0 -69
  105. package/dist/lib/filter/adjudicators.d.ts.map +0 -1
  106. package/dist/lib/logger.d.ts.map +0 -1
  107. package/dist/lib/metrics.d.ts.map +0 -1
@@ -1,92 +1,51 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
3
 
4
- import { Log as K8sLog, KubeConfig } from "@kubernetes/client-node";
4
+ import { Log as K8sLog, KubeConfig, KubernetesListObject } from "@kubernetes/client-node";
5
5
  import { K8s, kind } from "kubernetes-fluent-client";
6
6
  import stream from "stream";
7
7
  import { ResponseItem } from "../lib/types";
8
8
  import { RootCmd } from "./root";
9
9
 
10
- export default function (program: RootCmd) {
10
+ interface LogPayload {
11
+ namespace: string;
12
+ name: string;
13
+ res: {
14
+ uid: string;
15
+ allowed?: boolean;
16
+ patch?: string;
17
+ patchType?: string;
18
+ warnings?: string;
19
+ status?: {
20
+ message: string;
21
+ };
22
+ };
23
+ }
24
+
25
+ export default function (program: RootCmd): void {
11
26
  program
12
27
  .command("monitor [module-uuid]")
13
28
  .description("Monitor a Pepr Module")
14
29
  .action(async uuid => {
15
- let labels: string[];
16
- let errorMessage: string;
17
-
18
- if (!uuid) {
19
- labels = ["pepr.dev/controller", "admission"];
20
- errorMessage = `No pods found with admission labels`;
21
- } else {
22
- labels = ["app", `pepr-${uuid}`];
23
- errorMessage = `No pods found for module ${uuid}`;
24
- }
30
+ const { labels, errorMessage } = getLabelsAndErrorMessage(uuid);
25
31
 
26
32
  // Get the logs for the `app=pepr-${module}` or `pepr.dev/controller=admission` pod selector
27
- const pods = await K8s(kind.Pod)
33
+ const pods: KubernetesListObject<kind.Pod> = await K8s(kind.Pod)
28
34
  .InNamespace("pepr-system")
29
35
  .WithLabel(labels[0], labels[1])
30
36
  .Get();
31
37
 
32
- const podNames = pods.items.flatMap(pod => pod.metadata!.name) as string[];
38
+ // Pods will ways have a metadata and name fields
39
+ const podNames: string[] = pods.items.flatMap(pod => pod.metadata!.name || "");
33
40
 
34
41
  if (podNames.length < 1) {
35
42
  console.error(errorMessage);
36
43
  process.exit(1);
37
44
  }
38
45
 
39
- const kc = new KubeConfig();
40
- kc.loadFromDefault();
41
-
42
- const log = new K8sLog(kc);
43
-
44
- const logStream = new stream.PassThrough();
45
- logStream.on("data", async chunk => {
46
- const respMsg = `"msg":"Check response"`;
47
- // Split the chunk into lines
48
- const lines = await chunk.toString().split("\n");
49
-
50
- for (const line of lines) {
51
- // Check for `"msg":"Hello Pepr"`
52
- if (!line.includes(respMsg)) continue;
53
- try {
54
- const payload = JSON.parse(line.trim());
55
- const isMutate = payload.res.patchType || payload.res.warnings;
56
-
57
- const name = `${payload.namespace}${payload.name}`;
58
- const uid = payload.res.uid;
59
-
60
- if (isMutate) {
61
- const plainPatch =
62
- payload.res?.patch !== undefined && payload.res?.patch !== null
63
- ? atob(payload.res.patch)
64
- : "";
65
-
66
- const patch = plainPatch !== "" && JSON.stringify(JSON.parse(plainPatch), null, 2);
67
- const patchType = payload.res.patchType || payload.res.warnings || "";
68
- const allowOrDeny = payload.res.allowed ? "🔀" : "🚫";
69
- console.log(`\n${allowOrDeny} MUTATE ${name} (${uid})`);
70
- patchType.length > 0 && console.log(`\n\u001b[1;34m${patch}\u001b[0m`);
71
- } else {
72
- const failures = Array.isArray(payload.res) ? payload.res : [payload.res];
73
-
74
- const filteredFailures = failures
75
- .filter((r: ResponseItem) => !r.allowed)
76
- .map((r: ResponseItem) => r.status.message);
77
-
78
- console.log(
79
- `\n${filteredFailures.length > 0 ? "❌" : "✅"} VALIDATE ${name} (${uid})`,
80
- );
81
- console.log(
82
- filteredFailures.length > 0 ? `\u001b[1;31m${filteredFailures}\u001b[0m` : "",
83
- );
84
- }
85
- } catch {
86
- // Do nothing
87
- }
88
- }
89
- });
46
+ const log = getK8sLogFromKubeConfig();
47
+
48
+ const logStream = createLogStream();
90
49
 
91
50
  for (const podName of podNames) {
92
51
  await log.log("pepr-system", podName, "server", logStream, {
@@ -97,3 +56,87 @@ export default function (program: RootCmd) {
97
56
  }
98
57
  });
99
58
  }
59
+
60
+ export function getLabelsAndErrorMessage(uuid?: string): {
61
+ labels: string[];
62
+ errorMessage: string;
63
+ } {
64
+ let labels: string[];
65
+ let errorMessage: string;
66
+
67
+ if (!uuid) {
68
+ labels = ["pepr.dev/controller", "admission"];
69
+ errorMessage = `No pods found with admission labels`;
70
+ } else {
71
+ labels = ["app", `pepr-${uuid}`];
72
+ errorMessage = `No pods found for module ${uuid}`;
73
+ }
74
+
75
+ return { labels, errorMessage };
76
+ }
77
+
78
+ export function getK8sLogFromKubeConfig(): K8sLog {
79
+ const kc = new KubeConfig();
80
+ kc.loadFromDefault();
81
+ return new K8sLog(kc);
82
+ }
83
+
84
+ function createLogStream(): stream.PassThrough {
85
+ const logStream = new stream.PassThrough();
86
+
87
+ logStream.on("data", async chunk => {
88
+ const lines = chunk.toString().split("\n");
89
+ const respMsg = `"msg":"Check response"`;
90
+
91
+ for (const line of lines) {
92
+ if (!line.includes(respMsg)) continue;
93
+ processLogLine(line);
94
+ }
95
+ });
96
+
97
+ return logStream;
98
+ }
99
+
100
+ function processLogLine(line: string): void {
101
+ try {
102
+ const payload: LogPayload = JSON.parse(line.trim());
103
+ const isMutate = payload.res.patchType || payload.res.warnings;
104
+ const name = `${payload.namespace}${payload.name}`;
105
+ const uid = payload.res.uid;
106
+
107
+ if (isMutate) {
108
+ processMutateLog(payload, name, uid);
109
+ } else {
110
+ processValidateLog(payload, name, uid);
111
+ }
112
+ } catch {
113
+ // Do nothing
114
+ }
115
+ }
116
+
117
+ export function processMutateLog(payload: LogPayload, name: string, uid: string): void {
118
+ const plainPatch =
119
+ payload.res.patch !== undefined && payload.res.patch !== null ? atob(payload.res.patch) : "";
120
+
121
+ const patch = plainPatch !== "" && JSON.stringify(JSON.parse(plainPatch), null, 2);
122
+ const patchType = payload.res.patchType || payload.res.warnings || "";
123
+ const allowOrDeny = payload.res.allowed ? "🔀" : "🚫";
124
+
125
+ console.log(`\n${allowOrDeny} MUTATE ${name} (${uid})`);
126
+ if (patchType.length > 0) {
127
+ console.log(`\n\u001b[1;34m${patch}\u001b[0m`);
128
+ }
129
+ }
130
+
131
+ export function processValidateLog(payload: LogPayload, name: string, uid: string): void {
132
+ const failures = Array.isArray(payload.res) ? payload.res : [payload.res];
133
+
134
+ const filteredFailures = failures
135
+ .filter((r: ResponseItem) => !r.allowed)
136
+ .map((r: ResponseItem) => r.status?.message || "");
137
+
138
+ console.log(`\n${filteredFailures.length > 0 ? "❌" : "✅"} VALIDATE ${name} (${uid})`);
139
+ if (filteredFailures.length > 0) {
140
+ console.log(`\u001b[1;31m${filteredFailures}\u001b[0m`);
141
+ }
142
+ }
@@ -7,9 +7,9 @@ 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
- import { deployment, moduleSecret, namespace, watcher } from "./pods";
12
+ import { getDeployment, getModuleSecret, getNamespace, getWatcher } from "./pods";
13
13
  import { clusterRole, clusterRoleBinding, serviceAccount, storeRole, storeRoleBinding } from "./rbac";
14
14
  import { peprStoreCRD } from "./store";
15
15
  import { webhookConfig } from "./webhooks";
@@ -19,7 +19,7 @@ export async function deployImagePullSecret(imagePullSecret: ImagePullSecret, na
19
19
  try {
20
20
  await K8s(kind.Namespace).Get("pepr-system");
21
21
  } catch {
22
- await K8s(kind.Namespace).Apply(namespace());
22
+ await K8s(kind.Namespace).Apply(getNamespace());
23
23
  }
24
24
 
25
25
  try {
@@ -48,7 +48,7 @@ export async function deploy(assets: Assets, force: boolean, webhookTimeout?: nu
48
48
  const { name, host, path } = assets;
49
49
 
50
50
  Log.info("Applying pepr-system namespace");
51
- await K8s(kind.Namespace).Apply(namespace(assets.config.customLabels?.namespace));
51
+ await K8s(kind.Namespace).Apply(getNamespace(assets.config.customLabels?.namespace));
52
52
 
53
53
  // Create the mutating webhook configuration if it is needed
54
54
  const mutateWebhook = await webhookConfig(assets, "mutate", webhookTimeout);
@@ -123,7 +123,7 @@ async function setupController(assets: Assets, code: Buffer, hash: string, force
123
123
  const { name } = assets;
124
124
 
125
125
  Log.info("Applying module secret");
126
- const mod = moduleSecret(name, code, hash);
126
+ const mod = getModuleSecret(name, code, hash);
127
127
  await K8s(kind.Secret).Apply(mod, { force });
128
128
 
129
129
  Log.info("Applying controller service");
@@ -139,14 +139,14 @@ async function setupController(assets: Assets, code: Buffer, hash: string, force
139
139
  await K8s(kind.Secret).Apply(apiToken, { force });
140
140
 
141
141
  Log.info("Applying deployment");
142
- const dep = deployment(assets, hash, assets.buildTimestamp);
142
+ const dep = getDeployment(assets, hash, assets.buildTimestamp);
143
143
  await K8s(kind.Deployment).Apply(dep, { force });
144
144
  }
145
145
 
146
146
  // Setup the watcher deployment and service
147
147
  async function setupWatcher(assets: Assets, hash: string, force: boolean) {
148
148
  // If the module has a watcher, deploy it
149
- const watchDeployment = watcher(assets, hash, assets.buildTimestamp);
149
+ const watchDeployment = getWatcher(assets, hash, assets.buildTimestamp);
150
150
  if (watchDeployment) {
151
151
  Log.info("Applying watcher deployment");
152
152
  await K8s(kind.Deployment).Apply(watchDeployment, { force });
@@ -3,10 +3,10 @@
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
- export async function destroyModule(name: string) {
9
+ export async function destroyModule(name: string): Promise<void> {
10
10
  const namespace = "pepr-system";
11
11
 
12
12
  Log.info("Destroying Pepr module");
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
3
 
4
- export function clusterRoleTemplate() {
4
+ export function clusterRoleTemplate(): string {
5
5
  return `
6
6
  apiVersion: rbac.authorization.k8s.io/v1
7
7
  kind: ClusterRole
@@ -15,7 +15,7 @@ export function clusterRoleTemplate() {
15
15
  `;
16
16
  }
17
17
 
18
- export function nsTemplate() {
18
+ export function namespaceTemplate(): string {
19
19
  return `
20
20
  apiVersion: v1
21
21
  kind: Namespace
@@ -32,7 +32,7 @@ export function nsTemplate() {
32
32
  `;
33
33
  }
34
34
 
35
- export function chartYaml(name: string, description?: string) {
35
+ export function chartYaml(name: string, description?: string): string {
36
36
  return `
37
37
  apiVersion: v2
38
38
  name: ${name}
@@ -61,7 +61,7 @@ export function chartYaml(name: string, description?: string) {
61
61
  `;
62
62
  }
63
63
 
64
- export function watcherDeployTemplate(buildTimestamp: string) {
64
+ export function watcherDeployTemplate(buildTimestamp: string): string {
65
65
  return `
66
66
  apiVersion: apps/v1
67
67
  kind: Deployment
@@ -142,7 +142,7 @@ export function watcherDeployTemplate(buildTimestamp: string) {
142
142
  `;
143
143
  }
144
144
 
145
- export function admissionDeployTemplate(buildTimestamp: string) {
145
+ export function admissionDeployTemplate(buildTimestamp: string): string {
146
146
  return `
147
147
  apiVersion: apps/v1
148
148
  kind: Deployment
@@ -228,7 +228,7 @@ export function admissionDeployTemplate(buildTimestamp: string) {
228
228
  `;
229
229
  }
230
230
 
231
- export function serviceMonitorTemplate(name: string) {
231
+ export function serviceMonitorTemplate(name: string): string {
232
232
  return `
233
233
  {{- if .Values.${name}.serviceMonitor.enabled }}
234
234
  apiVersion: monitoring.coreos.com/v1
@@ -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,11 +12,11 @@ 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,
18
- nsTemplate,
19
+ namespaceTemplate,
19
20
  admissionDeployTemplate,
20
21
  watcherDeployTemplate,
21
22
  clusterRoleTemplate,
@@ -24,9 +25,72 @@ import {
24
25
  import { promises as fs } from "fs";
25
26
  import { webhookConfig } from "./webhooks";
26
27
  import { apiTokenSecret, service, tlsSecret, watcherService } from "./networking";
27
- import { watcher, moduleSecret } from "./pods";
28
+ import { getWatcher, getModuleSecret } 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): Record<string, Record<string, 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;
@@ -55,20 +119,20 @@ export class Assets {
55
119
  this.apiToken = crypto.randomBytes(32).toString("hex");
56
120
  }
57
121
 
58
- setHash = (hash: string) => {
122
+ setHash = (hash: string): void => {
59
123
  this.hash = hash;
60
124
  };
61
125
 
62
- deploy = async (force: boolean, webhookTimeout?: number) => {
126
+ deploy = async (force: boolean, webhookTimeout?: number): Promise<void> => {
63
127
  this.capabilities = await loadCapabilities(this.path);
64
128
  await deploy(this, force, webhookTimeout);
65
129
  };
66
130
 
67
- zarfYaml = (path: string) => zarfYaml(this, path);
131
+ zarfYaml = (path: string): string => zarfYaml(this, path);
68
132
 
69
- zarfYamlChart = (path: string) => zarfYamlChart(this, path);
133
+ zarfYamlChart = (path: string): string => zarfYamlChart(this, path);
70
134
 
71
- allYaml = async (imagePullSecret?: string) => {
135
+ allYaml = async (imagePullSecret?: string): Promise<string> => {
72
136
  this.capabilities = await loadCapabilities(this.path);
73
137
  // give error if namespaces are not respected
74
138
  for (const capability of this.capabilities) {
@@ -78,102 +142,59 @@ export class Assets {
78
142
  return allYaml(this, imagePullSecret);
79
143
  };
80
144
 
81
- 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`);
145
+ /* eslint max-statements: ["warn", 21] */
146
+ generateHelmChart = async (basePath: string): Promise<void> => {
147
+ const helm = helmLayout(basePath, this.config.uuid);
111
148
 
112
- // create templates dir
113
- await createDirectoryIfNotExists(`${CHAR_TEMPLATES_DIR}`);
114
-
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, (): string => dedent(chartYaml(this.config.uuid, this.config.description || ""))],
160
+ [helm.files.namespaceYaml, (): string => dedent(namespaceTemplate())],
161
+ [helm.files.watcherServiceYaml, (): string => toYaml(watcherService(this.name))],
162
+ [helm.files.admissionServiceYaml, (): string => toYaml(service(this.name))],
163
+ [helm.files.tlsSecretYaml, (): string => toYaml(tlsSecret(this.name, this.tls))],
164
+ [helm.files.apiTokenSecretYaml, (): string => toYaml(apiTokenSecret(this.name, this.apiToken))],
165
+ [helm.files.storeRoleYaml, (): string => toYaml(storeRole(this.name))],
166
+ [helm.files.storeRoleBindingYaml, (): string => toYaml(storeRoleBinding(this.name))],
167
+ [helm.files.clusterRoleYaml, (): string => dedent(clusterRoleTemplate())],
168
+ [helm.files.clusterRoleBindingYaml, (): string => toYaml(clusterRoleBinding(this.name))],
169
+ [helm.files.serviceAccountYaml, (): string => toYaml(serviceAccount(this.name))],
170
+ [helm.files.moduleSecretYaml, (): string => toYaml(getModuleSecret(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 = getWatcher(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}`);
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
3
 
4
- import { V1EnvVar } from "@kubernetes/client-node";
4
+ import { KubernetesObject, V1EnvVar } from "@kubernetes/client-node";
5
5
  import { kind } from "kubernetes-fluent-client";
6
6
  import { gzipSync } from "zlib";
7
7
  import { secretOverLimit } from "../helpers";
@@ -10,7 +10,7 @@ import { ModuleConfig } from "../module";
10
10
  import { Binding } from "../types";
11
11
 
12
12
  /** Generate the pepr-system namespace */
13
- export function namespace(namespaceLabels?: Record<string, string>) {
13
+ export function getNamespace(namespaceLabels?: Record<string, string>): KubernetesObject {
14
14
  if (namespaceLabels) {
15
15
  return {
16
16
  apiVersion: "v1",
@@ -31,7 +31,12 @@ export function namespace(namespaceLabels?: Record<string, string>) {
31
31
  }
32
32
  }
33
33
 
34
- export function watcher(assets: Assets, hash: string, buildTimestamp: string, imagePullSecret?: string) {
34
+ export function getWatcher(
35
+ assets: Assets,
36
+ hash: string,
37
+ buildTimestamp: string,
38
+ imagePullSecret?: string,
39
+ ): kind.Deployment | null {
35
40
  const { name, image, capabilities, config } = assets;
36
41
 
37
42
  let hasSchedule = false;
@@ -186,7 +191,7 @@ export function watcher(assets: Assets, hash: string, buildTimestamp: string, im
186
191
  return deploy;
187
192
  }
188
193
 
189
- export function deployment(
194
+ export function getDeployment(
190
195
  assets: Assets,
191
196
  hash: string,
192
197
  buildTimestamp: string,
@@ -336,7 +341,7 @@ export function deployment(
336
341
  return deploy;
337
342
  }
338
343
 
339
- export function moduleSecret(name: string, data: Buffer, hash: string): kind.Secret {
344
+ export function getModuleSecret(name: string, data: Buffer, hash: string): kind.Secret {
340
345
  // Compress the data
341
346
  const compressed = gzipSync(data);
342
347
  const path = `module-${hash}.js.gz`;
@@ -20,7 +20,7 @@ const peprIgnoreLabel: V1LabelSelectorRequirement = {
20
20
 
21
21
  const peprIgnoreNamespaces: string[] = ["kube-system", "pepr-system"];
22
22
 
23
- export async function generateWebhookRules(assets: Assets, isMutateWebhook: boolean) {
23
+ export async function generateWebhookRules(assets: Assets, isMutateWebhook: boolean): Promise<V1RuleWithOperations[]> {
24
24
  const { config, capabilities } = assets;
25
25
  const rules: V1RuleWithOperations[] = [];
26
26
 
@@ -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
  }