pepr 0.42.0 → 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.
- package/dist/cli/build.d.ts +1 -0
- package/dist/cli/build.d.ts.map +1 -1
- package/dist/cli/build.helpers.d.ts +66 -0
- package/dist/cli/build.helpers.d.ts.map +1 -1
- package/dist/cli/monitor.d.ts +23 -0
- package/dist/cli/monitor.d.ts.map +1 -1
- package/dist/cli.js +341 -283
- package/dist/controller.js +1 -1
- package/dist/lib/assets/destroy.d.ts.map +1 -1
- package/dist/lib/assets/helm.d.ts +1 -1
- package/dist/lib/assets/helm.d.ts.map +1 -1
- package/dist/lib/assets/index.d.ts.map +1 -1
- package/dist/lib/assets/pods.d.ts +5 -19
- package/dist/lib/assets/pods.d.ts.map +1 -1
- package/dist/lib/assets/webhooks.d.ts.map +1 -1
- package/dist/lib/assets/yaml.d.ts.map +1 -1
- package/dist/lib/capability.d.ts.map +1 -1
- package/dist/lib/controller/index.d.ts.map +1 -1
- package/dist/lib/controller/store.d.ts +0 -1
- package/dist/lib/controller/store.d.ts.map +1 -1
- package/dist/lib/controller/storeCache.d.ts +1 -0
- package/dist/lib/controller/storeCache.d.ts.map +1 -1
- package/dist/lib/mutate-request.d.ts +2 -2
- package/dist/lib/mutate-request.d.ts.map +1 -1
- package/dist/lib/queue.d.ts.map +1 -1
- package/dist/lib/storage.d.ts +4 -4
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/validate-processor.d.ts +4 -1
- package/dist/lib/validate-processor.d.ts.map +1 -1
- package/dist/lib/watch-processor.d.ts.map +1 -1
- package/dist/lib.js +136 -109
- package/dist/lib.js.map +3 -3
- package/package.json +1 -1
- package/src/cli/build.helpers.ts +180 -0
- package/src/cli/build.ts +85 -133
- package/src/cli/monitor.ts +108 -65
- package/src/lib/assets/deploy.ts +6 -6
- package/src/lib/assets/destroy.ts +1 -1
- package/src/lib/assets/helm.ts +6 -6
- package/src/lib/assets/index.ts +22 -22
- package/src/lib/assets/pods.ts +10 -5
- package/src/lib/assets/webhooks.ts +1 -1
- package/src/lib/assets/yaml.ts +12 -9
- package/src/lib/capability.ts +21 -10
- package/src/lib/controller/index.ts +9 -7
- package/src/lib/controller/store.ts +23 -10
- package/src/lib/controller/storeCache.ts +10 -1
- package/src/lib/mutate-request.ts +11 -11
- package/src/lib/queue.ts +12 -4
- package/src/lib/storage.ts +33 -24
- package/src/lib/utils.ts +5 -5
- package/src/lib/validate-processor.ts +47 -39
- package/src/lib/watch-processor.ts +11 -7
- package/src/sdk/cosign.ts +4 -4
package/src/cli/monitor.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
40
|
-
|
|
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
|
+
}
|
package/src/lib/assets/deploy.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { V1PolicyRule as PolicyRule } from "@kubernetes/client-node";
|
|
|
9
9
|
import { Assets } from ".";
|
|
10
10
|
import Log from "../telemetry/logger";
|
|
11
11
|
import { apiTokenSecret, service, tlsSecret, watcherService } from "./networking";
|
|
12
|
-
import {
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 });
|
|
@@ -6,7 +6,7 @@ import { K8s, kind } from "kubernetes-fluent-client";
|
|
|
6
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");
|
package/src/lib/assets/helm.ts
CHANGED
|
@@ -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
|
|
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
|
package/src/lib/assets/index.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { dedent } from "../helpers";
|
|
|
16
16
|
import { resolve } from "path";
|
|
17
17
|
import {
|
|
18
18
|
chartYaml,
|
|
19
|
-
|
|
19
|
+
namespaceTemplate,
|
|
20
20
|
admissionDeployTemplate,
|
|
21
21
|
watcherDeployTemplate,
|
|
22
22
|
clusterRoleTemplate,
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
import { promises as fs } from "fs";
|
|
26
26
|
import { webhookConfig } from "./webhooks";
|
|
27
27
|
import { apiTokenSecret, service, tlsSecret, watcherService } from "./networking";
|
|
28
|
-
import {
|
|
28
|
+
import { getWatcher, getModuleSecret } from "./pods";
|
|
29
29
|
|
|
30
30
|
import { clusterRoleBinding, serviceAccount, storeRole, storeRoleBinding } from "./rbac";
|
|
31
31
|
import { createDirectoryIfNotExists } from "../filesystemService";
|
|
@@ -51,7 +51,7 @@ function createWebhookYaml(
|
|
|
51
51
|
);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
function helmLayout(basePath: string, unique: string) {
|
|
54
|
+
function helmLayout(basePath: string, unique: string): Record<string, Record<string, string>> {
|
|
55
55
|
const helm: Record<string, Record<string, string>> = {
|
|
56
56
|
dirs: {
|
|
57
57
|
chart: resolve(`${basePath}/${unique}-chart`),
|
|
@@ -119,20 +119,20 @@ export class Assets {
|
|
|
119
119
|
this.apiToken = crypto.randomBytes(32).toString("hex");
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
setHash = (hash: string) => {
|
|
122
|
+
setHash = (hash: string): void => {
|
|
123
123
|
this.hash = hash;
|
|
124
124
|
};
|
|
125
125
|
|
|
126
|
-
deploy = async (force: boolean, webhookTimeout?: number) => {
|
|
126
|
+
deploy = async (force: boolean, webhookTimeout?: number): Promise<void> => {
|
|
127
127
|
this.capabilities = await loadCapabilities(this.path);
|
|
128
128
|
await deploy(this, force, webhookTimeout);
|
|
129
129
|
};
|
|
130
130
|
|
|
131
|
-
zarfYaml = (path: string) => zarfYaml(this, path);
|
|
131
|
+
zarfYaml = (path: string): string => zarfYaml(this, path);
|
|
132
132
|
|
|
133
|
-
zarfYamlChart = (path: string) => zarfYamlChart(this, path);
|
|
133
|
+
zarfYamlChart = (path: string): string => zarfYamlChart(this, path);
|
|
134
134
|
|
|
135
|
-
allYaml = async (imagePullSecret?: string) => {
|
|
135
|
+
allYaml = async (imagePullSecret?: string): Promise<string> => {
|
|
136
136
|
this.capabilities = await loadCapabilities(this.path);
|
|
137
137
|
// give error if namespaces are not respected
|
|
138
138
|
for (const capability of this.capabilities) {
|
|
@@ -143,7 +143,7 @@ export class Assets {
|
|
|
143
143
|
};
|
|
144
144
|
|
|
145
145
|
/* eslint max-statements: ["warn", 21] */
|
|
146
|
-
generateHelmChart = async (basePath: string) => {
|
|
146
|
+
generateHelmChart = async (basePath: string): Promise<void> => {
|
|
147
147
|
const helm = helmLayout(basePath, this.config.uuid);
|
|
148
148
|
|
|
149
149
|
try {
|
|
@@ -156,18 +156,18 @@ export class Assets {
|
|
|
156
156
|
const code = await fs.readFile(this.path);
|
|
157
157
|
|
|
158
158
|
const pairs: [string, () => string][] = [
|
|
159
|
-
[helm.files.chartYaml, () => dedent(chartYaml(this.config.uuid, this.config.description || ""))],
|
|
160
|
-
[helm.files.namespaceYaml, () => dedent(
|
|
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(
|
|
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
171
|
];
|
|
172
172
|
await Promise.all(pairs.map(async ([file, content]) => await fs.writeFile(file, content())));
|
|
173
173
|
|
|
@@ -191,7 +191,7 @@ export class Assets {
|
|
|
191
191
|
await fs.writeFile(helm.files.validationWebhookYaml, createWebhookYaml(this, validateWebhook));
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
const watchDeployment =
|
|
194
|
+
const watchDeployment = getWatcher(this, this.hash, this.buildTimestamp);
|
|
195
195
|
if (watchDeployment) {
|
|
196
196
|
await fs.writeFile(helm.files.watcherDeploymentYaml, dedent(watcherDeployTemplate(this.buildTimestamp)));
|
|
197
197
|
await fs.writeFile(helm.files.watcherServiceMonitorYaml, dedent(serviceMonitorTemplate("watcher")));
|
package/src/lib/assets/pods.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
package/src/lib/assets/yaml.ts
CHANGED
|
@@ -6,13 +6,16 @@ import crypto from "crypto";
|
|
|
6
6
|
import { promises as fs } from "fs";
|
|
7
7
|
import { Assets } from ".";
|
|
8
8
|
import { apiTokenSecret, service, tlsSecret, watcherService } from "./networking";
|
|
9
|
-
import {
|
|
9
|
+
import { getDeployment, getModuleSecret, getNamespace, getWatcher } from "./pods";
|
|
10
10
|
import { clusterRole, clusterRoleBinding, serviceAccount, storeRole, storeRoleBinding } from "./rbac";
|
|
11
11
|
import { webhookConfig } from "./webhooks";
|
|
12
12
|
import { genEnv } from "./pods";
|
|
13
13
|
|
|
14
14
|
// Helm Chart overrides file (values.yaml) generated from assets
|
|
15
|
-
export async function overridesFile(
|
|
15
|
+
export async function overridesFile(
|
|
16
|
+
{ hash, name, image, config, apiToken, capabilities }: Assets,
|
|
17
|
+
path: string,
|
|
18
|
+
): Promise<void> {
|
|
16
19
|
const rbacOverrides = clusterRole(name, capabilities, config.rbacMode, config.rbac).rules;
|
|
17
20
|
|
|
18
21
|
const overrides = {
|
|
@@ -166,7 +169,7 @@ export async function overridesFile({ hash, name, image, config, apiToken, capab
|
|
|
166
169
|
|
|
167
170
|
await fs.writeFile(path, dumpYaml(overrides, { noRefs: true, forceQuotes: true }));
|
|
168
171
|
}
|
|
169
|
-
export function zarfYaml({ name, image, config }: Assets, path: string) {
|
|
172
|
+
export function zarfYaml({ name, image, config }: Assets, path: string): string {
|
|
170
173
|
const zarfCfg = {
|
|
171
174
|
kind: "ZarfPackageConfig",
|
|
172
175
|
metadata: {
|
|
@@ -194,7 +197,7 @@ export function zarfYaml({ name, image, config }: Assets, path: string) {
|
|
|
194
197
|
return dumpYaml(zarfCfg, { noRefs: true });
|
|
195
198
|
}
|
|
196
199
|
|
|
197
|
-
export function zarfYamlChart({ name, image, config }: Assets, path: string) {
|
|
200
|
+
export function zarfYamlChart({ name, image, config }: Assets, path: string): string {
|
|
198
201
|
const zarfCfg = {
|
|
199
202
|
kind: "ZarfPackageConfig",
|
|
200
203
|
metadata: {
|
|
@@ -223,7 +226,7 @@ export function zarfYamlChart({ name, image, config }: Assets, path: string) {
|
|
|
223
226
|
return dumpYaml(zarfCfg, { noRefs: true });
|
|
224
227
|
}
|
|
225
228
|
|
|
226
|
-
export async function allYaml(assets: Assets, imagePullSecret?: string) {
|
|
229
|
+
export async function allYaml(assets: Assets, imagePullSecret?: string): Promise<string> {
|
|
227
230
|
const { name, tls, apiToken, path, config } = assets;
|
|
228
231
|
const code = await fs.readFile(path);
|
|
229
232
|
|
|
@@ -232,19 +235,19 @@ export async function allYaml(assets: Assets, imagePullSecret?: string) {
|
|
|
232
235
|
|
|
233
236
|
const mutateWebhook = await webhookConfig(assets, "mutate", assets.config.webhookTimeout);
|
|
234
237
|
const validateWebhook = await webhookConfig(assets, "validate", assets.config.webhookTimeout);
|
|
235
|
-
const watchDeployment =
|
|
238
|
+
const watchDeployment = getWatcher(assets, assets.hash, assets.buildTimestamp, imagePullSecret);
|
|
236
239
|
|
|
237
240
|
const resources = [
|
|
238
|
-
|
|
241
|
+
getNamespace(assets.config.customLabels?.namespace),
|
|
239
242
|
clusterRole(name, assets.capabilities, config.rbacMode, config.rbac),
|
|
240
243
|
clusterRoleBinding(name),
|
|
241
244
|
serviceAccount(name),
|
|
242
245
|
apiTokenSecret(name, apiToken),
|
|
243
246
|
tlsSecret(name, tls),
|
|
244
|
-
|
|
247
|
+
getDeployment(assets, assets.hash, assets.buildTimestamp, imagePullSecret),
|
|
245
248
|
service(name),
|
|
246
249
|
watcherService(name),
|
|
247
|
-
|
|
250
|
+
getModuleSecret(name, code, assets.hash),
|
|
248
251
|
storeRole(name),
|
|
249
252
|
storeRoleBinding(name),
|
|
250
253
|
];
|
package/src/lib/capability.ts
CHANGED
|
@@ -71,7 +71,7 @@ export class Capability implements CapabilityExport {
|
|
|
71
71
|
}
|
|
72
72
|
};
|
|
73
73
|
|
|
74
|
-
public getScheduleStore() {
|
|
74
|
+
public getScheduleStore(): Storage {
|
|
75
75
|
return this.#scheduleStore;
|
|
76
76
|
}
|
|
77
77
|
|
|
@@ -111,19 +111,19 @@ export class Capability implements CapabilityExport {
|
|
|
111
111
|
onReady: this.#scheduleStore.onReady,
|
|
112
112
|
};
|
|
113
113
|
|
|
114
|
-
get bindings() {
|
|
114
|
+
get bindings(): Binding[] {
|
|
115
115
|
return this.#bindings;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
get name() {
|
|
118
|
+
get name(): string {
|
|
119
119
|
return this.#name;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
get description() {
|
|
122
|
+
get description(): string {
|
|
123
123
|
return this.#description;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
get namespaces() {
|
|
126
|
+
get namespaces(): string[] {
|
|
127
127
|
return this.#namespaces || [];
|
|
128
128
|
}
|
|
129
129
|
|
|
@@ -207,8 +207,19 @@ export class Capability implements CapabilityExport {
|
|
|
207
207
|
const bindings = this.#bindings;
|
|
208
208
|
const prefix = `${this.#name}: ${model.name}`;
|
|
209
209
|
const commonChain = { WithLabel, WithAnnotation, WithDeletionTimestamp, Mutate, Validate, Watch, Reconcile, Alias };
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
|
|
211
|
+
type CommonChainType = typeof commonChain;
|
|
212
|
+
type ExtendedCommonChainType = CommonChainType & {
|
|
213
|
+
Alias: (alias: string) => CommonChainType;
|
|
214
|
+
InNamespace: (...namespaces: string[]) => BindingWithName<T>;
|
|
215
|
+
InNamespaceRegex: (...namespaces: RegExp[]) => BindingWithName<T>;
|
|
216
|
+
WithName: (name: string) => BindingFilter<T>;
|
|
217
|
+
WithNameRegex: (regexName: RegExp) => BindingFilter<T>;
|
|
218
|
+
WithDeletionTimestamp: () => BindingFilter<T>;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const isNotEmpty = (value: object): boolean => Object.keys(value).length > 0;
|
|
222
|
+
const log = (message: string, cbString: string): void => {
|
|
212
223
|
const filteredObj = pickBy(isNotEmpty, binding.filters);
|
|
213
224
|
|
|
214
225
|
Log.info(`${message} configured for ${binding.event}`, prefix);
|
|
@@ -329,7 +340,7 @@ export class Capability implements CapabilityExport {
|
|
|
329
340
|
isWatch: true,
|
|
330
341
|
isFinalize: true,
|
|
331
342
|
event: Event.UPDATE,
|
|
332
|
-
finalizeCallback: async (update: InstanceType<T>, logger = aliasLogger) => {
|
|
343
|
+
finalizeCallback: async (update: InstanceType<T>, logger = aliasLogger): Promise<boolean | void> => {
|
|
333
344
|
Log.info(`Executing finalize action with alias: ${binding.alias || "no alias provided"}`);
|
|
334
345
|
return await finalizeCallback(update, logger);
|
|
335
346
|
},
|
|
@@ -380,13 +391,13 @@ export class Capability implements CapabilityExport {
|
|
|
380
391
|
return commonChain;
|
|
381
392
|
}
|
|
382
393
|
|
|
383
|
-
function Alias(alias: string) {
|
|
394
|
+
function Alias(alias: string): CommonChainType {
|
|
384
395
|
Log.debug(`Adding prefix alias ${alias}`, prefix);
|
|
385
396
|
binding.alias = alias;
|
|
386
397
|
return commonChain;
|
|
387
398
|
}
|
|
388
399
|
|
|
389
|
-
function bindEvent(event: Event) {
|
|
400
|
+
function bindEvent(event: Event): ExtendedCommonChainType {
|
|
390
401
|
binding.event = event;
|
|
391
402
|
return {
|
|
392
403
|
...commonChain,
|