pepr 0.35.0 → 0.37.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 +3 -1
- package/dist/cli/init/templates.d.ts.map +1 -1
- package/dist/cli/init/utils.d.ts.map +1 -1
- package/dist/cli/init/walkthrough.d.ts +10 -3
- package/dist/cli/init/walkthrough.d.ts.map +1 -1
- package/dist/cli.js +253 -31
- package/dist/controller.js +138 -1
- package/dist/lib/adjudicators.d.ts +63 -0
- package/dist/lib/adjudicators.d.ts.map +1 -0
- package/dist/lib/adjudicators.test.d.ts +2 -0
- package/dist/lib/adjudicators.test.d.ts.map +1 -0
- package/dist/lib/assets/loader.d.ts.map +1 -1
- package/dist/lib/assets/pods.d.ts +1 -0
- package/dist/lib/assets/pods.d.ts.map +1 -1
- package/dist/lib/capability.d.ts +1 -0
- package/dist/lib/capability.d.ts.map +1 -1
- package/dist/lib/capability.test.d.ts +2 -0
- package/dist/lib/capability.test.d.ts.map +1 -0
- package/dist/lib/controller/index.d.ts.map +1 -1
- package/dist/lib/controller/store.d.ts +4 -0
- package/dist/lib/controller/store.d.ts.map +1 -1
- package/dist/lib/controller/store.test.d.ts +2 -0
- package/dist/lib/controller/store.test.d.ts.map +1 -0
- package/dist/lib/filter.d.ts +2 -3
- package/dist/lib/filter.d.ts.map +1 -1
- package/dist/lib/filter.test.d.ts +2 -1
- package/dist/lib/filter.test.d.ts.map +1 -1
- package/dist/lib/finalizer.d.ts +6 -0
- package/dist/lib/finalizer.d.ts.map +1 -0
- package/dist/lib/finalizer.test.d.ts +2 -0
- package/dist/lib/finalizer.test.d.ts.map +1 -0
- package/dist/lib/helpers.d.ts +2 -2
- package/dist/lib/helpers.d.ts.map +1 -1
- package/dist/lib/helpers.test.d.ts +1 -1
- package/dist/lib/helpers.test.d.ts.map +1 -1
- package/dist/lib/k8s.d.ts.map +1 -1
- package/dist/lib/logger.d.ts +1 -1
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/module.d.ts +2 -1
- package/dist/lib/module.d.ts.map +1 -1
- package/dist/lib/mutate-processor.d.ts +2 -1
- package/dist/lib/mutate-processor.d.ts.map +1 -1
- package/dist/lib/mutate-request.d.ts +1 -2
- package/dist/lib/mutate-request.d.ts.map +1 -1
- package/dist/lib/queue.d.ts +19 -3
- package/dist/lib/queue.d.ts.map +1 -1
- package/dist/lib/schedule.d.ts +1 -2
- package/dist/lib/schedule.d.ts.map +1 -1
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/types.d.ts +118 -6
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/validate-processor.d.ts +4 -2
- package/dist/lib/validate-processor.d.ts.map +1 -1
- package/dist/lib/validate-request.d.ts +1 -1
- package/dist/lib/validate-request.d.ts.map +1 -1
- package/dist/lib/watch-processor.d.ts +8 -6
- package/dist/lib/watch-processor.d.ts.map +1 -1
- package/dist/lib.js +467 -233
- package/dist/lib.js.map +4 -4
- package/dist/sdk/sdk.d.ts +5 -3
- package/dist/sdk/sdk.d.ts.map +1 -1
- package/package.json +13 -11
- package/src/cli/build.ts +3 -3
- package/src/cli/init/index.ts +20 -11
- package/src/cli/init/templates.ts +1 -1
- package/src/cli/init/utils.test.ts +11 -20
- package/src/cli/init/utils.ts +5 -0
- package/src/cli/init/walkthrough.test.ts +92 -11
- package/src/cli/init/walkthrough.ts +71 -16
- package/src/cli/monitor.ts +1 -1
- package/src/cli.ts +4 -2
- package/src/fixtures/data/create-pod.json +1 -1
- package/src/fixtures/data/delete-pod.json +1 -1
- package/src/lib/adjudicators.test.ts +1232 -0
- package/src/lib/adjudicators.ts +235 -0
- package/src/lib/assets/index.ts +1 -1
- package/src/lib/assets/loader.ts +1 -0
- package/src/lib/assets/webhooks.ts +1 -1
- package/src/lib/capability.test.ts +655 -0
- package/src/lib/capability.ts +112 -11
- package/src/lib/controller/index.ts +7 -4
- package/src/lib/controller/store.test.ts +131 -0
- package/src/lib/controller/store.ts +43 -5
- package/src/lib/filter.test.ts +279 -9
- package/src/lib/filter.ts +46 -98
- package/src/lib/finalizer.test.ts +236 -0
- package/src/lib/finalizer.ts +63 -0
- package/src/lib/helpers.test.ts +359 -65
- package/src/lib/helpers.ts +141 -95
- package/src/lib/k8s.ts +4 -0
- package/src/lib/module.ts +3 -3
- package/src/lib/mutate-processor.ts +5 -4
- package/src/lib/mutate-request.test.ts +1 -2
- package/src/lib/mutate-request.ts +1 -3
- package/src/lib/queue.test.ts +138 -44
- package/src/lib/queue.ts +48 -13
- package/src/lib/schedule.ts +1 -1
- package/src/lib/storage.ts +5 -6
- package/src/lib/types.ts +154 -5
- package/src/lib/validate-processor.ts +5 -2
- package/src/lib/validate-request.test.ts +1 -4
- package/src/lib/validate-request.ts +1 -1
- package/src/lib/watch-processor.test.ts +89 -124
- package/src/lib/watch-processor.ts +52 -35
- package/src/sdk/sdk.test.ts +46 -13
- package/src/sdk/sdk.ts +15 -6
package/src/lib/helpers.ts
CHANGED
|
@@ -6,6 +6,39 @@ import { K8s, KubernetesObject, kind } from "kubernetes-fluent-client";
|
|
|
6
6
|
import Log from "./logger";
|
|
7
7
|
import { Binding, CapabilityExport } from "./types";
|
|
8
8
|
import { sanitizeResourceName } from "../sdk/sdk";
|
|
9
|
+
import {
|
|
10
|
+
carriedAnnotations,
|
|
11
|
+
carriedLabels,
|
|
12
|
+
carriedName,
|
|
13
|
+
carriedNamespace,
|
|
14
|
+
carriesIgnoredNamespace,
|
|
15
|
+
definedAnnotations,
|
|
16
|
+
definedLabels,
|
|
17
|
+
definedName,
|
|
18
|
+
definedNameRegex,
|
|
19
|
+
definedNamespaces,
|
|
20
|
+
definedNamespaceRegexes,
|
|
21
|
+
misboundNamespace,
|
|
22
|
+
mismatchedAnnotations,
|
|
23
|
+
mismatchedDeletionTimestamp,
|
|
24
|
+
mismatchedLabels,
|
|
25
|
+
mismatchedName,
|
|
26
|
+
mismatchedNameRegex,
|
|
27
|
+
mismatchedNamespace,
|
|
28
|
+
mismatchedNamespaceRegex,
|
|
29
|
+
unbindableNamespaces,
|
|
30
|
+
uncarryableNamespace,
|
|
31
|
+
} from "./adjudicators";
|
|
32
|
+
|
|
33
|
+
export function matchesRegex(pattern: string, testString: string): boolean {
|
|
34
|
+
// edge-case
|
|
35
|
+
if (!pattern) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const regex = new RegExp(pattern);
|
|
40
|
+
return regex.test(testString);
|
|
41
|
+
}
|
|
9
42
|
|
|
10
43
|
export class ValidationError extends Error {}
|
|
11
44
|
|
|
@@ -35,36 +68,6 @@ type RBACMap = {
|
|
|
35
68
|
};
|
|
36
69
|
};
|
|
37
70
|
|
|
38
|
-
// check for overlap with labels and annotations between bindings and kubernetes objects
|
|
39
|
-
export function checkOverlap(bindingFilters: Record<string, string>, objectFilters: Record<string, string>): boolean {
|
|
40
|
-
// True if labels/annotations are empty
|
|
41
|
-
if (Object.keys(bindingFilters).length === 0) {
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
let matchCount = 0;
|
|
46
|
-
|
|
47
|
-
for (const key in bindingFilters) {
|
|
48
|
-
// object must have label/annotation
|
|
49
|
-
if (Object.prototype.hasOwnProperty.call(objectFilters, key)) {
|
|
50
|
-
const val1 = bindingFilters[key];
|
|
51
|
-
const val2 = objectFilters[key];
|
|
52
|
-
|
|
53
|
-
// If bindingFilter has empty value for this key, only need to ensure objectFilter has this key
|
|
54
|
-
if (val1 === "" && key in objectFilters) {
|
|
55
|
-
matchCount++;
|
|
56
|
-
}
|
|
57
|
-
// If bindingFilter has a value, it must match the value in objectFilter
|
|
58
|
-
else if (val1 !== "" && val1 === val2) {
|
|
59
|
-
matchCount++;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// For single-key objects in bindingFilter or matching all keys in multiple-keys scenario
|
|
65
|
-
return matchCount === Object.keys(bindingFilters).length;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
71
|
/**
|
|
69
72
|
* Decide to run callback after the event comes back from API Server
|
|
70
73
|
**/
|
|
@@ -72,71 +75,72 @@ export function filterNoMatchReason(
|
|
|
72
75
|
binding: Partial<Binding>,
|
|
73
76
|
obj: Partial<KubernetesObject>,
|
|
74
77
|
capabilityNamespaces: string[],
|
|
78
|
+
ignoredNamespaces?: string[],
|
|
75
79
|
): string {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
obj
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
binding
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
80
|
+
const prefix = "Ignoring Watch Callback:";
|
|
81
|
+
|
|
82
|
+
// prettier-ignore
|
|
83
|
+
return (
|
|
84
|
+
mismatchedDeletionTimestamp(binding, obj) ?
|
|
85
|
+
`${prefix} Binding defines deletionTimestamp but Object does not carry it.` :
|
|
86
|
+
|
|
87
|
+
mismatchedName(binding, obj) ?
|
|
88
|
+
`${prefix} Binding defines name '${definedName(binding)}' but Object carries '${carriedName(obj)}'.` :
|
|
89
|
+
|
|
90
|
+
misboundNamespace(binding) ?
|
|
91
|
+
`${prefix} Cannot use namespace filter on a namespace object.` :
|
|
92
|
+
|
|
93
|
+
mismatchedLabels(binding, obj) ?
|
|
94
|
+
(
|
|
95
|
+
`${prefix} Binding defines labels '${JSON.stringify(definedLabels(binding))}' ` +
|
|
96
|
+
`but Object carries '${JSON.stringify(carriedLabels(obj))}'.`
|
|
97
|
+
) :
|
|
98
|
+
|
|
99
|
+
mismatchedAnnotations(binding, obj) ?
|
|
100
|
+
(
|
|
101
|
+
`${prefix} Binding defines annotations '${JSON.stringify(definedAnnotations(binding))}' ` +
|
|
102
|
+
`but Object carries '${JSON.stringify(carriedAnnotations(obj))}'.`
|
|
103
|
+
) :
|
|
104
|
+
|
|
105
|
+
uncarryableNamespace(capabilityNamespaces, obj) ?
|
|
106
|
+
(
|
|
107
|
+
`${prefix} Object carries namespace '${carriedNamespace(obj)}' ` +
|
|
108
|
+
`but namespaces allowed by Capability are '${JSON.stringify(capabilityNamespaces)}'.`
|
|
109
|
+
) :
|
|
110
|
+
|
|
111
|
+
unbindableNamespaces(capabilityNamespaces, binding) ?
|
|
112
|
+
(
|
|
113
|
+
`${prefix} Binding defines namespaces ${JSON.stringify(definedNamespaces(binding))} ` +
|
|
114
|
+
`but namespaces allowed by Capability are '${JSON.stringify(capabilityNamespaces)}'.`
|
|
115
|
+
) :
|
|
116
|
+
|
|
117
|
+
mismatchedNamespace(binding, obj) ?
|
|
118
|
+
(
|
|
119
|
+
`${prefix} Binding defines namespaces '${JSON.stringify(definedNamespaces(binding))}' ` +
|
|
120
|
+
`but Object carries '${carriedNamespace(obj)}'.`
|
|
121
|
+
) :
|
|
122
|
+
|
|
123
|
+
mismatchedNamespaceRegex(binding, obj) ?
|
|
124
|
+
(
|
|
125
|
+
`${prefix} Binding defines namespace regexes ` +
|
|
126
|
+
`'${JSON.stringify(definedNamespaceRegexes(binding))}' ` +
|
|
127
|
+
`but Object carries '${carriedNamespace(obj)}'.`
|
|
128
|
+
) :
|
|
129
|
+
|
|
130
|
+
mismatchedNameRegex(binding, obj) ?
|
|
131
|
+
(
|
|
132
|
+
`${prefix} Binding defines name regex '${definedNameRegex(binding)}' ` +
|
|
133
|
+
`but Object carries '${carriedName(obj)}'.`
|
|
134
|
+
) :
|
|
135
|
+
|
|
136
|
+
carriesIgnoredNamespace(ignoredNamespaces, obj) ?
|
|
137
|
+
(
|
|
138
|
+
`${prefix} Object carries namespace '${carriedNamespace(obj)}' ` +
|
|
139
|
+
`but ignored namespaces include '${JSON.stringify(ignoredNamespaces)}'.`
|
|
140
|
+
) :
|
|
141
|
+
|
|
142
|
+
""
|
|
143
|
+
);
|
|
140
144
|
}
|
|
141
145
|
|
|
142
146
|
export function addVerbIfNotExists(verbs: string[], verb: string) {
|
|
@@ -239,7 +243,8 @@ export function generateWatchNamespaceError(
|
|
|
239
243
|
// namespaceComplianceValidator ensures that capability bindinds respect ignored and capability namespaces
|
|
240
244
|
export function namespaceComplianceValidator(capability: CapabilityExport, ignoredNamespaces?: string[]) {
|
|
241
245
|
const { namespaces: capabilityNamespaces, bindings, name } = capability;
|
|
242
|
-
const bindingNamespaces = bindings.flatMap(binding => binding.filters.namespaces);
|
|
246
|
+
const bindingNamespaces = bindings.flatMap((binding: Binding) => binding.filters.namespaces);
|
|
247
|
+
const bindingRegexNamespaces = bindings.flatMap((binding: Binding) => binding.filters.regexNamespaces || []);
|
|
243
248
|
|
|
244
249
|
const namespaceError = generateWatchNamespaceError(
|
|
245
250
|
ignoredNamespaces ? ignoredNamespaces : [],
|
|
@@ -251,6 +256,47 @@ export function namespaceComplianceValidator(capability: CapabilityExport, ignor
|
|
|
251
256
|
`Error in ${name} capability. A binding violates namespace rules. Please check ignoredNamespaces and capability namespaces: ${namespaceError}`,
|
|
252
257
|
);
|
|
253
258
|
}
|
|
259
|
+
|
|
260
|
+
// Ensure that each regexNamespace matches a capabilityNamespace
|
|
261
|
+
|
|
262
|
+
if (
|
|
263
|
+
bindingRegexNamespaces &&
|
|
264
|
+
bindingRegexNamespaces.length > 0 &&
|
|
265
|
+
capabilityNamespaces &&
|
|
266
|
+
capabilityNamespaces.length > 0
|
|
267
|
+
) {
|
|
268
|
+
for (const regexNamespace of bindingRegexNamespaces) {
|
|
269
|
+
let matches = false;
|
|
270
|
+
for (const capabilityNamespace of capabilityNamespaces) {
|
|
271
|
+
if (regexNamespace !== "" && matchesRegex(regexNamespace, capabilityNamespace)) {
|
|
272
|
+
matches = true;
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (!matches) {
|
|
277
|
+
throw new Error(
|
|
278
|
+
`Ignoring Watch Callback: Object namespace does not match any capability namespace with regex ${regexNamespace}.`,
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
// ensure regexNamespaces do not match ignored ns
|
|
284
|
+
if (
|
|
285
|
+
bindingRegexNamespaces &&
|
|
286
|
+
bindingRegexNamespaces.length > 0 &&
|
|
287
|
+
ignoredNamespaces &&
|
|
288
|
+
ignoredNamespaces.length > 0
|
|
289
|
+
) {
|
|
290
|
+
for (const regexNamespace of bindingRegexNamespaces) {
|
|
291
|
+
for (const ignoredNS of ignoredNamespaces) {
|
|
292
|
+
if (matchesRegex(regexNamespace, ignoredNS)) {
|
|
293
|
+
throw new Error(
|
|
294
|
+
`Ignoring Watch Callback: Regex namespace: ${regexNamespace}, is an ignored namespace: ${ignoredNS}.`,
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
254
300
|
}
|
|
255
301
|
|
|
256
302
|
// check to see if all replicas are ready for all deployments in the pepr-system namespace
|
package/src/lib/k8s.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { GenericKind, GroupVersionKind, KubernetesObject, RegisterKind } from "kubernetes-fluent-client";
|
|
5
5
|
|
|
6
|
+
// DEPRECATED: Use Operation in types.ts instead
|
|
6
7
|
export enum Operation {
|
|
7
8
|
CREATE = "CREATE",
|
|
8
9
|
UPDATE = "UPDATE",
|
|
@@ -27,6 +28,7 @@ export const peprStoreGVK = {
|
|
|
27
28
|
|
|
28
29
|
RegisterKind(PeprStore, peprStoreGVK);
|
|
29
30
|
|
|
31
|
+
// DEPRECATED: Use Operation in types.ts instead
|
|
30
32
|
/**
|
|
31
33
|
* GroupVersionResource unambiguously identifies a resource. It doesn't anonymously include GroupVersion
|
|
32
34
|
* to avoid automatic coercion. It doesn't use a GroupVersion to avoid custom marshalling
|
|
@@ -37,6 +39,8 @@ export interface GroupVersionResource {
|
|
|
37
39
|
readonly resource: string;
|
|
38
40
|
}
|
|
39
41
|
|
|
42
|
+
// DEPRECATED: Use Operation in types.ts instead
|
|
43
|
+
|
|
40
44
|
/**
|
|
41
45
|
* A Kubernetes admission request to be processed by a capability.
|
|
42
46
|
*/
|
package/src/lib/module.ts
CHANGED
|
@@ -4,8 +4,8 @@ import { clone } from "ramda";
|
|
|
4
4
|
import { Capability } from "./capability";
|
|
5
5
|
import { Controller } from "./controller";
|
|
6
6
|
import { ValidateError } from "./errors";
|
|
7
|
-
import {
|
|
8
|
-
import { CapabilityExport } from "./types";
|
|
7
|
+
import { MutateResponse, ValidateResponse, WebhookIgnore } from "./k8s";
|
|
8
|
+
import { CapabilityExport, AdmissionRequest } from "./types";
|
|
9
9
|
import { setupWatch } from "./watch-processor";
|
|
10
10
|
import { Log } from "../lib";
|
|
11
11
|
|
|
@@ -108,7 +108,7 @@ export class PeprModule {
|
|
|
108
108
|
// Wait for the controller to be ready before setting up watches
|
|
109
109
|
if (isWatchMode() || isDevMode()) {
|
|
110
110
|
try {
|
|
111
|
-
setupWatch(capabilities);
|
|
111
|
+
setupWatch(capabilities, pepr?.alwaysIgnore?.namespaces);
|
|
112
112
|
} catch (e) {
|
|
113
113
|
Log.error(e, "Error setting up watch");
|
|
114
114
|
process.exit(1);
|
|
@@ -7,7 +7,8 @@ import { kind } from "kubernetes-fluent-client";
|
|
|
7
7
|
import { Capability } from "./capability";
|
|
8
8
|
import { Errors } from "./errors";
|
|
9
9
|
import { shouldSkipRequest } from "./filter";
|
|
10
|
-
import { MutateResponse
|
|
10
|
+
import { MutateResponse } from "./k8s";
|
|
11
|
+
import { AdmissionRequest } from "./types";
|
|
11
12
|
import Log from "./logger";
|
|
12
13
|
import { ModuleConfig } from "./module";
|
|
13
14
|
import { PeprMutateRequest } from "./mutate-request";
|
|
@@ -42,7 +43,6 @@ export async function mutateProcessor(
|
|
|
42
43
|
|
|
43
44
|
for (const { name, bindings, namespaces } of capabilities) {
|
|
44
45
|
const actionMetadata = { ...reqMetadata, name };
|
|
45
|
-
|
|
46
46
|
for (const action of bindings) {
|
|
47
47
|
// Skip this action if it's not a mutate action
|
|
48
48
|
if (!action.mutateCallback) {
|
|
@@ -50,13 +50,12 @@ export async function mutateProcessor(
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
// Continue to the next action without doing anything if this one should be skipped
|
|
53
|
-
if (shouldSkipRequest(action, req, namespaces)) {
|
|
53
|
+
if (shouldSkipRequest(action, req, namespaces, config?.alwaysIgnore?.namespaces)) {
|
|
54
54
|
continue;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
const label = action.mutateCallback.name;
|
|
58
58
|
Log.info(actionMetadata, `Processing mutation action (${label})`);
|
|
59
|
-
|
|
60
59
|
matchedAction = true;
|
|
61
60
|
|
|
62
61
|
// Add annotations to the request to indicate that the capability started processing
|
|
@@ -79,6 +78,7 @@ export async function mutateProcessor(
|
|
|
79
78
|
// Run the action
|
|
80
79
|
await action.mutateCallback(wrapped);
|
|
81
80
|
|
|
81
|
+
// Log on success
|
|
82
82
|
Log.info(actionMetadata, `Mutation action succeeded (${label})`);
|
|
83
83
|
|
|
84
84
|
// Add annotations to the request to indicate that the capability succeeded
|
|
@@ -99,6 +99,7 @@ export async function mutateProcessor(
|
|
|
99
99
|
errorMessage = "An error occurred with the mutate action.";
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
// Log on failure
|
|
102
103
|
Log.error(actionMetadata, `Action failed: ${errorMessage}`);
|
|
103
104
|
response.warnings.push(`Action failed: ${errorMessage}`);
|
|
104
105
|
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { beforeEach, describe, expect, it } from "@jest/globals";
|
|
5
5
|
import { KubernetesObject } from "kubernetes-fluent-client";
|
|
6
|
-
|
|
7
|
-
import { Operation, AdmissionRequest } from "./k8s";
|
|
6
|
+
import { Operation, AdmissionRequest } from "./types";
|
|
8
7
|
import { PeprMutateRequest } from "./mutate-request";
|
|
9
8
|
|
|
10
9
|
describe("PeprMutateRequest", () => {
|
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { KubernetesObject } from "kubernetes-fluent-client";
|
|
5
5
|
import { clone, mergeDeepRight } from "ramda";
|
|
6
|
-
|
|
7
|
-
import { AdmissionRequest, Operation } from "./k8s";
|
|
8
|
-
import { DeepPartial } from "./types";
|
|
6
|
+
import { Operation, AdmissionRequest, DeepPartial } from "./types";
|
|
9
7
|
|
|
10
8
|
/**
|
|
11
9
|
* The RequestWrapper class provides methods to modify Kubernetes objects in the context
|
package/src/lib/queue.test.ts
CHANGED
|
@@ -1,58 +1,152 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { KubernetesObject } from "@kubernetes/client-node";
|
|
1
|
+
import { afterEach, describe, expect, jest, it } from "@jest/globals";
|
|
3
2
|
import { WatchPhase } from "kubernetes-fluent-client/dist/fluent/types";
|
|
4
3
|
import { Queue } from "./queue";
|
|
5
4
|
|
|
5
|
+
import Log from "./logger";
|
|
6
|
+
jest.mock("./logger");
|
|
7
|
+
|
|
6
8
|
describe("Queue", () => {
|
|
7
|
-
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
jest.resetAllMocks();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("is uniquely identifiable", () => {
|
|
14
|
+
const name = "kind/namespace";
|
|
15
|
+
const queue = new Queue(name);
|
|
16
|
+
|
|
17
|
+
const label = queue.label();
|
|
18
|
+
|
|
19
|
+
expect(label).toEqual(
|
|
20
|
+
expect.objectContaining({
|
|
21
|
+
// given name of queue
|
|
22
|
+
name,
|
|
23
|
+
|
|
24
|
+
// unique, generated value (to disambiguate similarly-named queues)
|
|
25
|
+
// <epoch timestamp (ms)>-<4 char hex>
|
|
26
|
+
uid: expect.stringMatching(/[0-9]{13}-[0-9a-f]{4}/),
|
|
27
|
+
}),
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("exposes runtime stats", async () => {
|
|
32
|
+
const name = "kind/namespace";
|
|
33
|
+
const queue = new Queue(name);
|
|
34
|
+
|
|
35
|
+
expect(queue.stats()).toEqual(
|
|
36
|
+
expect.objectContaining({
|
|
37
|
+
queue: queue.label(),
|
|
38
|
+
stats: { length: 0 },
|
|
39
|
+
}),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const kubeObj = { metadata: { name: "test-nm", namespace: "test-ns" } };
|
|
43
|
+
const watchCb = () =>
|
|
44
|
+
new Promise<void>(res => {
|
|
45
|
+
setTimeout(res, 100);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
await Promise.all([
|
|
49
|
+
queue.enqueue(kubeObj, WatchPhase.Added, watchCb),
|
|
50
|
+
queue.enqueue(kubeObj, WatchPhase.Added, watchCb),
|
|
51
|
+
queue.enqueue(kubeObj, WatchPhase.Added, watchCb),
|
|
52
|
+
queue.enqueue(kubeObj, WatchPhase.Added, watchCb),
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
const logDebug = Log.debug as jest.Mock;
|
|
56
|
+
const stats = logDebug.mock.calls
|
|
57
|
+
.flat()
|
|
58
|
+
.map(m => JSON.stringify(m))
|
|
59
|
+
.filter(m => m.includes('"stats":'));
|
|
8
60
|
|
|
9
|
-
|
|
10
|
-
|
|
61
|
+
[
|
|
62
|
+
'"length":1', // 1st entry runs near-immediately, so queue won't fill
|
|
63
|
+
'"length":1', // afterward, queue fills & unfills as callbacks process
|
|
64
|
+
'"length":2',
|
|
65
|
+
'"length":3',
|
|
66
|
+
'"length":3',
|
|
67
|
+
'"length":2',
|
|
68
|
+
'"length":1',
|
|
69
|
+
'"length":0',
|
|
70
|
+
].map((exp, idx) => {
|
|
71
|
+
expect(stats[idx]).toEqual(expect.stringContaining(exp));
|
|
72
|
+
});
|
|
11
73
|
});
|
|
12
74
|
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
75
|
+
it("resolves when an enqueued event dequeues without error", async () => {
|
|
76
|
+
const name = "kind/namespace";
|
|
77
|
+
const queue = new Queue(name);
|
|
78
|
+
|
|
79
|
+
const kubeObj = { metadata: { name: "test-nm", namespace: "test-ns" } };
|
|
80
|
+
const watchCb = () =>
|
|
81
|
+
new Promise<void>(res => {
|
|
82
|
+
setTimeout(res, 10);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const promise = queue.enqueue(kubeObj, WatchPhase.Added, watchCb);
|
|
18
86
|
expect(promise).toBeInstanceOf(Promise);
|
|
19
|
-
|
|
87
|
+
|
|
88
|
+
await expect(promise).resolves.not.toThrow();
|
|
20
89
|
});
|
|
21
90
|
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
91
|
+
it("rejects when an enqueued event dequeues with error", async () => {
|
|
92
|
+
const name = "kind/namespace";
|
|
93
|
+
const queue = new Queue(name);
|
|
94
|
+
|
|
95
|
+
const kubeObj = { metadata: { name: "test-nm", namespace: "test-ns" } };
|
|
96
|
+
const watchCb = () =>
|
|
97
|
+
new Promise<void>((_, reject) => {
|
|
98
|
+
setTimeout(() => {
|
|
99
|
+
reject("oof");
|
|
100
|
+
}, 10);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const promise = queue.enqueue(kubeObj, WatchPhase.Added, watchCb);
|
|
104
|
+
expect(promise).toBeInstanceOf(Promise);
|
|
105
|
+
|
|
106
|
+
await expect(promise).rejects.toBe("oof");
|
|
37
107
|
});
|
|
38
108
|
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
109
|
+
it("processes events in FIFO order", async () => {
|
|
110
|
+
const name = "kind/namespace";
|
|
111
|
+
const queue = new Queue(name);
|
|
112
|
+
|
|
113
|
+
const kubeObj = { metadata: { name: "test-nm", namespace: "test-ns" } };
|
|
114
|
+
const watchA = () =>
|
|
115
|
+
new Promise<void>(resolve => {
|
|
116
|
+
setTimeout(() => {
|
|
117
|
+
Log.info("watchA");
|
|
118
|
+
resolve();
|
|
119
|
+
}, 15);
|
|
120
|
+
});
|
|
121
|
+
const watchB = () =>
|
|
122
|
+
new Promise<void>(resolve => {
|
|
123
|
+
setTimeout(() => {
|
|
124
|
+
Log.info("watchB");
|
|
125
|
+
resolve();
|
|
126
|
+
}, 10);
|
|
127
|
+
});
|
|
128
|
+
const watchC = () =>
|
|
129
|
+
new Promise<void>(resolve => {
|
|
130
|
+
setTimeout(() => {
|
|
131
|
+
Log.info("watchC");
|
|
132
|
+
resolve();
|
|
133
|
+
}, 5);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
await Promise.all([
|
|
137
|
+
queue.enqueue(kubeObj, WatchPhase.Added, watchA),
|
|
138
|
+
queue.enqueue(kubeObj, WatchPhase.Added, watchB),
|
|
139
|
+
queue.enqueue(kubeObj, WatchPhase.Added, watchC),
|
|
140
|
+
]);
|
|
141
|
+
|
|
142
|
+
const logInfo = Log.info as jest.Mock;
|
|
143
|
+
const calls = logInfo.mock.calls
|
|
144
|
+
.flat()
|
|
145
|
+
.map(m => JSON.stringify(m))
|
|
146
|
+
.filter(m => /"watch[ABC]"/.test(m));
|
|
147
|
+
|
|
148
|
+
['"watchA"', '"watchB"', '"watchC"'].map((exp, idx) => {
|
|
149
|
+
expect(calls[idx]).toEqual(expect.stringContaining(exp));
|
|
150
|
+
});
|
|
57
151
|
});
|
|
58
152
|
});
|