pepr 0.32.7 → 0.34.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/LICENSE +1 -1
- package/README.md +2 -1
- package/dist/cli/banner.d.ts +2 -0
- package/dist/cli/banner.d.ts.map +1 -0
- package/dist/cli/build.d.ts +19 -0
- package/dist/cli/build.d.ts.map +1 -0
- package/dist/cli/deploy.d.ts +3 -0
- package/dist/cli/deploy.d.ts.map +1 -0
- package/dist/cli/dev.d.ts +3 -0
- package/dist/cli/dev.d.ts.map +1 -0
- package/dist/cli/format.d.ts +9 -0
- package/dist/cli/format.d.ts.map +1 -0
- package/dist/cli/init/index.d.ts +3 -0
- package/dist/cli/init/index.d.ts.map +1 -0
- package/dist/cli/init/templates.d.ts +196 -0
- package/dist/cli/init/templates.d.ts.map +1 -0
- package/dist/cli/init/utils.d.ts +21 -0
- package/dist/cli/init/utils.d.ts.map +1 -0
- package/dist/cli/init/utils.test.d.ts +2 -0
- package/dist/cli/init/utils.test.d.ts.map +1 -0
- package/dist/cli/init/walkthrough.d.ts +8 -0
- package/dist/cli/init/walkthrough.d.ts.map +1 -0
- package/dist/cli/init/walkthrough.test.d.ts +2 -0
- package/dist/cli/init/walkthrough.test.d.ts.map +1 -0
- package/dist/cli/kfc.d.ts +3 -0
- package/dist/cli/kfc.d.ts.map +1 -0
- package/dist/cli/monitor.d.ts +3 -0
- package/dist/cli/monitor.d.ts.map +1 -0
- package/dist/cli/root.d.ts +5 -0
- package/dist/cli/root.d.ts.map +1 -0
- package/dist/cli/update.d.ts +3 -0
- package/dist/cli/update.d.ts.map +1 -0
- package/dist/cli/uuid.d.ts +3 -0
- package/dist/cli/uuid.d.ts.map +1 -0
- package/dist/cli.js +66 -25
- package/dist/controller.js +1 -1
- package/dist/fixtures/loader.d.ts +5 -0
- package/dist/fixtures/loader.d.ts.map +1 -0
- package/dist/lib/assets/helm.d.ts +1 -0
- package/dist/lib/assets/helm.d.ts.map +1 -1
- package/dist/lib/assets/helm.test.d.ts +2 -0
- package/dist/lib/assets/helm.test.d.ts.map +1 -0
- package/dist/lib/assets/index.d.ts.map +1 -1
- package/dist/lib/assets/pods.d.ts +3 -0
- package/dist/lib/assets/pods.d.ts.map +1 -1
- package/dist/lib/assets/pods.test.d.ts +2 -0
- package/dist/lib/assets/pods.test.d.ts.map +1 -0
- package/dist/lib/assets/yaml.d.ts.map +1 -1
- package/dist/lib/controller/store.d.ts.map +1 -1
- package/dist/lib/errors.test.d.ts +2 -0
- package/dist/lib/errors.test.d.ts.map +1 -0
- package/dist/lib/filter.test.d.ts +2 -0
- package/dist/lib/filter.test.d.ts.map +1 -0
- package/dist/lib/helpers.test.d.ts +2 -0
- package/dist/lib/helpers.test.d.ts.map +1 -0
- package/dist/lib/included-files.test.d.ts +2 -0
- package/dist/lib/included-files.test.d.ts.map +1 -0
- package/dist/lib/logger.test.d.ts +2 -0
- package/dist/lib/logger.test.d.ts.map +1 -0
- package/dist/lib/metrics.d.ts +18 -0
- package/dist/lib/metrics.d.ts.map +1 -1
- package/dist/lib/metrics.test.d.ts +2 -0
- package/dist/lib/metrics.test.d.ts.map +1 -0
- package/dist/lib/module.test.d.ts +2 -0
- package/dist/lib/module.test.d.ts.map +1 -0
- package/dist/lib/mutate-request.test.d.ts +2 -0
- package/dist/lib/mutate-request.test.d.ts.map +1 -0
- package/dist/lib/queue.test.d.ts +2 -0
- package/dist/lib/queue.test.d.ts.map +1 -0
- package/dist/lib/schedule.test.d.ts +15 -0
- package/dist/lib/schedule.test.d.ts.map +1 -0
- package/dist/lib/storage.d.ts +2 -0
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/storage.test.d.ts +2 -0
- package/dist/lib/storage.test.d.ts.map +1 -0
- package/dist/lib/tls.test.d.ts +2 -0
- package/dist/lib/tls.test.d.ts.map +1 -0
- package/dist/lib/utils.test.d.ts +2 -0
- package/dist/lib/utils.test.d.ts.map +1 -0
- package/dist/lib/validate-request.test.d.ts +2 -0
- package/dist/lib/validate-request.test.d.ts.map +1 -0
- package/dist/lib/watch-processor.d.ts.map +1 -1
- package/dist/lib/watch-processor.test.d.ts +2 -0
- package/dist/lib/watch-processor.test.d.ts.map +1 -0
- package/dist/lib.js +142 -20
- package/dist/lib.js.map +3 -3
- package/dist/sdk/sdk.test.d.ts +2 -0
- package/dist/sdk/sdk.test.d.ts.map +1 -0
- package/package.json +26 -17
- package/src/cli/banner.ts +63 -0
- package/src/cli/build.ts +370 -0
- package/src/cli/deploy.ts +105 -0
- package/src/cli/dev.ts +118 -0
- package/src/cli/format.ts +83 -0
- package/src/cli/init/index.ts +99 -0
- package/src/cli/init/templates.ts +124 -0
- package/src/cli/init/utils.test.ts +28 -0
- package/src/cli/init/utils.ts +55 -0
- package/src/cli/init/walkthrough.test.ts +21 -0
- package/src/cli/init/walkthrough.ts +96 -0
- package/src/cli/kfc.ts +45 -0
- package/src/cli/monitor.ts +101 -0
- package/src/cli/root.ts +12 -0
- package/src/cli/update.ts +95 -0
- package/src/cli/uuid.ts +44 -0
- package/src/fixtures/data/create-pod.json +271 -0
- package/src/fixtures/data/delete-pod.json +271 -0
- package/src/fixtures/loader.ts +18 -0
- package/src/lib/.prettierrc +14 -0
- package/src/lib/assets/helm.test.ts +64 -0
- package/src/lib/assets/helm.ts +35 -0
- package/src/lib/assets/index.ts +5 -1
- package/src/lib/assets/pods.test.ts +553 -0
- package/src/lib/assets/pods.ts +14 -6
- package/src/lib/assets/yaml.ts +15 -18
- package/src/lib/controller/index.ts +2 -2
- package/src/lib/controller/store.ts +88 -2
- package/src/lib/errors.test.ts +85 -0
- package/src/lib/filter.test.ts +384 -0
- package/src/lib/helpers.test.ts +1192 -0
- package/src/lib/included-files.test.ts +22 -0
- package/src/lib/logger.test.ts +18 -0
- package/src/lib/metrics.test.ts +132 -0
- package/src/lib/metrics.ts +68 -6
- package/src/lib/module.test.ts +126 -0
- package/src/lib/mutate-request.test.ts +188 -0
- package/src/lib/queue.test.ts +58 -0
- package/src/lib/schedule.test.ts +217 -0
- package/src/lib/storage.test.ts +203 -0
- package/src/lib/storage.ts +22 -9
- package/src/lib/tls.test.ts +18 -0
- package/src/lib/utils.test.ts +69 -0
- package/src/lib/validate-request.test.ts +124 -0
- package/src/lib/watch-processor.test.ts +322 -0
- package/src/lib/watch-processor.ts +20 -4
- package/src/sdk/sdk.test.ts +243 -0
- package/src/templates/.eslintrc.json +6 -0
- package/.prettierignore +0 -1
- package/CODE_OF_CONDUCT.md +0 -133
- package/SECURITY.md +0 -18
- package/SUPPORT.md +0 -16
- package/codecov.yaml +0 -19
- package/commitlint.config.js +0 -1
package/src/lib/assets/yaml.ts
CHANGED
|
@@ -4,13 +4,12 @@
|
|
|
4
4
|
import { dumpYaml } from "@kubernetes/client-node";
|
|
5
5
|
import crypto from "crypto";
|
|
6
6
|
import { promises as fs } from "fs";
|
|
7
|
-
|
|
8
7
|
import { Assets } from ".";
|
|
9
8
|
import { apiTokenSecret, service, tlsSecret, watcherService } from "./networking";
|
|
10
9
|
import { deployment, moduleSecret, namespace, watcher } from "./pods";
|
|
11
10
|
import { clusterRole, clusterRoleBinding, serviceAccount, storeRole, storeRoleBinding } from "./rbac";
|
|
12
11
|
import { webhookConfig } from "./webhooks";
|
|
13
|
-
|
|
12
|
+
import { genEnv } from "./pods";
|
|
14
13
|
// Helm Chart overrides file (values.yaml) generated from assets
|
|
15
14
|
export async function overridesFile({ hash, name, image, config, apiToken }: Assets, path: string) {
|
|
16
15
|
const overrides = {
|
|
@@ -29,11 +28,8 @@ export async function overridesFile({ hash, name, image, config, apiToken }: Ass
|
|
|
29
28
|
terminationGracePeriodSeconds: 5,
|
|
30
29
|
failurePolicy: config.onError === "reject" ? "Fail" : "Ignore",
|
|
31
30
|
webhookTimeout: config.webhookTimeout,
|
|
32
|
-
env:
|
|
33
|
-
|
|
34
|
-
{ name: "PEPR_PRETTY_LOG", value: "false" },
|
|
35
|
-
{ name: "LOG_LEVEL", value: "info" },
|
|
36
|
-
],
|
|
31
|
+
env: genEnv(config, false, true),
|
|
32
|
+
envFrom: [],
|
|
37
33
|
image,
|
|
38
34
|
annotations: {
|
|
39
35
|
"pepr.dev/description": `${config.description}` || "",
|
|
@@ -74,14 +70,16 @@ export async function overridesFile({ hash, name, image, config, apiToken }: Ass
|
|
|
74
70
|
extraVolumeMounts: [],
|
|
75
71
|
extraVolumes: [],
|
|
76
72
|
affinity: {},
|
|
73
|
+
serviceMonitor: {
|
|
74
|
+
enabled: false,
|
|
75
|
+
labels: {},
|
|
76
|
+
annotations: {},
|
|
77
|
+
},
|
|
77
78
|
},
|
|
78
79
|
watcher: {
|
|
79
80
|
terminationGracePeriodSeconds: 5,
|
|
80
|
-
env:
|
|
81
|
-
|
|
82
|
-
{ name: "PEPR_PRETTY_LOG", value: "false" },
|
|
83
|
-
{ name: "LOG_LEVEL", value: "info" },
|
|
84
|
-
],
|
|
81
|
+
env: genEnv(config, true, true),
|
|
82
|
+
envFrom: [],
|
|
85
83
|
image,
|
|
86
84
|
annotations: {
|
|
87
85
|
"pepr.dev/description": `${config.description}` || "",
|
|
@@ -122,14 +120,13 @@ export async function overridesFile({ hash, name, image, config, apiToken }: Ass
|
|
|
122
120
|
extraVolumes: [],
|
|
123
121
|
affinity: {},
|
|
124
122
|
podAnnotations: {},
|
|
123
|
+
serviceMonitor: {
|
|
124
|
+
enabled: false,
|
|
125
|
+
labels: {},
|
|
126
|
+
annotations: {},
|
|
127
|
+
},
|
|
125
128
|
},
|
|
126
129
|
};
|
|
127
|
-
if (process.env.PEPR_MODE === "dev") {
|
|
128
|
-
overrides.admission.env.push({ name: "ZARF_VAR", value: "###ZARF_VAR_THING###" });
|
|
129
|
-
overrides.watcher.env.push({ name: "ZARF_VAR", value: "###ZARF_VAR_THING###" });
|
|
130
|
-
overrides.admission.env.push({ name: "MY_CUSTOM_VAR", value: "example-value" });
|
|
131
|
-
overrides.watcher.env.push({ name: "MY_CUSTOM_VAR", value: "example-value" });
|
|
132
|
-
}
|
|
133
130
|
|
|
134
131
|
await fs.writeFile(path, dumpYaml(overrides, { noRefs: true, forceQuotes: true }));
|
|
135
132
|
}
|
|
@@ -8,7 +8,7 @@ import https from "https";
|
|
|
8
8
|
import { Capability } from "../capability";
|
|
9
9
|
import { MutateResponse, AdmissionRequest, ValidateResponse } from "../k8s";
|
|
10
10
|
import Log from "../logger";
|
|
11
|
-
import { MetricsCollector } from "../metrics";
|
|
11
|
+
import { metricsCollector, MetricsCollector } from "../metrics";
|
|
12
12
|
import { ModuleConfig, isWatchMode } from "../module";
|
|
13
13
|
import { mutateProcessor } from "../mutate-processor";
|
|
14
14
|
import { validateProcessor } from "../validate-processor";
|
|
@@ -20,7 +20,7 @@ export class Controller {
|
|
|
20
20
|
#running = false;
|
|
21
21
|
|
|
22
22
|
// Metrics collector
|
|
23
|
-
#metricsCollector =
|
|
23
|
+
#metricsCollector = metricsCollector;
|
|
24
24
|
|
|
25
25
|
// The token used to authenticate requests
|
|
26
26
|
#token = "";
|
|
@@ -61,8 +61,8 @@ export class PeprControllerStore {
|
|
|
61
61
|
K8s(PeprStore)
|
|
62
62
|
.InNamespace(namespace)
|
|
63
63
|
.Get(this.#name)
|
|
64
|
-
// If the get succeeds, setup the watch
|
|
65
|
-
.then(this.#
|
|
64
|
+
// If the get succeeds, migrate and setup the watch
|
|
65
|
+
.then(async (store: PeprStore) => await this.#migrateAndSetupWatch(store))
|
|
66
66
|
// Otherwise, create the resource
|
|
67
67
|
.catch(this.#createStoreResource),
|
|
68
68
|
Math.random() * 3000,
|
|
@@ -74,6 +74,91 @@ export class PeprControllerStore {
|
|
|
74
74
|
watcher.start().catch(e => Log.error(e, "Error starting Pepr store watch"));
|
|
75
75
|
};
|
|
76
76
|
|
|
77
|
+
#migrateAndSetupWatch = async (store: PeprStore) => {
|
|
78
|
+
Log.debug(store, "Pepr Store migration");
|
|
79
|
+
const data: DataStore = store.data || {};
|
|
80
|
+
const migrateCache: Record<string, Operation> = {};
|
|
81
|
+
|
|
82
|
+
// Send the cached updates to the cluster
|
|
83
|
+
const flushCache = async () => {
|
|
84
|
+
const indexes = Object.keys(migrateCache);
|
|
85
|
+
const payload = Object.values(migrateCache);
|
|
86
|
+
|
|
87
|
+
// Loop over each key in the cache and delete it to avoid collisions with other sender calls
|
|
88
|
+
for (const idx of indexes) {
|
|
89
|
+
delete migrateCache[idx];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
// Send the patch to the cluster
|
|
94
|
+
await K8s(PeprStore, { namespace, name: this.#name }).Patch(payload);
|
|
95
|
+
} catch (err) {
|
|
96
|
+
Log.error(err, "Pepr store update failure");
|
|
97
|
+
|
|
98
|
+
if (err.status === 422) {
|
|
99
|
+
Object.keys(migrateCache).forEach(key => delete migrateCache[key]);
|
|
100
|
+
} else {
|
|
101
|
+
// On failure to update, re-add the operations to the cache to be retried
|
|
102
|
+
for (const idx of indexes) {
|
|
103
|
+
migrateCache[idx] = payload[Number(idx)];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const fillCache = (name: string, op: DataOp, key: string[], val?: string) => {
|
|
110
|
+
if (op === "add") {
|
|
111
|
+
// adjust the path for the capability
|
|
112
|
+
const path = `/data/${name}-v2-${key}`;
|
|
113
|
+
const value = val || "";
|
|
114
|
+
const cacheIdx = [op, path, value].join(":");
|
|
115
|
+
|
|
116
|
+
// Add the operation to the cache
|
|
117
|
+
migrateCache[cacheIdx] = { op, path, value };
|
|
118
|
+
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (op === "remove") {
|
|
123
|
+
if (key.length < 1) {
|
|
124
|
+
throw new Error(`Key is required for REMOVE operation`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (const k of key) {
|
|
128
|
+
const path = `/data/${name}-${k}`;
|
|
129
|
+
const cacheIdx = [op, path].join(":");
|
|
130
|
+
|
|
131
|
+
// Add the operation to the cache
|
|
132
|
+
migrateCache[cacheIdx] = { op, path };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// If we get here, the operation is not supported
|
|
139
|
+
throw new Error(`Unsupported operation: ${op}`);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
for (const name of Object.keys(this.#stores)) {
|
|
143
|
+
// Get the prefix offset for the keys
|
|
144
|
+
const offset = `${name}-`.length;
|
|
145
|
+
|
|
146
|
+
// Loop over each key in the store
|
|
147
|
+
for (const key of Object.keys(data)) {
|
|
148
|
+
// Match on the capability name as a prefix for non v2 keys
|
|
149
|
+
if (startsWith(name, key) && !startsWith(`${name}-v2`, key)) {
|
|
150
|
+
// populate migrate cache
|
|
151
|
+
fillCache(name, "remove", [key.slice(offset)], data[key]);
|
|
152
|
+
fillCache(name, "add", [key.slice(offset)], data[key]);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// await K8s(PeprStore, { namespace, name: this.#name }).Patch(payload);
|
|
157
|
+
}
|
|
158
|
+
await flushCache();
|
|
159
|
+
this.#setupWatch();
|
|
160
|
+
};
|
|
161
|
+
|
|
77
162
|
#receive = (store: PeprStore) => {
|
|
78
163
|
Log.debug(store, "Pepr Store update");
|
|
79
164
|
|
|
@@ -121,6 +206,7 @@ export class PeprControllerStore {
|
|
|
121
206
|
// Load the sendCache with patch operations
|
|
122
207
|
const fillCache = (op: DataOp, key: string[], val?: string) => {
|
|
123
208
|
if (op === "add") {
|
|
209
|
+
// adjust the path for the capability
|
|
124
210
|
const path = `/data/${capabilityName}-${key}`;
|
|
125
211
|
const value = val || "";
|
|
126
212
|
const cacheIdx = [op, path, value].join(":");
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
import { expect, test, describe } from "@jest/globals";
|
|
5
|
+
import * as fc from "fast-check";
|
|
6
|
+
import { Errors, ErrorList, ValidateError } from "./errors";
|
|
7
|
+
|
|
8
|
+
describe("ValidateError Fuzz Testing", () => {
|
|
9
|
+
test("should only accept predefined error values", () => {
|
|
10
|
+
fc.assert(
|
|
11
|
+
fc.property(fc.string(), error => {
|
|
12
|
+
if (ErrorList.includes(error)) {
|
|
13
|
+
expect(() => ValidateError(error)).not.toThrow();
|
|
14
|
+
} else {
|
|
15
|
+
expect(() => ValidateError(error)).toThrow(
|
|
16
|
+
`Invalid error: ${error}. Must be one of: ${ErrorList.join(", ")}`,
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
}),
|
|
20
|
+
{ verbose: true },
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
describe("ValidateError Fake Data Testing", () => {
|
|
25
|
+
test("should correctly handle typical fake error data", () => {
|
|
26
|
+
const fakeErrors = ["error", "failure", "null", "undefined", "exception"];
|
|
27
|
+
fakeErrors.forEach(fakeError => {
|
|
28
|
+
if (ErrorList.includes(fakeError)) {
|
|
29
|
+
expect(() => ValidateError(fakeError)).not.toThrow();
|
|
30
|
+
} else {
|
|
31
|
+
expect(() => ValidateError(fakeError)).toThrow(
|
|
32
|
+
`Invalid error: ${fakeError}. Must be one of: ${ErrorList.join(", ")}`,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe("ValidateError Property-Based Testing", () => {
|
|
39
|
+
test("should only validate errors that are part of the ErrorList", () => {
|
|
40
|
+
fc.assert(
|
|
41
|
+
fc.property(fc.constantFrom(...ErrorList), validError => {
|
|
42
|
+
expect(() => ValidateError(validError)).not.toThrow();
|
|
43
|
+
}),
|
|
44
|
+
{ verbose: true },
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
fc.assert(
|
|
48
|
+
fc.property(
|
|
49
|
+
fc.string().filter(e => !ErrorList.includes(e)),
|
|
50
|
+
invalidError => {
|
|
51
|
+
expect(() => ValidateError(invalidError)).toThrow(
|
|
52
|
+
`Invalid error: ${invalidError}. Must be one of: ${ErrorList.join(", ")}`,
|
|
53
|
+
);
|
|
54
|
+
},
|
|
55
|
+
),
|
|
56
|
+
{ verbose: true },
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("Errors object should have correct properties", () => {
|
|
62
|
+
expect(Errors).toEqual({
|
|
63
|
+
audit: "audit",
|
|
64
|
+
ignore: "ignore",
|
|
65
|
+
reject: "reject",
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("ErrorList should contain correct values", () => {
|
|
70
|
+
expect(ErrorList).toEqual(["audit", "ignore", "reject"]);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("ValidateError should not throw an error for valid errors", () => {
|
|
74
|
+
expect(() => {
|
|
75
|
+
ValidateError("audit");
|
|
76
|
+
ValidateError("ignore");
|
|
77
|
+
ValidateError("reject");
|
|
78
|
+
}).not.toThrow();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("ValidateError should throw an error for invalid errors", () => {
|
|
82
|
+
expect(() => ValidateError("invalidError")).toThrowError({
|
|
83
|
+
message: "Invalid error: invalidError. Must be one of: audit, ignore, reject",
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
import { expect, test, describe } from "@jest/globals";
|
|
5
|
+
import { kind, modelToGroupVersionKind } from "kubernetes-fluent-client";
|
|
6
|
+
import * as fc from "fast-check";
|
|
7
|
+
import { CreatePod, DeletePod } from "../fixtures/loader";
|
|
8
|
+
import { shouldSkipRequest } from "./filter";
|
|
9
|
+
import { Event, Binding } from "./types";
|
|
10
|
+
import { AdmissionRequest } from "./k8s";
|
|
11
|
+
|
|
12
|
+
const callback = () => undefined;
|
|
13
|
+
|
|
14
|
+
const podKind = modelToGroupVersionKind(kind.Pod.name);
|
|
15
|
+
|
|
16
|
+
describe("Fuzzing shouldSkipRequest", () => {
|
|
17
|
+
test("should handle random inputs without crashing", () => {
|
|
18
|
+
fc.assert(
|
|
19
|
+
fc.property(
|
|
20
|
+
fc.record({
|
|
21
|
+
event: fc.constantFrom("CREATE", "UPDATE", "DELETE", "ANY"),
|
|
22
|
+
kind: fc.record({
|
|
23
|
+
group: fc.string(),
|
|
24
|
+
version: fc.string(),
|
|
25
|
+
kind: fc.string(),
|
|
26
|
+
}),
|
|
27
|
+
filters: fc.record({
|
|
28
|
+
name: fc.string(),
|
|
29
|
+
namespaces: fc.array(fc.string()),
|
|
30
|
+
labels: fc.dictionary(fc.string(), fc.string()),
|
|
31
|
+
annotations: fc.dictionary(fc.string(), fc.string()),
|
|
32
|
+
}),
|
|
33
|
+
}),
|
|
34
|
+
fc.record({
|
|
35
|
+
operation: fc.string(),
|
|
36
|
+
uid: fc.string(),
|
|
37
|
+
name: fc.string(),
|
|
38
|
+
namespace: fc.string(),
|
|
39
|
+
kind: fc.record({
|
|
40
|
+
group: fc.string(),
|
|
41
|
+
version: fc.string(),
|
|
42
|
+
kind: fc.string(),
|
|
43
|
+
}),
|
|
44
|
+
}),
|
|
45
|
+
fc.array(fc.string()),
|
|
46
|
+
(binding, req, capabilityNamespaces) => {
|
|
47
|
+
expect(() =>
|
|
48
|
+
shouldSkipRequest(binding as Binding, req as AdmissionRequest, capabilityNamespaces),
|
|
49
|
+
).not.toThrow();
|
|
50
|
+
},
|
|
51
|
+
),
|
|
52
|
+
{ numRuns: 100 },
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
describe("Property-Based Testing shouldSkipRequest", () => {
|
|
57
|
+
test("should only skip requests that do not match the binding criteria", () => {
|
|
58
|
+
fc.assert(
|
|
59
|
+
fc.property(
|
|
60
|
+
fc.record({
|
|
61
|
+
event: fc.constantFrom("CREATE", "UPDATE", "DELETE", "ANY"),
|
|
62
|
+
kind: fc.record({
|
|
63
|
+
group: fc.string(),
|
|
64
|
+
version: fc.string(),
|
|
65
|
+
kind: fc.string(),
|
|
66
|
+
}),
|
|
67
|
+
filters: fc.record({
|
|
68
|
+
name: fc.string(),
|
|
69
|
+
namespaces: fc.array(fc.string()),
|
|
70
|
+
labels: fc.dictionary(fc.string(), fc.string()),
|
|
71
|
+
annotations: fc.dictionary(fc.string(), fc.string()),
|
|
72
|
+
}),
|
|
73
|
+
}),
|
|
74
|
+
fc.record({
|
|
75
|
+
operation: fc.string(),
|
|
76
|
+
uid: fc.string(),
|
|
77
|
+
name: fc.string(),
|
|
78
|
+
namespace: fc.string(),
|
|
79
|
+
kind: fc.record({
|
|
80
|
+
group: fc.string(),
|
|
81
|
+
version: fc.string(),
|
|
82
|
+
kind: fc.string(),
|
|
83
|
+
}),
|
|
84
|
+
}),
|
|
85
|
+
fc.array(fc.string()),
|
|
86
|
+
(binding, req, capabilityNamespaces) => {
|
|
87
|
+
const shouldSkip = shouldSkipRequest(binding as Binding, req as AdmissionRequest, capabilityNamespaces);
|
|
88
|
+
expect(typeof shouldSkip).toBe("boolean");
|
|
89
|
+
},
|
|
90
|
+
),
|
|
91
|
+
{ numRuns: 100 },
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("should reject when name does not match", () => {
|
|
97
|
+
const binding = {
|
|
98
|
+
model: kind.Pod,
|
|
99
|
+
event: Event.Any,
|
|
100
|
+
kind: podKind,
|
|
101
|
+
filters: {
|
|
102
|
+
name: "bleh",
|
|
103
|
+
namespaces: [],
|
|
104
|
+
labels: {},
|
|
105
|
+
annotations: {},
|
|
106
|
+
},
|
|
107
|
+
callback,
|
|
108
|
+
};
|
|
109
|
+
const pod = CreatePod();
|
|
110
|
+
|
|
111
|
+
expect(shouldSkipRequest(binding, pod, [])).toBe(true);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("should reject when kind does not match", () => {
|
|
115
|
+
const binding = {
|
|
116
|
+
model: kind.Pod,
|
|
117
|
+
event: Event.Any,
|
|
118
|
+
kind: modelToGroupVersionKind(kind.CronJob.name),
|
|
119
|
+
filters: {
|
|
120
|
+
name: "",
|
|
121
|
+
namespaces: [],
|
|
122
|
+
labels: {},
|
|
123
|
+
annotations: {},
|
|
124
|
+
},
|
|
125
|
+
callback,
|
|
126
|
+
};
|
|
127
|
+
const pod = CreatePod();
|
|
128
|
+
|
|
129
|
+
expect(shouldSkipRequest(binding, pod, [])).toBe(true);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("should reject when group does not match", () => {
|
|
133
|
+
const binding = {
|
|
134
|
+
model: kind.Pod,
|
|
135
|
+
event: Event.Any,
|
|
136
|
+
kind: modelToGroupVersionKind(kind.CronJob.name),
|
|
137
|
+
filters: {
|
|
138
|
+
name: "",
|
|
139
|
+
namespaces: [],
|
|
140
|
+
labels: {},
|
|
141
|
+
annotations: {},
|
|
142
|
+
},
|
|
143
|
+
callback,
|
|
144
|
+
};
|
|
145
|
+
const pod = CreatePod();
|
|
146
|
+
|
|
147
|
+
expect(shouldSkipRequest(binding, pod, [])).toBe(true);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("should reject when version does not match", () => {
|
|
151
|
+
const binding = {
|
|
152
|
+
model: kind.Pod,
|
|
153
|
+
event: Event.Any,
|
|
154
|
+
kind: {
|
|
155
|
+
group: "",
|
|
156
|
+
version: "v2",
|
|
157
|
+
kind: "Pod",
|
|
158
|
+
},
|
|
159
|
+
filters: {
|
|
160
|
+
name: "",
|
|
161
|
+
namespaces: [],
|
|
162
|
+
labels: {},
|
|
163
|
+
annotations: {},
|
|
164
|
+
},
|
|
165
|
+
callback,
|
|
166
|
+
};
|
|
167
|
+
const pod = CreatePod();
|
|
168
|
+
|
|
169
|
+
expect(shouldSkipRequest(binding, pod, [])).toBe(true);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("should allow when group, version, and kind match", () => {
|
|
173
|
+
const binding = {
|
|
174
|
+
model: kind.Pod,
|
|
175
|
+
event: Event.Any,
|
|
176
|
+
kind: podKind,
|
|
177
|
+
filters: {
|
|
178
|
+
name: "",
|
|
179
|
+
namespaces: [],
|
|
180
|
+
labels: {},
|
|
181
|
+
annotations: {},
|
|
182
|
+
},
|
|
183
|
+
callback,
|
|
184
|
+
};
|
|
185
|
+
const pod = CreatePod();
|
|
186
|
+
|
|
187
|
+
expect(shouldSkipRequest(binding, pod, [])).toBe(false);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("should allow when kind match and others are empty", () => {
|
|
191
|
+
const binding = {
|
|
192
|
+
model: kind.Pod,
|
|
193
|
+
event: Event.Any,
|
|
194
|
+
kind: {
|
|
195
|
+
group: "",
|
|
196
|
+
version: "",
|
|
197
|
+
kind: "Pod",
|
|
198
|
+
},
|
|
199
|
+
filters: {
|
|
200
|
+
name: "",
|
|
201
|
+
namespaces: [],
|
|
202
|
+
labels: {},
|
|
203
|
+
annotations: {},
|
|
204
|
+
},
|
|
205
|
+
callback,
|
|
206
|
+
};
|
|
207
|
+
const pod = CreatePod();
|
|
208
|
+
|
|
209
|
+
expect(shouldSkipRequest(binding, pod, [])).toBe(false);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("should reject when teh capability namespace does not match", () => {
|
|
213
|
+
const binding = {
|
|
214
|
+
model: kind.Pod,
|
|
215
|
+
event: Event.Any,
|
|
216
|
+
kind: podKind,
|
|
217
|
+
filters: {
|
|
218
|
+
name: "",
|
|
219
|
+
namespaces: [],
|
|
220
|
+
labels: {},
|
|
221
|
+
annotations: {},
|
|
222
|
+
},
|
|
223
|
+
callback,
|
|
224
|
+
};
|
|
225
|
+
const pod = CreatePod();
|
|
226
|
+
|
|
227
|
+
expect(shouldSkipRequest(binding, pod, ["bleh", "bleh2"])).toBe(true);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("should reject when namespace does not match", () => {
|
|
231
|
+
const binding = {
|
|
232
|
+
model: kind.Pod,
|
|
233
|
+
event: Event.Any,
|
|
234
|
+
kind: podKind,
|
|
235
|
+
filters: {
|
|
236
|
+
name: "",
|
|
237
|
+
namespaces: ["bleh"],
|
|
238
|
+
labels: {},
|
|
239
|
+
annotations: {},
|
|
240
|
+
},
|
|
241
|
+
callback,
|
|
242
|
+
};
|
|
243
|
+
const pod = CreatePod();
|
|
244
|
+
|
|
245
|
+
expect(shouldSkipRequest(binding, pod, [])).toBe(true);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("should allow when namespace is match", () => {
|
|
249
|
+
const binding = {
|
|
250
|
+
model: kind.Pod,
|
|
251
|
+
event: Event.Any,
|
|
252
|
+
kind: podKind,
|
|
253
|
+
filters: {
|
|
254
|
+
name: "",
|
|
255
|
+
namespaces: ["default", "unicorn", "things"],
|
|
256
|
+
labels: {},
|
|
257
|
+
annotations: {},
|
|
258
|
+
},
|
|
259
|
+
callback,
|
|
260
|
+
};
|
|
261
|
+
const pod = CreatePod();
|
|
262
|
+
|
|
263
|
+
expect(shouldSkipRequest(binding, pod, [])).toBe(false);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("should reject when label does not match", () => {
|
|
267
|
+
const binding = {
|
|
268
|
+
model: kind.Pod,
|
|
269
|
+
event: Event.Any,
|
|
270
|
+
kind: podKind,
|
|
271
|
+
filters: {
|
|
272
|
+
name: "",
|
|
273
|
+
namespaces: [],
|
|
274
|
+
labels: {
|
|
275
|
+
foo: "bar",
|
|
276
|
+
},
|
|
277
|
+
annotations: {},
|
|
278
|
+
},
|
|
279
|
+
callback,
|
|
280
|
+
};
|
|
281
|
+
const pod = CreatePod();
|
|
282
|
+
|
|
283
|
+
expect(shouldSkipRequest(binding, pod, [])).toBe(true);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test("should allow when label is match", () => {
|
|
287
|
+
const binding = {
|
|
288
|
+
model: kind.Pod,
|
|
289
|
+
event: Event.Any,
|
|
290
|
+
kind: podKind,
|
|
291
|
+
filters: {
|
|
292
|
+
name: "",
|
|
293
|
+
|
|
294
|
+
namespaces: [],
|
|
295
|
+
labels: {
|
|
296
|
+
foo: "bar",
|
|
297
|
+
test: "test1",
|
|
298
|
+
},
|
|
299
|
+
annotations: {},
|
|
300
|
+
},
|
|
301
|
+
callback,
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const pod = CreatePod();
|
|
305
|
+
pod.object.metadata = pod.object.metadata || {};
|
|
306
|
+
pod.object.metadata.labels = {
|
|
307
|
+
foo: "bar",
|
|
308
|
+
test: "test1",
|
|
309
|
+
test2: "test2",
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
expect(shouldSkipRequest(binding, pod, [])).toBe(false);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test("should reject when annotation does not match", () => {
|
|
316
|
+
const binding = {
|
|
317
|
+
model: kind.Pod,
|
|
318
|
+
event: Event.Any,
|
|
319
|
+
kind: podKind,
|
|
320
|
+
filters: {
|
|
321
|
+
name: "",
|
|
322
|
+
namespaces: [],
|
|
323
|
+
labels: {},
|
|
324
|
+
annotations: {
|
|
325
|
+
foo: "bar",
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
callback,
|
|
329
|
+
};
|
|
330
|
+
const pod = CreatePod();
|
|
331
|
+
|
|
332
|
+
expect(shouldSkipRequest(binding, pod, [])).toBe(true);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test("should allow when annotation is match", () => {
|
|
336
|
+
const binding = {
|
|
337
|
+
model: kind.Pod,
|
|
338
|
+
event: Event.Any,
|
|
339
|
+
kind: podKind,
|
|
340
|
+
filters: {
|
|
341
|
+
name: "",
|
|
342
|
+
namespaces: [],
|
|
343
|
+
labels: {},
|
|
344
|
+
annotations: {
|
|
345
|
+
foo: "bar",
|
|
346
|
+
test: "test1",
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
callback,
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
const pod = CreatePod();
|
|
353
|
+
pod.object.metadata = pod.object.metadata || {};
|
|
354
|
+
pod.object.metadata.annotations = {
|
|
355
|
+
foo: "bar",
|
|
356
|
+
test: "test1",
|
|
357
|
+
test2: "test2",
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
expect(shouldSkipRequest(binding, pod, [])).toBe(false);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
test("should use `oldObject` when the operation is `DELETE`", () => {
|
|
364
|
+
const binding = {
|
|
365
|
+
model: kind.Pod,
|
|
366
|
+
event: Event.Delete,
|
|
367
|
+
kind: podKind,
|
|
368
|
+
filters: {
|
|
369
|
+
name: "",
|
|
370
|
+
namespaces: [],
|
|
371
|
+
labels: {
|
|
372
|
+
"app.kubernetes.io/name": "cool-name-podinfo",
|
|
373
|
+
},
|
|
374
|
+
annotations: {
|
|
375
|
+
"prometheus.io/scrape": "true",
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
callback,
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
const pod = DeletePod();
|
|
382
|
+
|
|
383
|
+
expect(shouldSkipRequest(binding, pod, [])).toBe(false);
|
|
384
|
+
});
|