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.
Files changed (107) 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/logger.d.ts +1 -1
  39. package/dist/lib/logger.d.ts.map +1 -1
  40. package/dist/lib/module.d.ts +2 -1
  41. package/dist/lib/module.d.ts.map +1 -1
  42. package/dist/lib/mutate-processor.d.ts +2 -1
  43. package/dist/lib/mutate-processor.d.ts.map +1 -1
  44. package/dist/lib/mutate-request.d.ts +1 -2
  45. package/dist/lib/mutate-request.d.ts.map +1 -1
  46. package/dist/lib/queue.d.ts +19 -3
  47. package/dist/lib/queue.d.ts.map +1 -1
  48. package/dist/lib/schedule.d.ts +1 -2
  49. package/dist/lib/schedule.d.ts.map +1 -1
  50. package/dist/lib/storage.d.ts.map +1 -1
  51. package/dist/lib/types.d.ts +118 -6
  52. package/dist/lib/types.d.ts.map +1 -1
  53. package/dist/lib/validate-processor.d.ts +4 -2
  54. package/dist/lib/validate-processor.d.ts.map +1 -1
  55. package/dist/lib/validate-request.d.ts +1 -1
  56. package/dist/lib/validate-request.d.ts.map +1 -1
  57. package/dist/lib/watch-processor.d.ts +8 -6
  58. package/dist/lib/watch-processor.d.ts.map +1 -1
  59. package/dist/lib.js +467 -233
  60. package/dist/lib.js.map +4 -4
  61. package/dist/sdk/sdk.d.ts +5 -3
  62. package/dist/sdk/sdk.d.ts.map +1 -1
  63. package/package.json +13 -11
  64. package/src/cli/build.ts +3 -3
  65. package/src/cli/init/index.ts +20 -11
  66. package/src/cli/init/templates.ts +1 -1
  67. package/src/cli/init/utils.test.ts +11 -20
  68. package/src/cli/init/utils.ts +5 -0
  69. package/src/cli/init/walkthrough.test.ts +92 -11
  70. package/src/cli/init/walkthrough.ts +71 -16
  71. package/src/cli/monitor.ts +1 -1
  72. package/src/cli.ts +4 -2
  73. package/src/fixtures/data/create-pod.json +1 -1
  74. package/src/fixtures/data/delete-pod.json +1 -1
  75. package/src/lib/adjudicators.test.ts +1232 -0
  76. package/src/lib/adjudicators.ts +235 -0
  77. package/src/lib/assets/index.ts +1 -1
  78. package/src/lib/assets/loader.ts +1 -0
  79. package/src/lib/assets/webhooks.ts +1 -1
  80. package/src/lib/capability.test.ts +655 -0
  81. package/src/lib/capability.ts +112 -11
  82. package/src/lib/controller/index.ts +7 -4
  83. package/src/lib/controller/store.test.ts +131 -0
  84. package/src/lib/controller/store.ts +43 -5
  85. package/src/lib/filter.test.ts +279 -9
  86. package/src/lib/filter.ts +46 -98
  87. package/src/lib/finalizer.test.ts +236 -0
  88. package/src/lib/finalizer.ts +63 -0
  89. package/src/lib/helpers.test.ts +359 -65
  90. package/src/lib/helpers.ts +141 -95
  91. package/src/lib/k8s.ts +4 -0
  92. package/src/lib/module.ts +3 -3
  93. package/src/lib/mutate-processor.ts +5 -4
  94. package/src/lib/mutate-request.test.ts +1 -2
  95. package/src/lib/mutate-request.ts +1 -3
  96. package/src/lib/queue.test.ts +138 -44
  97. package/src/lib/queue.ts +48 -13
  98. package/src/lib/schedule.ts +1 -1
  99. package/src/lib/storage.ts +5 -6
  100. package/src/lib/types.ts +154 -5
  101. package/src/lib/validate-processor.ts +5 -2
  102. package/src/lib/validate-request.test.ts +1 -4
  103. package/src/lib/validate-request.ts +1 -1
  104. package/src/lib/watch-processor.test.ts +89 -124
  105. package/src/lib/watch-processor.ts +52 -35
  106. package/src/sdk/sdk.test.ts +46 -13
  107. package/src/sdk/sdk.ts +15 -6
@@ -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,62 +1067,189 @@ 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);
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
+ });
1010
1095
  });
1011
1096
 
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);
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
+ });
1014
1119
  });
1015
1120
 
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);
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
+ });
1024
1146
  });
1025
1147
 
1026
- test("should return false if there is no overlap", () => {
1027
- expect(checkOverlap({ key1: "value1" }, { key2: "value2" })).toBe(false);
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
+ });
1028
1171
  });
1029
1172
 
1030
- test("should return true since object has key1 and value1", () => {
1031
- expect(checkOverlap({ key1: "value1" }, { key1: "value1", key2: "value2" })).toBe(true);
1173
+ test("returns namespace filter error for namespace objects with namespace filters", () => {
1174
+ const binding = {
1175
+ kind: { kind: "Namespace" },
1176
+ filters: { namespaces: ["ns1"] },
1177
+ };
1178
+ const obj = {};
1179
+ const capabilityNamespaces: string[] = [];
1180
+ const result = filterNoMatchReason(
1181
+ binding as unknown as Partial<Binding>,
1182
+ obj as unknown as Partial<KubernetesObject>,
1183
+ capabilityNamespaces,
1184
+ );
1185
+ expect(result).toEqual("Ignoring Watch Callback: Cannot use namespace filter on a namespace object.");
1032
1186
  });
1033
1187
 
1034
- test("should return false since object value does not match binding value", () => {
1035
- expect(checkOverlap({ key1: "value1" }, { key1: "value2" })).toBe(false);
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("");
1036
1219
  });
1037
1220
 
1038
- test("should return true if the object has no labels and neither does the binding", () => {
1039
- expect(checkOverlap({}, {})).toBe(true);
1221
+ test("return deletionTimestamp error when there is no deletionTimestamp in the object", () => {
1222
+ const binding = {
1223
+ filters: { deletionTimestamp: true },
1224
+ };
1225
+ const obj = {
1226
+ metadata: {},
1227
+ };
1228
+ const capabilityNamespaces: string[] = [];
1229
+ const result = filterNoMatchReason(
1230
+ binding as unknown as Partial<Binding>,
1231
+ obj as unknown as Partial<KubernetesObject>,
1232
+ capabilityNamespaces,
1233
+ );
1234
+ expect(result).toEqual("Ignoring Watch Callback: Binding defines deletionTimestamp but Object does not carry it.");
1040
1235
  });
1041
- });
1042
1236
 
1043
- describe("filterMatcher", () => {
1044
- test("returns namespace filter error for namespace objects with namespace filters", () => {
1237
+ test("return no deletionTimestamp error when there is a deletionTimestamp in the object", () => {
1045
1238
  const binding = {
1046
- kind: { kind: "Namespace" },
1047
- filters: { namespaces: ["ns1"] },
1239
+ filters: { deletionTimestamp: true },
1240
+ };
1241
+ const obj = {
1242
+ metadata: {
1243
+ deletionTimestamp: "2021-01-01T00:00:00Z",
1244
+ },
1048
1245
  };
1049
- const obj = {};
1050
1246
  const capabilityNamespaces: string[] = [];
1051
1247
  const result = filterNoMatchReason(
1052
1248
  binding as unknown as Partial<Binding>,
1053
1249
  obj as unknown as Partial<KubernetesObject>,
1054
1250
  capabilityNamespaces,
1055
1251
  );
1056
- expect(result).toEqual("Ignoring Watch Callback: Cannot use a namespace filter in a namespace object.");
1252
+ expect(result).not.toEqual("Ignoring Watch Callback: Binding defines deletionTimestamp Object does not carry it.");
1057
1253
  });
1058
1254
 
1059
1255
  test("returns label overlap error when there is no overlap between binding and object labels", () => {
@@ -1070,7 +1266,7 @@ describe("filterMatcher", () => {
1070
1266
  capabilityNamespaces,
1071
1267
  );
1072
1268
  expect(result).toEqual(
1073
- '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"}'.`,
1074
1270
  );
1075
1271
  });
1076
1272
 
@@ -1088,29 +1284,48 @@ describe("filterMatcher", () => {
1088
1284
  capabilityNamespaces,
1089
1285
  );
1090
1286
  expect(result).toEqual(
1091
- '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"}'.`,
1092
1288
  );
1093
1289
  });
1094
1290
 
1095
1291
  test("returns capability namespace error when object is not in capability namespaces", () => {
1096
- 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
+
1097
1312
  const obj = {
1098
- metadata: { namespace: "ns2" },
1313
+ metadata: { namespace: "ns2", name: "bleh" },
1099
1314
  };
1100
1315
  const capabilityNamespaces = ["ns1"];
1101
1316
  const result = filterNoMatchReason(
1102
- binding as unknown as Partial<Binding>,
1317
+ binding as Binding,
1103
1318
  obj as unknown as Partial<KubernetesObject>,
1104
1319
  capabilityNamespaces,
1105
1320
  );
1106
1321
  expect(result).toEqual(
1107
- "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"]'.`,
1108
1323
  );
1109
1324
  });
1110
1325
 
1111
1326
  test("returns binding namespace error when filter namespace is not part of capability namespaces", () => {
1112
1327
  const binding = {
1113
- filters: { namespaces: ["ns3"] },
1328
+ filters: { namespaces: ["ns3"], regexNamespaces: [] },
1114
1329
  };
1115
1330
  const obj = {};
1116
1331
  const capabilityNamespaces = ["ns1", "ns2"];
@@ -1120,13 +1335,13 @@ describe("filterMatcher", () => {
1120
1335
  capabilityNamespaces,
1121
1336
  );
1122
1337
  expect(result).toEqual(
1123
- "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"]'.`,
1124
1339
  );
1125
1340
  });
1126
1341
 
1127
1342
  test("returns binding and object namespace error when they do not overlap", () => {
1128
1343
  const binding = {
1129
- filters: { namespaces: ["ns1"] },
1344
+ filters: { namespaces: ["ns1"], regexNamespaces: [] },
1130
1345
  };
1131
1346
  const obj = {
1132
1347
  metadata: { namespace: "ns2" },
@@ -1137,8 +1352,26 @@ describe("filterMatcher", () => {
1137
1352
  obj as unknown as Partial<KubernetesObject>,
1138
1353
  capabilityNamespaces,
1139
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
+ );
1140
1373
  expect(result).toEqual(
1141
- "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"]'.`,
1142
1375
  );
1143
1376
  });
1144
1377
 
@@ -1190,3 +1423,64 @@ describe("validateHash", () => {
1190
1423
  expect(() => validateHash(validHash)).not.toThrow();
1191
1424
  });
1192
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
+ });