pepr 0.34.1 → 0.36.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 (40) hide show
  1. package/dist/cli/init/templates.d.ts +1 -0
  2. package/dist/cli/init/templates.d.ts.map +1 -1
  3. package/dist/cli.js +48 -23
  4. package/dist/controller.js +1 -1
  5. package/dist/lib/assets/helm.d.ts.map +1 -1
  6. package/dist/lib/assets/pods.d.ts.map +1 -1
  7. package/dist/lib/assets/yaml.d.ts.map +1 -1
  8. package/dist/lib/capability.d.ts.map +1 -1
  9. package/dist/lib/filter.d.ts.map +1 -1
  10. package/dist/lib/helpers.d.ts.map +1 -1
  11. package/dist/lib/logger.d.ts +1 -1
  12. package/dist/lib/logger.d.ts.map +1 -1
  13. package/dist/lib/queue.d.ts +19 -3
  14. package/dist/lib/queue.d.ts.map +1 -1
  15. package/dist/lib/types.d.ts +3 -0
  16. package/dist/lib/types.d.ts.map +1 -1
  17. package/dist/lib/watch-processor.d.ts +10 -1
  18. package/dist/lib/watch-processor.d.ts.map +1 -1
  19. package/dist/lib.js +104 -32
  20. package/dist/lib.js.map +3 -3
  21. package/dist/sdk/sdk.d.ts +5 -3
  22. package/dist/sdk/sdk.d.ts.map +1 -1
  23. package/package.json +10 -10
  24. package/src/cli/init/templates.ts +1 -0
  25. package/src/lib/assets/helm.ts +4 -16
  26. package/src/lib/assets/pods.ts +4 -0
  27. package/src/lib/assets/yaml.ts +32 -0
  28. package/src/lib/capability.ts +9 -1
  29. package/src/lib/filter.test.ts +85 -1
  30. package/src/lib/filter.ts +9 -0
  31. package/src/lib/helpers.test.ts +34 -0
  32. package/src/lib/helpers.ts +5 -0
  33. package/src/lib/queue.test.ts +138 -44
  34. package/src/lib/queue.ts +48 -13
  35. package/src/lib/types.ts +3 -0
  36. package/src/lib/watch-processor.test.ts +101 -5
  37. package/src/lib/watch-processor.ts +49 -16
  38. package/src/sdk/sdk.test.ts +46 -13
  39. package/src/sdk/sdk.ts +15 -6
  40. package/src/templates/capabilities/hello-pepr.ts +9 -0
package/dist/lib.js CHANGED
@@ -244,6 +244,12 @@ function shouldSkipRequest(binding, req, capabilityNamespaces) {
244
244
  const srcObject = operation === "DELETE" /* DELETE */ ? req.oldObject : req.object;
245
245
  const { metadata } = srcObject || {};
246
246
  const combinedNamespaces = [...namespaces, ...capabilityNamespaces];
247
+ if (binding.event.includes("DELETE" /* Delete */) && binding.filters?.deletionTimestamp) {
248
+ return true;
249
+ }
250
+ if (binding.filters?.deletionTimestamp && !req.object.metadata?.deletionTimestamp) {
251
+ return true;
252
+ }
247
253
  if (!binding.event.includes(operation) && !binding.event.includes("*" /* Any */)) {
248
254
  return true;
249
255
  }
@@ -1154,14 +1160,17 @@ async function writeEvent(cr, event, eventType, eventReason, reportingComponent,
1154
1160
  reportingInstance
1155
1161
  });
1156
1162
  }
1157
- function getOwnerRefFrom(cr) {
1158
- const { name, uid } = cr.metadata;
1163
+ function getOwnerRefFrom(customResource, blockOwnerDeletion, controller) {
1164
+ const { apiVersion, kind: kind4, metadata } = customResource;
1165
+ const { name, uid } = metadata;
1159
1166
  return [
1160
1167
  {
1161
- apiVersion: cr.apiVersion,
1162
- kind: cr.kind,
1168
+ apiVersion,
1169
+ kind: kind4,
1163
1170
  uid,
1164
- name
1171
+ name,
1172
+ ...blockOwnerDeletion !== void 0 && { blockOwnerDeletion },
1173
+ ...controller !== void 0 && { controller }
1165
1174
  }
1166
1175
  ];
1167
1176
  }
@@ -1189,6 +1198,9 @@ function checkOverlap(bindingFilters, objectFilters) {
1189
1198
  return matchCount === Object.keys(bindingFilters).length;
1190
1199
  }
1191
1200
  function filterNoMatchReason(binding, obj, capabilityNamespaces) {
1201
+ if (binding.filters?.deletionTimestamp && !obj.metadata?.deletionTimestamp) {
1202
+ return `Ignoring Watch Callback: Object does not have a deletion timestamp.`;
1203
+ }
1192
1204
  if (binding.kind && binding.kind.kind === "Namespace" && binding.filters && binding.filters.namespaces.length !== 0) {
1193
1205
  return `Ignoring Watch Callback: Cannot use a namespace filter in a namespace object.`;
1194
1206
  }
@@ -1223,27 +1235,49 @@ function filterNoMatchReason(binding, obj, capabilityNamespaces) {
1223
1235
  }
1224
1236
 
1225
1237
  // src/lib/queue.ts
1238
+ var import_node_crypto = require("node:crypto");
1226
1239
  var Queue = class {
1240
+ #name;
1241
+ #uid;
1227
1242
  #queue = [];
1228
1243
  #pendingPromise = false;
1229
- #reconcile;
1230
- constructor() {
1231
- this.#reconcile = async () => await new Promise((resolve) => resolve());
1244
+ constructor(name) {
1245
+ this.#name = name;
1246
+ this.#uid = `${Date.now()}-${(0, import_node_crypto.randomBytes)(2).toString("hex")}`;
1232
1247
  }
1233
- setReconcile(reconcile) {
1234
- this.#reconcile = reconcile;
1248
+ label() {
1249
+ return { name: this.#name, uid: this.#uid };
1250
+ }
1251
+ stats() {
1252
+ return {
1253
+ queue: this.label(),
1254
+ stats: {
1255
+ length: this.#queue.length
1256
+ }
1257
+ };
1235
1258
  }
1236
1259
  /**
1237
1260
  * Enqueue adds an item to the queue and returns a promise that resolves when the item is
1238
1261
  * reconciled.
1239
1262
  *
1240
1263
  * @param item The object to reconcile
1264
+ * @param type The watch phase requested for reconcile
1265
+ * @param reconcile The callback to enqueue for reconcile
1241
1266
  * @returns A promise that resolves when the object is reconciled
1242
1267
  */
1243
- enqueue(item, type) {
1244
- logger_default.debug(`Enqueueing ${item.metadata.namespace}/${item.metadata.name}`);
1268
+ enqueue(item, phase, reconcile) {
1269
+ const note = {
1270
+ queue: this.label(),
1271
+ item: {
1272
+ name: item.metadata?.name,
1273
+ namespace: item.metadata?.namespace,
1274
+ resourceVersion: item.metadata?.resourceVersion
1275
+ }
1276
+ };
1277
+ logger_default.debug(note, "Enqueueing");
1245
1278
  return new Promise((resolve, reject) => {
1246
- this.#queue.push({ item, type, resolve, reject });
1279
+ this.#queue.push({ item, phase, callback: reconcile, resolve, reject });
1280
+ logger_default.debug(this.stats(), "Queue stats - push");
1247
1281
  return this.#dequeue();
1248
1282
  });
1249
1283
  }
@@ -1264,15 +1298,23 @@ var Queue = class {
1264
1298
  }
1265
1299
  try {
1266
1300
  this.#pendingPromise = true;
1267
- if (this.#reconcile) {
1268
- logger_default.debug(`Reconciling ${element.item.metadata.name}`);
1269
- await this.#reconcile(element.item, element.type);
1270
- }
1301
+ const note = {
1302
+ queue: this.label(),
1303
+ item: {
1304
+ name: element.item.metadata?.name,
1305
+ namespace: element.item.metadata?.namespace,
1306
+ resourceVersion: element.item.metadata?.resourceVersion
1307
+ }
1308
+ };
1309
+ logger_default.debug(note, "Reconciling");
1310
+ await element.callback(element.item, element.phase);
1311
+ logger_default.debug(note, "Reconciled");
1271
1312
  element.resolve();
1272
1313
  } catch (e) {
1273
1314
  logger_default.debug(`Error reconciling ${element.item.metadata.name}`, { error: e });
1274
1315
  element.reject(e);
1275
1316
  } finally {
1317
+ logger_default.debug(this.stats(), "Queue stats - shift");
1276
1318
  logger_default.debug("Resetting pending promise and dequeuing");
1277
1319
  this.#pendingPromise = false;
1278
1320
  await this.#dequeue();
@@ -1281,11 +1323,35 @@ var Queue = class {
1281
1323
  };
1282
1324
 
1283
1325
  // src/lib/watch-processor.ts
1326
+ var queues = {};
1327
+ function queueKey(obj) {
1328
+ const options = ["kind", "kindNs", "kindNsName", "global"];
1329
+ const d3fault = "kind";
1330
+ let strat = process.env.PEPR_RECONCILE_STRATEGY || d3fault;
1331
+ strat = options.includes(strat) ? strat : d3fault;
1332
+ const ns = obj.metadata?.namespace ?? "cluster-scoped";
1333
+ const kind4 = obj.kind ?? "UnknownKind";
1334
+ const name = obj.metadata?.name ?? "Unnamed";
1335
+ const lookup = {
1336
+ kind: `${kind4}`,
1337
+ kindNs: `${kind4}/${ns}`,
1338
+ kindNsName: `${kind4}/${ns}/${name}`,
1339
+ global: "global"
1340
+ };
1341
+ return lookup[strat];
1342
+ }
1343
+ function getOrCreateQueue(obj) {
1344
+ const key = queueKey(obj);
1345
+ if (!queues[key]) {
1346
+ queues[key] = new Queue(key);
1347
+ }
1348
+ return queues[key];
1349
+ }
1284
1350
  var watchCfg = {
1285
1351
  resyncFailureMax: process.env.PEPR_RESYNC_FAILURE_MAX ? parseInt(process.env.PEPR_RESYNC_FAILURE_MAX, 10) : 5,
1286
1352
  resyncDelaySec: process.env.PEPR_RESYNC_DELAY_SECONDS ? parseInt(process.env.PEPR_RESYNC_DELAY_SECONDS, 10) : 5,
1287
1353
  lastSeenLimitSeconds: process.env.PEPR_LAST_SEEN_LIMIT_SECONDS ? parseInt(process.env.PEPR_LAST_SEEN_LIMIT_SECONDS, 10) : 300,
1288
- relistIntervalSec: process.env.PEPR_RELIST_INTERVAL_SECONDS ? parseInt(process.env.PEPR_RELIST_INTERVAL_SECONDS, 10) : 1800
1354
+ relistIntervalSec: process.env.PEPR_RELIST_INTERVAL_SECONDS ? parseInt(process.env.PEPR_RELIST_INTERVAL_SECONDS, 10) : 600
1289
1355
  };
1290
1356
  var eventToPhaseMap = {
1291
1357
  ["CREATE" /* Create */]: [import_types2.WatchPhase.Added],
@@ -1302,12 +1368,12 @@ function setupWatch(capabilities) {
1302
1368
  async function runBinding(binding, capabilityNamespaces) {
1303
1369
  const phaseMatch = eventToPhaseMap[binding.event] || eventToPhaseMap["*" /* Any */];
1304
1370
  logger_default.debug({ watchCfg }, "Effective WatchConfig");
1305
- const watchCallback = async (obj, type) => {
1306
- if (phaseMatch.includes(type)) {
1371
+ const watchCallback = async (obj, phase) => {
1372
+ if (phaseMatch.includes(phase)) {
1307
1373
  try {
1308
1374
  const filterMatch = filterNoMatchReason(binding, obj, capabilityNamespaces);
1309
1375
  if (filterMatch === "") {
1310
- await binding.watchCallback?.(obj, type);
1376
+ await binding.watchCallback?.(obj, phase);
1311
1377
  } else {
1312
1378
  logger_default.debug(filterMatch);
1313
1379
  }
@@ -1316,14 +1382,13 @@ async function runBinding(binding, capabilityNamespaces) {
1316
1382
  }
1317
1383
  }
1318
1384
  };
1319
- const queue = new Queue();
1320
- queue.setReconcile(watchCallback);
1321
- const watcher = (0, import_kubernetes_fluent_client5.K8s)(binding.model, binding.filters).Watch(async (obj, type) => {
1322
- logger_default.debug(obj, `Watch event ${type} received`);
1385
+ const watcher = (0, import_kubernetes_fluent_client5.K8s)(binding.model, binding.filters).Watch(async (obj, phase) => {
1386
+ logger_default.debug(obj, `Watch event ${phase} received`);
1323
1387
  if (binding.isQueue) {
1324
- await queue.enqueue(obj, type);
1388
+ const queue = getOrCreateQueue(obj);
1389
+ await queue.enqueue(obj, phase, watchCallback);
1325
1390
  } else {
1326
- await watchCallback(obj, type);
1391
+ await watchCallback(obj, phase);
1327
1392
  }
1328
1393
  }, watchCfg);
1329
1394
  watcher.events.on(import_kubernetes_fluent_client5.WatchEvent.GIVE_UP, (err) => {
@@ -1359,8 +1424,8 @@ async function runBinding(binding, capabilityNamespaces) {
1359
1424
  process.exit(1);
1360
1425
  }
1361
1426
  }
1362
- function logEvent(type, message = "", obj) {
1363
- const logMessage = `Watch event ${type} received${message ? `. ${message}.` : "."}`;
1427
+ function logEvent(event, message = "", obj) {
1428
+ const logMessage = `Watch event ${event} received${message ? `. ${message}.` : "."}`;
1364
1429
  if (obj) {
1365
1430
  logger_default.debug(obj, logMessage);
1366
1431
  } else {
@@ -1824,12 +1889,13 @@ var Capability = class {
1824
1889
  name: "",
1825
1890
  namespaces: [],
1826
1891
  labels: {},
1827
- annotations: {}
1892
+ annotations: {},
1893
+ deletionTimestamp: false
1828
1894
  }
1829
1895
  };
1830
1896
  const bindings = this.#bindings;
1831
1897
  const prefix = `${this.#name}: ${model.name}`;
1832
- const commonChain = { WithLabel, WithAnnotation, Mutate, Validate, Watch, Reconcile };
1898
+ const commonChain = { WithLabel, WithAnnotation, WithDeletionTimestamp, Mutate, Validate, Watch, Reconcile };
1833
1899
  const isNotEmpty = (value) => Object.keys(value).length > 0;
1834
1900
  const log = (message, cbString) => {
1835
1901
  const filteredObj = (0, import_ramda6.pickBy)(isNotEmpty, binding.filters);
@@ -1885,6 +1951,11 @@ var Capability = class {
1885
1951
  binding.filters.namespaces.push(...namespaces);
1886
1952
  return { ...commonChain, WithName };
1887
1953
  }
1954
+ function WithDeletionTimestamp() {
1955
+ logger_default.debug("Add deletionTimestamp filter");
1956
+ binding.filters.deletionTimestamp = true;
1957
+ return commonChain;
1958
+ }
1888
1959
  function WithName(name) {
1889
1960
  logger_default.debug(`Add name filter ${name}`, prefix);
1890
1961
  binding.filters.name = name;
@@ -1905,7 +1976,8 @@ var Capability = class {
1905
1976
  return {
1906
1977
  ...commonChain,
1907
1978
  InNamespace,
1908
- WithName
1979
+ WithName,
1980
+ WithDeletionTimestamp
1909
1981
  };
1910
1982
  }
1911
1983
  return {