pepr 0.0.0-development

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 (58) hide show
  1. package/.prettierignore +1 -0
  2. package/CODE_OF_CONDUCT.md +133 -0
  3. package/LICENSE +201 -0
  4. package/README.md +151 -0
  5. package/SECURITY.md +18 -0
  6. package/SUPPORT.md +16 -0
  7. package/codecov.yaml +19 -0
  8. package/commitlint.config.js +1 -0
  9. package/package.json +70 -0
  10. package/src/cli.ts +48 -0
  11. package/src/lib/assets/deploy.ts +122 -0
  12. package/src/lib/assets/destroy.ts +33 -0
  13. package/src/lib/assets/helm.ts +219 -0
  14. package/src/lib/assets/index.ts +175 -0
  15. package/src/lib/assets/loader.ts +41 -0
  16. package/src/lib/assets/networking.ts +89 -0
  17. package/src/lib/assets/pods.ts +353 -0
  18. package/src/lib/assets/rbac.ts +111 -0
  19. package/src/lib/assets/store.ts +49 -0
  20. package/src/lib/assets/webhooks.ts +147 -0
  21. package/src/lib/assets/yaml.ts +234 -0
  22. package/src/lib/capability.ts +314 -0
  23. package/src/lib/controller/index.ts +326 -0
  24. package/src/lib/controller/store.ts +219 -0
  25. package/src/lib/errors.ts +20 -0
  26. package/src/lib/filter.ts +110 -0
  27. package/src/lib/helpers.ts +342 -0
  28. package/src/lib/included-files.ts +19 -0
  29. package/src/lib/k8s.ts +169 -0
  30. package/src/lib/logger.ts +27 -0
  31. package/src/lib/metrics.ts +120 -0
  32. package/src/lib/module.ts +136 -0
  33. package/src/lib/mutate-processor.ts +160 -0
  34. package/src/lib/mutate-request.ts +153 -0
  35. package/src/lib/queue.ts +89 -0
  36. package/src/lib/schedule.ts +175 -0
  37. package/src/lib/storage.ts +192 -0
  38. package/src/lib/tls.ts +90 -0
  39. package/src/lib/types.ts +215 -0
  40. package/src/lib/utils.ts +57 -0
  41. package/src/lib/validate-processor.ts +80 -0
  42. package/src/lib/validate-request.ts +102 -0
  43. package/src/lib/watch-processor.ts +124 -0
  44. package/src/lib.ts +27 -0
  45. package/src/runtime/controller.ts +75 -0
  46. package/src/sdk/sdk.ts +116 -0
  47. package/src/templates/.eslintrc.template.json +18 -0
  48. package/src/templates/.prettierrc.json +13 -0
  49. package/src/templates/README.md +21 -0
  50. package/src/templates/capabilities/hello-pepr.samples.json +160 -0
  51. package/src/templates/capabilities/hello-pepr.ts +426 -0
  52. package/src/templates/gitignore +4 -0
  53. package/src/templates/package.json +20 -0
  54. package/src/templates/pepr.code-snippets.json +21 -0
  55. package/src/templates/pepr.ts +17 -0
  56. package/src/templates/settings.json +10 -0
  57. package/src/templates/tsconfig.json +9 -0
  58. package/src/templates/tsconfig.module.json +19 -0
@@ -0,0 +1,122 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+
4
+ import crypto from "crypto";
5
+ import { promises as fs } from "fs";
6
+ import { K8s, kind } from "kubernetes-fluent-client";
7
+
8
+ import { Assets } from ".";
9
+ import Log from "../logger";
10
+ import { apiTokenSecret, service, tlsSecret, watcherService } from "./networking";
11
+ import { deployment, moduleSecret, namespace, watcher } from "./pods";
12
+ import { clusterRole, clusterRoleBinding, serviceAccount, storeRole, storeRoleBinding } from "./rbac";
13
+ import { peprStoreCRD } from "./store";
14
+ import { webhookConfig } from "./webhooks";
15
+ import { CapabilityExport } from "../types";
16
+
17
+ export async function deploy(assets: Assets, force: boolean, webhookTimeout?: number) {
18
+ Log.info("Establishing connection to Kubernetes");
19
+
20
+ const { name, host, path } = assets;
21
+
22
+ Log.info("Applying pepr-system namespace");
23
+ await K8s(kind.Namespace).Apply(namespace(assets.config.customLabels?.namespace));
24
+
25
+ // Create the mutating webhook configuration if it is needed
26
+ const mutateWebhook = await webhookConfig(assets, "mutate", webhookTimeout);
27
+ if (mutateWebhook) {
28
+ Log.info("Applying mutating webhook");
29
+ await K8s(kind.MutatingWebhookConfiguration).Apply(mutateWebhook, { force });
30
+ } else {
31
+ Log.info("Mutating webhook not needed, removing if it exists");
32
+ await K8s(kind.MutatingWebhookConfiguration).Delete(name);
33
+ }
34
+
35
+ // Create the validating webhook configuration if it is needed
36
+ const validateWebhook = await webhookConfig(assets, "validate", webhookTimeout);
37
+ if (validateWebhook) {
38
+ Log.info("Applying validating webhook");
39
+ await K8s(kind.ValidatingWebhookConfiguration).Apply(validateWebhook, { force });
40
+ } else {
41
+ Log.info("Validating webhook not needed, removing if it exists");
42
+ await K8s(kind.ValidatingWebhookConfiguration).Delete(name);
43
+ }
44
+
45
+ Log.info("Applying the Pepr Store CRD if it doesn't exist");
46
+ await K8s(kind.CustomResourceDefinition).Apply(peprStoreCRD, { force });
47
+
48
+ // If a host is specified, we don't need to deploy the rest of the resources
49
+ if (host) {
50
+ return;
51
+ }
52
+
53
+ const code = await fs.readFile(path);
54
+ const hash = crypto.createHash("sha256").update(code).digest("hex");
55
+
56
+ if (code.length < 1) {
57
+ throw new Error("No code provided");
58
+ }
59
+
60
+ await setupRBAC(name, assets.capabilities, force);
61
+ await setupController(assets, code, hash, force);
62
+ await setupWatcher(assets, hash, force);
63
+ }
64
+
65
+ async function setupRBAC(name: string, capabilities: CapabilityExport[], force: boolean) {
66
+ Log.info("Applying cluster role binding");
67
+ const crb = clusterRoleBinding(name);
68
+ await K8s(kind.ClusterRoleBinding).Apply(crb, { force });
69
+
70
+ Log.info("Applying cluster role");
71
+ const cr = clusterRole(name, capabilities);
72
+ await K8s(kind.ClusterRole).Apply(cr, { force });
73
+
74
+ Log.info("Applying service account");
75
+ const sa = serviceAccount(name);
76
+ await K8s(kind.ServiceAccount).Apply(sa, { force });
77
+
78
+ Log.info("Applying store role");
79
+ const role = storeRole(name);
80
+ await K8s(kind.Role).Apply(role, { force });
81
+
82
+ Log.info("Applying store role binding");
83
+ const roleBinding = storeRoleBinding(name);
84
+ await K8s(kind.RoleBinding).Apply(roleBinding, { force });
85
+ }
86
+
87
+ async function setupController(assets: Assets, code: Buffer, hash: string, force: boolean) {
88
+ const { name } = assets;
89
+
90
+ Log.info("Applying module secret");
91
+ const mod = moduleSecret(name, code, hash);
92
+ await K8s(kind.Secret).Apply(mod, { force });
93
+
94
+ Log.info("Applying controller service");
95
+ const svc = service(name);
96
+ await K8s(kind.Service).Apply(svc, { force });
97
+
98
+ Log.info("Applying TLS secret");
99
+ const tls = tlsSecret(name, assets.tls);
100
+ await K8s(kind.Secret).Apply(tls, { force });
101
+
102
+ Log.info("Applying API token secret");
103
+ const apiToken = apiTokenSecret(name, assets.apiToken);
104
+ await K8s(kind.Secret).Apply(apiToken, { force });
105
+
106
+ Log.info("Applying deployment");
107
+ const dep = deployment(assets, hash, assets.buildTimestamp);
108
+ await K8s(kind.Deployment).Apply(dep, { force });
109
+ }
110
+
111
+ async function setupWatcher(assets: Assets, hash: string, force: boolean) {
112
+ // If the module has a watcher, deploy it
113
+ const watchDeployment = watcher(assets, hash, assets.buildTimestamp);
114
+ if (watchDeployment) {
115
+ Log.info("Applying watcher deployment");
116
+ await K8s(kind.Deployment).Apply(watchDeployment, { force });
117
+
118
+ Log.info("Applying watcher service");
119
+ const watchSvc = watcherService(assets.name);
120
+ await K8s(kind.Service).Apply(watchSvc, { force });
121
+ }
122
+ }
@@ -0,0 +1,33 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+
4
+ import { K8s, kind } from "kubernetes-fluent-client";
5
+
6
+ import Log from "../logger";
7
+ import { peprStoreCRD } from "./store";
8
+
9
+ export async function destroyModule(name: string) {
10
+ const namespace = "pepr-system";
11
+
12
+ Log.info("Destroying Pepr module");
13
+
14
+ await Promise.all([
15
+ K8s(kind.MutatingWebhookConfiguration).Delete(name),
16
+ K8s(kind.ValidatingWebhookConfiguration).Delete(name),
17
+
18
+ K8s(kind.CustomResourceDefinition).Delete(peprStoreCRD),
19
+ K8s(kind.ClusterRoleBinding).Delete(name),
20
+ K8s(kind.ClusterRole).Delete(name),
21
+ K8s(kind.ServiceAccount, { namespace }).Delete(name),
22
+ K8s(kind.Role, { namespace }).Delete(name),
23
+ K8s(kind.RoleBinding, { namespace }).Delete(`${name}-store`),
24
+
25
+ K8s(kind.Secret, { namespace }).Delete(`${name}-module`),
26
+ K8s(kind.Service, { namespace }).Delete(name),
27
+ K8s(kind.Secret, { namespace }).Delete(`${name}-tls`),
28
+ K8s(kind.Secret, { namespace }).Delete(`${name}-api-token`),
29
+ K8s(kind.Deployment, { namespace }).Delete(name),
30
+ K8s(kind.Deployment, { namespace }).Delete(`${name}-watcher`),
31
+ K8s(kind.Service, { namespace }).Delete(`${name}-watcher`),
32
+ ]);
33
+ }
@@ -0,0 +1,219 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+
4
+ export function nsTemplate() {
5
+ return `
6
+ apiVersion: v1
7
+ kind: Namespace
8
+ metadata:
9
+ name: pepr-system
10
+ {{- if .Values.namespace.annotations }}
11
+ annotations:
12
+ {{- toYaml .Values.namespace.annotations | nindent 6 }}
13
+ {{- end }}
14
+ {{- if .Values.namespace.labels }}
15
+ labels:
16
+ {{- toYaml .Values.namespace.labels | nindent 6 }}
17
+ {{- end }}
18
+ `;
19
+ }
20
+
21
+ export function chartYaml(name: string, description?: string) {
22
+ return `
23
+ apiVersion: v2
24
+ name: ${name}
25
+ description: ${description || ""}
26
+
27
+ # A chart can be either an 'application' or a 'library' chart.
28
+ #
29
+ # Application charts are a collection of templates that can be packaged into versioned archives
30
+ # to be deployed.
31
+ #
32
+ # Library charts provide useful utilities or functions for the chart developer. They're included as
33
+ # a dependency of application charts to inject those utilities and functions into the rendering
34
+ # pipeline. Library charts do not define any templates and therefore cannot be deployed.
35
+ type: application
36
+
37
+ # This is the chart version. This version number should be incremented each time you make changes
38
+ # to the chart and its templates, including the app version.
39
+ # Versions are expected to follow Semantic Versioning (https://semver.org/)
40
+ version: 0.1.0
41
+
42
+ # This is the version number of the application being deployed. This version number should be
43
+ # incremented each time you make changes to the application. Versions are not expected to
44
+ # follow Semantic Versioning. They should reflect the version the application is using.
45
+ # It is recommended to use it with quotes.
46
+ appVersion: "1.16.0"
47
+ `;
48
+ }
49
+
50
+ export function watcherDeployTemplate(buildTimestamp: string) {
51
+ return `
52
+ apiVersion: apps/v1
53
+ kind: Deployment
54
+ metadata:
55
+ name: {{ .Values.uuid }}-watcher
56
+ namespace: pepr-system
57
+ annotations:
58
+ {{- toYaml .Values.watcher.annotations | nindent 4 }}
59
+ labels:
60
+ {{- toYaml .Values.watcher.labels | nindent 4 }}
61
+ spec:
62
+ replicas: 1
63
+ strategy:
64
+ type: Recreate
65
+ selector:
66
+ matchLabels:
67
+ app: {{ .Values.uuid }}-watcher
68
+ pepr.dev/controller: watcher
69
+ template:
70
+ metadata:
71
+ annotations:
72
+ buildTimestamp: "${buildTimestamp}"
73
+ {{- if .Values.watcher.podAnnotations }}
74
+ {{- toYaml .Values.watcher.podAnnotations | nindent 8 }}
75
+ {{- end }}
76
+ labels:
77
+ app: {{ .Values.uuid }}-watcher
78
+ pepr.dev/controller: watcher
79
+ spec:
80
+ terminationGracePeriodSeconds: {{ .Values.watcher.terminationGracePeriodSeconds }}
81
+ serviceAccountName: {{ .Values.uuid }}
82
+ securityContext:
83
+ {{- toYaml .Values.admission.securityContext | nindent 8 }}
84
+ containers:
85
+ - name: watcher
86
+ image: {{ .Values.watcher.image }}
87
+ imagePullPolicy: IfNotPresent
88
+ command:
89
+ - node
90
+ - /app/node_modules/pepr/dist/controller.js
91
+ - {{ .Values.hash }}
92
+ readinessProbe:
93
+ httpGet:
94
+ path: /healthz
95
+ port: 3000
96
+ scheme: HTTPS
97
+ livenessProbe:
98
+ httpGet:
99
+ path: /healthz
100
+ port: 3000
101
+ scheme: HTTPS
102
+ ports:
103
+ - containerPort: 3000
104
+ resources:
105
+ {{- toYaml .Values.watcher.resources | nindent 12 }}
106
+ env:
107
+ {{- toYaml .Values.watcher.env | nindent 12 }}
108
+ securityContext:
109
+ {{- toYaml .Values.watcher.containerSecurityContext | nindent 12 }}
110
+ volumeMounts:
111
+ - name: tls-certs
112
+ mountPath: /etc/certs
113
+ readOnly: true
114
+ - name: module
115
+ mountPath: /app/load
116
+ readOnly: true
117
+ {{- if .Values.watcher.extraVolumeMounts }}
118
+ {{- toYaml .Values.watcher.extraVolumeMounts | nindent 12 }}
119
+ {{- end }}
120
+ volumes:
121
+ - name: tls-certs
122
+ secret:
123
+ secretName: {{ .Values.uuid }}-tls
124
+ - name: module
125
+ secret:
126
+ secretName: {{ .Values.uuid }}-module
127
+ {{- if .Values.watcher.extraVolumes }}
128
+ {{- toYaml .Values.watcher.extraVolumes | nindent 8 }}
129
+ {{- end }}
130
+ `;
131
+ }
132
+
133
+ export function admissionDeployTemplate(buildTimestamp: string) {
134
+ return `
135
+ apiVersion: apps/v1
136
+ kind: Deployment
137
+ metadata:
138
+ name: {{ .Values.uuid }}
139
+ namespace: pepr-system
140
+ annotations:
141
+ {{- toYaml .Values.admission.annotations | nindent 4 }}
142
+ labels:
143
+ {{- toYaml .Values.admission.labels | nindent 4 }}
144
+ spec:
145
+ replicas: 2
146
+ selector:
147
+ matchLabels:
148
+ app: {{ .Values.uuid }}
149
+ pepr.dev/controller: admission
150
+ template:
151
+ metadata:
152
+ annotations:
153
+ buildTimestamp: "${buildTimestamp}"
154
+ {{- if .Values.admission.podAnnotations }}
155
+ {{- toYaml .Values.admission.podAnnotations | nindent 8 }}
156
+ {{- end }}
157
+ labels:
158
+ app: {{ .Values.uuid }}
159
+ pepr.dev/controller: admission
160
+ spec:
161
+ terminationGracePeriodSeconds: {{ .Values.admission.terminationGracePeriodSeconds }}
162
+ priorityClassName: system-node-critical
163
+ serviceAccountName: {{ .Values.uuid }}
164
+ securityContext:
165
+ {{- toYaml .Values.admission.securityContext | nindent 8 }}
166
+ containers:
167
+ - name: server
168
+ image: {{ .Values.admission.image }}
169
+ imagePullPolicy: IfNotPresent
170
+ command:
171
+ - node
172
+ - /app/node_modules/pepr/dist/controller.js
173
+ - {{ .Values.hash }}
174
+ readinessProbe:
175
+ httpGet:
176
+ path: /healthz
177
+ port: 3000
178
+ scheme: HTTPS
179
+ livenessProbe:
180
+ httpGet:
181
+ path: /healthz
182
+ port: 3000
183
+ scheme: HTTPS
184
+ ports:
185
+ - containerPort: 3000
186
+ resources:
187
+ {{- toYaml .Values.admission.resources | nindent 12 }}
188
+ env:
189
+ {{- toYaml .Values.admission.env | nindent 12 }}
190
+ securityContext:
191
+ {{- toYaml .Values.admission.containerSecurityContext | nindent 12 }}
192
+ volumeMounts:
193
+ - name: tls-certs
194
+ mountPath: /etc/certs
195
+ readOnly: true
196
+ - name: api-token
197
+ mountPath: /app/api-token
198
+ readOnly: true
199
+ - name: module
200
+ mountPath: /app/load
201
+ readOnly: true
202
+ {{- if .Values.admission.extraVolumeMounts }}
203
+ {{- toYaml .Values.admission.extraVolumeMounts | nindent 12 }}
204
+ {{- end }}
205
+ volumes:
206
+ - name: tls-certs
207
+ secret:
208
+ secretName: {{ .Values.uuid }}-tls
209
+ - name: api-token
210
+ secret:
211
+ secretName: {{ .Values.uuid }}-api-token
212
+ - name: module
213
+ secret:
214
+ secretName: {{ .Values.uuid }}-module
215
+ {{- if .Values.admission.extraVolumes }}
216
+ {{- toYaml .Values.admission.extraVolumes | nindent 8 }}
217
+ {{- end }}
218
+ `;
219
+ }
@@ -0,0 +1,175 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+
4
+ import crypto from "crypto";
5
+ import { dumpYaml } from "@kubernetes/client-node";
6
+ import { ModuleConfig } from "../module";
7
+ import { TLSOut, genTLS } from "../tls";
8
+ import { CapabilityExport } from "../types";
9
+ import { WebhookIgnore } from "../k8s";
10
+ import { deploy } from "./deploy";
11
+ import { loadCapabilities } from "./loader";
12
+ import { allYaml, zarfYaml, overridesFile, zarfYamlChart } from "./yaml";
13
+ import { namespaceComplianceValidator, replaceString } from "../helpers";
14
+ import { createDirectoryIfNotExists, dedent } from "../helpers";
15
+ import { resolve } from "path";
16
+ import { chartYaml, nsTemplate, admissionDeployTemplate, watcherDeployTemplate } from "./helm";
17
+ import { promises as fs } from "fs";
18
+ import { webhookConfig } from "./webhooks";
19
+ import { apiTokenSecret, service, tlsSecret, watcherService } from "./networking";
20
+ import { watcher, moduleSecret } from "./pods";
21
+
22
+ import { clusterRole, clusterRoleBinding, serviceAccount, storeRole, storeRoleBinding } from "./rbac";
23
+ export class Assets {
24
+ readonly name: string;
25
+ readonly tls: TLSOut;
26
+ readonly apiToken: string;
27
+ readonly alwaysIgnore!: WebhookIgnore;
28
+ capabilities!: CapabilityExport[];
29
+
30
+ image: string;
31
+ buildTimestamp: string;
32
+ hash: string;
33
+
34
+ constructor(
35
+ readonly config: ModuleConfig,
36
+ readonly path: string,
37
+ readonly host?: string,
38
+ ) {
39
+ this.name = `pepr-${config.uuid}`;
40
+ this.buildTimestamp = `${Date.now()}`;
41
+ this.alwaysIgnore = config.alwaysIgnore;
42
+ this.image = `ghcr.io/defenseunicorns/pepr/controller:v${config.peprVersion}`;
43
+ this.hash = "";
44
+ // Generate the ephemeral tls things
45
+ this.tls = genTLS(this.host || `${this.name}.pepr-system.svc`);
46
+
47
+ // Generate the api token for the controller / webhook
48
+ this.apiToken = crypto.randomBytes(32).toString("hex");
49
+ }
50
+
51
+ setHash = (hash: string) => {
52
+ this.hash = hash;
53
+ };
54
+
55
+ deploy = async (force: boolean, webhookTimeout?: number) => {
56
+ this.capabilities = await loadCapabilities(this.path);
57
+ await deploy(this, force, webhookTimeout);
58
+ };
59
+
60
+ zarfYaml = (path: string) => zarfYaml(this, path);
61
+
62
+ zarfYamlChart = (path: string) => zarfYamlChart(this, path);
63
+
64
+ allYaml = async (rbacMode: string) => {
65
+ this.capabilities = await loadCapabilities(this.path);
66
+ // give error if namespaces are not respected
67
+ for (const capability of this.capabilities) {
68
+ namespaceComplianceValidator(capability, this.alwaysIgnore.namespaces);
69
+ }
70
+
71
+ return allYaml(this, rbacMode);
72
+ };
73
+
74
+ generateHelmChart = async (basePath: string) => {
75
+ const CHART_DIR = `${basePath}/${this.config.uuid}-chart`;
76
+ const CHAR_TEMPLATES_DIR = `${CHART_DIR}/templates`;
77
+ const valuesPath = resolve(CHART_DIR, `values.yaml`);
78
+ const chartPath = resolve(CHART_DIR, `Chart.yaml`);
79
+ const nsPath = resolve(CHAR_TEMPLATES_DIR, `namespace.yaml`);
80
+ const watcherSVCPath = resolve(CHAR_TEMPLATES_DIR, `watcher-service.yaml`);
81
+ const admissionSVCPath = resolve(CHAR_TEMPLATES_DIR, `admission-service.yaml`);
82
+ const mutationWebhookPath = resolve(CHAR_TEMPLATES_DIR, `mutation-webhook.yaml`);
83
+ const validationWebhookPath = resolve(CHAR_TEMPLATES_DIR, `validation-webhook.yaml`);
84
+ const admissionDeployPath = resolve(CHAR_TEMPLATES_DIR, `admission-deployment.yaml`);
85
+ const watcherDeployPath = resolve(CHAR_TEMPLATES_DIR, `watcher-deployment.yaml`);
86
+ const tlsSecretPath = resolve(CHAR_TEMPLATES_DIR, `tls-secret.yaml`);
87
+ const apiTokenSecretPath = resolve(CHAR_TEMPLATES_DIR, `api-token-secret.yaml`);
88
+ const moduleSecretPath = resolve(CHAR_TEMPLATES_DIR, `module-secret.yaml`);
89
+ const storeRolePath = resolve(CHAR_TEMPLATES_DIR, `store-role.yaml`);
90
+ const storeRoleBindingPath = resolve(CHAR_TEMPLATES_DIR, `store-role-binding.yaml`);
91
+ const clusterRolePath = resolve(CHAR_TEMPLATES_DIR, `cluster-role.yaml`);
92
+ const clusterRoleBindingPath = resolve(CHAR_TEMPLATES_DIR, `cluster-role-binding.yaml`);
93
+ const serviceAccountPath = resolve(CHAR_TEMPLATES_DIR, `service-account.yaml`);
94
+
95
+ // create helm chart
96
+ try {
97
+ // create chart dir
98
+ await createDirectoryIfNotExists(CHART_DIR);
99
+
100
+ // create charts dir
101
+ await createDirectoryIfNotExists(`${CHART_DIR}/charts`);
102
+
103
+ // create templates dir
104
+ await createDirectoryIfNotExists(`${CHAR_TEMPLATES_DIR}`);
105
+
106
+ // create values file
107
+ await overridesFile(this, valuesPath);
108
+
109
+ // create the chart.yaml
110
+ await fs.writeFile(chartPath, dedent(chartYaml(this.config.uuid, this.config.description || "")));
111
+
112
+ // create the namespace.yaml in templates
113
+ await fs.writeFile(nsPath, dedent(nsTemplate()));
114
+
115
+ const code = await fs.readFile(this.path);
116
+
117
+ await fs.writeFile(watcherSVCPath, dumpYaml(watcherService(this.name), { noRefs: true }));
118
+ await fs.writeFile(admissionSVCPath, dumpYaml(service(this.name), { noRefs: true }));
119
+ await fs.writeFile(tlsSecretPath, dumpYaml(tlsSecret(this.name, this.tls), { noRefs: true }));
120
+ await fs.writeFile(apiTokenSecretPath, dumpYaml(apiTokenSecret(this.name, this.apiToken), { noRefs: true }));
121
+ await fs.writeFile(moduleSecretPath, dumpYaml(moduleSecret(this.name, code, this.hash), { noRefs: true }));
122
+ await fs.writeFile(storeRolePath, dumpYaml(storeRole(this.name), { noRefs: true }));
123
+ await fs.writeFile(storeRoleBindingPath, dumpYaml(storeRoleBinding(this.name), { noRefs: true }));
124
+ await fs.writeFile(
125
+ clusterRolePath,
126
+ dumpYaml(clusterRole(this.name, this.capabilities, "rbac"), { noRefs: true }),
127
+ );
128
+ await fs.writeFile(clusterRoleBindingPath, dumpYaml(clusterRoleBinding(this.name), { noRefs: true }));
129
+ await fs.writeFile(serviceAccountPath, dumpYaml(serviceAccount(this.name), { noRefs: true }));
130
+
131
+ const mutateWebhook = await webhookConfig(this, "mutate", this.config.webhookTimeout);
132
+ const validateWebhook = await webhookConfig(this, "validate", this.config.webhookTimeout);
133
+ const watchDeployment = watcher(this, this.hash, this.buildTimestamp);
134
+
135
+ if (validateWebhook || mutateWebhook) {
136
+ await fs.writeFile(admissionDeployPath, dedent(admissionDeployTemplate(this.buildTimestamp)));
137
+ }
138
+
139
+ if (mutateWebhook) {
140
+ const yamlMutateWebhook = dumpYaml(mutateWebhook, { noRefs: true });
141
+ const mutateWebhookTemplate = replaceString(
142
+ replaceString(
143
+ replaceString(yamlMutateWebhook, this.name, "{{ .Values.uuid }}"),
144
+ this.config.onError === "reject" ? "Fail" : "Ignore",
145
+ "{{ .Values.admission.failurePolicy }}",
146
+ ),
147
+ `${this.config.webhookTimeout}` || "10",
148
+ "{{ .Values.admission.webhookTimeout }}",
149
+ );
150
+ await fs.writeFile(mutationWebhookPath, mutateWebhookTemplate);
151
+ }
152
+
153
+ if (validateWebhook) {
154
+ const yamlValidateWebhook = dumpYaml(validateWebhook, { noRefs: true });
155
+ const validateWebhookTemplate = replaceString(
156
+ replaceString(
157
+ replaceString(yamlValidateWebhook, this.name, "{{ .Values.uuid }}"),
158
+ this.config.onError === "reject" ? "Fail" : "Ignore",
159
+ "{{ .Values.admission.failurePolicy }}",
160
+ ),
161
+ `${this.config.webhookTimeout}` || "10",
162
+ "{{ .Values.admission.webhookTimeout }}",
163
+ );
164
+ await fs.writeFile(validationWebhookPath, validateWebhookTemplate);
165
+ }
166
+
167
+ if (watchDeployment) {
168
+ await fs.writeFile(watcherDeployPath, dedent(watcherDeployTemplate(this.buildTimestamp)));
169
+ }
170
+ } catch (err) {
171
+ console.error(`Error generating helm chart: ${err.message}`);
172
+ process.exit(1);
173
+ }
174
+ };
175
+ }
@@ -0,0 +1,41 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+
4
+ import { fork } from "child_process";
5
+
6
+ import { CapabilityExport } from "../types";
7
+
8
+ /**
9
+ * Read the capabilities from the module by running it in build mode
10
+ * @param path
11
+ * @returns
12
+ */
13
+ export function loadCapabilities(path: string): Promise<CapabilityExport[]> {
14
+ return new Promise((resolve, reject) => {
15
+ // Fork is needed with the PEPR_MODE env var to ensure the module is loaded in build mode and will send back the capabilities
16
+ const program = fork(path, {
17
+ env: {
18
+ ...process.env,
19
+ LOG_LEVEL: "warn",
20
+ PEPR_MODE: "build",
21
+ },
22
+ });
23
+
24
+ // Wait for the module to send back the capabilities
25
+ program.on("message", message => {
26
+ // Cast the message to the ModuleCapabilities type
27
+ const capabilities = message.valueOf() as CapabilityExport[];
28
+
29
+ // Iterate through the capabilities and generate the rules
30
+ for (const capability of capabilities) {
31
+ console.info(`Registered Pepr Capability "${capability.name}"`);
32
+ }
33
+
34
+ resolve(capabilities);
35
+ });
36
+
37
+ program.on("error", error => {
38
+ reject(error);
39
+ });
40
+ });
41
+ }
@@ -0,0 +1,89 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+
4
+ import { kind } from "kubernetes-fluent-client";
5
+
6
+ import { TLSOut } from "../tls";
7
+
8
+ export function apiTokenSecret(name: string, apiToken: string): kind.Secret {
9
+ return {
10
+ apiVersion: "v1",
11
+ kind: "Secret",
12
+ metadata: {
13
+ name: `${name}-api-token`,
14
+ namespace: "pepr-system",
15
+ },
16
+ type: "Opaque",
17
+ data: {
18
+ value: Buffer.from(apiToken).toString("base64"),
19
+ },
20
+ };
21
+ }
22
+
23
+ export function tlsSecret(name: string, tls: TLSOut): kind.Secret {
24
+ return {
25
+ apiVersion: "v1",
26
+ kind: "Secret",
27
+ metadata: {
28
+ name: `${name}-tls`,
29
+ namespace: "pepr-system",
30
+ },
31
+ type: "kubernetes.io/tls",
32
+ data: {
33
+ "tls.crt": tls.crt,
34
+ "tls.key": tls.key,
35
+ },
36
+ };
37
+ }
38
+
39
+ export function service(name: string): kind.Service {
40
+ return {
41
+ apiVersion: "v1",
42
+ kind: "Service",
43
+ metadata: {
44
+ name,
45
+ namespace: "pepr-system",
46
+ labels: {
47
+ "pepr.dev/controller": "admission",
48
+ },
49
+ },
50
+ spec: {
51
+ selector: {
52
+ app: name,
53
+ "pepr.dev/controller": "admission",
54
+ },
55
+ ports: [
56
+ {
57
+ port: 443,
58
+ targetPort: 3000,
59
+ },
60
+ ],
61
+ },
62
+ };
63
+ }
64
+
65
+ export function watcherService(name: string): kind.Service {
66
+ return {
67
+ apiVersion: "v1",
68
+ kind: "Service",
69
+ metadata: {
70
+ name: `${name}-watcher`,
71
+ namespace: "pepr-system",
72
+ labels: {
73
+ "pepr.dev/controller": "watcher",
74
+ },
75
+ },
76
+ spec: {
77
+ selector: {
78
+ app: `${name}-watcher`,
79
+ "pepr.dev/controller": "watcher",
80
+ },
81
+ ports: [
82
+ {
83
+ port: 443,
84
+ targetPort: 3000,
85
+ },
86
+ ],
87
+ },
88
+ };
89
+ }