pepr 0.2.10 → 0.3.0-rc0

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.
Files changed (81) hide show
  1. package/dist/fixtures/data/cm1.json +75 -0
  2. package/dist/fixtures/data/deployment1.json +170 -0
  3. package/dist/fixtures/data/ns1.json +72 -0
  4. package/dist/fixtures/data/pod1.json +271 -0
  5. package/dist/fixtures/data/pod2.json +257 -0
  6. package/dist/fixtures/data/svc1.json +100 -0
  7. package/dist/fixtures/loader.js +60 -0
  8. package/dist/package.json +21 -39
  9. package/dist/src/cli/build.js +3 -1
  10. package/dist/src/cli/dev.js +31 -19
  11. package/dist/src/cli/index.js +1 -0
  12. package/dist/src/cli/init/index.js +3 -1
  13. package/dist/src/cli/init/templates.js +3 -2
  14. package/dist/src/cli/init/utils.js +1 -1
  15. package/dist/src/cli/init/utils.test.js +29 -0
  16. package/dist/src/cli/init/walkthrough.js +1 -1
  17. package/dist/src/cli/init/walkthrough.test.js +21 -0
  18. package/dist/src/cli/run.js +17 -17
  19. package/dist/src/cli/update.js +3 -1
  20. package/dist/src/lib/capability.js +1 -1
  21. package/dist/src/lib/controller.js +9 -1
  22. package/dist/src/lib/fetch.js +39 -6
  23. package/dist/src/lib/fetch.test.js +98 -0
  24. package/dist/src/lib/filter.test.js +208 -0
  25. package/dist/src/lib/k8s/kinds.test.js +296 -0
  26. package/dist/src/lib/k8s/webhook.js +22 -22
  27. package/dist/src/lib/logger.test.js +64 -0
  28. package/dist/src/lib/processor.js +4 -1
  29. package/{dist/index.d.ts → index.ts} +21 -3
  30. package/package.json +21 -39
  31. package/src/lib/capability.ts +158 -0
  32. package/src/lib/controller.ts +127 -0
  33. package/src/lib/fetch.test.ts +115 -0
  34. package/src/lib/fetch.ts +75 -0
  35. package/src/lib/filter.test.ts +231 -0
  36. package/src/lib/filter.ts +87 -0
  37. package/{dist/src/lib/k8s/index.d.ts → src/lib/k8s/index.ts} +6 -0
  38. package/src/lib/k8s/kinds.test.ts +333 -0
  39. package/src/lib/k8s/kinds.ts +489 -0
  40. package/src/lib/k8s/tls.ts +90 -0
  41. package/src/lib/k8s/types.ts +183 -0
  42. package/src/lib/k8s/upstream.ts +49 -0
  43. package/src/lib/k8s/webhook.ts +547 -0
  44. package/src/lib/logger.test.ts +80 -0
  45. package/src/lib/logger.ts +136 -0
  46. package/src/lib/module.ts +63 -0
  47. package/src/lib/processor.ts +98 -0
  48. package/src/lib/request.ts +140 -0
  49. package/src/lib/types.ts +211 -0
  50. package/dist/cli.d.ts +0 -2
  51. package/dist/cli.js +0 -4
  52. package/dist/run.d.ts +0 -2
  53. package/dist/run.js +0 -4
  54. package/dist/src/cli/banner.d.ts +0 -1
  55. package/dist/src/cli/build.d.ts +0 -7
  56. package/dist/src/cli/capability.d.ts +0 -2
  57. package/dist/src/cli/deploy.d.ts +0 -2
  58. package/dist/src/cli/dev.d.ts +0 -2
  59. package/dist/src/cli/index.d.ts +0 -1
  60. package/dist/src/cli/init/index.d.ts +0 -2
  61. package/dist/src/cli/init/templates.d.ts +0 -94
  62. package/dist/src/cli/init/utils.d.ts +0 -20
  63. package/dist/src/cli/init/walkthrough.d.ts +0 -7
  64. package/dist/src/cli/root.d.ts +0 -4
  65. package/dist/src/cli/run.d.ts +0 -1
  66. package/dist/src/cli/test.d.ts +0 -2
  67. package/dist/src/cli/update.d.ts +0 -2
  68. package/dist/src/lib/capability.d.ts +0 -28
  69. package/dist/src/lib/controller.d.ts +0 -17
  70. package/dist/src/lib/fetch.d.ts +0 -23
  71. package/dist/src/lib/filter.d.ts +0 -10
  72. package/dist/src/lib/k8s/kinds.d.ts +0 -11
  73. package/dist/src/lib/k8s/tls.d.ts +0 -17
  74. package/dist/src/lib/k8s/types.d.ts +0 -147
  75. package/dist/src/lib/k8s/upstream.d.ts +0 -3
  76. package/dist/src/lib/k8s/webhook.d.ts +0 -34
  77. package/dist/src/lib/logger.d.ts +0 -55
  78. package/dist/src/lib/module.d.ts +0 -32
  79. package/dist/src/lib/processor.d.ts +0 -4
  80. package/dist/src/lib/request.d.ts +0 -77
  81. package/dist/src/lib/types.d.ts +0 -187
@@ -0,0 +1,158 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+
4
+ import { GroupVersionKind, modelToGroupVersionKind } from "./k8s";
5
+ import logger from "./logger";
6
+ import {
7
+ BindToAction,
8
+ Binding,
9
+ BindingFilter,
10
+ BindingWithName,
11
+ CapabilityAction,
12
+ CapabilityCfg,
13
+ DeepPartial,
14
+ Event,
15
+ GenericClass,
16
+ HookPhase,
17
+ WhenSelector,
18
+ } from "./types";
19
+
20
+ /**
21
+ * A capability is a unit of functionality that can be registered with the Pepr runtime.
22
+ */
23
+ export class Capability implements CapabilityCfg {
24
+ private _name: string;
25
+ private _description: string;
26
+ private _namespaces?: string[] | undefined;
27
+
28
+ // Currently everything is considered a mutation
29
+ private _mutateOrValidate = HookPhase.mutate;
30
+
31
+ private _bindings: Binding[] = [];
32
+
33
+ get bindings(): Binding[] {
34
+ return this._bindings;
35
+ }
36
+
37
+ get name() {
38
+ return this._name;
39
+ }
40
+
41
+ get description() {
42
+ return this._description;
43
+ }
44
+
45
+ get namespaces() {
46
+ return this._namespaces || [];
47
+ }
48
+
49
+ get mutateOrValidate() {
50
+ return this._mutateOrValidate;
51
+ }
52
+
53
+ constructor(cfg: CapabilityCfg) {
54
+ this._name = cfg.name;
55
+ this._description = cfg.description;
56
+ this._namespaces = cfg.namespaces;
57
+ logger.info(`Capability ${this._name} registered`);
58
+ logger.debug(cfg);
59
+ }
60
+
61
+ /**
62
+ * The When method is used to register a capability action to be executed when a Kubernetes resource is
63
+ * processed by Pepr. The action will be executed if the resource matches the specified kind and any
64
+ * filters that are applied.
65
+ *
66
+ * @param model the KubernetesObject model to match
67
+ * @param kind if using a custom KubernetesObject not available in `a.*`, specify the GroupVersionKind
68
+ * @returns
69
+ */
70
+ When = <T extends GenericClass>(model: T, kind?: GroupVersionKind): WhenSelector<T> => {
71
+ const matchedKind = modelToGroupVersionKind(model.name);
72
+
73
+ // If the kind is not specified and the model is not a KubernetesObject, throw an error
74
+ if (!matchedKind && !kind) {
75
+ throw new Error(`Kind not specified for ${model.name}`);
76
+ }
77
+
78
+ const binding: Binding = {
79
+ // If the kind is not specified, use the matched kind from the model
80
+ kind: kind || matchedKind,
81
+ filters: {
82
+ name: "",
83
+ namespaces: [],
84
+ labels: {},
85
+ annotations: {},
86
+ },
87
+ callback: () => undefined,
88
+ };
89
+
90
+ const prefix = `${this._name}: ${model.name}`;
91
+
92
+ logger.info(`Binding created`, prefix);
93
+
94
+ const Then = (cb: CapabilityAction<T>): BindToAction<T> => {
95
+ logger.info(`Binding action created`, prefix);
96
+ logger.debug(cb.toString(), prefix);
97
+ // Push the binding to the list of bindings for this capability as a new BindingAction
98
+ // with the callback function to preserve
99
+ this._bindings.push({
100
+ ...binding,
101
+ callback: cb,
102
+ });
103
+
104
+ // Now only allow adding actions to the same binding
105
+ return { Then };
106
+ };
107
+
108
+ const ThenSet = (merge: DeepPartial<InstanceType<T>>): BindToAction<T> => {
109
+ // Add the new action to the binding
110
+ Then(req => req.Merge(merge));
111
+
112
+ return { Then };
113
+ };
114
+
115
+ function InNamespace(...namespaces: string[]): BindingWithName<T> {
116
+ logger.debug(`Add namespaces filter ${namespaces}`, prefix);
117
+ binding.filters.namespaces.push(...namespaces);
118
+ return { WithLabel, WithAnnotation, WithName, Then, ThenSet };
119
+ }
120
+
121
+ function WithName(name: string): BindingFilter<T> {
122
+ logger.debug(`Add name filter ${name}`, prefix);
123
+ binding.filters.name = name;
124
+ return { WithLabel, WithAnnotation, Then, ThenSet };
125
+ }
126
+
127
+ function WithLabel(key: string, value = ""): BindingFilter<T> {
128
+ logger.debug(`Add label filter ${key}=${value}`, prefix);
129
+ binding.filters.labels[key] = value;
130
+ return { WithLabel, WithAnnotation, Then, ThenSet };
131
+ }
132
+
133
+ const WithAnnotation = (key: string, value = ""): BindingFilter<T> => {
134
+ logger.debug(`Add annotation filter ${key}=${value}`, prefix);
135
+ binding.filters.annotations[key] = value;
136
+ return { WithLabel, WithAnnotation, Then, ThenSet };
137
+ };
138
+
139
+ const bindEvent = (event: Event) => {
140
+ binding.event = event;
141
+ return {
142
+ InNamespace,
143
+ Then,
144
+ ThenSet,
145
+ WithAnnotation,
146
+ WithLabel,
147
+ WithName,
148
+ };
149
+ };
150
+
151
+ return {
152
+ IsCreatedOrUpdated: () => bindEvent(Event.CreateOrUpdate),
153
+ IsCreated: () => bindEvent(Event.Create),
154
+ IsUpdated: () => bindEvent(Event.Update),
155
+ IsDeleted: () => bindEvent(Event.Delete),
156
+ };
157
+ };
158
+ }
@@ -0,0 +1,127 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+
4
+ import express from "express";
5
+ import fs from "fs";
6
+ import https from "https";
7
+ import { Capability } from "./capability";
8
+ import { Request, Response } from "./k8s/types";
9
+ import { processor } from "./processor";
10
+ import { ModuleConfig } from "./types";
11
+
12
+ // Load SSL certificate and key
13
+ const options = {
14
+ key: fs.readFileSync(process.env.SSL_KEY_PATH || "/etc/certs/tls.key"),
15
+ cert: fs.readFileSync(process.env.SSL_CERT_PATH || "/etc/certs/tls.crt"),
16
+ };
17
+
18
+ export class Controller {
19
+ private readonly app = express();
20
+ private running = false;
21
+
22
+ constructor(
23
+ private readonly config: ModuleConfig,
24
+ private readonly capabilities: Capability[],
25
+ private readonly beforeHook?: (req: Request) => void,
26
+ private readonly afterHook?: (res: Response) => void
27
+ ) {
28
+ // Middleware for logging requests
29
+ this.app.use(this.logger);
30
+
31
+ // Middleware for parsing JSON
32
+ this.app.use(express.json());
33
+
34
+ // Health check endpoint
35
+ this.app.get("/healthz", this.healthz);
36
+
37
+ // Mutate endpoint
38
+ this.app.post("/mutate", this.mutate);
39
+
40
+ if (beforeHook) {
41
+ console.info(`Using beforeHook: ${beforeHook}`);
42
+ }
43
+
44
+ if (afterHook) {
45
+ console.info(`Using afterHook: ${afterHook}`);
46
+ }
47
+ }
48
+
49
+ /** Start the webhook server */
50
+ public startServer = (port: number) => {
51
+ if (this.running) {
52
+ throw new Error("Cannot start Pepr module: Pepr module was not instantiated with deferStart=true");
53
+ }
54
+
55
+ // Create HTTPS server
56
+ const server = https.createServer(options, this.app).listen(port, () => {
57
+ console.log(`Server listening on port ${port}`);
58
+ // Track that the server is running
59
+ this.running = true;
60
+ });
61
+
62
+ // Listen for the SIGTERM signal and gracefully close the server
63
+ process.on("SIGTERM", () => {
64
+ console.log("Received SIGTERM, closing server");
65
+ server.close(() => {
66
+ console.log("Server closed");
67
+ process.exit(0);
68
+ });
69
+ });
70
+ };
71
+
72
+ private logger = (req: express.Request, res: express.Response, next: express.NextFunction) => {
73
+ const startTime = Date.now();
74
+
75
+ res.on("finish", () => {
76
+ const now = new Date().toISOString();
77
+ const elapsedTime = Date.now() - startTime;
78
+ const message = `[${now}] ${req.method} ${req.originalUrl} - ${res.statusCode} - ${elapsedTime} ms\n`;
79
+
80
+ res.statusCode >= 400 ? console.error(message) : console.info(message);
81
+ });
82
+
83
+ next();
84
+ };
85
+
86
+ private healthz = (req: express.Request, res: express.Response) => {
87
+ try {
88
+ res.send("OK");
89
+ } catch (err) {
90
+ console.error(err);
91
+ res.status(500).send("Internal Server Error");
92
+ }
93
+ };
94
+
95
+ private mutate = async (req: express.Request, res: express.Response) => {
96
+ try {
97
+ // Run the before hook if it exists
98
+ this.beforeHook && this.beforeHook(req.body?.request || {});
99
+
100
+ const name = req.body?.request?.name || "";
101
+ const namespace = req.body?.request?.namespace || "";
102
+ const gvk = req.body?.request?.kind || { group: "", version: "", kind: "" };
103
+
104
+ console.log(`Mutate request: ${gvk.group}/${gvk.version}/${gvk.kind}`);
105
+ name && console.log(` ${namespace}/${name}\n`);
106
+
107
+ // Process the request
108
+ const response = await processor(this.config, this.capabilities, req.body.request);
109
+
110
+ // Run the after hook if it exists
111
+ this.afterHook && this.afterHook(response);
112
+
113
+ // Log the response
114
+ console.debug(response);
115
+
116
+ // Send a no prob bob response
117
+ res.send({
118
+ apiVersion: "admission.k8s.io/v1",
119
+ kind: "AdmissionReview",
120
+ response,
121
+ });
122
+ } catch (err) {
123
+ console.error(err);
124
+ res.status(500).send("Internal Server Error");
125
+ }
126
+ };
127
+ }
@@ -0,0 +1,115 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+ // fetch.test.ts
4
+
5
+ import test from "ava";
6
+ import { StatusCodes } from "http-status-codes";
7
+ import nock from "nock";
8
+ import { RequestInit } from "node-fetch";
9
+ import { fetch } from "./fetch";
10
+
11
+ test.beforeEach(() => {
12
+ nock("https://jsonplaceholder.typicode.com")
13
+ .get("/todos/1")
14
+ .reply(200, {
15
+ userId: 1,
16
+ id: 1,
17
+ title: "Example title",
18
+ completed: false,
19
+ })
20
+ .post("/todos", {
21
+ title: "test todo",
22
+ userId: 1,
23
+ completed: false,
24
+ })
25
+ .reply(200, (uri, requestBody) => requestBody)
26
+ .get("/todos/empty-null")
27
+ .reply(200, undefined)
28
+ .get("/todos/empty-string")
29
+ .reply(200, "")
30
+ .get("/todos/empty-object")
31
+ .reply(200, {})
32
+ .get("/todos/invalid")
33
+ .replyWithError("Something bad happened");
34
+ });
35
+
36
+ test("fetch: should return without type data", async t => {
37
+ const url = "https://jsonplaceholder.typicode.com/todos/1";
38
+ const { data, ok } = await fetch<{ title: string }>(url);
39
+ t.is(ok, true);
40
+ t.is(data["title"], "Example title");
41
+ });
42
+
43
+ test("fetch: should return parsed JSON response as a specific type", async t => {
44
+ interface Todo {
45
+ userId: number;
46
+ id: number;
47
+ title: string;
48
+ completed: boolean;
49
+ }
50
+
51
+ const url = "https://jsonplaceholder.typicode.com/todos/1";
52
+ const { data, ok } = await fetch<Todo>(url);
53
+ t.is(ok, true);
54
+ t.is(data.id, 1);
55
+ t.is(typeof data.title, "string");
56
+ t.is(typeof data.completed, "boolean");
57
+ });
58
+
59
+ test("fetch: should handle additional request options", async t => {
60
+ const url = "https://jsonplaceholder.typicode.com/todos";
61
+ const requestOptions: RequestInit = {
62
+ method: "POST",
63
+ body: JSON.stringify({
64
+ title: "test todo",
65
+ userId: 1,
66
+ completed: false,
67
+ }),
68
+ headers: {
69
+ "Content-type": "application/json; charset=UTF-8",
70
+ },
71
+ };
72
+
73
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
74
+ const { data, ok } = await fetch<any>(url, requestOptions);
75
+ t.is(ok, true);
76
+ t.is(data["title"], "test todo");
77
+ t.is(data["userId"], 1);
78
+ t.is(data["completed"], false);
79
+ });
80
+
81
+ test("fetch: should handle empty (null) responses", async t => {
82
+ const url = "https://jsonplaceholder.typicode.com/todos/empty-null";
83
+ const resp = await fetch(url);
84
+
85
+ t.is(resp.data, "");
86
+ t.is(resp.ok, true);
87
+ t.is(resp.status, StatusCodes.OK);
88
+ });
89
+
90
+ test("fetch: should handle empty (string) responses", async t => {
91
+ const url = "https://jsonplaceholder.typicode.com/todos/empty-string";
92
+ const resp = await fetch(url);
93
+
94
+ t.is(resp.data, "");
95
+ t.is(resp.ok, true);
96
+ t.is(resp.status, StatusCodes.OK);
97
+ });
98
+
99
+ test("fetch: should handle empty (object) responses", async t => {
100
+ const url = "https://jsonplaceholder.typicode.com/todos/empty-object";
101
+ const resp = await fetch(url);
102
+
103
+ t.deepEqual(resp.data, {});
104
+ t.is(resp.ok, true);
105
+ t.is(resp.status, StatusCodes.OK);
106
+ });
107
+
108
+ test("fetch: should handle failed requests without throwing an error", async t => {
109
+ const url = "https://jsonplaceholder.typicode.com/todos/invalid";
110
+ const resp = await fetch(url);
111
+
112
+ t.is(resp.data, undefined);
113
+ t.is(resp.ok, false);
114
+ t.is(resp.status, StatusCodes.BAD_REQUEST);
115
+ });
@@ -0,0 +1,75 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+
4
+ import { StatusCodes } from "http-status-codes";
5
+ import f, { FetchError, RequestInfo, RequestInit } from "node-fetch";
6
+ import logger from "./logger";
7
+ export { f as fetchRaw };
8
+
9
+ export type FetchResponse<T> = {
10
+ data: T;
11
+ ok: boolean;
12
+ status: number;
13
+ statusText: string;
14
+ };
15
+
16
+ /**
17
+ * Perform an async HTTP call and return the parsed JSON response, optionally
18
+ * as a specific type.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * fetch<string[]>("https://example.com/api/foo");
23
+ * ```
24
+ *
25
+ * @param url The URL or Request object to fetch
26
+ * @param init Additional options for the request
27
+ * @returns
28
+ */
29
+ export async function fetch<T>(url: URL | RequestInfo, init?: RequestInit): Promise<FetchResponse<T>> {
30
+ let data = undefined as unknown as T;
31
+ try {
32
+ logger.debug(`Fetching ${url}`);
33
+
34
+ const resp = await f(url, init);
35
+ const contentType = resp.headers.get("content-type") || "";
36
+
37
+ if (resp.ok) {
38
+ // Parse the response as JSON if the content type is JSON
39
+ if (contentType.includes("application/json")) {
40
+ data = await resp.json();
41
+ } else {
42
+ // Otherwise, return however the response was read
43
+ data = (await resp.text()) as unknown as T;
44
+ }
45
+ }
46
+
47
+ return {
48
+ data,
49
+ ok: resp.ok,
50
+ status: resp.status,
51
+ statusText: resp.statusText,
52
+ };
53
+ } catch (e) {
54
+ if (e instanceof FetchError) {
55
+ logger.debug(`Fetch failed: ${e instanceof Error ? e.message : e}`);
56
+
57
+ // Parse the error code from the FetchError or default to 400 (Bad Request)
58
+ const status = parseInt(e.code || "400");
59
+
60
+ return {
61
+ data,
62
+ ok: false,
63
+ status,
64
+ statusText: e.message,
65
+ };
66
+ }
67
+
68
+ return {
69
+ data,
70
+ ok: false,
71
+ status: StatusCodes.BAD_REQUEST,
72
+ statusText: "Unknown error",
73
+ };
74
+ }
75
+ }
@@ -0,0 +1,231 @@
1
+ import test from "ava";
2
+ import { POD1 } from "../../fixtures/loader";
3
+ import { shouldSkipRequest } from "./filter";
4
+ import { gvkMap } from "./k8s";
5
+
6
+ const callback = () => undefined;
7
+
8
+ test("should reject when name does not match", t => {
9
+ const binding = {
10
+ kind: gvkMap.V1Pod,
11
+ filters: {
12
+ name: "bleh",
13
+ namespaces: [],
14
+ labels: {},
15
+ annotations: {},
16
+ },
17
+ callback,
18
+ };
19
+ const pod = POD1();
20
+
21
+ t.true(shouldSkipRequest(binding, pod));
22
+ });
23
+
24
+ test("should reject when kind does not match", t => {
25
+ const binding = {
26
+ kind: gvkMap.V1ConfigMap,
27
+ filters: {
28
+ name: "",
29
+ namespaces: [],
30
+ labels: {},
31
+ annotations: {},
32
+ },
33
+ callback,
34
+ };
35
+ const pod = POD1();
36
+
37
+ t.true(shouldSkipRequest(binding, pod));
38
+ });
39
+
40
+ test("should reject when group does not match", t => {
41
+ const binding = {
42
+ kind: gvkMap.V1CronJob,
43
+ filters: {
44
+ name: "",
45
+ namespaces: [],
46
+ labels: {},
47
+ annotations: {},
48
+ },
49
+ callback,
50
+ };
51
+ const pod = POD1();
52
+
53
+ t.true(shouldSkipRequest(binding, pod));
54
+ });
55
+
56
+ test("should reject when version does not match", t => {
57
+ const binding = {
58
+ kind: {
59
+ group: "",
60
+ version: "v2",
61
+ kind: "Pod",
62
+ },
63
+ filters: {
64
+ name: "",
65
+ namespaces: [],
66
+ labels: {},
67
+ annotations: {},
68
+ },
69
+ callback,
70
+ };
71
+ const pod = POD1();
72
+
73
+ t.true(shouldSkipRequest(binding, pod));
74
+ });
75
+
76
+ test("should allow when group, version, and kind match", t => {
77
+ const binding = {
78
+ kind: gvkMap.V1Pod,
79
+ filters: {
80
+ name: "",
81
+ namespaces: [],
82
+ labels: {},
83
+ annotations: {},
84
+ },
85
+ callback,
86
+ };
87
+ const pod = POD1();
88
+
89
+ t.false(shouldSkipRequest(binding, pod));
90
+ });
91
+
92
+ test("should allow when kind match and others are empty", t => {
93
+ const binding = {
94
+ kind: {
95
+ group: "",
96
+ version: "",
97
+ kind: "Pod",
98
+ },
99
+ filters: {
100
+ name: "",
101
+ namespaces: [],
102
+ labels: {},
103
+ annotations: {},
104
+ },
105
+ callback,
106
+ };
107
+ const pod = POD1();
108
+
109
+ t.false(shouldSkipRequest(binding, pod));
110
+ });
111
+
112
+ test("should reject when namespace does not match", t => {
113
+ const binding = {
114
+ kind: gvkMap.V1Pod,
115
+ filters: {
116
+ name: "",
117
+ namespaces: ["bleh"],
118
+ labels: {},
119
+ annotations: {},
120
+ },
121
+ callback,
122
+ };
123
+ const pod = POD1();
124
+
125
+ t.true(shouldSkipRequest(binding, pod));
126
+ });
127
+
128
+ test("should allow when namespace is match", t => {
129
+ const binding = {
130
+ kind: gvkMap.V1Pod,
131
+ filters: {
132
+ name: "",
133
+ namespaces: ["default", "unicorn", "things"],
134
+ labels: {},
135
+ annotations: {},
136
+ },
137
+ callback,
138
+ };
139
+ const pod = POD1();
140
+
141
+ t.false(shouldSkipRequest(binding, pod));
142
+ });
143
+
144
+ test("should reject when label does not match", t => {
145
+ const binding = {
146
+ kind: gvkMap.V1Pod,
147
+ filters: {
148
+ name: "",
149
+ namespaces: [],
150
+ labels: {
151
+ foo: "bar",
152
+ },
153
+ annotations: {},
154
+ },
155
+ callback,
156
+ };
157
+ const pod = POD1();
158
+
159
+ t.true(shouldSkipRequest(binding, pod));
160
+ });
161
+
162
+ test("should allow when label is match", t => {
163
+ const binding = {
164
+ kind: gvkMap.V1Pod,
165
+ filters: {
166
+ name: "",
167
+
168
+ namespaces: [],
169
+ labels: {
170
+ foo: "bar",
171
+ test: "test1",
172
+ },
173
+ annotations: {},
174
+ },
175
+ callback,
176
+ };
177
+
178
+ const pod = POD1();
179
+ pod.object.metadata = pod.object.metadata || {};
180
+ pod.object.metadata.labels = {
181
+ foo: "bar",
182
+ test: "test1",
183
+ test2: "test2",
184
+ };
185
+
186
+ t.false(shouldSkipRequest(binding, pod));
187
+ });
188
+
189
+ test("should reject when annotation does not match", t => {
190
+ const binding = {
191
+ kind: gvkMap.V1Pod,
192
+ filters: {
193
+ name: "",
194
+ namespaces: [],
195
+ labels: {},
196
+ annotations: {
197
+ foo: "bar",
198
+ },
199
+ },
200
+ callback,
201
+ };
202
+ const pod = POD1();
203
+
204
+ t.true(shouldSkipRequest(binding, pod));
205
+ });
206
+
207
+ test("should allow when annotation is match", t => {
208
+ const binding = {
209
+ kind: gvkMap.V1Pod,
210
+ filters: {
211
+ name: "",
212
+ namespaces: [],
213
+ labels: {},
214
+ annotations: {
215
+ foo: "bar",
216
+ test: "test1",
217
+ },
218
+ },
219
+ callback,
220
+ };
221
+
222
+ const pod = POD1();
223
+ pod.object.metadata = pod.object.metadata || {};
224
+ pod.object.metadata.annotations = {
225
+ foo: "bar",
226
+ test: "test1",
227
+ test2: "test2",
228
+ };
229
+
230
+ t.false(shouldSkipRequest(binding, pod));
231
+ });