pepr 0.46.0 → 0.46.1-nightly.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/init/index.d.ts.map +1 -1
- package/dist/cli/init/templates.d.ts +2 -3
- package/dist/cli/init/templates.d.ts.map +1 -1
- package/dist/cli.js +36 -23
- package/dist/controller.js +1 -1
- package/dist/lib/assets/defaultTestObjects.d.ts +3 -0
- package/dist/lib/assets/defaultTestObjects.d.ts.map +1 -0
- package/dist/lib/filter/adjudicators/admissionRequest.d.ts +9 -0
- package/dist/lib/filter/adjudicators/admissionRequest.d.ts.map +1 -0
- package/dist/lib/filter/adjudicators/binding.d.ts +31 -0
- package/dist/lib/filter/adjudicators/binding.d.ts.map +1 -0
- package/dist/lib/filter/adjudicators/kubernetesObject.d.ts +22 -0
- package/dist/lib/filter/adjudicators/kubernetesObject.d.ts.map +1 -0
- package/dist/lib/filter/adjudicators/mismatch.d.ts +16 -0
- package/dist/lib/filter/adjudicators/mismatch.d.ts.map +1 -0
- package/dist/lib/filter/adjudicators/postCollection.d.ts +10 -0
- package/dist/lib/filter/adjudicators/postCollection.d.ts.map +1 -0
- package/dist/lib/filter/filter.d.ts.map +1 -1
- package/dist/lib.js +196 -152
- package/dist/lib.js.map +4 -4
- package/package.json +5 -6
- package/src/cli/init/index.ts +38 -18
- package/src/cli/init/templates.ts +5 -7
- package/src/lib/assets/defaultTestObjects.ts +533 -0
- package/src/lib/filter/adjudicators/admissionRequest.ts +25 -0
- package/src/lib/filter/adjudicators/binding.ts +85 -0
- package/src/lib/filter/adjudicators/kubernetesObject.ts +90 -0
- package/src/lib/filter/adjudicators/mismatch.ts +129 -0
- package/src/lib/filter/adjudicators/postCollection.ts +77 -0
- package/src/lib/filter/filter.ts +22 -24
- package/src/lib/processors/watch-processor.ts +1 -1
- package/dist/lib/filter/adjudicators/adjudicators.d.ts +0 -74
- package/dist/lib/filter/adjudicators/adjudicators.d.ts.map +0 -1
- package/src/lib/filter/adjudicators/adjudicators.ts +0 -334
package/package.json
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"!src/**/*.test.ts",
|
|
16
16
|
"!dist/**/*.test.d.ts*"
|
|
17
17
|
],
|
|
18
|
-
"version": "0.46.0",
|
|
18
|
+
"version": "0.46.1-nightly.0",
|
|
19
19
|
"main": "dist/lib.js",
|
|
20
20
|
"types": "dist/lib.d.ts",
|
|
21
21
|
"scripts": {
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"follow-redirects": "1.15.9",
|
|
53
53
|
"http-status-codes": "^2.3.0",
|
|
54
54
|
"json-pointer": "^0.6.2",
|
|
55
|
-
"kubernetes-fluent-client": "3.4.
|
|
55
|
+
"kubernetes-fluent-client": "3.4.2",
|
|
56
56
|
"pino": "9.6.0",
|
|
57
57
|
"pino-pretty": "13.0.0",
|
|
58
58
|
"prom-client": "15.1.3",
|
|
@@ -76,12 +76,11 @@
|
|
|
76
76
|
"jest": "29.7.0",
|
|
77
77
|
"js-yaml": "^4.1.0",
|
|
78
78
|
"shellcheck": "^3.0.0",
|
|
79
|
-
"ts-jest": "29.2.
|
|
79
|
+
"ts-jest": "29.2.6",
|
|
80
80
|
"undici": "^7.0.1"
|
|
81
81
|
},
|
|
82
82
|
"overrides": {
|
|
83
|
-
"glob": "^9.0.0"
|
|
84
|
-
"jsonpath-plus": "^10.3.0"
|
|
83
|
+
"glob": "^9.0.0"
|
|
85
84
|
},
|
|
86
85
|
"peerDependencies": {
|
|
87
86
|
"@types/prompts": "2.4.9",
|
|
@@ -96,4 +95,4 @@
|
|
|
96
95
|
"typescript": "5.7.3",
|
|
97
96
|
"uuid": "11.0.5"
|
|
98
97
|
}
|
|
99
|
-
}
|
|
98
|
+
}
|
package/src/cli/init/index.ts
CHANGED
|
@@ -9,10 +9,11 @@ import { RootCmd } from "../root";
|
|
|
9
9
|
import {
|
|
10
10
|
codeSettings,
|
|
11
11
|
eslint,
|
|
12
|
-
|
|
12
|
+
peprTSTemplate,
|
|
13
13
|
genPkgJSON,
|
|
14
14
|
gitignore,
|
|
15
15
|
helloPepr,
|
|
16
|
+
peprPackageJSON,
|
|
16
17
|
prettier,
|
|
17
18
|
readme,
|
|
18
19
|
samplesYaml,
|
|
@@ -50,29 +51,15 @@ export default function (program: RootCmd): void {
|
|
|
50
51
|
.action(async opts => {
|
|
51
52
|
const dirName = sanitizeName(response.name);
|
|
52
53
|
const packageJSON = genPkgJSON(response, pkgOverride);
|
|
53
|
-
const peprTS = genPeprTS();
|
|
54
54
|
|
|
55
|
-
const confirmed = await confirm(dirName, packageJSON,
|
|
55
|
+
const confirmed = await confirm(dirName, packageJSON, peprTSTemplate.path, opts.confirm);
|
|
56
56
|
|
|
57
57
|
if (confirmed) {
|
|
58
58
|
console.log("Creating new Pepr module...");
|
|
59
59
|
|
|
60
60
|
try {
|
|
61
|
-
await
|
|
62
|
-
await
|
|
63
|
-
await createDir(resolve(dirName, "capabilities"));
|
|
64
|
-
|
|
65
|
-
await write(resolve(dirName, gitignore.path), gitignore.data);
|
|
66
|
-
await write(resolve(dirName, eslint.path), eslint.data);
|
|
67
|
-
await write(resolve(dirName, prettier.path), prettier.data);
|
|
68
|
-
await write(resolve(dirName, packageJSON.path), packageJSON.data);
|
|
69
|
-
await write(resolve(dirName, readme.path), readme.data);
|
|
70
|
-
await write(resolve(dirName, tsConfig.path), tsConfig.data);
|
|
71
|
-
await write(resolve(dirName, peprTS.path), peprTS.data);
|
|
72
|
-
await write(resolve(dirName, ".vscode", snippet.path), snippet.data);
|
|
73
|
-
await write(resolve(dirName, ".vscode", codeSettings.path), codeSettings.data);
|
|
74
|
-
await write(resolve(dirName, "capabilities", samplesYaml.path), samplesYaml.data);
|
|
75
|
-
await write(resolve(dirName, "capabilities", helloPepr.path), helloPepr.data);
|
|
61
|
+
await setupProjectStructure(dirName);
|
|
62
|
+
await createProjectFiles(dirName, packageJSON);
|
|
76
63
|
|
|
77
64
|
if (!opts.skipPostInit) {
|
|
78
65
|
doPostInitActions(dirName);
|
|
@@ -90,6 +77,39 @@ export default function (program: RootCmd): void {
|
|
|
90
77
|
});
|
|
91
78
|
}
|
|
92
79
|
|
|
80
|
+
async function setupProjectStructure(dirName: string): Promise<void> {
|
|
81
|
+
await createDir(dirName);
|
|
82
|
+
await createDir(resolve(dirName, ".vscode"));
|
|
83
|
+
await createDir(resolve(dirName, "capabilities"));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function createProjectFiles(dirName: string, packageJSON: peprPackageJSON): Promise<void> {
|
|
87
|
+
const files = [
|
|
88
|
+
{ path: gitignore.path, data: gitignore.data },
|
|
89
|
+
{ path: eslint.path, data: eslint.data },
|
|
90
|
+
{ path: prettier.path, data: prettier.data },
|
|
91
|
+
{ path: packageJSON.path, data: packageJSON.data },
|
|
92
|
+
{ path: readme.path, data: readme.data },
|
|
93
|
+
{ path: tsConfig.path, data: tsConfig.data },
|
|
94
|
+
{ path: peprTSTemplate.path, data: peprTSTemplate.data },
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
const nestedFiles = [
|
|
98
|
+
{ dir: ".vscode", path: snippet.path, data: snippet.data },
|
|
99
|
+
{ dir: ".vscode", path: codeSettings.path, data: codeSettings.data },
|
|
100
|
+
{ dir: "capabilities", path: samplesYaml.path, data: samplesYaml.data },
|
|
101
|
+
{ dir: "capabilities", path: helloPepr.path, data: helloPepr.data },
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
for (const file of files) {
|
|
105
|
+
await write(resolve(dirName, file.path), file.data);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
for (const file of nestedFiles) {
|
|
109
|
+
await write(resolve(dirName, file.dir, file.path), file.data);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
93
113
|
const doPostInitActions = (dirName: string): void => {
|
|
94
114
|
// run npm install from the new directory
|
|
95
115
|
process.chdir(dirName);
|
|
@@ -20,7 +20,7 @@ import { sanitizeName } from "./utils";
|
|
|
20
20
|
|
|
21
21
|
export const { dependencies, devDependencies, peerDependencies, scripts, version } = packageJSON;
|
|
22
22
|
|
|
23
|
-
type peprPackageJSON = {
|
|
23
|
+
export type peprPackageJSON = {
|
|
24
24
|
data: {
|
|
25
25
|
name: string;
|
|
26
26
|
version: string;
|
|
@@ -101,12 +101,10 @@ export function genPkgJSON(opts: InitOptions, pgkVerOverride?: string): peprPack
|
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
export
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
};
|
|
109
|
-
}
|
|
104
|
+
export const peprTSTemplate = {
|
|
105
|
+
path: "pepr.ts",
|
|
106
|
+
data: peprTS,
|
|
107
|
+
};
|
|
110
108
|
|
|
111
109
|
export const readme = {
|
|
112
110
|
path: "README.md",
|
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
import { GenericClass } from "kubernetes-fluent-client";
|
|
2
|
+
import { Event } from "../enums";
|
|
3
|
+
import { CapabilityExport } from "../types";
|
|
4
|
+
import { describe, beforeEach, jest, it, expect } from "@jest/globals";
|
|
5
|
+
import { V1PolicyRule as PolicyRule } from "@kubernetes/client-node";
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import { clusterRole } from "./rbac";
|
|
8
|
+
import * as helpers from "../helpers";
|
|
9
|
+
|
|
10
|
+
export const mockCapabilities: CapabilityExport[] = [
|
|
11
|
+
{
|
|
12
|
+
rbac: [
|
|
13
|
+
{
|
|
14
|
+
apiGroups: ["pepr.dev"],
|
|
15
|
+
resources: ["peprstores"],
|
|
16
|
+
verbs: ["create", "get", "patch", "watch"],
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
bindings: [
|
|
20
|
+
{
|
|
21
|
+
kind: { group: "pepr.dev", version: "v1", kind: "peprstore", plural: "peprstores" },
|
|
22
|
+
isWatch: false,
|
|
23
|
+
event: Event.CREATE,
|
|
24
|
+
model: {} as GenericClass,
|
|
25
|
+
filters: {
|
|
26
|
+
name: "",
|
|
27
|
+
regexName: "",
|
|
28
|
+
namespaces: [],
|
|
29
|
+
regexNamespaces: [],
|
|
30
|
+
labels: {},
|
|
31
|
+
annotations: {},
|
|
32
|
+
deletionTimestamp: false,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
hasSchedule: false,
|
|
37
|
+
name: "",
|
|
38
|
+
description: "",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
rbac: [
|
|
42
|
+
{
|
|
43
|
+
apiGroups: ["apiextensions.k8s.io"],
|
|
44
|
+
resources: ["customresourcedefinitions"],
|
|
45
|
+
verbs: ["patch", "create"],
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
bindings: [
|
|
49
|
+
{
|
|
50
|
+
kind: {
|
|
51
|
+
group: "apiextensions.k8s.io",
|
|
52
|
+
version: "v1",
|
|
53
|
+
kind: "customresourcedefinition",
|
|
54
|
+
plural: "customresourcedefinitions",
|
|
55
|
+
},
|
|
56
|
+
isWatch: false,
|
|
57
|
+
isFinalize: false,
|
|
58
|
+
event: Event.CREATE,
|
|
59
|
+
model: {} as GenericClass,
|
|
60
|
+
filters: {
|
|
61
|
+
name: "",
|
|
62
|
+
regexName: "",
|
|
63
|
+
namespaces: [],
|
|
64
|
+
regexNamespaces: [],
|
|
65
|
+
labels: {},
|
|
66
|
+
annotations: {},
|
|
67
|
+
deletionTimestamp: false,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
hasSchedule: false,
|
|
72
|
+
name: "",
|
|
73
|
+
description: "",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
rbac: [
|
|
77
|
+
{
|
|
78
|
+
apiGroups: [""],
|
|
79
|
+
resources: ["namespaces"],
|
|
80
|
+
verbs: ["watch"],
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
bindings: [
|
|
84
|
+
{
|
|
85
|
+
kind: { group: "", version: "v1", kind: "namespace", plural: "namespaces" },
|
|
86
|
+
isWatch: true,
|
|
87
|
+
isFinalize: false,
|
|
88
|
+
event: Event.CREATE,
|
|
89
|
+
model: {} as GenericClass,
|
|
90
|
+
filters: {
|
|
91
|
+
name: "",
|
|
92
|
+
regexName: "",
|
|
93
|
+
namespaces: [],
|
|
94
|
+
regexNamespaces: [],
|
|
95
|
+
labels: {},
|
|
96
|
+
annotations: {},
|
|
97
|
+
deletionTimestamp: false,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
hasSchedule: false,
|
|
102
|
+
name: "",
|
|
103
|
+
description: "",
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
rbac: [
|
|
107
|
+
{
|
|
108
|
+
apiGroups: [""],
|
|
109
|
+
resources: ["configmaps"],
|
|
110
|
+
verbs: ["watch"],
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
bindings: [
|
|
114
|
+
{
|
|
115
|
+
kind: { group: "", version: "v1", kind: "configmap", plural: "configmaps" },
|
|
116
|
+
isWatch: true,
|
|
117
|
+
isFinalize: false,
|
|
118
|
+
event: Event.CREATE,
|
|
119
|
+
model: {} as GenericClass,
|
|
120
|
+
filters: {
|
|
121
|
+
name: "",
|
|
122
|
+
regexName: "",
|
|
123
|
+
namespaces: [],
|
|
124
|
+
regexNamespaces: [],
|
|
125
|
+
labels: {},
|
|
126
|
+
annotations: {},
|
|
127
|
+
deletionTimestamp: false,
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
hasSchedule: false,
|
|
132
|
+
name: "",
|
|
133
|
+
description: "",
|
|
134
|
+
},
|
|
135
|
+
];
|
|
136
|
+
describe("RBAC generation", () => {
|
|
137
|
+
beforeEach(() => {
|
|
138
|
+
jest.clearAllMocks();
|
|
139
|
+
const mockPackageJsonRBAC = {};
|
|
140
|
+
|
|
141
|
+
jest.spyOn(fs, "readFileSync").mockImplementation((path: unknown) => {
|
|
142
|
+
if (typeof path === "string" && path.includes("package.json")) {
|
|
143
|
+
return JSON.stringify({ rbac: mockPackageJsonRBAC });
|
|
144
|
+
}
|
|
145
|
+
return "{}";
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("should generate correct ClusterRole rules in scoped mode", () => {
|
|
150
|
+
const result = clusterRole("test-role", mockCapabilities, "scoped", []);
|
|
151
|
+
|
|
152
|
+
expect(result.rules).toEqual([
|
|
153
|
+
{
|
|
154
|
+
apiGroups: ["pepr.dev"],
|
|
155
|
+
resources: ["peprstores"],
|
|
156
|
+
verbs: ["create", "get", "patch", "watch"],
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
apiGroups: ["apiextensions.k8s.io"],
|
|
160
|
+
resources: ["customresourcedefinitions"],
|
|
161
|
+
verbs: ["patch", "create"],
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
apiGroups: [""],
|
|
165
|
+
resources: ["namespaces"],
|
|
166
|
+
verbs: ["watch"],
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
apiGroups: [""],
|
|
170
|
+
resources: ["configmaps"],
|
|
171
|
+
verbs: ["watch"],
|
|
172
|
+
},
|
|
173
|
+
]);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("should generate a ClusterRole with wildcard rules when not in scoped mode", () => {
|
|
177
|
+
const expectedWildcardRules = [
|
|
178
|
+
{
|
|
179
|
+
apiGroups: ["*"],
|
|
180
|
+
resources: ["*"],
|
|
181
|
+
verbs: ["create", "delete", "get", "list", "patch", "update", "watch"],
|
|
182
|
+
},
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
const result = clusterRole("test-role", mockCapabilities, "admin", []);
|
|
186
|
+
|
|
187
|
+
expect(result.rules).toEqual(expectedWildcardRules);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("should return an empty rules array when capabilities are empty in scoped mode", () => {
|
|
191
|
+
const result = clusterRole("test-role", [], "scoped", []);
|
|
192
|
+
|
|
193
|
+
expect(result.rules).toEqual([]);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("should include finalize verbs if isFinalize is true in scoped mode", () => {
|
|
197
|
+
const capabilitiesWithFinalize: CapabilityExport[] = [
|
|
198
|
+
{
|
|
199
|
+
rbac: [
|
|
200
|
+
{
|
|
201
|
+
apiGroups: ["pepr.dev"],
|
|
202
|
+
resources: ["peprstores"],
|
|
203
|
+
verbs: ["patch"],
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
bindings: [
|
|
207
|
+
{
|
|
208
|
+
kind: { group: "pepr.dev", version: "v1", kind: "peprstore", plural: "peprstores" },
|
|
209
|
+
isWatch: false,
|
|
210
|
+
isFinalize: true,
|
|
211
|
+
event: Event.CREATE,
|
|
212
|
+
model: {} as GenericClass,
|
|
213
|
+
filters: {
|
|
214
|
+
name: "",
|
|
215
|
+
regexName: "",
|
|
216
|
+
namespaces: [],
|
|
217
|
+
regexNamespaces: [],
|
|
218
|
+
labels: {},
|
|
219
|
+
annotations: {},
|
|
220
|
+
deletionTimestamp: false,
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
hasSchedule: false,
|
|
225
|
+
name: "",
|
|
226
|
+
description: "",
|
|
227
|
+
},
|
|
228
|
+
];
|
|
229
|
+
|
|
230
|
+
const result = clusterRole(
|
|
231
|
+
"test-role",
|
|
232
|
+
capabilitiesWithFinalize,
|
|
233
|
+
"scoped",
|
|
234
|
+
capabilitiesWithFinalize.flatMap(c => c.rbac).filter((rule): rule is PolicyRule => rule !== undefined),
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
expect(result.rules).toEqual([
|
|
238
|
+
{
|
|
239
|
+
apiGroups: ["pepr.dev"],
|
|
240
|
+
resources: ["peprstores"],
|
|
241
|
+
verbs: ["patch"],
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
apiGroups: ["apiextensions.k8s.io"],
|
|
245
|
+
resources: ["customresourcedefinitions"],
|
|
246
|
+
verbs: ["patch", "create"],
|
|
247
|
+
},
|
|
248
|
+
]);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("should deduplicate verbs and resources in rules", () => {
|
|
252
|
+
const capabilitiesWithDuplicates: CapabilityExport[] = [
|
|
253
|
+
{
|
|
254
|
+
rbac: [
|
|
255
|
+
{
|
|
256
|
+
apiGroups: ["pepr.dev"],
|
|
257
|
+
resources: ["peprstores"],
|
|
258
|
+
verbs: ["create", "get"],
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
bindings: [
|
|
262
|
+
{
|
|
263
|
+
kind: { group: "pepr.dev", version: "v1", kind: "peprlog", plural: "peprlogs" },
|
|
264
|
+
isWatch: false,
|
|
265
|
+
event: Event.CREATE,
|
|
266
|
+
model: {} as GenericClass,
|
|
267
|
+
filters: {
|
|
268
|
+
name: "",
|
|
269
|
+
regexName: "",
|
|
270
|
+
namespaces: [],
|
|
271
|
+
regexNamespaces: [],
|
|
272
|
+
labels: {},
|
|
273
|
+
annotations: {},
|
|
274
|
+
deletionTimestamp: false,
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
hasSchedule: false,
|
|
279
|
+
name: "",
|
|
280
|
+
description: "",
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
rbac: [
|
|
284
|
+
{
|
|
285
|
+
apiGroups: ["pepr.dev"],
|
|
286
|
+
resources: ["peprstores"],
|
|
287
|
+
verbs: ["get", "patch"],
|
|
288
|
+
},
|
|
289
|
+
],
|
|
290
|
+
bindings: [
|
|
291
|
+
{
|
|
292
|
+
kind: { group: "pepr.dev", version: "v1", kind: "peprlog", plural: "peprlogs" },
|
|
293
|
+
isWatch: false,
|
|
294
|
+
event: Event.CREATE,
|
|
295
|
+
model: {} as GenericClass,
|
|
296
|
+
filters: {
|
|
297
|
+
name: "",
|
|
298
|
+
regexName: "",
|
|
299
|
+
namespaces: [],
|
|
300
|
+
regexNamespaces: [],
|
|
301
|
+
labels: {},
|
|
302
|
+
annotations: {},
|
|
303
|
+
deletionTimestamp: false,
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
],
|
|
307
|
+
hasSchedule: false,
|
|
308
|
+
name: "",
|
|
309
|
+
description: "",
|
|
310
|
+
},
|
|
311
|
+
];
|
|
312
|
+
|
|
313
|
+
const result = clusterRole(
|
|
314
|
+
"test-role",
|
|
315
|
+
capabilitiesWithDuplicates,
|
|
316
|
+
"scoped",
|
|
317
|
+
capabilitiesWithDuplicates.flatMap(c => c.rbac).filter((rule): rule is PolicyRule => rule !== undefined),
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
// Filter out only the rules for 'pepr.dev' and 'peprstores'
|
|
321
|
+
const filteredRules = result.rules?.filter(
|
|
322
|
+
rule => rule.apiGroups?.includes("pepr.dev") && rule.resources?.includes("peprstores"),
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
expect(filteredRules).toEqual([
|
|
326
|
+
{
|
|
327
|
+
apiGroups: ["pepr.dev"],
|
|
328
|
+
resources: ["peprstores"],
|
|
329
|
+
verbs: ["create", "get", "patch", "watch"],
|
|
330
|
+
},
|
|
331
|
+
]);
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
describe("clusterRole", () => {
|
|
335
|
+
// Mocking the readRBACFromPackageJson function to return null
|
|
336
|
+
jest.mock("./rbac", () => ({
|
|
337
|
+
...(jest.requireActual("./rbac") as object),
|
|
338
|
+
readRBACFromPackageJson: jest.fn(() => null),
|
|
339
|
+
}));
|
|
340
|
+
|
|
341
|
+
// Mocking createRBACMap to isolate the behavior of clusterRole function
|
|
342
|
+
jest.mock("../helpers", () => ({
|
|
343
|
+
...(jest.requireActual("../helpers") as object),
|
|
344
|
+
createRBACMap: jest.fn(),
|
|
345
|
+
}));
|
|
346
|
+
|
|
347
|
+
beforeEach(() => {
|
|
348
|
+
jest.clearAllMocks();
|
|
349
|
+
jest.restoreAllMocks();
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it("should handle keys with less than 3 segments and set group to an empty string", () => {
|
|
353
|
+
jest.spyOn(helpers, "createRBACMap").mockReturnValue({
|
|
354
|
+
nodes: {
|
|
355
|
+
plural: "nodes",
|
|
356
|
+
verbs: ["get"],
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
const capabilitiesWithShortKey: CapabilityExport[] = [
|
|
361
|
+
{
|
|
362
|
+
rbac: [
|
|
363
|
+
{
|
|
364
|
+
apiGroups: [""],
|
|
365
|
+
resources: ["nodes"],
|
|
366
|
+
verbs: ["get"],
|
|
367
|
+
},
|
|
368
|
+
],
|
|
369
|
+
bindings: [
|
|
370
|
+
{
|
|
371
|
+
kind: { group: "", version: "v1", kind: "node", plural: "nodes" },
|
|
372
|
+
isWatch: false,
|
|
373
|
+
event: Event.CREATE,
|
|
374
|
+
model: {} as GenericClass,
|
|
375
|
+
filters: {
|
|
376
|
+
name: "",
|
|
377
|
+
regexName: "",
|
|
378
|
+
namespaces: [],
|
|
379
|
+
regexNamespaces: [],
|
|
380
|
+
labels: {},
|
|
381
|
+
annotations: {},
|
|
382
|
+
deletionTimestamp: false,
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
],
|
|
386
|
+
hasSchedule: false,
|
|
387
|
+
name: "",
|
|
388
|
+
description: "",
|
|
389
|
+
},
|
|
390
|
+
];
|
|
391
|
+
|
|
392
|
+
const result = clusterRole(
|
|
393
|
+
"test-role",
|
|
394
|
+
capabilitiesWithShortKey,
|
|
395
|
+
"scoped",
|
|
396
|
+
capabilitiesWithShortKey.flatMap(c => c.rbac).filter((rule): rule is PolicyRule => rule !== undefined),
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
expect(result.rules).toEqual([
|
|
400
|
+
{
|
|
401
|
+
apiGroups: [""],
|
|
402
|
+
resources: ["nodes"],
|
|
403
|
+
verbs: ["get"],
|
|
404
|
+
},
|
|
405
|
+
]);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it("should handle keys with 3 or more segments and set group correctly", () => {
|
|
409
|
+
jest.spyOn(helpers, "createRBACMap").mockReturnValue({
|
|
410
|
+
"apps/v1/deployments": {
|
|
411
|
+
plural: "deployments",
|
|
412
|
+
verbs: ["create"],
|
|
413
|
+
},
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
const capabilitiesWithLongKey: CapabilityExport[] = [
|
|
417
|
+
{
|
|
418
|
+
rbac: [
|
|
419
|
+
{
|
|
420
|
+
apiGroups: ["apps"],
|
|
421
|
+
resources: ["deployments"],
|
|
422
|
+
verbs: ["create"],
|
|
423
|
+
},
|
|
424
|
+
],
|
|
425
|
+
bindings: [
|
|
426
|
+
{
|
|
427
|
+
kind: { group: "apps", version: "v1", kind: "deployment", plural: "deployments" },
|
|
428
|
+
isWatch: false,
|
|
429
|
+
event: Event.CREATE,
|
|
430
|
+
model: {} as GenericClass,
|
|
431
|
+
filters: {
|
|
432
|
+
name: "",
|
|
433
|
+
regexName: "",
|
|
434
|
+
namespaces: [],
|
|
435
|
+
regexNamespaces: [],
|
|
436
|
+
labels: {},
|
|
437
|
+
annotations: {},
|
|
438
|
+
deletionTimestamp: false,
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
],
|
|
442
|
+
hasSchedule: false,
|
|
443
|
+
name: "",
|
|
444
|
+
description: "",
|
|
445
|
+
},
|
|
446
|
+
];
|
|
447
|
+
|
|
448
|
+
const result = clusterRole(
|
|
449
|
+
"test-role",
|
|
450
|
+
capabilitiesWithLongKey,
|
|
451
|
+
"scoped",
|
|
452
|
+
capabilitiesWithLongKey.flatMap(c => c.rbac).filter((rule): rule is PolicyRule => rule !== undefined),
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
expect(result.rules).toEqual([
|
|
456
|
+
{
|
|
457
|
+
apiGroups: ["apps"],
|
|
458
|
+
resources: ["deployments"],
|
|
459
|
+
verbs: ["create"],
|
|
460
|
+
},
|
|
461
|
+
]);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it("should handle non-array custom RBAC by defaulting to an empty array", () => {
|
|
465
|
+
// Mock readRBACFromPackageJson to return a non-array value
|
|
466
|
+
jest.spyOn(fs, "readFileSync").mockImplementation(() => {
|
|
467
|
+
return JSON.stringify({
|
|
468
|
+
pepr: {
|
|
469
|
+
rbac: "not-an-array", // Simulate invalid RBAC structure
|
|
470
|
+
},
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
const result = clusterRole(
|
|
475
|
+
"test-role",
|
|
476
|
+
mockCapabilities,
|
|
477
|
+
"scoped",
|
|
478
|
+
mockCapabilities.flatMap(c => c.rbac).filter((rule): rule is PolicyRule => rule !== undefined),
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
// The result should only contain rules from the capabilities, not from the invalid custom RBAC
|
|
482
|
+
expect(result.rules).toEqual([
|
|
483
|
+
{
|
|
484
|
+
apiGroups: ["pepr.dev"],
|
|
485
|
+
resources: ["peprstores"],
|
|
486
|
+
verbs: ["create", "get", "patch", "watch"],
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
apiGroups: ["apiextensions.k8s.io"],
|
|
490
|
+
resources: ["customresourcedefinitions"],
|
|
491
|
+
verbs: ["patch", "create"],
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
apiGroups: [""],
|
|
495
|
+
resources: ["namespaces"],
|
|
496
|
+
verbs: ["watch"],
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
apiGroups: [""],
|
|
500
|
+
resources: ["configmaps"],
|
|
501
|
+
verbs: ["watch"],
|
|
502
|
+
},
|
|
503
|
+
]);
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it("should default to an empty verbs array if rule.verbs is undefined", () => {
|
|
507
|
+
// Simulate a custom RBAC rule with empty verbs
|
|
508
|
+
const customRbacWithNoVerbs: PolicyRule[] = [
|
|
509
|
+
{
|
|
510
|
+
apiGroups: ["pepr.dev"],
|
|
511
|
+
resources: ["customresources"],
|
|
512
|
+
verbs: [], // Set verbs to an empty array to satisfy the V1PolicyRule type
|
|
513
|
+
},
|
|
514
|
+
];
|
|
515
|
+
|
|
516
|
+
jest.spyOn(fs, "readFileSync").mockImplementation(() => {
|
|
517
|
+
return JSON.stringify({
|
|
518
|
+
pepr: {
|
|
519
|
+
rbac: customRbacWithNoVerbs,
|
|
520
|
+
},
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
const result = clusterRole("test-role", mockCapabilities, "scoped", customRbacWithNoVerbs);
|
|
525
|
+
|
|
526
|
+
// Check that the verbs array is empty for the custom RBAC rule
|
|
527
|
+
expect(result.rules).toContainEqual({
|
|
528
|
+
apiGroups: ["pepr.dev"],
|
|
529
|
+
resources: ["customresources"],
|
|
530
|
+
verbs: [],
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
import { Operation } from "../../enums";
|
|
5
|
+
import { AdmissionRequest } from "../../types";
|
|
6
|
+
import { defaultTo, pipe } from "ramda";
|
|
7
|
+
import { KubernetesObject } from "kubernetes-fluent-client";
|
|
8
|
+
|
|
9
|
+
export const declaredOperation = pipe(
|
|
10
|
+
(request: AdmissionRequest<KubernetesObject>): Operation => request?.operation,
|
|
11
|
+
defaultTo(""),
|
|
12
|
+
);
|
|
13
|
+
export const declaredGroup = pipe(
|
|
14
|
+
(request: AdmissionRequest<KubernetesObject>): string => request?.kind?.group,
|
|
15
|
+
defaultTo(""),
|
|
16
|
+
);
|
|
17
|
+
export const declaredVersion = pipe(
|
|
18
|
+
(request: AdmissionRequest<KubernetesObject>): string | undefined => request?.kind?.version,
|
|
19
|
+
defaultTo(""),
|
|
20
|
+
);
|
|
21
|
+
export const declaredKind = pipe(
|
|
22
|
+
(request: AdmissionRequest<KubernetesObject>): string => request?.kind?.kind,
|
|
23
|
+
defaultTo(""),
|
|
24
|
+
);
|
|
25
|
+
export const declaredUid = pipe((request: AdmissionRequest<KubernetesObject>): string => request?.uid, defaultTo(""));
|