pepr 0.55.6-nightly.0 → 0.55.6-nightly.10
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/init/templates.d.ts +0 -1
- package/dist/cli/init/templates.d.ts.map +1 -1
- package/dist/cli.js +86 -125
- package/dist/controller.js +1 -1
- package/dist/lib/assets/ignoredNamespaces.d.ts +2 -0
- package/dist/lib/assets/ignoredNamespaces.d.ts.map +1 -1
- package/dist/lib/assets/webhooks.d.ts +2 -2
- package/dist/lib/assets/webhooks.d.ts.map +1 -1
- package/dist/lib/assets/yaml/overridesFile.d.ts.map +1 -1
- package/dist/lib/controller/index.d.ts.map +1 -1
- package/dist/lib/core/capability.d.ts.map +1 -1
- package/dist/lib/core/module.d.ts +9 -7
- package/dist/lib/core/module.d.ts.map +1 -1
- package/dist/lib/processors/mutate-processor.d.ts.map +1 -1
- package/dist/lib/processors/validate-processor.d.ts.map +1 -1
- package/dist/lib.js +59 -50
- package/dist/lib.js.map +2 -2
- package/package.json +5 -6
- package/src/lib/assets/ignoredNamespaces.ts +9 -0
- package/src/lib/assets/webhooks.ts +11 -15
- package/src/lib/assets/yaml/overridesFile.ts +117 -116
- package/src/lib/controller/index.ts +14 -5
- package/src/lib/core/capability.ts +3 -0
- package/src/lib/core/module.ts +53 -48
- package/src/lib/processors/mutate-processor.ts +2 -6
- package/src/lib/processors/validate-processor.ts +2 -11
- package/src/lib/processors/watch-processor.ts +1 -1
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"!src/fixtures/**",
|
|
17
17
|
"!dist/**/*.test.d.ts*"
|
|
18
18
|
],
|
|
19
|
-
"version": "0.55.6-nightly.
|
|
19
|
+
"version": "0.55.6-nightly.10",
|
|
20
20
|
"main": "dist/lib.js",
|
|
21
21
|
"types": "dist/lib.d.ts",
|
|
22
22
|
"scripts": {
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@types/ramda": "0.31.1",
|
|
53
53
|
"command-line-args": "^6.0.1",
|
|
54
|
-
"commander": "14.0.
|
|
54
|
+
"commander": "14.0.2",
|
|
55
55
|
"express": "5.1.0",
|
|
56
56
|
"fast-json-patch": "3.1.1",
|
|
57
57
|
"heredoc": "^1.3.1",
|
|
@@ -68,14 +68,13 @@
|
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@commitlint/cli": "20.1.0",
|
|
70
70
|
"@commitlint/config-conventional": "20.0.0",
|
|
71
|
-
"@fast-check/vitest": "^0.2.1",
|
|
72
71
|
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
73
72
|
"@semantic-release/git": "^10.0.1",
|
|
74
73
|
"@semantic-release/npm": "^13.0.0",
|
|
75
74
|
"@semantic-release/release-notes-generator": "^14.1.0",
|
|
76
75
|
"@types/command-line-args": "^5.2.3",
|
|
77
76
|
"@types/eslint": "9.6.1",
|
|
78
|
-
"@types/express": "5.0.
|
|
77
|
+
"@types/express": "5.0.5",
|
|
79
78
|
"@types/json-pointer": "^1.0.34",
|
|
80
79
|
"@types/json-schema": "^7.0.15",
|
|
81
80
|
"@types/node": "24.x.x",
|
|
@@ -83,7 +82,7 @@
|
|
|
83
82
|
"@types/readable-stream": "^4.0.21",
|
|
84
83
|
"@types/urijs": "^1.19.25",
|
|
85
84
|
"@types/ws": "^8.18.1",
|
|
86
|
-
"@vitest/coverage-v8": "^
|
|
85
|
+
"@vitest/coverage-v8": "^4.0.4",
|
|
87
86
|
"fast-check": "^4.0.0",
|
|
88
87
|
"globals": "^16.0.0",
|
|
89
88
|
"husky": "^9.1.6",
|
|
@@ -91,7 +90,7 @@
|
|
|
91
90
|
"shellcheck": "^4.1.0",
|
|
92
91
|
"tsx": "^4.20.3",
|
|
93
92
|
"undici": "^7.0.1",
|
|
94
|
-
"vitest": "^
|
|
93
|
+
"vitest": "^4.0.4"
|
|
95
94
|
},
|
|
96
95
|
"overrides": {
|
|
97
96
|
"glob": "^9.0.0"
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
import { ModuleConfig } from "../types";
|
|
4
|
+
|
|
5
|
+
export function getIgnoreNamespaces(config?: ModuleConfig): string[] {
|
|
6
|
+
const fromConfig = config?.alwaysIgnore?.namespaces?.length
|
|
7
|
+
? config.alwaysIgnore.namespaces
|
|
8
|
+
: config?.admission?.alwaysIgnore?.namespaces;
|
|
9
|
+
|
|
10
|
+
return resolveIgnoreNamespaces(fromConfig);
|
|
11
|
+
}
|
|
3
12
|
|
|
4
13
|
export function resolveIgnoreNamespaces(ignoredNSConfig: string[] = []): string[] {
|
|
5
14
|
const ignoredNSEnv = process.env.PEPR_ADDITIONAL_IGNORED_NAMESPACES;
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "@kubernetes/client-node";
|
|
11
11
|
import { kind } from "kubernetes-fluent-client";
|
|
12
12
|
import { concat, equals, uniqWith } from "ramda";
|
|
13
|
-
import {
|
|
13
|
+
import { getIgnoreNamespaces } from "./ignoredNamespaces";
|
|
14
14
|
import { Assets } from "./assets";
|
|
15
15
|
import { Event, WebhookType } from "../enums";
|
|
16
16
|
import { Binding, AdditionalWebhook } from "../types";
|
|
@@ -46,9 +46,10 @@ export const validateRule = (
|
|
|
46
46
|
|
|
47
47
|
export async function generateWebhookRules(
|
|
48
48
|
assets: Assets,
|
|
49
|
-
|
|
49
|
+
webhookType: WebhookType,
|
|
50
50
|
): Promise<V1RuleWithOperations[]> {
|
|
51
51
|
const { capabilities } = assets;
|
|
52
|
+
const isMutateWebhook = webhookType === WebhookType.MUTATE;
|
|
52
53
|
|
|
53
54
|
const rules = capabilities.flatMap(capability =>
|
|
54
55
|
capability.bindings
|
|
@@ -61,20 +62,13 @@ export async function generateWebhookRules(
|
|
|
61
62
|
|
|
62
63
|
export async function webhookConfigGenerator(
|
|
63
64
|
assets: Assets,
|
|
64
|
-
|
|
65
|
+
webhookType: WebhookType,
|
|
65
66
|
timeoutSeconds = 10,
|
|
66
67
|
): Promise<kind.MutatingWebhookConfiguration | kind.ValidatingWebhookConfiguration | null> {
|
|
67
68
|
const ignore: V1LabelSelectorRequirement[] = [];
|
|
68
69
|
|
|
69
70
|
const { name, tls, config, apiPath, host } = assets;
|
|
70
|
-
const ignoreNS = concat(
|
|
71
|
-
peprIgnoreNamespaces,
|
|
72
|
-
resolveIgnoreNamespaces(
|
|
73
|
-
config?.alwaysIgnore?.namespaces?.length
|
|
74
|
-
? config?.alwaysIgnore?.namespaces
|
|
75
|
-
: config?.admission?.alwaysIgnore?.namespaces,
|
|
76
|
-
),
|
|
77
|
-
);
|
|
71
|
+
const ignoreNS = concat(peprIgnoreNamespaces, getIgnoreNamespaces(config));
|
|
78
72
|
|
|
79
73
|
// Add any namespaces to ignore
|
|
80
74
|
if (ignoreNS) {
|
|
@@ -90,7 +84,7 @@ export async function webhookConfigGenerator(
|
|
|
90
84
|
};
|
|
91
85
|
|
|
92
86
|
// The URL must include the API Path
|
|
93
|
-
const fullApiPath = `/${
|
|
87
|
+
const fullApiPath = `/${webhookType}/${apiPath}`;
|
|
94
88
|
|
|
95
89
|
// If a host is specified, use that with a port of 3000
|
|
96
90
|
if (host) {
|
|
@@ -104,8 +98,7 @@ export async function webhookConfigGenerator(
|
|
|
104
98
|
};
|
|
105
99
|
}
|
|
106
100
|
|
|
107
|
-
const
|
|
108
|
-
const rules = await generateWebhookRules(assets, isMutate);
|
|
101
|
+
const rules = await generateWebhookRules(assets, webhookType);
|
|
109
102
|
|
|
110
103
|
// If there are no rules, return null
|
|
111
104
|
if (rules.length < 1) {
|
|
@@ -114,7 +107,10 @@ export async function webhookConfigGenerator(
|
|
|
114
107
|
|
|
115
108
|
const webhookConfig = {
|
|
116
109
|
apiVersion: "admissionregistration.k8s.io/v1",
|
|
117
|
-
kind:
|
|
110
|
+
kind:
|
|
111
|
+
webhookType === WebhookType.MUTATE
|
|
112
|
+
? "MutatingWebhookConfiguration"
|
|
113
|
+
: "ValidatingWebhookConfiguration",
|
|
118
114
|
metadata: { name },
|
|
119
115
|
webhooks: [
|
|
120
116
|
{
|
|
@@ -3,7 +3,7 @@ import { CapabilityExport, ModuleConfig } from "../../types";
|
|
|
3
3
|
import { dumpYaml } from "@kubernetes/client-node";
|
|
4
4
|
import { clusterRole } from "../rbac";
|
|
5
5
|
import { promises as fs } from "fs";
|
|
6
|
-
import {
|
|
6
|
+
import { getIgnoreNamespaces } from "../ignoredNamespaces";
|
|
7
7
|
import { quicktype, InputData, jsonInputForTargetLanguage } from "quicktype-core";
|
|
8
8
|
|
|
9
9
|
export type ChartOverrides = {
|
|
@@ -14,6 +14,33 @@ export type ChartOverrides = {
|
|
|
14
14
|
name: string;
|
|
15
15
|
image: string;
|
|
16
16
|
};
|
|
17
|
+
type Probes = {
|
|
18
|
+
readinessProbe: {
|
|
19
|
+
httpGet: { path: string; port: number; scheme: "HTTPS" };
|
|
20
|
+
initialDelaySeconds: number;
|
|
21
|
+
};
|
|
22
|
+
livenessProbe: {
|
|
23
|
+
httpGet: { path: string; port: number; scheme: "HTTPS" };
|
|
24
|
+
initialDelaySeconds: number;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
type Resources = {
|
|
28
|
+
requests: { memory: string; cpu: string };
|
|
29
|
+
limits: { memory: string; cpu: string };
|
|
30
|
+
};
|
|
31
|
+
type PodSecurityContext = {
|
|
32
|
+
runAsUser: number;
|
|
33
|
+
runAsGroup: number;
|
|
34
|
+
runAsNonRoot: true;
|
|
35
|
+
fsGroup: number;
|
|
36
|
+
};
|
|
37
|
+
type ContainerSecurityContext = {
|
|
38
|
+
runAsUser: number;
|
|
39
|
+
runAsGroup: number;
|
|
40
|
+
runAsNonRoot: true;
|
|
41
|
+
allowPrivilegeEscalation: false;
|
|
42
|
+
capabilities: { drop: ["ALL"] };
|
|
43
|
+
};
|
|
17
44
|
|
|
18
45
|
// Helm Chart overrides file (values.yaml) generated from assets
|
|
19
46
|
export async function overridesFile(
|
|
@@ -26,25 +53,14 @@ export async function overridesFile(
|
|
|
26
53
|
|
|
27
54
|
const overrides = {
|
|
28
55
|
imagePullSecrets,
|
|
29
|
-
additionalIgnoredNamespaces:
|
|
30
|
-
config?.alwaysIgnore?.namespaces?.length
|
|
31
|
-
? config?.alwaysIgnore?.namespaces
|
|
32
|
-
: config?.admission?.alwaysIgnore?.namespaces,
|
|
33
|
-
),
|
|
56
|
+
additionalIgnoredNamespaces: getIgnoreNamespaces(config),
|
|
34
57
|
rbac: rbacOverrides,
|
|
35
|
-
secrets: {
|
|
36
|
-
apiPath: Buffer.from(apiPath).toString("base64"),
|
|
37
|
-
},
|
|
58
|
+
secrets: { apiPath: Buffer.from(apiPath).toString("base64") },
|
|
38
59
|
hash,
|
|
39
|
-
namespace:
|
|
40
|
-
annotations: {},
|
|
41
|
-
labels: config.customLabels?.namespace ?? {
|
|
42
|
-
"pepr.dev": "",
|
|
43
|
-
},
|
|
44
|
-
},
|
|
60
|
+
namespace: namespaceBlock(config),
|
|
45
61
|
uuid: name,
|
|
46
62
|
admission: {
|
|
47
|
-
enabled: controllerType.admission === true
|
|
63
|
+
enabled: controllerType.admission === true,
|
|
48
64
|
antiAffinity: false,
|
|
49
65
|
terminationGracePeriodSeconds: 5,
|
|
50
66
|
failurePolicy: config.onError === "reject" ? "Fail" : "Ignore",
|
|
@@ -52,55 +68,12 @@ export async function overridesFile(
|
|
|
52
68
|
env: genEnv(config, false, true),
|
|
53
69
|
envFrom: [],
|
|
54
70
|
image,
|
|
55
|
-
annotations:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
"pepr.dev/uuid": config.uuid,
|
|
62
|
-
},
|
|
63
|
-
securityContext: {
|
|
64
|
-
runAsUser: image.includes("private") ? 1000 : 65532,
|
|
65
|
-
runAsGroup: image.includes("private") ? 1000 : 65532,
|
|
66
|
-
runAsNonRoot: true,
|
|
67
|
-
fsGroup: image.includes("private") ? 1000 : 65532,
|
|
68
|
-
},
|
|
69
|
-
readinessProbe: {
|
|
70
|
-
httpGet: {
|
|
71
|
-
path: "/healthz",
|
|
72
|
-
port: 3000,
|
|
73
|
-
scheme: "HTTPS",
|
|
74
|
-
},
|
|
75
|
-
initialDelaySeconds: 10,
|
|
76
|
-
},
|
|
77
|
-
livenessProbe: {
|
|
78
|
-
httpGet: {
|
|
79
|
-
path: "/healthz",
|
|
80
|
-
port: 3000,
|
|
81
|
-
scheme: "HTTPS",
|
|
82
|
-
},
|
|
83
|
-
initialDelaySeconds: 10,
|
|
84
|
-
},
|
|
85
|
-
resources: {
|
|
86
|
-
requests: {
|
|
87
|
-
memory: "256Mi",
|
|
88
|
-
cpu: "200m",
|
|
89
|
-
},
|
|
90
|
-
limits: {
|
|
91
|
-
memory: "512Mi",
|
|
92
|
-
cpu: "500m",
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
containerSecurityContext: {
|
|
96
|
-
runAsUser: image.includes("private") ? 1000 : 65532,
|
|
97
|
-
runAsGroup: image.includes("private") ? 1000 : 65532,
|
|
98
|
-
runAsNonRoot: true,
|
|
99
|
-
allowPrivilegeEscalation: false,
|
|
100
|
-
capabilities: {
|
|
101
|
-
drop: ["ALL"],
|
|
102
|
-
},
|
|
103
|
-
},
|
|
71
|
+
annotations: controllerAnnotations(config.description),
|
|
72
|
+
labels: controllerLabels(name, config.uuid, "admission"),
|
|
73
|
+
securityContext: podSecurityContext(image),
|
|
74
|
+
...commonProbes(),
|
|
75
|
+
resources: commonResources(),
|
|
76
|
+
containerSecurityContext: containerSecurityContext(image),
|
|
104
77
|
podAnnotations: {},
|
|
105
78
|
podLabels: {},
|
|
106
79
|
nodeSelector: {},
|
|
@@ -115,60 +88,17 @@ export async function overridesFile(
|
|
|
115
88
|
},
|
|
116
89
|
},
|
|
117
90
|
watcher: {
|
|
118
|
-
enabled: controllerType.watcher === true
|
|
91
|
+
enabled: controllerType.watcher === true,
|
|
119
92
|
terminationGracePeriodSeconds: 5,
|
|
120
93
|
env: genEnv(config, true, true),
|
|
121
94
|
envFrom: [],
|
|
122
95
|
image,
|
|
123
|
-
annotations:
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
"pepr.dev/uuid": config.uuid,
|
|
130
|
-
},
|
|
131
|
-
securityContext: {
|
|
132
|
-
runAsUser: image.includes("private") ? 1000 : 65532,
|
|
133
|
-
runAsGroup: image.includes("private") ? 1000 : 65532,
|
|
134
|
-
runAsNonRoot: true,
|
|
135
|
-
fsGroup: image.includes("private") ? 1000 : 65532,
|
|
136
|
-
},
|
|
137
|
-
readinessProbe: {
|
|
138
|
-
httpGet: {
|
|
139
|
-
path: "/healthz",
|
|
140
|
-
port: 3000,
|
|
141
|
-
scheme: "HTTPS",
|
|
142
|
-
},
|
|
143
|
-
initialDelaySeconds: 10,
|
|
144
|
-
},
|
|
145
|
-
livenessProbe: {
|
|
146
|
-
httpGet: {
|
|
147
|
-
path: "/healthz",
|
|
148
|
-
port: 3000,
|
|
149
|
-
scheme: "HTTPS",
|
|
150
|
-
},
|
|
151
|
-
initialDelaySeconds: 10,
|
|
152
|
-
},
|
|
153
|
-
resources: {
|
|
154
|
-
requests: {
|
|
155
|
-
memory: "256Mi",
|
|
156
|
-
cpu: "200m",
|
|
157
|
-
},
|
|
158
|
-
limits: {
|
|
159
|
-
memory: "512Mi",
|
|
160
|
-
cpu: "500m",
|
|
161
|
-
},
|
|
162
|
-
},
|
|
163
|
-
containerSecurityContext: {
|
|
164
|
-
runAsUser: image.includes("private") ? 1000 : 65532,
|
|
165
|
-
runAsGroup: image.includes("private") ? 1000 : 65532,
|
|
166
|
-
runAsNonRoot: true,
|
|
167
|
-
allowPrivilegeEscalation: false,
|
|
168
|
-
capabilities: {
|
|
169
|
-
drop: ["ALL"],
|
|
170
|
-
},
|
|
171
|
-
},
|
|
96
|
+
annotations: controllerAnnotations(config.description),
|
|
97
|
+
labels: controllerLabels(name, config.uuid, "watcher"),
|
|
98
|
+
securityContext: podSecurityContext(image),
|
|
99
|
+
...commonProbes(),
|
|
100
|
+
resources: commonResources(),
|
|
101
|
+
containerSecurityContext: containerSecurityContext(image),
|
|
172
102
|
nodeSelector: {},
|
|
173
103
|
tolerations: [],
|
|
174
104
|
extraVolumeMounts: [],
|
|
@@ -183,7 +113,6 @@ export async function overridesFile(
|
|
|
183
113
|
},
|
|
184
114
|
},
|
|
185
115
|
};
|
|
186
|
-
/** write values.schema.yaml */
|
|
187
116
|
await writeSchemaYamlFromObject(JSON.stringify(overrides, null, 2), path);
|
|
188
117
|
await fs.writeFile(path, dumpYaml(overrides, { noRefs: true, forceQuotes: true }));
|
|
189
118
|
}
|
|
@@ -206,3 +135,75 @@ export async function writeSchemaYamlFromObject(
|
|
|
206
135
|
|
|
207
136
|
await fs.writeFile(schemaPath, JSON.stringify(schemaObj, null, 2), "utf8");
|
|
208
137
|
}
|
|
138
|
+
|
|
139
|
+
function runIdsForImage(image: string): { uid: number; gid: number; fsGroup: number } {
|
|
140
|
+
const id = image.includes("private") ? 1000 : 65532;
|
|
141
|
+
return { uid: id, gid: id, fsGroup: id };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function commonProbes(): Probes {
|
|
145
|
+
return {
|
|
146
|
+
readinessProbe: {
|
|
147
|
+
httpGet: { path: "/healthz", port: 3000, scheme: "HTTPS" },
|
|
148
|
+
initialDelaySeconds: 10,
|
|
149
|
+
},
|
|
150
|
+
livenessProbe: {
|
|
151
|
+
httpGet: { path: "/healthz", port: 3000, scheme: "HTTPS" },
|
|
152
|
+
initialDelaySeconds: 10,
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function commonResources(): Resources {
|
|
158
|
+
return {
|
|
159
|
+
requests: { memory: "256Mi", cpu: "200m" },
|
|
160
|
+
limits: { memory: "512Mi", cpu: "500m" },
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function podSecurityContext(image: string): PodSecurityContext {
|
|
165
|
+
const ids = runIdsForImage(image);
|
|
166
|
+
return {
|
|
167
|
+
runAsUser: ids.uid,
|
|
168
|
+
runAsGroup: ids.gid,
|
|
169
|
+
runAsNonRoot: true,
|
|
170
|
+
fsGroup: ids.fsGroup,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function containerSecurityContext(image: string): ContainerSecurityContext {
|
|
175
|
+
const ids = runIdsForImage(image);
|
|
176
|
+
return {
|
|
177
|
+
runAsUser: ids.uid,
|
|
178
|
+
runAsGroup: ids.gid,
|
|
179
|
+
runAsNonRoot: true,
|
|
180
|
+
allowPrivilegeEscalation: false,
|
|
181
|
+
capabilities: { drop: ["ALL"] },
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function controllerLabels(
|
|
186
|
+
name: string,
|
|
187
|
+
uuid: string,
|
|
188
|
+
kind: "admission" | "watcher",
|
|
189
|
+
): Record<string, string> {
|
|
190
|
+
return {
|
|
191
|
+
app: kind === "admission" ? name : `${name}-watcher`,
|
|
192
|
+
"pepr.dev/controller": kind,
|
|
193
|
+
"pepr.dev/uuid": uuid,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function controllerAnnotations(description?: string): Record<string, string> {
|
|
198
|
+
return { "pepr.dev/description": `${description ?? ""}` };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function namespaceBlock(config: ModuleConfig): {
|
|
202
|
+
annotations: Record<string, string>;
|
|
203
|
+
labels: Record<string, string>;
|
|
204
|
+
} {
|
|
205
|
+
return {
|
|
206
|
+
annotations: {},
|
|
207
|
+
labels: config.customLabels?.namespace ?? { "pepr.dev": "" },
|
|
208
|
+
};
|
|
209
|
+
}
|
|
@@ -17,6 +17,7 @@ import { StoreController } from "./store";
|
|
|
17
17
|
import { karForMutate, karForValidate, KubeAdmissionReview } from "./index.util";
|
|
18
18
|
import { AdmissionRequest } from "../common-types";
|
|
19
19
|
import { featureFlagStore } from "../features/store";
|
|
20
|
+
import { GroupVersionKind } from "kubernetes-fluent-client";
|
|
20
21
|
|
|
21
22
|
export interface ControllerHooks {
|
|
22
23
|
beforeHook?: (req: AdmissionRequest) => void;
|
|
@@ -228,11 +229,7 @@ export class Controller {
|
|
|
228
229
|
// Get the request from the body or create an empty request
|
|
229
230
|
const request: AdmissionRequest = req.body?.request || ({} as AdmissionRequest);
|
|
230
231
|
|
|
231
|
-
const { name, namespace, gvk } =
|
|
232
|
-
name: request?.name ? `/${request.name}` : "",
|
|
233
|
-
namespace: request?.namespace || "",
|
|
234
|
-
gvk: request?.kind || { group: "", version: "", kind: "" },
|
|
235
|
-
};
|
|
232
|
+
const { name, namespace, gvk } = getRequestValues(request);
|
|
236
233
|
|
|
237
234
|
const reqMetadata = { uid: request.uid, namespace, name };
|
|
238
235
|
Log.info(
|
|
@@ -322,3 +319,15 @@ export class Controller {
|
|
|
322
319
|
}
|
|
323
320
|
}
|
|
324
321
|
}
|
|
322
|
+
|
|
323
|
+
function getRequestValues(request: AdmissionRequest): {
|
|
324
|
+
name: string;
|
|
325
|
+
namespace: string;
|
|
326
|
+
gvk: GroupVersionKind;
|
|
327
|
+
} {
|
|
328
|
+
return {
|
|
329
|
+
name: request?.name ? `/${request.name}` : "",
|
|
330
|
+
namespace: request?.namespace || "",
|
|
331
|
+
gvk: request?.kind || { group: "", version: "", kind: "" },
|
|
332
|
+
};
|
|
333
|
+
}
|
|
@@ -180,6 +180,9 @@ export class Capability implements CapabilityExport {
|
|
|
180
180
|
* @param kind if using a custom KubernetesObject not available in `a.*`, specify the GroupVersionKind
|
|
181
181
|
* @returns
|
|
182
182
|
*/
|
|
183
|
+
// This method intentionally defines a fluent, closure-based DSL for chaining capability actions.
|
|
184
|
+
// Multiple inline helper functions are required to preserve runtime behavior and readability.
|
|
185
|
+
// eslint-disable-next-line max-statements
|
|
183
186
|
When = <T extends GenericClass>(model: T, kind?: GroupVersionKind): WhenSelector<T> => {
|
|
184
187
|
const matchedKind = modelToGroupVersionKind(model.name);
|
|
185
188
|
|
package/src/lib/core/module.ts
CHANGED
|
@@ -4,82 +4,87 @@ import { clone } from "ramda";
|
|
|
4
4
|
import { Capability } from "./capability";
|
|
5
5
|
import { Controller } from "../controller";
|
|
6
6
|
import { ValidateError } from "../errors";
|
|
7
|
-
import { CapabilityExport } from "../types";
|
|
7
|
+
import { CapabilityExport, PackageJSON, PeprModuleOptions, ModuleConfig } from "../types";
|
|
8
8
|
import { isBuildMode } from "./envChecks";
|
|
9
|
-
import { PackageJSON, PeprModuleOptions, ModuleConfig } from "../types";
|
|
10
9
|
import { createControllerHooks } from "../controller/createHooks";
|
|
11
10
|
|
|
12
11
|
export class PeprModule {
|
|
13
12
|
#controller!: Controller;
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
|
-
*
|
|
15
|
+
* Initialize a new Pepr runtime module.
|
|
17
16
|
*
|
|
18
|
-
* @param
|
|
19
|
-
* @param capabilities The capabilities to
|
|
20
|
-
* @param opts Options for the Pepr runtime
|
|
17
|
+
* @param pkg The package.json data containing Pepr configuration.
|
|
18
|
+
* @param capabilities The list of capabilities to load.
|
|
19
|
+
* @param opts Options for the Pepr runtime settings (e.g., deferStart).
|
|
21
20
|
*/
|
|
21
|
+
|
|
22
22
|
constructor(
|
|
23
23
|
{ description, pepr }: PackageJSON,
|
|
24
24
|
capabilities: Capability[] = [],
|
|
25
25
|
opts: PeprModuleOptions = {},
|
|
26
26
|
) {
|
|
27
|
-
const config
|
|
28
|
-
config
|
|
27
|
+
const config = PeprModule.#initializeConfig(description, pepr);
|
|
28
|
+
PeprModule.#validateConfig(config);
|
|
29
29
|
|
|
30
|
-
// Need to validate at runtime since TS gets sad about parsing the package.json
|
|
31
|
-
ValidateError(config.onError);
|
|
32
|
-
|
|
33
|
-
// Handle build mode
|
|
34
30
|
if (isBuildMode()) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const exportedCapabilities: CapabilityExport[] = [];
|
|
41
|
-
|
|
42
|
-
// Send capability map to parent process
|
|
43
|
-
for (const capability of capabilities) {
|
|
44
|
-
// Convert the capability to a capability config
|
|
45
|
-
exportedCapabilities.push({
|
|
46
|
-
name: capability.name,
|
|
47
|
-
description: capability.description,
|
|
48
|
-
namespaces: capability.namespaces,
|
|
49
|
-
bindings: capability.bindings,
|
|
50
|
-
hasSchedule: capability.hasSchedule,
|
|
51
|
-
});
|
|
52
|
-
}
|
|
31
|
+
PeprModule.#handleBuildMode(capabilities);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
53
34
|
|
|
54
|
-
|
|
55
|
-
|
|
35
|
+
const controllerHooks = PeprModule.#createHooks(opts, capabilities, pepr, config);
|
|
36
|
+
this.#controller = new Controller(config, capabilities, controllerHooks);
|
|
56
37
|
|
|
57
|
-
|
|
38
|
+
if (!opts.deferStart) {
|
|
39
|
+
this.start();
|
|
58
40
|
}
|
|
41
|
+
}
|
|
59
42
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
: config?.watch?.alwaysIgnore?.namespaces,
|
|
66
|
-
);
|
|
43
|
+
static #initializeConfig(description: string, pepr: PackageJSON["pepr"]): ModuleConfig {
|
|
44
|
+
const config: ModuleConfig = clone(pepr);
|
|
45
|
+
config.description = description;
|
|
46
|
+
return config;
|
|
47
|
+
}
|
|
67
48
|
|
|
68
|
-
|
|
49
|
+
static #validateConfig(config: ModuleConfig): void {
|
|
50
|
+
ValidateError(config.onError);
|
|
51
|
+
}
|
|
69
52
|
|
|
70
|
-
|
|
71
|
-
if (
|
|
72
|
-
|
|
53
|
+
static #handleBuildMode(capabilities: Capability[]): void {
|
|
54
|
+
if (!process.send) {
|
|
55
|
+
throw new Error("process.send is not defined");
|
|
73
56
|
}
|
|
74
57
|
|
|
75
|
-
|
|
58
|
+
const exportedCapabilities: CapabilityExport[] = capabilities.map(cap => ({
|
|
59
|
+
name: cap.name,
|
|
60
|
+
description: cap.description,
|
|
61
|
+
namespaces: cap.namespaces,
|
|
62
|
+
bindings: cap.bindings,
|
|
63
|
+
hasSchedule: cap.hasSchedule,
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
process.send(exportedCapabilities);
|
|
76
67
|
}
|
|
77
68
|
|
|
69
|
+
static #createHooks(
|
|
70
|
+
opts: PeprModuleOptions,
|
|
71
|
+
capabilities: Capability[],
|
|
72
|
+
pepr: PackageJSON["pepr"],
|
|
73
|
+
config: ModuleConfig,
|
|
74
|
+
): ReturnType<typeof createControllerHooks> {
|
|
75
|
+
const ignored = pepr?.alwaysIgnore?.namespaces?.length
|
|
76
|
+
? pepr.alwaysIgnore.namespaces
|
|
77
|
+
: config?.watch?.alwaysIgnore?.namespaces;
|
|
78
|
+
|
|
79
|
+
return createControllerHooks(opts, capabilities, ignored);
|
|
80
|
+
}
|
|
78
81
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
82
|
+
* Starts the Pepr runtime manually.
|
|
83
|
+
*
|
|
84
|
+
* Normally this is called automatically when the Pepr module is instantiated,
|
|
85
|
+
* but it can be invoked manually if `deferStart` is set to `true` in the constructor.
|
|
81
86
|
*
|
|
82
|
-
* @param port
|
|
87
|
+
* @param port - The port number to start the server on (default: 3000).
|
|
83
88
|
*/
|
|
84
89
|
start = (port = 3000): void => {
|
|
85
90
|
this.#controller.startServer(port);
|
|
@@ -13,7 +13,7 @@ import { ModuleConfig } from "../types";
|
|
|
13
13
|
import { PeprMutateRequest } from "../mutate-request";
|
|
14
14
|
import { base64Encode } from "../utils";
|
|
15
15
|
import { OnError } from "../../cli/init/enums";
|
|
16
|
-
import {
|
|
16
|
+
import { getIgnoreNamespaces } from "../assets/ignoredNamespaces";
|
|
17
17
|
import { Operation } from "fast-json-patch";
|
|
18
18
|
import { WebhookType } from "../enums";
|
|
19
19
|
|
|
@@ -149,11 +149,7 @@ export async function mutateProcessor(
|
|
|
149
149
|
bind.binding,
|
|
150
150
|
bind.req,
|
|
151
151
|
bind.namespaces,
|
|
152
|
-
|
|
153
|
-
bind?.config?.alwaysIgnore?.namespaces?.length
|
|
154
|
-
? bind.config?.alwaysIgnore?.namespaces
|
|
155
|
-
: bind.config?.admission?.alwaysIgnore?.namespaces,
|
|
156
|
-
),
|
|
152
|
+
getIgnoreNamespaces(config),
|
|
157
153
|
);
|
|
158
154
|
if (shouldSkip !== "") {
|
|
159
155
|
Log.debug(shouldSkip);
|
|
@@ -10,7 +10,7 @@ import Log from "../telemetry/logger";
|
|
|
10
10
|
import { convertFromBase64Map } from "../utils";
|
|
11
11
|
import { PeprValidateRequest } from "../validate-request";
|
|
12
12
|
import { ModuleConfig } from "../types";
|
|
13
|
-
import {
|
|
13
|
+
import { getIgnoreNamespaces } from "../assets/ignoredNamespaces";
|
|
14
14
|
import { MeasureWebhookTimeout } from "../telemetry/webhookTimeouts";
|
|
15
15
|
import { WebhookType } from "../enums";
|
|
16
16
|
import { AdmissionRequest } from "../common-types";
|
|
@@ -93,16 +93,7 @@ export async function validateProcessor(
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
// Continue to the next action without doing anything if this one should be skipped
|
|
96
|
-
const shouldSkip = shouldSkipRequest(
|
|
97
|
-
binding,
|
|
98
|
-
req,
|
|
99
|
-
namespaces,
|
|
100
|
-
resolveIgnoreNamespaces(
|
|
101
|
-
config?.alwaysIgnore?.namespaces?.length
|
|
102
|
-
? config?.alwaysIgnore?.namespaces
|
|
103
|
-
: config?.admission?.alwaysIgnore?.namespaces,
|
|
104
|
-
),
|
|
105
|
-
);
|
|
96
|
+
const shouldSkip = shouldSkipRequest(binding, req, namespaces, getIgnoreNamespaces(config));
|
|
106
97
|
if (shouldSkip !== "") {
|
|
107
98
|
Log.debug(shouldSkip);
|
|
108
99
|
continue;
|
|
@@ -89,7 +89,7 @@ const eventToPhaseMap = {
|
|
|
89
89
|
export function setupWatch(capabilities: Capability[], ignoredNamespaces?: string[]): void {
|
|
90
90
|
for (const capability of capabilities) {
|
|
91
91
|
for (const binding of capability.bindings.filter(b => b.isWatch)) {
|
|
92
|
-
runBinding(binding, capability.namespaces, ignoredNamespaces);
|
|
92
|
+
void runBinding(binding, capability.namespaces, ignoredNamespaces);
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
}
|