pepr 0.26.2 → 0.27.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.
- package/dist/cli.js +517 -80
- package/dist/controller.js +1 -1
- package/dist/lib/assets/helm.d.ts +5 -0
- package/dist/lib/assets/helm.d.ts.map +1 -0
- package/dist/lib/assets/index.d.ts +4 -0
- package/dist/lib/assets/index.d.ts.map +1 -1
- package/dist/lib/assets/pods.d.ts +9 -2
- package/dist/lib/assets/pods.d.ts.map +1 -1
- package/dist/lib/assets/yaml.d.ts +1 -0
- package/dist/lib/assets/yaml.d.ts.map +1 -1
- package/dist/lib/filter.d.ts.map +1 -1
- package/dist/lib/helpers.d.ts +2 -0
- package/dist/lib/helpers.d.ts.map +1 -1
- package/dist/lib.js +1 -1
- package/dist/lib.js.map +2 -2
- package/package.json +6 -6
- package/src/lib/assets/deploy.ts +2 -2
- package/src/lib/assets/helm.ts +199 -0
- package/src/lib/assets/index.ts +121 -4
- package/src/lib/assets/pods.ts +22 -12
- package/src/lib/assets/yaml.ts +116 -4
- package/src/lib/filter.ts +4 -1
- package/src/lib/helpers.ts +25 -0
- package/src/templates/package.json +3 -1
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"engines": {
|
|
10
10
|
"node": ">=18.0.0"
|
|
11
11
|
},
|
|
12
|
-
"version": "0.
|
|
12
|
+
"version": "0.27.0",
|
|
13
13
|
"main": "dist/lib.js",
|
|
14
14
|
"types": "dist/lib.d.ts",
|
|
15
15
|
"scripts": {
|
|
@@ -34,24 +34,24 @@
|
|
|
34
34
|
"@types/ramda": "0.29.10",
|
|
35
35
|
"express": "4.18.2",
|
|
36
36
|
"fast-json-patch": "3.1.1",
|
|
37
|
-
"kubernetes-fluent-client": "2.2.
|
|
37
|
+
"kubernetes-fluent-client": "2.2.1",
|
|
38
38
|
"pino": "8.19.0",
|
|
39
39
|
"pino-pretty": "10.3.1",
|
|
40
40
|
"prom-client": "15.1.0",
|
|
41
41
|
"ramda": "0.29.1"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@commitlint/cli": "
|
|
45
|
-
"@commitlint/config-conventional": "
|
|
44
|
+
"@commitlint/cli": "19.0.1",
|
|
45
|
+
"@commitlint/config-conventional": "19.0.0",
|
|
46
46
|
"@jest/globals": "29.7.0",
|
|
47
|
-
"@types/eslint": "8.56.
|
|
47
|
+
"@types/eslint": "8.56.4",
|
|
48
48
|
"@types/express": "4.17.21",
|
|
49
49
|
"@types/node": "18.x.x",
|
|
50
50
|
"@types/node-forge": "1.3.11",
|
|
51
51
|
"@types/prompts": "2.4.9",
|
|
52
52
|
"@types/uuid": "9.0.8",
|
|
53
53
|
"jest": "29.7.0",
|
|
54
|
-
"nock": "13.5.
|
|
54
|
+
"nock": "13.5.4",
|
|
55
55
|
"ts-jest": "29.1.2"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
package/src/lib/assets/deploy.ts
CHANGED
|
@@ -104,13 +104,13 @@ async function setupController(assets: Assets, code: Buffer, hash: string, force
|
|
|
104
104
|
await K8s(kind.Secret).Apply(apiToken, { force });
|
|
105
105
|
|
|
106
106
|
Log.info("Applying deployment");
|
|
107
|
-
const dep = deployment(assets, hash);
|
|
107
|
+
const dep = deployment(assets, hash, assets.buildTimestamp);
|
|
108
108
|
await K8s(kind.Deployment).Apply(dep, { force });
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
async function setupWatcher(assets: Assets, hash: string, force: boolean) {
|
|
112
112
|
// If the module has a watcher, deploy it
|
|
113
|
-
const watchDeployment = watcher(assets, hash);
|
|
113
|
+
const watchDeployment = watcher(assets, hash, assets.buildTimestamp);
|
|
114
114
|
if (watchDeployment) {
|
|
115
115
|
Log.info("Applying watcher deployment");
|
|
116
116
|
await K8s(kind.Deployment).Apply(watchDeployment, { force });
|
|
@@ -0,0 +1,199 @@
|
|
|
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
|
+
labels:
|
|
74
|
+
app: {{ .Values.uuid }}-watcher
|
|
75
|
+
pepr.dev/controller: watcher
|
|
76
|
+
spec:
|
|
77
|
+
serviceAccountName: {{ .Values.uuid }}
|
|
78
|
+
securityContext:
|
|
79
|
+
{{- toYaml .Values.admission.securityContext | nindent 8 }}
|
|
80
|
+
containers:
|
|
81
|
+
- name: watcher
|
|
82
|
+
image: {{ .Values.watcher.image }}
|
|
83
|
+
imagePullPolicy: IfNotPresent
|
|
84
|
+
command:
|
|
85
|
+
- node
|
|
86
|
+
- /app/node_modules/pepr/dist/controller.js
|
|
87
|
+
- {{ .Values.hash }}
|
|
88
|
+
readinessProbe:
|
|
89
|
+
httpGet:
|
|
90
|
+
path: /healthz
|
|
91
|
+
port: 3000
|
|
92
|
+
scheme: HTTPS
|
|
93
|
+
livenessProbe:
|
|
94
|
+
httpGet:
|
|
95
|
+
path: /healthz
|
|
96
|
+
port: 3000
|
|
97
|
+
scheme: HTTPS
|
|
98
|
+
ports:
|
|
99
|
+
- containerPort: 3000
|
|
100
|
+
resources:
|
|
101
|
+
{{- toYaml .Values.watcher.resources | nindent 12 }}
|
|
102
|
+
env:
|
|
103
|
+
{{- toYaml .Values.watcher.env | nindent 12 }}
|
|
104
|
+
securityContext:
|
|
105
|
+
{{- toYaml .Values.watcher.containerSecurityContext | nindent 12 }}
|
|
106
|
+
volumeMounts:
|
|
107
|
+
- name: tls-certs
|
|
108
|
+
mountPath: /etc/certs
|
|
109
|
+
readOnly: true
|
|
110
|
+
- name: module
|
|
111
|
+
mountPath: /app/load
|
|
112
|
+
readOnly: true
|
|
113
|
+
volumes:
|
|
114
|
+
- name: tls-certs
|
|
115
|
+
secret:
|
|
116
|
+
secretName: {{ .Values.uuid }}-tls
|
|
117
|
+
- name: module
|
|
118
|
+
secret:
|
|
119
|
+
secretName: {{ .Values.uuid }}-module
|
|
120
|
+
`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function admissionDeployTemplate(buildTimestamp: string) {
|
|
124
|
+
return `
|
|
125
|
+
apiVersion: apps/v1
|
|
126
|
+
kind: Deployment
|
|
127
|
+
metadata:
|
|
128
|
+
name: {{ .Values.uuid }}
|
|
129
|
+
namespace: pepr-system
|
|
130
|
+
annotations:
|
|
131
|
+
{{- toYaml .Values.admission.annotations | nindent 4 }}
|
|
132
|
+
labels:
|
|
133
|
+
{{- toYaml .Values.admission.labels | nindent 4 }}
|
|
134
|
+
spec:
|
|
135
|
+
replicas: 2
|
|
136
|
+
selector:
|
|
137
|
+
matchLabels:
|
|
138
|
+
app: {{ .Values.uuid }}
|
|
139
|
+
pepr.dev/controller: admission
|
|
140
|
+
template:
|
|
141
|
+
metadata:
|
|
142
|
+
annotations:
|
|
143
|
+
buildTimestamp: "${buildTimestamp}"
|
|
144
|
+
labels:
|
|
145
|
+
app: {{ .Values.uuid }}
|
|
146
|
+
pepr.dev/controller: admission
|
|
147
|
+
spec:
|
|
148
|
+
priorityClassName: system-node-critical
|
|
149
|
+
serviceAccountName: {{ .Values.uuid }}
|
|
150
|
+
securityContext:
|
|
151
|
+
{{- toYaml .Values.admission.securityContext | nindent 8 }}
|
|
152
|
+
containers:
|
|
153
|
+
- name: server
|
|
154
|
+
image: {{ .Values.admission.image }}
|
|
155
|
+
imagePullPolicy: IfNotPresent
|
|
156
|
+
command:
|
|
157
|
+
- node
|
|
158
|
+
- /app/node_modules/pepr/dist/controller.js
|
|
159
|
+
- {{ .Values.hash }}
|
|
160
|
+
readinessProbe:
|
|
161
|
+
httpGet:
|
|
162
|
+
path: /healthz
|
|
163
|
+
port: 3000
|
|
164
|
+
scheme: HTTPS
|
|
165
|
+
livenessProbe:
|
|
166
|
+
httpGet:
|
|
167
|
+
path: /healthz
|
|
168
|
+
port: 3000
|
|
169
|
+
scheme: HTTPS
|
|
170
|
+
ports:
|
|
171
|
+
- containerPort: 3000
|
|
172
|
+
resources:
|
|
173
|
+
{{- toYaml .Values.admission.resources | nindent 12 }}
|
|
174
|
+
env:
|
|
175
|
+
{{- toYaml .Values.admission.env | nindent 12 }}
|
|
176
|
+
securityContext:
|
|
177
|
+
{{- toYaml .Values.admission.containerSecurityContext | nindent 12 }}
|
|
178
|
+
volumeMounts:
|
|
179
|
+
- name: tls-certs
|
|
180
|
+
mountPath: /etc/certs
|
|
181
|
+
readOnly: true
|
|
182
|
+
- name: api-token
|
|
183
|
+
mountPath: /app/api-token
|
|
184
|
+
readOnly: true
|
|
185
|
+
- name: module
|
|
186
|
+
mountPath: /app/load
|
|
187
|
+
readOnly: true
|
|
188
|
+
volumes:
|
|
189
|
+
- name: tls-certs
|
|
190
|
+
secret:
|
|
191
|
+
secretName: {{ .Values.uuid }}-tls
|
|
192
|
+
- name: api-token
|
|
193
|
+
secret:
|
|
194
|
+
secretName: {{ .Values.uuid }}-api-token
|
|
195
|
+
- name: module
|
|
196
|
+
secret:
|
|
197
|
+
secretName: {{ .Values.uuid }}-module
|
|
198
|
+
`;
|
|
199
|
+
}
|
package/src/lib/assets/index.ts
CHANGED
|
@@ -2,16 +2,24 @@
|
|
|
2
2
|
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
3
|
|
|
4
4
|
import crypto from "crypto";
|
|
5
|
-
|
|
5
|
+
import { dumpYaml } from "@kubernetes/client-node";
|
|
6
6
|
import { ModuleConfig } from "../module";
|
|
7
7
|
import { TLSOut, genTLS } from "../tls";
|
|
8
8
|
import { CapabilityExport } from "../types";
|
|
9
9
|
import { WebhookIgnore } from "../k8s";
|
|
10
10
|
import { deploy } from "./deploy";
|
|
11
11
|
import { loadCapabilities } from "./loader";
|
|
12
|
-
import { allYaml, zarfYaml } from "./yaml";
|
|
13
|
-
import { namespaceComplianceValidator } from "../helpers";
|
|
12
|
+
import { allYaml, zarfYaml, overridesFile } 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";
|
|
14
21
|
|
|
22
|
+
import { clusterRole, clusterRoleBinding, serviceAccount, storeRole, storeRoleBinding } from "./rbac";
|
|
15
23
|
export class Assets {
|
|
16
24
|
readonly name: string;
|
|
17
25
|
readonly tls: TLSOut;
|
|
@@ -20,6 +28,8 @@ export class Assets {
|
|
|
20
28
|
capabilities!: CapabilityExport[];
|
|
21
29
|
|
|
22
30
|
image: string;
|
|
31
|
+
buildTimestamp: string;
|
|
32
|
+
hash: string;
|
|
23
33
|
|
|
24
34
|
constructor(
|
|
25
35
|
readonly config: ModuleConfig,
|
|
@@ -27,9 +37,10 @@ export class Assets {
|
|
|
27
37
|
readonly host?: string,
|
|
28
38
|
) {
|
|
29
39
|
this.name = `pepr-${config.uuid}`;
|
|
40
|
+
this.buildTimestamp = `${Date.now()}`;
|
|
30
41
|
this.alwaysIgnore = config.alwaysIgnore;
|
|
31
42
|
this.image = `ghcr.io/defenseunicorns/pepr/controller:v${config.peprVersion}`;
|
|
32
|
-
|
|
43
|
+
this.hash = "";
|
|
33
44
|
// Generate the ephemeral tls things
|
|
34
45
|
this.tls = genTLS(this.host || `${this.name}.pepr-system.svc`);
|
|
35
46
|
|
|
@@ -37,6 +48,10 @@ export class Assets {
|
|
|
37
48
|
this.apiToken = crypto.randomBytes(32).toString("hex");
|
|
38
49
|
}
|
|
39
50
|
|
|
51
|
+
setHash = (hash: string) => {
|
|
52
|
+
this.hash = hash;
|
|
53
|
+
};
|
|
54
|
+
|
|
40
55
|
deploy = async (force: boolean, webhookTimeout?: number) => {
|
|
41
56
|
this.capabilities = await loadCapabilities(this.path);
|
|
42
57
|
await deploy(this, force, webhookTimeout);
|
|
@@ -53,4 +68,106 @@ export class Assets {
|
|
|
53
68
|
|
|
54
69
|
return allYaml(this, rbacMode);
|
|
55
70
|
};
|
|
71
|
+
|
|
72
|
+
generateHelmChart = async (basePath: string) => {
|
|
73
|
+
const CHART_DIR = `${basePath}/${this.config.uuid}-chart`;
|
|
74
|
+
const CHAR_TEMPLATES_DIR = `${CHART_DIR}/templates`;
|
|
75
|
+
const valuesPath = resolve(CHART_DIR, `values.yaml`);
|
|
76
|
+
const chartPath = resolve(CHART_DIR, `Chart.yaml`);
|
|
77
|
+
const nsPath = resolve(CHAR_TEMPLATES_DIR, `namespace.yaml`);
|
|
78
|
+
const watcherSVCPath = resolve(CHAR_TEMPLATES_DIR, `watcher-service.yaml`);
|
|
79
|
+
const admissionSVCPath = resolve(CHAR_TEMPLATES_DIR, `admission-service.yaml`);
|
|
80
|
+
const mutationWebhookPath = resolve(CHAR_TEMPLATES_DIR, `mutation-webhook.yaml`);
|
|
81
|
+
const validationWebhookPath = resolve(CHAR_TEMPLATES_DIR, `validation-webhook.yaml`);
|
|
82
|
+
const admissionDeployPath = resolve(CHAR_TEMPLATES_DIR, `admission-deployment.yaml`);
|
|
83
|
+
const watcherDeployPath = resolve(CHAR_TEMPLATES_DIR, `watcher-deployment.yaml`);
|
|
84
|
+
const tlsSecretPath = resolve(CHAR_TEMPLATES_DIR, `tls-secret.yaml`);
|
|
85
|
+
const apiTokenSecretPath = resolve(CHAR_TEMPLATES_DIR, `api-token-secret.yaml`);
|
|
86
|
+
const moduleSecretPath = resolve(CHAR_TEMPLATES_DIR, `module-secret.yaml`);
|
|
87
|
+
const storeRolePath = resolve(CHAR_TEMPLATES_DIR, `store-role.yaml`);
|
|
88
|
+
const storeRoleBindingPath = resolve(CHAR_TEMPLATES_DIR, `store-role-binding.yaml`);
|
|
89
|
+
const clusterRolePath = resolve(CHAR_TEMPLATES_DIR, `cluster-role.yaml`);
|
|
90
|
+
const clusterRoleBindingPath = resolve(CHAR_TEMPLATES_DIR, `cluster-role-binding.yaml`);
|
|
91
|
+
const serviceAccountPath = resolve(CHAR_TEMPLATES_DIR, `service-account.yaml`);
|
|
92
|
+
|
|
93
|
+
// create helm chart
|
|
94
|
+
try {
|
|
95
|
+
// create chart dir
|
|
96
|
+
await createDirectoryIfNotExists(CHART_DIR);
|
|
97
|
+
|
|
98
|
+
// create charts dir
|
|
99
|
+
await createDirectoryIfNotExists(`${CHART_DIR}/charts`);
|
|
100
|
+
|
|
101
|
+
// create templates dir
|
|
102
|
+
await createDirectoryIfNotExists(`${CHAR_TEMPLATES_DIR}`);
|
|
103
|
+
|
|
104
|
+
// create values file
|
|
105
|
+
await overridesFile(this, valuesPath);
|
|
106
|
+
|
|
107
|
+
// create the chart.yaml
|
|
108
|
+
await fs.writeFile(chartPath, dedent(chartYaml(this.config.uuid, this.config.description || "")));
|
|
109
|
+
|
|
110
|
+
// create the namespace.yaml in templates
|
|
111
|
+
await fs.writeFile(nsPath, dedent(nsTemplate()));
|
|
112
|
+
|
|
113
|
+
const code = await fs.readFile(this.path);
|
|
114
|
+
|
|
115
|
+
await fs.writeFile(watcherSVCPath, dumpYaml(watcherService(this.name), { noRefs: true }));
|
|
116
|
+
await fs.writeFile(admissionSVCPath, dumpYaml(service(this.name), { noRefs: true }));
|
|
117
|
+
await fs.writeFile(tlsSecretPath, dumpYaml(tlsSecret(this.name, this.tls), { noRefs: true }));
|
|
118
|
+
await fs.writeFile(apiTokenSecretPath, dumpYaml(apiTokenSecret(this.name, this.apiToken), { noRefs: true }));
|
|
119
|
+
await fs.writeFile(moduleSecretPath, dumpYaml(moduleSecret(this.name, code, this.hash), { noRefs: true }));
|
|
120
|
+
await fs.writeFile(storeRolePath, dumpYaml(storeRole(this.name), { noRefs: true }));
|
|
121
|
+
await fs.writeFile(storeRoleBindingPath, dumpYaml(storeRoleBinding(this.name), { noRefs: true }));
|
|
122
|
+
await fs.writeFile(
|
|
123
|
+
clusterRolePath,
|
|
124
|
+
dumpYaml(clusterRole(this.name, this.capabilities, "rbac"), { noRefs: true }),
|
|
125
|
+
);
|
|
126
|
+
await fs.writeFile(clusterRoleBindingPath, dumpYaml(clusterRoleBinding(this.name), { noRefs: true }));
|
|
127
|
+
await fs.writeFile(serviceAccountPath, dumpYaml(serviceAccount(this.name), { noRefs: true }));
|
|
128
|
+
|
|
129
|
+
const mutateWebhook = await webhookConfig(this, "mutate", this.config.webhookTimeout);
|
|
130
|
+
const validateWebhook = await webhookConfig(this, "validate", this.config.webhookTimeout);
|
|
131
|
+
const watchDeployment = watcher(this, this.hash, this.buildTimestamp);
|
|
132
|
+
|
|
133
|
+
if (validateWebhook || mutateWebhook) {
|
|
134
|
+
await fs.writeFile(admissionDeployPath, dedent(admissionDeployTemplate(this.buildTimestamp)));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (mutateWebhook) {
|
|
138
|
+
const yamlMutateWebhook = dumpYaml(mutateWebhook, { noRefs: true });
|
|
139
|
+
const mutateWebhookTemplate = replaceString(
|
|
140
|
+
replaceString(
|
|
141
|
+
replaceString(yamlMutateWebhook, this.name, "{{ .Values.uuid }}"),
|
|
142
|
+
this.config.onError === "reject" ? "Fail" : "Ignore",
|
|
143
|
+
"{{ .Values.admission.failurePolicy }}",
|
|
144
|
+
),
|
|
145
|
+
`${this.config.webhookTimeout}` || "10",
|
|
146
|
+
"{{ .Values.admission.webhookTimeout }}",
|
|
147
|
+
);
|
|
148
|
+
await fs.writeFile(mutationWebhookPath, mutateWebhookTemplate);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (validateWebhook) {
|
|
152
|
+
const yamlValidateWebhook = dumpYaml(validateWebhook, { noRefs: true });
|
|
153
|
+
const validateWebhookTemplate = replaceString(
|
|
154
|
+
replaceString(
|
|
155
|
+
replaceString(yamlValidateWebhook, this.name, "{{ .Values.uuid }}"),
|
|
156
|
+
this.config.onError === "reject" ? "Fail" : "Ignore",
|
|
157
|
+
"{{ .Values.admission.failurePolicy }}",
|
|
158
|
+
),
|
|
159
|
+
`${this.config.webhookTimeout}` || "10",
|
|
160
|
+
"{{ .Values.admission.webhookTimeout }}",
|
|
161
|
+
);
|
|
162
|
+
await fs.writeFile(validationWebhookPath, validateWebhookTemplate);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (watchDeployment) {
|
|
166
|
+
await fs.writeFile(watcherDeployPath, dedent(watcherDeployTemplate(this.buildTimestamp)));
|
|
167
|
+
}
|
|
168
|
+
} catch (err) {
|
|
169
|
+
console.error(`Error generating helm chart: ${err.message}`);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
56
173
|
}
|
package/src/lib/assets/pods.ts
CHANGED
|
@@ -11,17 +11,27 @@ import { Binding } from "../types";
|
|
|
11
11
|
|
|
12
12
|
/** Generate the pepr-system namespace */
|
|
13
13
|
export function namespace(namespaceLabels?: Record<string, string>) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
if (namespaceLabels) {
|
|
15
|
+
return {
|
|
16
|
+
apiVersion: "v1",
|
|
17
|
+
kind: "Namespace",
|
|
18
|
+
metadata: {
|
|
19
|
+
name: "pepr-system",
|
|
20
|
+
labels: namespaceLabels ?? {},
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
} else {
|
|
24
|
+
return {
|
|
25
|
+
apiVersion: "v1",
|
|
26
|
+
kind: "Namespace",
|
|
27
|
+
metadata: {
|
|
28
|
+
name: "pepr-system",
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
22
32
|
}
|
|
23
33
|
|
|
24
|
-
export function watcher(assets: Assets, hash: string) {
|
|
34
|
+
export function watcher(assets: Assets, hash: string, buildTimestamp: string) {
|
|
25
35
|
const { name, image, capabilities, config } = assets;
|
|
26
36
|
|
|
27
37
|
let hasSchedule = false;
|
|
@@ -73,7 +83,7 @@ export function watcher(assets: Assets, hash: string) {
|
|
|
73
83
|
template: {
|
|
74
84
|
metadata: {
|
|
75
85
|
annotations: {
|
|
76
|
-
buildTimestamp: `${
|
|
86
|
+
buildTimestamp: `${buildTimestamp}`,
|
|
77
87
|
},
|
|
78
88
|
labels: {
|
|
79
89
|
app,
|
|
@@ -167,7 +177,7 @@ export function watcher(assets: Assets, hash: string) {
|
|
|
167
177
|
};
|
|
168
178
|
}
|
|
169
179
|
|
|
170
|
-
export function deployment(assets: Assets, hash: string): kind.Deployment {
|
|
180
|
+
export function deployment(assets: Assets, hash: string, buildTimestamp: string): kind.Deployment {
|
|
171
181
|
const { name, image, config } = assets;
|
|
172
182
|
const app = name;
|
|
173
183
|
|
|
@@ -197,7 +207,7 @@ export function deployment(assets: Assets, hash: string): kind.Deployment {
|
|
|
197
207
|
template: {
|
|
198
208
|
metadata: {
|
|
199
209
|
annotations: {
|
|
200
|
-
buildTimestamp: `${
|
|
210
|
+
buildTimestamp: `${buildTimestamp}`,
|
|
201
211
|
},
|
|
202
212
|
labels: {
|
|
203
213
|
app,
|
package/src/lib/assets/yaml.ts
CHANGED
|
@@ -11,6 +11,118 @@ import { deployment, moduleSecret, namespace, watcher } from "./pods";
|
|
|
11
11
|
import { clusterRole, clusterRoleBinding, serviceAccount, storeRole, storeRoleBinding } from "./rbac";
|
|
12
12
|
import { webhookConfig } from "./webhooks";
|
|
13
13
|
|
|
14
|
+
// Helm Chart overrides file (values.yaml) generated from assets
|
|
15
|
+
export async function overridesFile({ hash, name, image, config, apiToken }: Assets, path: string) {
|
|
16
|
+
const overrides = {
|
|
17
|
+
secrets: {
|
|
18
|
+
apiToken: Buffer.from(apiToken).toString("base64"),
|
|
19
|
+
},
|
|
20
|
+
hash,
|
|
21
|
+
namespace: {
|
|
22
|
+
annotations: {},
|
|
23
|
+
labels: {
|
|
24
|
+
"pepr.dev": "",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
uuid: name,
|
|
28
|
+
admission: {
|
|
29
|
+
failurePolicy: config.onError === "reject" ? "Fail" : "Ignore",
|
|
30
|
+
webhookTimeout: config.webhookTimeout,
|
|
31
|
+
env: [
|
|
32
|
+
{ name: "PEPR_WATCH_MODE", value: "false" },
|
|
33
|
+
{ name: "PEPR_PRETTY_LOG", value: "false" },
|
|
34
|
+
{ name: "LOG_LEVEL", value: "debug" },
|
|
35
|
+
process.env.PEPR_MODE === "dev" && { name: "MY_CUSTOM_VAR", value: "example-value" },
|
|
36
|
+
process.env.PEPR_MODE === "dev" && { name: "ZARF_VAR", value: "###ZARF_VAR_THING###" },
|
|
37
|
+
],
|
|
38
|
+
image,
|
|
39
|
+
annotations: {
|
|
40
|
+
"pepr.dev/description": `${config.description}` || "",
|
|
41
|
+
},
|
|
42
|
+
labels: {
|
|
43
|
+
app: name,
|
|
44
|
+
"pepr.dev/controller": "admission",
|
|
45
|
+
"pepr.dev/uuid": config.uuid,
|
|
46
|
+
},
|
|
47
|
+
securityContext: {
|
|
48
|
+
runAsUser: 65532,
|
|
49
|
+
runAsGroup: 65532,
|
|
50
|
+
runAsNonRoot: true,
|
|
51
|
+
fsGroup: 65532,
|
|
52
|
+
},
|
|
53
|
+
resources: {
|
|
54
|
+
requests: {
|
|
55
|
+
memory: "64Mi",
|
|
56
|
+
cpu: "100m",
|
|
57
|
+
},
|
|
58
|
+
limits: {
|
|
59
|
+
memory: "256Mi",
|
|
60
|
+
cpu: "500m",
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
containerSecurityContext: {
|
|
64
|
+
runAsUser: 65532,
|
|
65
|
+
runAsGroup: 65532,
|
|
66
|
+
runAsNonRoot: true,
|
|
67
|
+
allowPrivilegeEscalation: false,
|
|
68
|
+
capabilities: {
|
|
69
|
+
drop: ["ALL"],
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
nodeSelector: {},
|
|
73
|
+
tolerations: [],
|
|
74
|
+
affinity: {},
|
|
75
|
+
},
|
|
76
|
+
watcher: {
|
|
77
|
+
env: [
|
|
78
|
+
{ name: "PEPR_WATCH_MODE", value: "true" },
|
|
79
|
+
{ name: "PEPR_PRETTY_LOG", value: "false" },
|
|
80
|
+
{ name: "LOG_LEVEL", value: "debug" },
|
|
81
|
+
process.env.PEPR_MODE === "dev" && { name: "MY_CUSTOM_VAR", value: "example-value" },
|
|
82
|
+
process.env.PEPR_MODE === "dev" && { name: "ZARF_VAR", value: "###ZARF_VAR_THING###" },
|
|
83
|
+
],
|
|
84
|
+
image,
|
|
85
|
+
annotations: {
|
|
86
|
+
"pepr.dev/description": `${config.description}` || "",
|
|
87
|
+
},
|
|
88
|
+
labels: {
|
|
89
|
+
app: `${name}-watcher`,
|
|
90
|
+
"pepr.dev/controller": "watcher",
|
|
91
|
+
"pepr.dev/uuid": config.uuid,
|
|
92
|
+
},
|
|
93
|
+
securityContext: {
|
|
94
|
+
runAsUser: 65532,
|
|
95
|
+
runAsGroup: 65532,
|
|
96
|
+
runAsNonRoot: true,
|
|
97
|
+
fsGroup: 65532,
|
|
98
|
+
},
|
|
99
|
+
resources: {
|
|
100
|
+
requests: {
|
|
101
|
+
memory: "64Mi",
|
|
102
|
+
cpu: "100m",
|
|
103
|
+
},
|
|
104
|
+
limits: {
|
|
105
|
+
memory: "256Mi",
|
|
106
|
+
cpu: "500m",
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
containerSecurityContext: {
|
|
110
|
+
runAsUser: 65532,
|
|
111
|
+
runAsGroup: 65532,
|
|
112
|
+
runAsNonRoot: true,
|
|
113
|
+
allowPrivilegeEscalation: false,
|
|
114
|
+
capabilities: {
|
|
115
|
+
drop: ["ALL"],
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
nodeSelector: {},
|
|
119
|
+
tolerations: [],
|
|
120
|
+
affinity: {},
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
await fs.writeFile(path, dumpYaml(overrides, { noRefs: true, forceQuotes: true }));
|
|
125
|
+
}
|
|
14
126
|
export function zarfYaml({ name, image, config }: Assets, path: string) {
|
|
15
127
|
const zarfCfg = {
|
|
16
128
|
kind: "ZarfPackageConfig",
|
|
@@ -45,11 +157,11 @@ export async function allYaml(assets: Assets, rbacMode: string) {
|
|
|
45
157
|
const code = await fs.readFile(path);
|
|
46
158
|
|
|
47
159
|
// Generate a hash of the code
|
|
48
|
-
|
|
160
|
+
assets.hash = crypto.createHash("sha256").update(code).digest("hex");
|
|
49
161
|
|
|
50
162
|
const mutateWebhook = await webhookConfig(assets, "mutate", assets.config.webhookTimeout);
|
|
51
163
|
const validateWebhook = await webhookConfig(assets, "validate", assets.config.webhookTimeout);
|
|
52
|
-
const watchDeployment = watcher(assets, hash);
|
|
164
|
+
const watchDeployment = watcher(assets, assets.hash, assets.buildTimestamp);
|
|
53
165
|
|
|
54
166
|
const resources = [
|
|
55
167
|
namespace(assets.config.customLabels?.namespace),
|
|
@@ -58,10 +170,10 @@ export async function allYaml(assets: Assets, rbacMode: string) {
|
|
|
58
170
|
serviceAccount(name),
|
|
59
171
|
apiTokenSecret(name, apiToken),
|
|
60
172
|
tlsSecret(name, tls),
|
|
61
|
-
deployment(assets, hash),
|
|
173
|
+
deployment(assets, assets.hash, assets.buildTimestamp),
|
|
62
174
|
service(name),
|
|
63
175
|
watcherService(name),
|
|
64
|
-
moduleSecret(name, code, hash),
|
|
176
|
+
moduleSecret(name, code, assets.hash),
|
|
65
177
|
storeRole(name),
|
|
66
178
|
storeRoleBinding(name),
|
|
67
179
|
];
|
package/src/lib/filter.ts
CHANGED
|
@@ -47,7 +47,10 @@ export function shouldSkipRequest(binding: Binding, req: AdmissionRequest, capab
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
// Test for matching namespaces
|
|
50
|
-
if (
|
|
50
|
+
if (
|
|
51
|
+
(combinedNamespaces.length && !combinedNamespaces.includes(req.namespace || "")) ||
|
|
52
|
+
(!namespaces.includes(req.namespace || "") && capabilityNamespaces.length !== 0 && namespaces.length !== 0)
|
|
53
|
+
) {
|
|
51
54
|
logger.debug("Namespace does not match");
|
|
52
55
|
return true;
|
|
53
56
|
}
|
package/src/lib/helpers.ts
CHANGED
|
@@ -185,3 +185,28 @@ export const parseTimeout = (value: string, previous: unknown): number => {
|
|
|
185
185
|
}
|
|
186
186
|
return parsedValue;
|
|
187
187
|
};
|
|
188
|
+
|
|
189
|
+
// Remove leading whitespace while keeping format of file
|
|
190
|
+
export function dedent(file: string) {
|
|
191
|
+
// Check if the first line is empty and remove it
|
|
192
|
+
const lines = file.split("\n");
|
|
193
|
+
if (lines[0].trim() === "") {
|
|
194
|
+
lines.shift(); // Remove the first line if it's empty
|
|
195
|
+
file = lines.join("\n"); // Rejoin the remaining lines back into a single string
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const match = file.match(/^[ \t]*(?=\S)/gm);
|
|
199
|
+
const indent = match && Math.min(...match.map(el => el.length));
|
|
200
|
+
if (indent && indent > 0) {
|
|
201
|
+
const re = new RegExp(`^[ \\t]{${indent}}`, "gm");
|
|
202
|
+
return file.replace(re, "");
|
|
203
|
+
}
|
|
204
|
+
return file;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function replaceString(str: string, stringA: string, stringB: string) {
|
|
208
|
+
//eslint-disable-next-line
|
|
209
|
+
const escapedStringA = stringA.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
210
|
+
const regExp = new RegExp(escapedStringA, "g");
|
|
211
|
+
return str.replace(regExp, stringB);
|
|
212
|
+
}
|