pepr 0.46.0 → 0.46.1-nightly.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/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 +21 -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 +137 -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.
|
|
18
|
+
"version": "0.46.1-nightly.1",
|
|
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,137 @@
|
|
|
1
|
+
import { GenericClass } from "kubernetes-fluent-client";
|
|
2
|
+
import { Event } from "../enums";
|
|
3
|
+
import { Binding, CapabilityExport } from "../types";
|
|
4
|
+
import { defaultFilters } from "../filter/adjudicators/defaultTestObjects";
|
|
5
|
+
import { V1PolicyRule as PolicyRule } from "@kubernetes/client-node";
|
|
6
|
+
|
|
7
|
+
export const createMockRbacRule = (
|
|
8
|
+
apiGroups: string[] = ["pepr.dev"],
|
|
9
|
+
resources: string[] = ["peprstores"],
|
|
10
|
+
verbs: string[] = ["create", "get", "patch", "watch"],
|
|
11
|
+
): PolicyRule => ({
|
|
12
|
+
apiGroups,
|
|
13
|
+
resources,
|
|
14
|
+
verbs,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const createMockBinding = (
|
|
18
|
+
kindDetails: { group?: string; version?: string; kind?: string; plural?: string } = {},
|
|
19
|
+
options: { isWatch?: boolean; event?: Event; isFinalize?: boolean } = {},
|
|
20
|
+
): Binding => {
|
|
21
|
+
const { group = "pepr.dev", version = "v1", kind = "peprstore", plural = "peprstores" } = kindDetails;
|
|
22
|
+
|
|
23
|
+
const { isWatch = false, event = Event.CREATE, isFinalize } = options;
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
kind: { group, version, kind, plural },
|
|
27
|
+
isWatch,
|
|
28
|
+
...(isFinalize !== undefined && { isFinalize }),
|
|
29
|
+
event,
|
|
30
|
+
model: {} as GenericClass,
|
|
31
|
+
filters: { ...defaultFilters, regexName: "" },
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const createMockCapability = (
|
|
36
|
+
rbacRules = [createMockRbacRule()],
|
|
37
|
+
bindings = [createMockBinding()],
|
|
38
|
+
): CapabilityExport => ({
|
|
39
|
+
name: "",
|
|
40
|
+
hasSchedule: false,
|
|
41
|
+
description: "",
|
|
42
|
+
rbac: rbacRules,
|
|
43
|
+
bindings,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export const mockCapabilities: CapabilityExport[] = [
|
|
47
|
+
createMockCapability(),
|
|
48
|
+
createMockCapability(
|
|
49
|
+
[createMockRbacRule(["apiextensions.k8s.io"], ["customresourcedefinitions"], ["patch", "create"])],
|
|
50
|
+
[
|
|
51
|
+
createMockBinding(
|
|
52
|
+
{
|
|
53
|
+
group: "apiextensions.k8s.io",
|
|
54
|
+
version: "v1",
|
|
55
|
+
kind: "customresourcedefinition",
|
|
56
|
+
plural: "customresourcedefinitions",
|
|
57
|
+
},
|
|
58
|
+
{ isWatch: false, event: Event.CREATE, isFinalize: false },
|
|
59
|
+
),
|
|
60
|
+
],
|
|
61
|
+
),
|
|
62
|
+
createMockCapability(
|
|
63
|
+
[createMockRbacRule([""], ["namespaces"], ["watch"])],
|
|
64
|
+
[
|
|
65
|
+
createMockBinding(
|
|
66
|
+
{ group: "", version: "v1", kind: "namespace", plural: "namespaces" },
|
|
67
|
+
{ isWatch: true, event: Event.CREATE, isFinalize: false },
|
|
68
|
+
),
|
|
69
|
+
],
|
|
70
|
+
),
|
|
71
|
+
createMockCapability(
|
|
72
|
+
[createMockRbacRule([""], ["configmaps"], ["watch"])],
|
|
73
|
+
[
|
|
74
|
+
createMockBinding(
|
|
75
|
+
{ group: "", version: "v1", kind: "configmap", plural: "configmaps" },
|
|
76
|
+
{ isWatch: true, event: Event.CREATE, isFinalize: false },
|
|
77
|
+
),
|
|
78
|
+
],
|
|
79
|
+
),
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
export const capabilityWithFinalize: CapabilityExport[] = [
|
|
83
|
+
createMockCapability(
|
|
84
|
+
[createMockRbacRule(["pepr.dev"], ["peprstores"], ["patch"])],
|
|
85
|
+
[
|
|
86
|
+
createMockBinding(
|
|
87
|
+
{ group: "pepr.dev", version: "v1", kind: "peprstore", plural: "peprstores" },
|
|
88
|
+
{ isWatch: false, event: Event.CREATE, isFinalize: true },
|
|
89
|
+
),
|
|
90
|
+
],
|
|
91
|
+
),
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
export const capabilityWithDuplicates: CapabilityExport[] = [
|
|
95
|
+
createMockCapability(
|
|
96
|
+
[createMockRbacRule(["pepr.dev"], ["peprstores"], ["create", "get"])],
|
|
97
|
+
[
|
|
98
|
+
createMockBinding(
|
|
99
|
+
{ group: "pepr.dev", version: "v1", kind: "peprlog", plural: "peprlogs" },
|
|
100
|
+
{ isWatch: false, event: Event.CREATE },
|
|
101
|
+
),
|
|
102
|
+
],
|
|
103
|
+
),
|
|
104
|
+
createMockCapability(
|
|
105
|
+
[createMockRbacRule(["pepr.dev"], ["peprstores"], ["get", "patch"])],
|
|
106
|
+
[
|
|
107
|
+
createMockBinding(
|
|
108
|
+
{ group: "pepr.dev", version: "v1", kind: "peprlog", plural: "peprlogs" },
|
|
109
|
+
{ isWatch: false, event: Event.CREATE },
|
|
110
|
+
),
|
|
111
|
+
],
|
|
112
|
+
),
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
export const capabilityWithShortKey: CapabilityExport[] = [
|
|
116
|
+
createMockCapability(
|
|
117
|
+
[createMockRbacRule([""], ["nodes"], ["get"])],
|
|
118
|
+
[
|
|
119
|
+
createMockBinding(
|
|
120
|
+
{ group: "", version: "v1", kind: "node", plural: "nodes" },
|
|
121
|
+
{ isWatch: false, event: Event.CREATE },
|
|
122
|
+
),
|
|
123
|
+
],
|
|
124
|
+
),
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
export const capabilityWithLongKey: CapabilityExport[] = [
|
|
128
|
+
createMockCapability(
|
|
129
|
+
[createMockRbacRule(["apps"], ["deployments"], ["create"])],
|
|
130
|
+
[
|
|
131
|
+
createMockBinding(
|
|
132
|
+
{ group: "apps", version: "v1", kind: "deployment", plural: "deployments" },
|
|
133
|
+
{ isWatch: false, event: Event.CREATE },
|
|
134
|
+
),
|
|
135
|
+
],
|
|
136
|
+
),
|
|
137
|
+
];
|
|
@@ -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(""));
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
import { Event } from "../../enums";
|
|
5
|
+
import { Binding, FinalizeAction, WatchLogAction, MutateAction, ValidateAction } from "../../types";
|
|
6
|
+
import { complement, defaultTo, equals, not, pipe } from "ramda";
|
|
7
|
+
import { GenericClass } from "kubernetes-fluent-client";
|
|
8
|
+
|
|
9
|
+
export const definesDeletionTimestamp = pipe(
|
|
10
|
+
(binding: Binding): boolean => binding?.filters?.deletionTimestamp ?? false,
|
|
11
|
+
defaultTo(false),
|
|
12
|
+
);
|
|
13
|
+
export const ignoresDeletionTimestamp = complement(definesDeletionTimestamp);
|
|
14
|
+
|
|
15
|
+
export const definedName = pipe((binding: Binding): string => {
|
|
16
|
+
return binding.filters.name;
|
|
17
|
+
}, defaultTo(""));
|
|
18
|
+
export const definesName = pipe(definedName, equals(""), not);
|
|
19
|
+
export const ignoresName = complement(definesName);
|
|
20
|
+
|
|
21
|
+
export const definedNameRegex = pipe(
|
|
22
|
+
(binding: Partial<Binding>): string | undefined => binding.filters?.regexName,
|
|
23
|
+
defaultTo(""),
|
|
24
|
+
);
|
|
25
|
+
export const definesNameRegex = pipe(definedNameRegex, equals(""), not);
|
|
26
|
+
|
|
27
|
+
export const definedNamespaces = pipe(binding => binding?.filters?.namespaces, defaultTo([]));
|
|
28
|
+
export const definesNamespaces = pipe(definedNamespaces, equals([]), not);
|
|
29
|
+
|
|
30
|
+
export const definedNamespaceRegexes = pipe(binding => binding?.filters?.regexNamespaces, defaultTo([]));
|
|
31
|
+
export const definesNamespaceRegexes = pipe(definedNamespaceRegexes, equals([]), not);
|
|
32
|
+
|
|
33
|
+
export const definedAnnotations = pipe((binding: Partial<Binding>) => binding?.filters?.annotations, defaultTo({}));
|
|
34
|
+
export const definesAnnotations = pipe(definedAnnotations, equals({}), not);
|
|
35
|
+
|
|
36
|
+
export const definedLabels = pipe((binding: Partial<Binding>) => binding?.filters?.labels, defaultTo({}));
|
|
37
|
+
export const definesLabels = pipe(definedLabels, equals({}), not);
|
|
38
|
+
|
|
39
|
+
export const definedEvent = (binding: Binding): Event => {
|
|
40
|
+
return binding.event;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const definesDelete = pipe(definedEvent, equals(Event.DELETE));
|
|
44
|
+
|
|
45
|
+
export const definedGroup = pipe((binding): string => binding?.kind?.group, defaultTo(""));
|
|
46
|
+
export const definesGroup = pipe(definedGroup, equals(""), not);
|
|
47
|
+
|
|
48
|
+
export const definedVersion = pipe(
|
|
49
|
+
(binding: Partial<Binding>): string | undefined => binding?.kind?.version,
|
|
50
|
+
defaultTo(""),
|
|
51
|
+
);
|
|
52
|
+
export const definesVersion = pipe(definedVersion, equals(""), not);
|
|
53
|
+
|
|
54
|
+
export const definedKind = pipe((binding): string => binding?.kind?.kind, defaultTo(""));
|
|
55
|
+
export const definesKind = pipe(definedKind, equals(""), not);
|
|
56
|
+
|
|
57
|
+
export const definedCategory = (binding: Partial<Binding>): string => {
|
|
58
|
+
// Ordering matters, finalize is a "watch"
|
|
59
|
+
const categories: { [key: string]: boolean | undefined } = {
|
|
60
|
+
Finalize: binding.isFinalize,
|
|
61
|
+
Watch: binding.isWatch,
|
|
62
|
+
Mutate: binding.isMutate,
|
|
63
|
+
Validate: binding.isValidate,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return Object.keys(categories).find(key => categories[key]) || "";
|
|
67
|
+
};
|
|
68
|
+
export type DefinedCallbackReturnType =
|
|
69
|
+
| FinalizeAction<GenericClass, InstanceType<GenericClass>>
|
|
70
|
+
| WatchLogAction<GenericClass, InstanceType<GenericClass>>
|
|
71
|
+
| MutateAction<GenericClass, InstanceType<GenericClass>>
|
|
72
|
+
| ValidateAction<GenericClass, InstanceType<GenericClass>>
|
|
73
|
+
| null
|
|
74
|
+
| undefined;
|
|
75
|
+
|
|
76
|
+
export const definedCallback = (binding: Partial<Binding>): DefinedCallbackReturnType => {
|
|
77
|
+
// Ordering matters, finalize is a "watch"
|
|
78
|
+
// prettier-ignore
|
|
79
|
+
return binding.isFinalize ? binding.finalizeCallback :
|
|
80
|
+
binding.isWatch ? binding.watchCallback :
|
|
81
|
+
binding.isMutate ? binding.mutateCallback :
|
|
82
|
+
binding.isValidate ? binding.validateCallback :
|
|
83
|
+
null;
|
|
84
|
+
};
|
|
85
|
+
export const definedCallbackName = pipe(definedCallback, defaultTo({ name: "" }), callback => callback.name);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
import { __, allPass, complement, defaultTo, equals, length, gt, not, nthArg, pipe } from "ramda";
|
|
5
|
+
import { KubernetesObject } from "kubernetes-fluent-client";
|
|
6
|
+
|
|
7
|
+
export const carriesDeletionTimestamp = pipe(
|
|
8
|
+
kubernetesObject => !!kubernetesObject.metadata?.deletionTimestamp,
|
|
9
|
+
defaultTo(false),
|
|
10
|
+
);
|
|
11
|
+
export const missingDeletionTimestamp = complement(carriesDeletionTimestamp);
|
|
12
|
+
|
|
13
|
+
export const carriedKind = pipe(
|
|
14
|
+
(kubernetesObject: KubernetesObject): string | undefined => kubernetesObject?.kind,
|
|
15
|
+
defaultTo("not set"),
|
|
16
|
+
);
|
|
17
|
+
export const carriedVersion = pipe(
|
|
18
|
+
(kubernetesObject: KubernetesObject): string | undefined => kubernetesObject?.metadata?.resourceVersion,
|
|
19
|
+
defaultTo("not set"),
|
|
20
|
+
);
|
|
21
|
+
export const carriedName = pipe(
|
|
22
|
+
(kubernetesObject: KubernetesObject): string | undefined => kubernetesObject?.metadata?.name,
|
|
23
|
+
defaultTo(""),
|
|
24
|
+
);
|
|
25
|
+
export const carriesName = pipe(carriedName, equals(""), not);
|
|
26
|
+
export const missingName = complement(carriesName);
|
|
27
|
+
|
|
28
|
+
export const carriedNamespace = pipe(
|
|
29
|
+
(kubernetesObject: KubernetesObject): string | undefined => kubernetesObject?.metadata?.namespace,
|
|
30
|
+
defaultTo(""),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
export const carriesNamespace = pipe(carriedNamespace, equals(""), not);
|
|
34
|
+
|
|
35
|
+
export const carriedAnnotations = pipe(
|
|
36
|
+
(kubernetesObject: KubernetesObject): { [key: string]: string } | undefined =>
|
|
37
|
+
kubernetesObject?.metadata?.annotations,
|
|
38
|
+
defaultTo({}),
|
|
39
|
+
);
|
|
40
|
+
export const carriesAnnotations = pipe(carriedAnnotations, equals({}), not);
|
|
41
|
+
|
|
42
|
+
export const carriedLabels = pipe(
|
|
43
|
+
(kubernetesObject: KubernetesObject): { [key: string]: string } | undefined => kubernetesObject?.metadata?.labels,
|
|
44
|
+
defaultTo({}),
|
|
45
|
+
);
|
|
46
|
+
export const carriesLabels = pipe(carriedLabels, equals({}), not);
|
|
47
|
+
|
|
48
|
+
/*
|
|
49
|
+
* If the object does not have a namespace, and it is not a namespace,
|
|
50
|
+
* then we must return false because it cannot be uncarryable
|
|
51
|
+
*/
|
|
52
|
+
export const uncarryableNamespace = allPass([
|
|
53
|
+
pipe(nthArg(0), length, gt(__, 0)),
|
|
54
|
+
pipe((namespaceSelector, kubernetesObject) => {
|
|
55
|
+
if (kubernetesObject?.kind === "Namespace") {
|
|
56
|
+
return namespaceSelector.includes(kubernetesObject?.metadata?.name);
|
|
57
|
+
}
|
|
58
|
+
if (carriesNamespace(kubernetesObject)) {
|
|
59
|
+
return namespaceSelector.includes(carriedNamespace(kubernetesObject));
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
}, not),
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
export const missingCarriableNamespace = allPass([
|
|
66
|
+
pipe(nthArg(0), length, gt(__, 0)),
|
|
67
|
+
pipe((namespaceSelector: string[], kubernetesObject: KubernetesObject): boolean =>
|
|
68
|
+
kubernetesObject.kind === "Namespace"
|
|
69
|
+
? !namespaceSelector.includes(kubernetesObject.metadata!.name!)
|
|
70
|
+
: !carriesNamespace(kubernetesObject),
|
|
71
|
+
),
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
/*
|
|
75
|
+
* If the object does not have a namespace, and it is not a namespace,
|
|
76
|
+
* then we must return false because it cannot be ignored
|
|
77
|
+
*/
|
|
78
|
+
export const carriesIgnoredNamespace = allPass([
|
|
79
|
+
pipe(nthArg(0), length, gt(__, 0)),
|
|
80
|
+
pipe((namespaceSelector, kubernetesObject) => {
|
|
81
|
+
if (kubernetesObject?.kind === "Namespace") {
|
|
82
|
+
return namespaceSelector.includes(kubernetesObject?.metadata?.name);
|
|
83
|
+
}
|
|
84
|
+
if (carriesNamespace(kubernetesObject)) {
|
|
85
|
+
return namespaceSelector.includes(carriedNamespace(kubernetesObject));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return false;
|
|
89
|
+
}),
|
|
90
|
+
]);
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
import { AdmissionRequest, Binding } from "../../types";
|
|
5
|
+
import { allPass, any, anyPass, equals, not, nthArg, pipe } from "ramda";
|
|
6
|
+
import {
|
|
7
|
+
definedAnnotations,
|
|
8
|
+
definedEvent,
|
|
9
|
+
definedGroup,
|
|
10
|
+
definedKind,
|
|
11
|
+
definedLabels,
|
|
12
|
+
definedName,
|
|
13
|
+
definedNameRegex,
|
|
14
|
+
definedNamespaceRegexes,
|
|
15
|
+
definedNamespaces,
|
|
16
|
+
definedVersion,
|
|
17
|
+
definesAnnotations,
|
|
18
|
+
definesDeletionTimestamp,
|
|
19
|
+
definesGroup,
|
|
20
|
+
definesKind,
|
|
21
|
+
definesLabels,
|
|
22
|
+
definesName,
|
|
23
|
+
definesNameRegex,
|
|
24
|
+
definesNamespaceRegexes,
|
|
25
|
+
definesNamespaces,
|
|
26
|
+
definesVersion,
|
|
27
|
+
} from "./binding";
|
|
28
|
+
import {
|
|
29
|
+
carriedAnnotations,
|
|
30
|
+
carriedLabels,
|
|
31
|
+
carriedName,
|
|
32
|
+
carriedNamespace,
|
|
33
|
+
missingDeletionTimestamp,
|
|
34
|
+
} from "./kubernetesObject";
|
|
35
|
+
import { declaredOperation, declaredGroup, declaredVersion, declaredKind } from "./admissionRequest";
|
|
36
|
+
import { Event, Operation } from "../../enums";
|
|
37
|
+
|
|
38
|
+
export const mismatchedDeletionTimestamp = allPass([
|
|
39
|
+
pipe(nthArg(0), definesDeletionTimestamp),
|
|
40
|
+
pipe(nthArg(1), missingDeletionTimestamp),
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
export const mismatchedName = allPass([
|
|
44
|
+
pipe(nthArg(0), definesName),
|
|
45
|
+
pipe((binding, kubernetesObject) => definedName(binding) !== carriedName(kubernetesObject)),
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
export const mismatchedNameRegex = allPass([
|
|
49
|
+
pipe(nthArg(0), definesNameRegex),
|
|
50
|
+
pipe((binding, kubernetesObject) => new RegExp(definedNameRegex(binding)).test(carriedName(kubernetesObject)), not),
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
export const mismatchedNamespace = allPass([
|
|
54
|
+
pipe(nthArg(0), definesNamespaces),
|
|
55
|
+
pipe((binding, kubernetesObject) => definedNamespaces(binding).includes(carriedNamespace(kubernetesObject)), not),
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
export const mismatchedNamespaceRegex = allPass([
|
|
59
|
+
pipe(nthArg(0), definesNamespaceRegexes),
|
|
60
|
+
pipe((binding, kubernetesObject) =>
|
|
61
|
+
pipe(
|
|
62
|
+
any((regEx: string) => new RegExp(regEx).test(carriedNamespace(kubernetesObject))),
|
|
63
|
+
not,
|
|
64
|
+
)(definedNamespaceRegexes(binding)),
|
|
65
|
+
),
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
export const metasMismatch = pipe(
|
|
69
|
+
(defined, carried) => {
|
|
70
|
+
const result = { defined, carried, unalike: {} };
|
|
71
|
+
|
|
72
|
+
result.unalike = Object.entries(result.defined)
|
|
73
|
+
.map(([key, value]) => {
|
|
74
|
+
const keyMissing = !Object.hasOwn(result.carried, key);
|
|
75
|
+
const noValue = !value;
|
|
76
|
+
const valMissing = !result.carried[key];
|
|
77
|
+
const valDiffers = result.carried[key] !== result.defined[key];
|
|
78
|
+
|
|
79
|
+
// prettier-ignore
|
|
80
|
+
return (
|
|
81
|
+
keyMissing ? { [key]: value } :
|
|
82
|
+
noValue ? {} :
|
|
83
|
+
valMissing ? { [key]: value } :
|
|
84
|
+
valDiffers ? { [key]: value } :
|
|
85
|
+
{}
|
|
86
|
+
)
|
|
87
|
+
})
|
|
88
|
+
.reduce((acc, cur) => ({ ...acc, ...cur }), {});
|
|
89
|
+
|
|
90
|
+
return result.unalike;
|
|
91
|
+
},
|
|
92
|
+
unalike => Object.keys(unalike).length > 0,
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
export const mismatchedAnnotations = allPass([
|
|
96
|
+
pipe(nthArg(0), definesAnnotations),
|
|
97
|
+
pipe((binding, kubernetesObject) => metasMismatch(definedAnnotations(binding), carriedAnnotations(kubernetesObject))),
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
export const mismatchedLabels = allPass([
|
|
101
|
+
pipe(nthArg(0), definesLabels),
|
|
102
|
+
pipe((binding, kubernetesObject) => metasMismatch(definedLabels(binding), carriedLabels(kubernetesObject))),
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
export const mismatchedEvent = pipe(
|
|
106
|
+
(binding: Binding, request: AdmissionRequest): boolean =>
|
|
107
|
+
operationMatchesEvent(declaredOperation(request), definedEvent(binding)),
|
|
108
|
+
not,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
export const mismatchedGroup = allPass([
|
|
112
|
+
pipe(nthArg(0), definesGroup),
|
|
113
|
+
pipe((binding, request) => definedGroup(binding) !== declaredGroup(request)),
|
|
114
|
+
]);
|
|
115
|
+
|
|
116
|
+
export const mismatchedVersion = allPass([
|
|
117
|
+
pipe(nthArg(0), definesVersion),
|
|
118
|
+
pipe((binding, request) => definedVersion(binding) !== declaredVersion(request)),
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
export const mismatchedKind = allPass([
|
|
122
|
+
pipe(nthArg(0), definesKind),
|
|
123
|
+
pipe((binding, request) => definedKind(binding) !== declaredKind(request)),
|
|
124
|
+
]);
|
|
125
|
+
export const operationMatchesEvent = anyPass([
|
|
126
|
+
pipe(nthArg(1), equals(Event.ANY)),
|
|
127
|
+
pipe((operation: Operation, event: Event): boolean => operation.valueOf() === event.valueOf()),
|
|
128
|
+
pipe((operation: Operation, event: Event): boolean => (operation ? event.includes(operation) : false)),
|
|
129
|
+
]);
|