pepr 0.36.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.
Files changed (96) hide show
  1. package/dist/cli/init/index.d.ts.map +1 -1
  2. package/dist/cli/init/templates.d.ts +3 -1
  3. package/dist/cli/init/templates.d.ts.map +1 -1
  4. package/dist/cli/init/utils.d.ts.map +1 -1
  5. package/dist/cli/init/walkthrough.d.ts +10 -3
  6. package/dist/cli/init/walkthrough.d.ts.map +1 -1
  7. package/dist/cli.js +253 -31
  8. package/dist/controller.js +138 -1
  9. package/dist/lib/adjudicators.d.ts +63 -0
  10. package/dist/lib/adjudicators.d.ts.map +1 -0
  11. package/dist/lib/adjudicators.test.d.ts +2 -0
  12. package/dist/lib/adjudicators.test.d.ts.map +1 -0
  13. package/dist/lib/assets/loader.d.ts.map +1 -1
  14. package/dist/lib/assets/pods.d.ts +1 -0
  15. package/dist/lib/assets/pods.d.ts.map +1 -1
  16. package/dist/lib/capability.d.ts +1 -0
  17. package/dist/lib/capability.d.ts.map +1 -1
  18. package/dist/lib/capability.test.d.ts +2 -0
  19. package/dist/lib/capability.test.d.ts.map +1 -0
  20. package/dist/lib/controller/index.d.ts.map +1 -1
  21. package/dist/lib/controller/store.d.ts +4 -0
  22. package/dist/lib/controller/store.d.ts.map +1 -1
  23. package/dist/lib/controller/store.test.d.ts +2 -0
  24. package/dist/lib/controller/store.test.d.ts.map +1 -0
  25. package/dist/lib/filter.d.ts +2 -3
  26. package/dist/lib/filter.d.ts.map +1 -1
  27. package/dist/lib/filter.test.d.ts +2 -1
  28. package/dist/lib/filter.test.d.ts.map +1 -1
  29. package/dist/lib/finalizer.d.ts +6 -0
  30. package/dist/lib/finalizer.d.ts.map +1 -0
  31. package/dist/lib/finalizer.test.d.ts +2 -0
  32. package/dist/lib/finalizer.test.d.ts.map +1 -0
  33. package/dist/lib/helpers.d.ts +2 -2
  34. package/dist/lib/helpers.d.ts.map +1 -1
  35. package/dist/lib/helpers.test.d.ts +1 -1
  36. package/dist/lib/helpers.test.d.ts.map +1 -1
  37. package/dist/lib/k8s.d.ts.map +1 -1
  38. package/dist/lib/module.d.ts +2 -1
  39. package/dist/lib/module.d.ts.map +1 -1
  40. package/dist/lib/mutate-processor.d.ts +2 -1
  41. package/dist/lib/mutate-processor.d.ts.map +1 -1
  42. package/dist/lib/mutate-request.d.ts +1 -2
  43. package/dist/lib/mutate-request.d.ts.map +1 -1
  44. package/dist/lib/schedule.d.ts +1 -2
  45. package/dist/lib/schedule.d.ts.map +1 -1
  46. package/dist/lib/storage.d.ts.map +1 -1
  47. package/dist/lib/types.d.ts +115 -6
  48. package/dist/lib/types.d.ts.map +1 -1
  49. package/dist/lib/validate-processor.d.ts +4 -2
  50. package/dist/lib/validate-processor.d.ts.map +1 -1
  51. package/dist/lib/validate-request.d.ts +1 -1
  52. package/dist/lib/validate-request.d.ts.map +1 -1
  53. package/dist/lib/watch-processor.d.ts +1 -1
  54. package/dist/lib/watch-processor.d.ts.map +1 -1
  55. package/dist/lib.js +383 -204
  56. package/dist/lib.js.map +4 -4
  57. package/package.json +9 -7
  58. package/src/cli/build.ts +3 -3
  59. package/src/cli/init/index.ts +20 -11
  60. package/src/cli/init/templates.ts +1 -1
  61. package/src/cli/init/utils.test.ts +11 -20
  62. package/src/cli/init/utils.ts +5 -0
  63. package/src/cli/init/walkthrough.test.ts +92 -11
  64. package/src/cli/init/walkthrough.ts +71 -16
  65. package/src/cli/monitor.ts +1 -1
  66. package/src/cli.ts +4 -2
  67. package/src/fixtures/data/create-pod.json +1 -1
  68. package/src/fixtures/data/delete-pod.json +1 -1
  69. package/src/lib/adjudicators.test.ts +1232 -0
  70. package/src/lib/adjudicators.ts +235 -0
  71. package/src/lib/assets/index.ts +1 -1
  72. package/src/lib/assets/loader.ts +1 -0
  73. package/src/lib/assets/webhooks.ts +1 -1
  74. package/src/lib/capability.test.ts +655 -0
  75. package/src/lib/capability.ts +104 -11
  76. package/src/lib/controller/index.ts +7 -4
  77. package/src/lib/controller/store.test.ts +131 -0
  78. package/src/lib/controller/store.ts +43 -5
  79. package/src/lib/filter.test.ts +194 -8
  80. package/src/lib/filter.ts +46 -107
  81. package/src/lib/finalizer.test.ts +236 -0
  82. package/src/lib/finalizer.ts +63 -0
  83. package/src/lib/helpers.test.ts +329 -69
  84. package/src/lib/helpers.ts +141 -100
  85. package/src/lib/k8s.ts +4 -0
  86. package/src/lib/module.ts +3 -3
  87. package/src/lib/mutate-processor.ts +5 -4
  88. package/src/lib/mutate-request.test.ts +1 -2
  89. package/src/lib/mutate-request.ts +1 -3
  90. package/src/lib/schedule.ts +1 -1
  91. package/src/lib/storage.ts +5 -6
  92. package/src/lib/types.ts +151 -5
  93. package/src/lib/validate-processor.ts +5 -2
  94. package/src/lib/validate-request.test.ts +1 -4
  95. package/src/lib/validate-request.ts +1 -1
  96. package/src/lib/watch-processor.ts +19 -5
@@ -1,37 +1,38 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
3
 
4
- import { Binding, CapabilityExport } from "./types";
4
+ import { Binding, CapabilityExport, Event } from "./types";
5
5
  import {
6
- createRBACMap,
7
6
  addVerbIfNotExists,
8
- checkOverlap,
7
+ bindingAndCapabilityNSConflict,
8
+ createDirectoryIfNotExists,
9
+ createRBACMap,
10
+ checkDeploymentStatus,
9
11
  filterNoMatchReason,
12
+ dedent,
13
+ generateWatchNamespaceError,
14
+ hasAnyOverlap,
15
+ hasEveryOverlap,
16
+ ignoredNamespaceConflict,
17
+ matchesRegex,
18
+ namespaceDeploymentsReady,
19
+ namespaceComplianceValidator,
20
+ parseTimeout,
21
+ replaceString,
22
+ secretOverLimit,
10
23
  validateHash,
11
- ValidationError,
12
24
  validateCapabilityNames,
25
+ ValidationError,
13
26
  } from "./helpers";
14
27
  import { sanitizeResourceName } from "../sdk/sdk";
15
28
  import * as fc from "fast-check";
16
29
  import { expect, describe, test, jest, beforeEach, afterEach } from "@jest/globals";
17
- import { parseTimeout, secretOverLimit, replaceString } from "./helpers";
18
30
  import { promises as fs } from "fs";
19
-
20
- import {
21
- createDirectoryIfNotExists,
22
- hasAnyOverlap,
23
- hasEveryOverlap,
24
- ignoredNamespaceConflict,
25
- bindingAndCapabilityNSConflict,
26
- generateWatchNamespaceError,
27
- namespaceComplianceValidator,
28
- dedent,
29
- } from "./helpers";
30
31
  import { SpiedFunction } from "jest-mock";
31
-
32
- import { K8s, GenericClass, KubernetesObject } from "kubernetes-fluent-client";
32
+ import { K8s, GenericClass, KubernetesObject, kind } from "kubernetes-fluent-client";
33
33
  import { K8sInit } from "kubernetes-fluent-client/dist/fluent/types";
34
- import { checkDeploymentStatus, namespaceDeploymentsReady } from "./helpers";
34
+
35
+ export const callback = () => undefined;
35
36
 
36
37
  jest.mock("kubernetes-fluent-client", () => {
37
38
  return {
@@ -336,6 +337,7 @@ describe("validateCapabilityNames", () => {
336
337
  expect(validateCapabilityNames(undefined)).toBe(undefined);
337
338
  });
338
339
  });
340
+
339
341
  describe("createRBACMap", () => {
340
342
  test("should return the correct RBACMap for given capabilities", () => {
341
343
  const result = createRBACMap(mockCapabilities);
@@ -606,8 +608,75 @@ describe("namespaceComplianceValidator", () => {
606
608
  afterEach(() => {
607
609
  errorSpy.mockRestore();
608
610
  });
609
-
610
- test("should not throw an error for invalid namespaces", () => {
611
+ test("should throw error for invalid regex namespaces", () => {
612
+ const nsViolationCapability: CapabilityExport = {
613
+ ...nonNsViolation[0],
614
+ bindings: nonNsViolation[0].bindings.map(binding => ({
615
+ ...binding,
616
+ filters: {
617
+ ...binding.filters,
618
+ namespaces: [],
619
+ regexNamespaces: [new RegExp(/^system/).source],
620
+ },
621
+ })),
622
+ };
623
+ expect(() => {
624
+ namespaceComplianceValidator(nsViolationCapability);
625
+ }).toThrowError(
626
+ `Ignoring Watch Callback: Object namespace does not match any capability namespace with regex ${nsViolationCapability.bindings[0].filters.regexNamespaces[0]}.`,
627
+ );
628
+ });
629
+ test("should not throw an error for valid regex namespaces", () => {
630
+ const nonnsViolationCapability: CapabilityExport = {
631
+ ...nonNsViolation[0],
632
+ bindings: nonNsViolation[0].bindings.map(binding => ({
633
+ ...binding,
634
+ filters: {
635
+ ...binding.filters,
636
+ namespaces: [],
637
+ regexNamespaces: [new RegExp(/^mia/).source],
638
+ },
639
+ })),
640
+ };
641
+ expect(() => {
642
+ namespaceComplianceValidator(nonnsViolationCapability);
643
+ }).not.toThrow();
644
+ });
645
+ test("should throw error for invalid regex ignored namespaces", () => {
646
+ const nsViolationCapability: CapabilityExport = {
647
+ ...nonNsViolation[0],
648
+ bindings: nonNsViolation[0].bindings.map(binding => ({
649
+ ...binding,
650
+ filters: {
651
+ ...binding.filters,
652
+ namespaces: [],
653
+ regexNamespaces: [new RegExp(/^mia/).source],
654
+ },
655
+ })),
656
+ };
657
+ expect(() => {
658
+ namespaceComplianceValidator(nsViolationCapability, ["miami"]);
659
+ }).toThrowError(
660
+ `Ignoring Watch Callback: Regex namespace: ${nsViolationCapability.bindings[0].filters.regexNamespaces[0]}, is an ignored namespace: miami.`,
661
+ );
662
+ });
663
+ test("should not throw an error for valid regex ignored namespaces", () => {
664
+ const nonnsViolationCapability: CapabilityExport = {
665
+ ...nonNsViolation[0],
666
+ bindings: nonNsViolation[0].bindings.map(binding => ({
667
+ ...binding,
668
+ filters: {
669
+ ...binding.filters,
670
+ namespaces: [],
671
+ regexNamespaces: [new RegExp(/^mia/).source],
672
+ },
673
+ })),
674
+ };
675
+ expect(() => {
676
+ namespaceComplianceValidator(nonnsViolationCapability, ["Seattle"]);
677
+ }).not.toThrow();
678
+ });
679
+ test("should not throw an error for valid namespaces", () => {
611
680
  expect(() => {
612
681
  namespaceComplianceValidator(nonNsViolation[0]);
613
682
  }).not.toThrow();
@@ -998,49 +1067,109 @@ describe("replaceString", () => {
998
1067
  });
999
1068
  });
1000
1069
 
1001
- describe("checkOverlap", () => {
1002
- test("should return false since all binding annotations/labels do not exist on the object", () => {
1003
- expect(checkOverlap({ key1: "", key2: "" }, { key1: "something" })).toBe(false);
1004
- });
1005
- test("should return false since all binding annotations/labels values do not match on the object values", () => {
1006
- expect(checkOverlap({ key1: "key1", key2: "key2" }, { key1: "value1", key2: "key2" })).toBe(false);
1007
- });
1008
- test("should return true since all binding annotations/labels keys and values match the object keys and values", () => {
1009
- expect(checkOverlap({ key1: "key1", key2: "key2" }, { key1: "key1", key2: "key2" })).toBe(true);
1010
- });
1011
-
1012
- test("should return true since all binding annotations/labels keys exist on the object", () => {
1013
- expect(checkOverlap({ key1: "", key2: "" }, { key1: "key1", key2: "key2" })).toBe(true);
1014
- });
1015
-
1016
- test("(Mixed) should return true since key and key value match on object", () => {
1017
- expect(checkOverlap({ key1: "one", key2: "" }, { key1: "one", key2: "something" })).toBe(true);
1018
- });
1019
- test("(Mixed) should return false since key1 value is different on object", () => {
1020
- expect(checkOverlap({ key1: "one", key2: "" }, { key1: "different", key2: "" })).toBe(false);
1021
- });
1022
- test("should return true if binding has no labels or annotations", () => {
1023
- expect(checkOverlap({}, { key1: "value1" })).toBe(true);
1024
- });
1025
-
1026
- test("should return false if there is no overlap", () => {
1027
- expect(checkOverlap({ key1: "value1" }, { key2: "value2" })).toBe(false);
1070
+ describe("filterNoMatchReason", () => {
1071
+ test("returns regex namespace filter error for Pods whos namespace does not match the regex", () => {
1072
+ const binding = {
1073
+ kind: { kind: "Pod" },
1074
+ filters: { regexNamespaces: ["(.*)-system"], namespaces: [] },
1075
+ };
1076
+ const obj = { metadata: { namespace: "pepr-demo" } };
1077
+ const objArray = [
1078
+ { ...obj },
1079
+ { ...obj, metadata: { namespace: "pepr-uds" } },
1080
+ { ...obj, metadata: { namespace: "pepr-core" } },
1081
+ { ...obj, metadata: { namespace: "uds-ns" } },
1082
+ { ...obj, metadata: { namespace: "uds" } },
1083
+ ];
1084
+ const capabilityNamespaces: string[] = [];
1085
+ objArray.map(object => {
1086
+ const result = filterNoMatchReason(
1087
+ binding as unknown as Partial<Binding>,
1088
+ object as unknown as Partial<KubernetesObject>,
1089
+ capabilityNamespaces,
1090
+ );
1091
+ expect(result).toEqual(
1092
+ `Ignoring Watch Callback: Binding defines namespace regexes '["(.*)-system"]' but Object carries '${object?.metadata?.namespace}'.`,
1093
+ );
1094
+ });
1028
1095
  });
1029
1096
 
1030
- test("should return true since object has key1 and value1", () => {
1031
- expect(checkOverlap({ key1: "value1" }, { key1: "value1", key2: "value2" })).toBe(true);
1097
+ test("returns no regex namespace filter error for Pods whos namespace does match the regex", () => {
1098
+ const binding = {
1099
+ kind: { kind: "Pod" },
1100
+ filters: { regexNamespaces: [/(.*)-system/], namespaces: [] },
1101
+ };
1102
+ const obj = { metadata: { namespace: "pepr-demo" } };
1103
+ const objArray = [
1104
+ { ...obj, metadata: { namespace: "pepr-system" } },
1105
+ { ...obj, metadata: { namespace: "pepr-uds-system" } },
1106
+ { ...obj, metadata: { namespace: "uds-system" } },
1107
+ { ...obj, metadata: { namespace: "some-thing-that-is-a-system" } },
1108
+ { ...obj, metadata: { namespace: "your-system" } },
1109
+ ];
1110
+ const capabilityNamespaces: string[] = [];
1111
+ objArray.map(object => {
1112
+ const result = filterNoMatchReason(
1113
+ binding as unknown as Partial<Binding>,
1114
+ object as unknown as Partial<KubernetesObject>,
1115
+ capabilityNamespaces,
1116
+ );
1117
+ expect(result).toEqual(``);
1118
+ });
1032
1119
  });
1033
1120
 
1034
- test("should return false since object value does not match binding value", () => {
1035
- expect(checkOverlap({ key1: "value1" }, { key1: "value2" })).toBe(false);
1121
+ // Names Fail
1122
+ test("returns regex name filter error for Pods whos name does not match the regex", () => {
1123
+ const binding = {
1124
+ kind: { kind: "Pod" },
1125
+ filters: { regexName: "^system", namespaces: [] },
1126
+ };
1127
+ const obj = { metadata: { name: "pepr-demo" } };
1128
+ const objArray = [
1129
+ { ...obj },
1130
+ { ...obj, metadata: { name: "pepr-uds" } },
1131
+ { ...obj, metadata: { name: "pepr-core" } },
1132
+ { ...obj, metadata: { name: "uds-ns" } },
1133
+ { ...obj, metadata: { name: "uds" } },
1134
+ ];
1135
+ const capabilityNamespaces: string[] = [];
1136
+ objArray.map(object => {
1137
+ const result = filterNoMatchReason(
1138
+ binding as unknown as Partial<Binding>,
1139
+ object as unknown as Partial<KubernetesObject>,
1140
+ capabilityNamespaces,
1141
+ );
1142
+ expect(result).toEqual(
1143
+ `Ignoring Watch Callback: Binding defines name regex '^system' but Object carries '${object?.metadata?.name}'.`,
1144
+ );
1145
+ });
1036
1146
  });
1037
1147
 
1038
- test("should return true if the object has no labels and neither does the binding", () => {
1039
- expect(checkOverlap({}, {})).toBe(true);
1148
+ // Names Pass
1149
+ test("returns no regex name filter error for Pods whos name does match the regex", () => {
1150
+ const binding = {
1151
+ kind: { kind: "Pod" },
1152
+ filters: { regexName: /^system/, namespaces: [] },
1153
+ };
1154
+ const obj = { metadata: { name: "pepr-demo" } };
1155
+ const objArray = [
1156
+ { ...obj, metadata: { name: "systemd" } },
1157
+ { ...obj, metadata: { name: "systemic" } },
1158
+ { ...obj, metadata: { name: "system-of-kube-apiserver" } },
1159
+ { ...obj, metadata: { name: "system" } },
1160
+ { ...obj, metadata: { name: "system-uds" } },
1161
+ ];
1162
+ const capabilityNamespaces: string[] = [];
1163
+ objArray.map(object => {
1164
+ const result = filterNoMatchReason(
1165
+ binding as unknown as Partial<Binding>,
1166
+ object as unknown as Partial<KubernetesObject>,
1167
+ capabilityNamespaces,
1168
+ );
1169
+ expect(result).toEqual(``);
1170
+ });
1040
1171
  });
1041
- });
1042
1172
 
1043
- describe("filterMatcher", () => {
1044
1173
  test("returns namespace filter error for namespace objects with namespace filters", () => {
1045
1174
  const binding = {
1046
1175
  kind: { kind: "Namespace" },
@@ -1053,7 +1182,40 @@ describe("filterMatcher", () => {
1053
1182
  obj as unknown as Partial<KubernetesObject>,
1054
1183
  capabilityNamespaces,
1055
1184
  );
1056
- expect(result).toEqual("Ignoring Watch Callback: Cannot use a namespace filter in a namespace object.");
1185
+ expect(result).toEqual("Ignoring Watch Callback: Cannot use namespace filter on a namespace object.");
1186
+ });
1187
+
1188
+ test("return an Ignoring Watch Callback string if the binding name and object name are different", () => {
1189
+ const binding = {
1190
+ filters: { name: "pepr" },
1191
+ };
1192
+ const obj = {
1193
+ metadata: {
1194
+ name: "not-pepr",
1195
+ },
1196
+ };
1197
+ const capabilityNamespaces: string[] = [];
1198
+ const result = filterNoMatchReason(
1199
+ binding as unknown as Partial<Binding>,
1200
+ obj as unknown as Partial<KubernetesObject>,
1201
+ capabilityNamespaces,
1202
+ );
1203
+ expect(result).toEqual(`Ignoring Watch Callback: Binding defines name 'pepr' but Object carries 'not-pepr'.`);
1204
+ });
1205
+ test("returns no Ignoring Watch Callback string if the binding name and object name are the same", () => {
1206
+ const binding = {
1207
+ filters: { name: "pepr" },
1208
+ };
1209
+ const obj = {
1210
+ metadata: { name: "pepr" },
1211
+ };
1212
+ const capabilityNamespaces: string[] = [];
1213
+ const result = filterNoMatchReason(
1214
+ binding as unknown as Partial<Binding>,
1215
+ obj as unknown as Partial<KubernetesObject>,
1216
+ capabilityNamespaces,
1217
+ );
1218
+ expect(result).toEqual("");
1057
1219
  });
1058
1220
 
1059
1221
  test("return deletionTimestamp error when there is no deletionTimestamp in the object", () => {
@@ -1069,7 +1231,7 @@ describe("filterMatcher", () => {
1069
1231
  obj as unknown as Partial<KubernetesObject>,
1070
1232
  capabilityNamespaces,
1071
1233
  );
1072
- expect(result).toEqual("Ignoring Watch Callback: Object does not have a deletion timestamp.");
1234
+ expect(result).toEqual("Ignoring Watch Callback: Binding defines deletionTimestamp but Object does not carry it.");
1073
1235
  });
1074
1236
 
1075
1237
  test("return no deletionTimestamp error when there is a deletionTimestamp in the object", () => {
@@ -1087,7 +1249,7 @@ describe("filterMatcher", () => {
1087
1249
  obj as unknown as Partial<KubernetesObject>,
1088
1250
  capabilityNamespaces,
1089
1251
  );
1090
- expect(result).not.toEqual("Ignoring Watch Callback: Object does not have a deletion timestamp.");
1252
+ expect(result).not.toEqual("Ignoring Watch Callback: Binding defines deletionTimestamp Object does not carry it.");
1091
1253
  });
1092
1254
 
1093
1255
  test("returns label overlap error when there is no overlap between binding and object labels", () => {
@@ -1104,7 +1266,7 @@ describe("filterMatcher", () => {
1104
1266
  capabilityNamespaces,
1105
1267
  );
1106
1268
  expect(result).toEqual(
1107
- 'Ignoring Watch Callback: No overlap between binding and object labels. Binding labels {"key":"value"}, Object Labels {"anotherKey":"anotherValue"}.',
1269
+ `Ignoring Watch Callback: Binding defines labels '{"key":"value"}' but Object carries '{"anotherKey":"anotherValue"}'.`,
1108
1270
  );
1109
1271
  });
1110
1272
 
@@ -1122,29 +1284,48 @@ describe("filterMatcher", () => {
1122
1284
  capabilityNamespaces,
1123
1285
  );
1124
1286
  expect(result).toEqual(
1125
- 'Ignoring Watch Callback: No overlap between binding and object annotations. Binding annotations {"key":"value"}, Object annotations {"anotherKey":"anotherValue"}.',
1287
+ `Ignoring Watch Callback: Binding defines annotations '{"key":"value"}' but Object carries '{"anotherKey":"anotherValue"}'.`,
1126
1288
  );
1127
1289
  });
1128
1290
 
1129
1291
  test("returns capability namespace error when object is not in capability namespaces", () => {
1130
- const binding = {};
1292
+ const binding = {
1293
+ model: kind.Pod,
1294
+ event: Event.Any,
1295
+ kind: {
1296
+ group: "",
1297
+ version: "v1",
1298
+ kind: "Pod",
1299
+ },
1300
+ filters: {
1301
+ name: "bleh",
1302
+ namespaces: [],
1303
+ regexNamespaces: [],
1304
+ regexName: "",
1305
+ labels: {},
1306
+ annotations: {},
1307
+ deletionTimestamp: false,
1308
+ },
1309
+ callback,
1310
+ };
1311
+
1131
1312
  const obj = {
1132
- metadata: { namespace: "ns2" },
1313
+ metadata: { namespace: "ns2", name: "bleh" },
1133
1314
  };
1134
1315
  const capabilityNamespaces = ["ns1"];
1135
1316
  const result = filterNoMatchReason(
1136
- binding as unknown as Partial<Binding>,
1317
+ binding as Binding,
1137
1318
  obj as unknown as Partial<KubernetesObject>,
1138
1319
  capabilityNamespaces,
1139
1320
  );
1140
1321
  expect(result).toEqual(
1141
- "Ignoring Watch Callback: Object is not in the capability namespace. Capability namespaces: ns1, Object namespace: ns2.",
1322
+ `Ignoring Watch Callback: Object carries namespace 'ns2' but namespaces allowed by Capability are '["ns1"]'.`,
1142
1323
  );
1143
1324
  });
1144
1325
 
1145
1326
  test("returns binding namespace error when filter namespace is not part of capability namespaces", () => {
1146
1327
  const binding = {
1147
- filters: { namespaces: ["ns3"] },
1328
+ filters: { namespaces: ["ns3"], regexNamespaces: [] },
1148
1329
  };
1149
1330
  const obj = {};
1150
1331
  const capabilityNamespaces = ["ns1", "ns2"];
@@ -1154,13 +1335,13 @@ describe("filterMatcher", () => {
1154
1335
  capabilityNamespaces,
1155
1336
  );
1156
1337
  expect(result).toEqual(
1157
- "Ignoring Watch Callback: Binding namespace is not part of capability namespaces. Capability namespaces: ns1, ns2, Binding namespaces: ns3.",
1338
+ `Ignoring Watch Callback: Binding defines namespaces ["ns3"] but namespaces allowed by Capability are '["ns1","ns2"]'.`,
1158
1339
  );
1159
1340
  });
1160
1341
 
1161
1342
  test("returns binding and object namespace error when they do not overlap", () => {
1162
1343
  const binding = {
1163
- filters: { namespaces: ["ns1"] },
1344
+ filters: { namespaces: ["ns1"], regexNamespaces: [] },
1164
1345
  };
1165
1346
  const obj = {
1166
1347
  metadata: { namespace: "ns2" },
@@ -1171,8 +1352,26 @@ describe("filterMatcher", () => {
1171
1352
  obj as unknown as Partial<KubernetesObject>,
1172
1353
  capabilityNamespaces,
1173
1354
  );
1355
+ expect(result).toEqual(`Ignoring Watch Callback: Binding defines namespaces '["ns1"]' but Object carries 'ns2'.`);
1356
+ });
1357
+
1358
+ test("return watch violation message when object is in an ignored namespace", () => {
1359
+ const binding = {
1360
+ filters: { namespaces: ["ns3"] },
1361
+ };
1362
+ const obj = {
1363
+ metadata: { namespace: "ns3" },
1364
+ };
1365
+ const capabilityNamespaces = ["ns3"];
1366
+ const ignoredNamespaces = ["ns3"];
1367
+ const result = filterNoMatchReason(
1368
+ binding as unknown as Partial<Binding>,
1369
+ obj as unknown as Partial<KubernetesObject>,
1370
+ capabilityNamespaces,
1371
+ ignoredNamespaces,
1372
+ );
1174
1373
  expect(result).toEqual(
1175
- "Ignoring Watch Callback: Binding namespace and object namespace are not the same. Binding namespaces: ns1, Object namespace: ns2.",
1374
+ `Ignoring Watch Callback: Object carries namespace 'ns3' but ignored namespaces include '["ns3"]'.`,
1176
1375
  );
1177
1376
  });
1178
1377
 
@@ -1224,3 +1423,64 @@ describe("validateHash", () => {
1224
1423
  expect(() => validateHash(validHash)).not.toThrow();
1225
1424
  });
1226
1425
  });
1426
+
1427
+ describe("matchesRegex", () => {
1428
+ test("should return true for a valid pattern that matches the string", () => {
1429
+ const pattern = /abc/;
1430
+ const testString = "abc123";
1431
+ const result = matchesRegex(new RegExp(pattern).source, testString);
1432
+ expect(result).toBe(true);
1433
+ });
1434
+
1435
+ test("should return false for a valid pattern that does not match the string", () => {
1436
+ const pattern = /xyz/;
1437
+ const testString = "abc123";
1438
+ const result = matchesRegex(new RegExp(pattern).source, testString);
1439
+ expect(result).toBe(false);
1440
+ });
1441
+
1442
+ test("should return false for an invalid regex pattern", () => {
1443
+ const invalidPattern = new RegExp(/^p/); // Invalid regex with unclosed bracket
1444
+ const testString = "test";
1445
+ const result = matchesRegex(invalidPattern.source, testString);
1446
+ expect(result).toBe(false);
1447
+ });
1448
+
1449
+ test("should return false when pattern is null or undefined", () => {
1450
+ const testString = "abc123";
1451
+ // Check for undefined
1452
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1453
+ expect(matchesRegex(undefined as any, testString)).toBe(false);
1454
+ // Check for null
1455
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1456
+ expect(matchesRegex(null as any, testString)).toBe(false);
1457
+ });
1458
+
1459
+ test("should return true for an empty string matching an empty regex", () => {
1460
+ const pattern = new RegExp("");
1461
+ const testString = "";
1462
+ const result = matchesRegex(new RegExp(pattern).source, testString);
1463
+ expect(result).toBe(true);
1464
+ });
1465
+
1466
+ test("should return false for an empty string and a non-empty regex", () => {
1467
+ const pattern = new RegExp("abc");
1468
+ const testString = "";
1469
+ const result = matchesRegex(new RegExp(pattern).source, testString);
1470
+ expect(result).toBe(false);
1471
+ });
1472
+
1473
+ test("should return true for a complex valid regex that matches", () => {
1474
+ const pattern = /^[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[A-Za-z]+$/;
1475
+ const testString = "test@example.com";
1476
+ const result = matchesRegex(new RegExp(pattern).source, testString);
1477
+ expect(result).toBe(true);
1478
+ });
1479
+
1480
+ test("should return false for a complex valid regex that does not match", () => {
1481
+ const pattern = /^[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[A-Za-z]+$/;
1482
+ const testString = "invalid-email.com";
1483
+ const result = matchesRegex(new RegExp(pattern).source, testString);
1484
+ expect(result).toBe(false);
1485
+ });
1486
+ });