pepr 0.40.1 → 0.42.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 (78) hide show
  1. package/README.md +11 -5
  2. package/dist/cli/build.d.ts.map +1 -1
  3. package/dist/cli/deploy.d.ts.map +1 -1
  4. package/dist/cli/init/templates.d.ts +2 -2
  5. package/dist/cli.js +228 -179
  6. package/dist/controller.js +52 -27
  7. package/dist/lib/assets/index.d.ts.map +1 -1
  8. package/dist/lib/capability.d.ts.map +1 -1
  9. package/dist/lib/controller/index.d.ts.map +1 -1
  10. package/dist/lib/controller/index.util.d.ts +10 -0
  11. package/dist/lib/controller/index.util.d.ts.map +1 -0
  12. package/dist/lib/controller/store.d.ts +1 -1
  13. package/dist/lib/deploymentChecks.d.ts +3 -0
  14. package/dist/lib/deploymentChecks.d.ts.map +1 -0
  15. package/dist/lib/enums.d.ts +5 -5
  16. package/dist/lib/enums.d.ts.map +1 -1
  17. package/dist/lib/filesystemService.d.ts +2 -0
  18. package/dist/lib/filesystemService.d.ts.map +1 -0
  19. package/dist/lib/filter/adjudicators/adjudicators.d.ts +73 -0
  20. package/dist/lib/filter/adjudicators/adjudicators.d.ts.map +1 -0
  21. package/dist/lib/filter/adjudicators/defaultTestObjects.d.ts +7 -0
  22. package/dist/lib/filter/adjudicators/defaultTestObjects.d.ts.map +1 -0
  23. package/dist/lib/helpers.d.ts +1 -4
  24. package/dist/lib/helpers.d.ts.map +1 -1
  25. package/dist/lib/schedule.d.ts.map +1 -1
  26. package/dist/lib/storage.d.ts +1 -1
  27. package/dist/lib/storage.d.ts.map +1 -1
  28. package/dist/lib/{logger.d.ts → telemetry/logger.d.ts} +1 -1
  29. package/dist/lib/telemetry/logger.d.ts.map +1 -0
  30. package/dist/lib/{metrics.d.ts → telemetry/metrics.d.ts} +3 -1
  31. package/dist/lib/telemetry/metrics.d.ts.map +1 -0
  32. package/dist/lib/types.d.ts +10 -9
  33. package/dist/lib/types.d.ts.map +1 -1
  34. package/dist/lib.d.ts +1 -1
  35. package/dist/lib.d.ts.map +1 -1
  36. package/dist/lib.js +151 -126
  37. package/dist/lib.js.map +4 -4
  38. package/dist/sdk/sdk.d.ts +3 -4
  39. package/dist/sdk/sdk.d.ts.map +1 -1
  40. package/package.json +5 -5
  41. package/src/cli/build.ts +2 -1
  42. package/src/cli/deploy.ts +2 -1
  43. package/src/cli/init/templates.ts +1 -1
  44. package/src/lib/assets/deploy.ts +1 -1
  45. package/src/lib/assets/destroy.ts +1 -1
  46. package/src/lib/assets/index.ts +102 -81
  47. package/src/lib/assets/webhooks.ts +2 -2
  48. package/src/lib/capability.ts +8 -9
  49. package/src/lib/controller/index.ts +32 -62
  50. package/src/lib/controller/index.util.ts +47 -0
  51. package/src/lib/controller/store.ts +2 -2
  52. package/src/lib/controller/storeCache.ts +1 -1
  53. package/src/lib/deploymentChecks.ts +43 -0
  54. package/src/lib/enums.ts +5 -5
  55. package/src/lib/filesystemService.ts +16 -0
  56. package/src/lib/filter/{adjudicators.ts → adjudicators/adjudicators.ts} +67 -35
  57. package/src/lib/filter/adjudicators/defaultTestObjects.ts +46 -0
  58. package/src/lib/filter/filter.ts +1 -1
  59. package/src/lib/finalizer.ts +1 -1
  60. package/src/lib/helpers.ts +31 -88
  61. package/src/lib/mutate-processor.ts +1 -1
  62. package/src/lib/queue.ts +1 -1
  63. package/src/lib/schedule.ts +8 -8
  64. package/src/lib/storage.ts +17 -17
  65. package/src/lib/{logger.ts → telemetry/logger.ts} +1 -1
  66. package/src/lib/{metrics.ts → telemetry/metrics.ts} +18 -17
  67. package/src/lib/types.ts +12 -9
  68. package/src/lib/utils.ts +1 -1
  69. package/src/lib/validate-processor.ts +1 -1
  70. package/src/lib/watch-processor.ts +8 -8
  71. package/src/lib.ts +1 -1
  72. package/src/runtime/controller.ts +1 -1
  73. package/src/sdk/sdk.ts +6 -9
  74. package/src/templates/capabilities/hello-pepr.ts +19 -9
  75. package/dist/lib/filter/adjudicators.d.ts +0 -69
  76. package/dist/lib/filter/adjudicators.d.ts.map +0 -1
  77. package/dist/lib/logger.d.ts.map +0 -1
  78. package/dist/lib/metrics.d.ts.map +0 -1
package/dist/cli.js CHANGED
@@ -170,10 +170,10 @@ function genCert(key, name2, issuer) {
170
170
 
171
171
  // src/lib/assets/deploy.ts
172
172
  var import_crypto = __toESM(require("crypto"));
173
- var import_fs3 = require("fs");
174
- var import_kubernetes_fluent_client4 = require("kubernetes-fluent-client");
173
+ var import_fs2 = require("fs");
174
+ var import_kubernetes_fluent_client3 = require("kubernetes-fluent-client");
175
175
 
176
- // src/lib/logger.ts
176
+ // src/lib/telemetry/logger.ts
177
177
  var import_pino = require("pino");
178
178
  var isPrettyLog = true;
179
179
  var pretty = {
@@ -277,17 +277,13 @@ function watcherService(name2) {
277
277
  // src/lib/assets/pods.ts
278
278
  var import_zlib = require("zlib");
279
279
 
280
- // src/lib/helpers.ts
281
- var import_fs2 = require("fs");
282
- var import_kubernetes_fluent_client2 = require("kubernetes-fluent-client");
283
-
284
280
  // src/sdk/sdk.ts
285
281
  var import_kubernetes_fluent_client = require("kubernetes-fluent-client");
286
282
  function sanitizeResourceName(name2) {
287
283
  return name2.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 250).replace(/^[^a-z]+|[^a-z]+$/g, "");
288
284
  }
289
285
 
290
- // src/lib/filter/adjudicators.ts
286
+ // src/lib/filter/adjudicators/adjudicators.ts
291
287
  var import_ramda = require("ramda");
292
288
  var declaredOperation = (0, import_ramda.pipe)(
293
289
  (request) => request?.operation,
@@ -311,23 +307,49 @@ var carriesDeletionTimestamp = (0, import_ramda.pipe)(
311
307
  (0, import_ramda.defaultTo)(false)
312
308
  );
313
309
  var missingDeletionTimestamp = (0, import_ramda.complement)(carriesDeletionTimestamp);
314
- var carriedKind = (0, import_ramda.pipe)((kubernetesObject) => kubernetesObject?.metadata?.kind, (0, import_ramda.defaultTo)("not set"));
315
- var carriedVersion = (0, import_ramda.pipe)((kubernetesObject) => kubernetesObject?.metadata?.version, (0, import_ramda.defaultTo)("not set"));
316
- var carriedName = (0, import_ramda.pipe)((kubernetesObject) => kubernetesObject?.metadata?.name, (0, import_ramda.defaultTo)(""));
310
+ var carriedKind = (0, import_ramda.pipe)(
311
+ (kubernetesObject) => kubernetesObject?.kind,
312
+ (0, import_ramda.defaultTo)("not set")
313
+ );
314
+ var carriedVersion = (0, import_ramda.pipe)(
315
+ (kubernetesObject) => kubernetesObject?.metadata?.resourceVersion,
316
+ (0, import_ramda.defaultTo)("not set")
317
+ );
318
+ var carriedName = (0, import_ramda.pipe)(
319
+ (kubernetesObject) => kubernetesObject?.metadata?.name,
320
+ (0, import_ramda.defaultTo)("")
321
+ );
317
322
  var carriesName = (0, import_ramda.pipe)(carriedName, (0, import_ramda.equals)(""), import_ramda.not);
318
323
  var missingName = (0, import_ramda.complement)(carriesName);
319
- var carriedNamespace = (0, import_ramda.pipe)((kubernetesObject) => kubernetesObject?.metadata?.namespace, (0, import_ramda.defaultTo)(""));
324
+ var carriedNamespace = (0, import_ramda.pipe)(
325
+ (kubernetesObject) => kubernetesObject?.metadata?.namespace,
326
+ (0, import_ramda.defaultTo)("")
327
+ );
320
328
  var carriesNamespace = (0, import_ramda.pipe)(carriedNamespace, (0, import_ramda.equals)(""), import_ramda.not);
321
- var carriedAnnotations = (0, import_ramda.pipe)((kubernetesObject) => kubernetesObject?.metadata?.annotations, (0, import_ramda.defaultTo)({}));
329
+ var carriedAnnotations = (0, import_ramda.pipe)(
330
+ (kubernetesObject) => kubernetesObject?.metadata?.annotations,
331
+ (0, import_ramda.defaultTo)({})
332
+ );
322
333
  var carriesAnnotations = (0, import_ramda.pipe)(carriedAnnotations, (0, import_ramda.equals)({}), import_ramda.not);
323
- var carriedLabels = (0, import_ramda.pipe)((kubernetesObject) => kubernetesObject?.metadata?.labels, (0, import_ramda.defaultTo)({}));
334
+ var carriedLabels = (0, import_ramda.pipe)(
335
+ (kubernetesObject) => kubernetesObject?.metadata?.labels,
336
+ (0, import_ramda.defaultTo)({})
337
+ );
324
338
  var carriesLabels = (0, import_ramda.pipe)(carriedLabels, (0, import_ramda.equals)({}), import_ramda.not);
325
- var definesDeletionTimestamp = (0, import_ramda.pipe)((binding) => binding?.filters?.deletionTimestamp, (0, import_ramda.defaultTo)(false));
339
+ var definesDeletionTimestamp = (0, import_ramda.pipe)(
340
+ (binding) => binding?.filters?.deletionTimestamp ?? false,
341
+ (0, import_ramda.defaultTo)(false)
342
+ );
326
343
  var ignoresDeletionTimestamp = (0, import_ramda.complement)(definesDeletionTimestamp);
327
- var definedName = (0, import_ramda.pipe)((binding) => binding?.filters?.name, (0, import_ramda.defaultTo)(""));
344
+ var definedName = (0, import_ramda.pipe)((binding) => {
345
+ return binding.filters.name;
346
+ }, (0, import_ramda.defaultTo)(""));
328
347
  var definesName = (0, import_ramda.pipe)(definedName, (0, import_ramda.equals)(""), import_ramda.not);
329
348
  var ignoresName = (0, import_ramda.complement)(definesName);
330
- var definedNameRegex = (0, import_ramda.pipe)((binding) => binding?.filters?.regexName, (0, import_ramda.defaultTo)(""));
349
+ var definedNameRegex = (0, import_ramda.pipe)(
350
+ (binding) => binding.filters?.regexName,
351
+ (0, import_ramda.defaultTo)("")
352
+ );
331
353
  var definesNameRegex = (0, import_ramda.pipe)(definedNameRegex, (0, import_ramda.equals)(""), import_ramda.not);
332
354
  var definedNamespaces = (0, import_ramda.pipe)((binding) => binding?.filters?.namespaces, (0, import_ramda.defaultTo)([]));
333
355
  var definesNamespaces = (0, import_ramda.pipe)(definedNamespaces, (0, import_ramda.equals)([]), import_ramda.not);
@@ -337,20 +359,22 @@ var definedAnnotations = (0, import_ramda.pipe)((binding) => binding?.filters?.a
337
359
  var definesAnnotations = (0, import_ramda.pipe)(definedAnnotations, (0, import_ramda.equals)({}), import_ramda.not);
338
360
  var definedLabels = (0, import_ramda.pipe)((binding) => binding?.filters?.labels, (0, import_ramda.defaultTo)({}));
339
361
  var definesLabels = (0, import_ramda.pipe)(definedLabels, (0, import_ramda.equals)({}), import_ramda.not);
340
- var definedEvent = (0, import_ramda.pipe)((binding) => binding?.event, (0, import_ramda.defaultTo)(""));
362
+ var definedEvent = (binding) => {
363
+ return binding.event;
364
+ };
341
365
  var definesDelete = (0, import_ramda.pipe)(definedEvent, (0, import_ramda.equals)("DELETE" /* DELETE */));
342
366
  var definedGroup = (0, import_ramda.pipe)((binding) => binding?.kind?.group, (0, import_ramda.defaultTo)(""));
343
367
  var definesGroup = (0, import_ramda.pipe)(definedGroup, (0, import_ramda.equals)(""), import_ramda.not);
344
- var definedVersion = (0, import_ramda.pipe)((binding) => binding?.kind?.version, (0, import_ramda.defaultTo)(""));
368
+ var definedVersion = (0, import_ramda.pipe)(
369
+ (binding) => binding?.kind?.version,
370
+ (0, import_ramda.defaultTo)("")
371
+ );
345
372
  var definesVersion = (0, import_ramda.pipe)(definedVersion, (0, import_ramda.equals)(""), import_ramda.not);
346
373
  var definedKind = (0, import_ramda.pipe)((binding) => binding?.kind?.kind, (0, import_ramda.defaultTo)(""));
347
374
  var definesKind = (0, import_ramda.pipe)(definedKind, (0, import_ramda.equals)(""), import_ramda.not);
348
- var definedCategory = (0, import_ramda.pipe)((binding) => {
349
- return binding.isFinalize ? "Finalize" : binding.isWatch ? "Watch" : binding.isMutate ? "Mutate" : binding.isValidate ? "Validate" : "";
350
- });
351
- var definedCallback = (0, import_ramda.pipe)((binding) => {
375
+ var definedCallback = (binding) => {
352
376
  return binding.isFinalize ? binding.finalizeCallback : binding.isWatch ? binding.watchCallback : binding.isMutate ? binding.mutateCallback : binding.isValidate ? binding.validateCallback : null;
353
- });
377
+ };
354
378
  var definedCallbackName = (0, import_ramda.pipe)(definedCallback, (0, import_ramda.defaultTo)({ name: "" }), (callback) => callback.name);
355
379
  var mismatchedDeletionTimestamp = (0, import_ramda.allPass)([
356
380
  (0, import_ramda.pipe)((0, import_ramda.nthArg)(0), definesDeletionTimestamp),
@@ -432,8 +456,8 @@ var unbindableNamespaces = (0, import_ramda.allPass)([
432
456
  ]);
433
457
  var misboundDeleteWithDeletionTimestamp = (0, import_ramda.allPass)([definesDelete, definesDeletionTimestamp]);
434
458
  var operationMatchesEvent = (0, import_ramda.anyPass)([
435
- (0, import_ramda.pipe)((0, import_ramda.nthArg)(1), (0, import_ramda.equals)("*" /* Any */)),
436
- (0, import_ramda.pipe)((operation, event) => operation === event),
459
+ (0, import_ramda.pipe)((0, import_ramda.nthArg)(1), (0, import_ramda.equals)("*" /* ANY */)),
460
+ (0, import_ramda.pipe)((operation, event) => operation.valueOf() === event.valueOf()),
437
461
  (0, import_ramda.pipe)((operation, event) => operation ? event.includes(operation) : false)
438
462
  ]);
439
463
  var mismatchedEvent = (0, import_ramda.pipe)(
@@ -455,11 +479,7 @@ var mismatchedKind = (0, import_ramda.allPass)([
455
479
 
456
480
  // src/lib/helpers.ts
457
481
  function matchesRegex(pattern, testString) {
458
- if (!pattern) {
459
- return false;
460
- }
461
- const regex = new RegExp(pattern);
462
- return regex.test(testString);
482
+ return new RegExp(pattern).test(testString);
463
483
  }
464
484
  var ValidationError = class extends Error {
465
485
  };
@@ -500,17 +520,6 @@ function createRBACMap(capabilities) {
500
520
  return acc;
501
521
  }, {});
502
522
  }
503
- async function createDirectoryIfNotExists(path) {
504
- try {
505
- await import_fs2.promises.access(path);
506
- } catch (error) {
507
- if (error.code === "ENOENT") {
508
- await import_fs2.promises.mkdir(path, { recursive: true });
509
- } else {
510
- throw error;
511
- }
512
- }
513
- }
514
523
  function hasEveryOverlap(array1, array2) {
515
524
  if (!Array.isArray(array1) || !Array.isArray(array2)) {
516
525
  return false;
@@ -549,7 +558,9 @@ function generateWatchNamespaceError(ignoredNamespaces, bindingNamespaces, capab
549
558
  function namespaceComplianceValidator(capability, ignoredNamespaces) {
550
559
  const { namespaces: capabilityNamespaces, bindings, name: name2 } = capability;
551
560
  const bindingNamespaces = bindings.flatMap((binding) => binding.filters.namespaces);
552
- const bindingRegexNamespaces = bindings.flatMap((binding) => binding.filters.regexNamespaces || []);
561
+ const bindingRegexNamespaces = bindings.flatMap(
562
+ (binding) => binding.filters.regexNamespaces || []
563
+ );
553
564
  const namespaceError = generateWatchNamespaceError(
554
565
  ignoredNamespaces ? ignoredNamespaces : [],
555
566
  bindingNamespaces,
@@ -582,40 +593,6 @@ function namespaceComplianceValidator(capability, ignoredNamespaces) {
582
593
  }
583
594
  }
584
595
  }
585
- async function checkDeploymentStatus(namespace2) {
586
- const deployments = await (0, import_kubernetes_fluent_client2.K8s)(import_kubernetes_fluent_client2.kind.Deployment).InNamespace(namespace2).Get();
587
- let status = false;
588
- let readyCount = 0;
589
- for (const deployment2 of deployments.items) {
590
- const readyReplicas = deployment2.status?.readyReplicas ? deployment2.status?.readyReplicas : 0;
591
- if (deployment2.status?.readyReplicas !== deployment2.spec?.replicas) {
592
- logger_default.info(
593
- `Waiting for deployment ${deployment2.metadata?.name} rollout to finish: ${readyReplicas} of ${deployment2.spec?.replicas} replicas are available`
594
- );
595
- } else {
596
- logger_default.info(
597
- `Deployment ${deployment2.metadata?.name} rolled out: ${readyReplicas} of ${deployment2.spec?.replicas} replicas are available`
598
- );
599
- readyCount++;
600
- }
601
- }
602
- if (readyCount === deployments.items.length) {
603
- status = true;
604
- }
605
- return status;
606
- }
607
- async function namespaceDeploymentsReady(namespace2 = "pepr-system") {
608
- logger_default.info(`Checking ${namespace2} deployments status...`);
609
- let ready = false;
610
- while (!ready) {
611
- ready = await checkDeploymentStatus(namespace2);
612
- if (ready) {
613
- return ready;
614
- }
615
- await new Promise((resolve5) => setTimeout(resolve5, 1e3));
616
- }
617
- logger_default.info(`All ${namespace2} deployments are ready`);
618
- }
619
596
  function secretOverLimit(str) {
620
597
  const encoder = new TextEncoder();
621
598
  const encoded = encoder.encode(str);
@@ -1103,19 +1080,19 @@ function storeRoleBinding(name2) {
1103
1080
  }
1104
1081
 
1105
1082
  // src/lib/k8s.ts
1106
- var import_kubernetes_fluent_client3 = require("kubernetes-fluent-client");
1107
- var Store = class extends import_kubernetes_fluent_client3.GenericKind {
1083
+ var import_kubernetes_fluent_client2 = require("kubernetes-fluent-client");
1084
+ var Store = class extends import_kubernetes_fluent_client2.GenericKind {
1108
1085
  };
1109
1086
  var peprStoreGVK = {
1110
1087
  kind: "PeprStore",
1111
1088
  version: "v1",
1112
1089
  group: "pepr.dev"
1113
1090
  };
1114
- (0, import_kubernetes_fluent_client3.RegisterKind)(Store, peprStoreGVK);
1091
+ (0, import_kubernetes_fluent_client2.RegisterKind)(Store, peprStoreGVK);
1115
1092
 
1116
1093
  // src/lib/assets/store.ts
1117
- var { group, version, kind: kind3 } = peprStoreGVK;
1118
- var singular = kind3.toLocaleLowerCase();
1094
+ var { group, version, kind: kind2 } = peprStoreGVK;
1095
+ var singular = kind2.toLocaleLowerCase();
1119
1096
  var plural = `${singular}s`;
1120
1097
  var name = `${plural}.${group}`;
1121
1098
  var peprStoreCRD = {
@@ -1151,7 +1128,7 @@ var peprStoreCRD = {
1151
1128
  names: {
1152
1129
  plural,
1153
1130
  singular,
1154
- kind: kind3
1131
+ kind: kind2
1155
1132
  }
1156
1133
  }
1157
1134
  };
@@ -1178,8 +1155,8 @@ async function generateWebhookRules(assets, isMutateWebhook) {
1178
1155
  continue;
1179
1156
  }
1180
1157
  const operations = [];
1181
- if (event === "CREATEORUPDATE" /* CreateOrUpdate */) {
1182
- operations.push("CREATE" /* Create */, "UPDATE" /* Update */);
1158
+ if (event === "CREATEORUPDATE" /* CREATE_OR_UPDATE */) {
1159
+ operations.push("CREATE" /* CREATE */, "UPDATE" /* UPDATE */);
1183
1160
  } else {
1184
1161
  operations.push(event);
1185
1162
  }
@@ -1256,12 +1233,12 @@ async function webhookConfig(assets, mutateOrValidate, timeoutSeconds = 10) {
1256
1233
  // src/lib/assets/deploy.ts
1257
1234
  async function deployImagePullSecret(imagePullSecret, name2) {
1258
1235
  try {
1259
- await (0, import_kubernetes_fluent_client4.K8s)(import_kubernetes_fluent_client4.kind.Namespace).Get("pepr-system");
1236
+ await (0, import_kubernetes_fluent_client3.K8s)(import_kubernetes_fluent_client3.kind.Namespace).Get("pepr-system");
1260
1237
  } catch {
1261
- await (0, import_kubernetes_fluent_client4.K8s)(import_kubernetes_fluent_client4.kind.Namespace).Apply(namespace());
1238
+ await (0, import_kubernetes_fluent_client3.K8s)(import_kubernetes_fluent_client3.kind.Namespace).Apply(namespace());
1262
1239
  }
1263
1240
  try {
1264
- await (0, import_kubernetes_fluent_client4.K8s)(import_kubernetes_fluent_client4.kind.Secret).Apply(
1241
+ await (0, import_kubernetes_fluent_client3.K8s)(import_kubernetes_fluent_client3.kind.Secret).Apply(
1265
1242
  {
1266
1243
  apiVersion: "v1",
1267
1244
  kind: "Secret",
@@ -1284,29 +1261,29 @@ async function deploy(assets, force, webhookTimeout) {
1284
1261
  logger_default.info("Establishing connection to Kubernetes");
1285
1262
  const { name: name2, host, path } = assets;
1286
1263
  logger_default.info("Applying pepr-system namespace");
1287
- await (0, import_kubernetes_fluent_client4.K8s)(import_kubernetes_fluent_client4.kind.Namespace).Apply(namespace(assets.config.customLabels?.namespace));
1264
+ await (0, import_kubernetes_fluent_client3.K8s)(import_kubernetes_fluent_client3.kind.Namespace).Apply(namespace(assets.config.customLabels?.namespace));
1288
1265
  const mutateWebhook = await webhookConfig(assets, "mutate", webhookTimeout);
1289
1266
  if (mutateWebhook) {
1290
1267
  logger_default.info("Applying mutating webhook");
1291
- await (0, import_kubernetes_fluent_client4.K8s)(import_kubernetes_fluent_client4.kind.MutatingWebhookConfiguration).Apply(mutateWebhook, { force });
1268
+ await (0, import_kubernetes_fluent_client3.K8s)(import_kubernetes_fluent_client3.kind.MutatingWebhookConfiguration).Apply(mutateWebhook, { force });
1292
1269
  } else {
1293
1270
  logger_default.info("Mutating webhook not needed, removing if it exists");
1294
- await (0, import_kubernetes_fluent_client4.K8s)(import_kubernetes_fluent_client4.kind.MutatingWebhookConfiguration).Delete(name2);
1271
+ await (0, import_kubernetes_fluent_client3.K8s)(import_kubernetes_fluent_client3.kind.MutatingWebhookConfiguration).Delete(name2);
1295
1272
  }
1296
1273
  const validateWebhook = await webhookConfig(assets, "validate", webhookTimeout);
1297
1274
  if (validateWebhook) {
1298
1275
  logger_default.info("Applying validating webhook");
1299
- await (0, import_kubernetes_fluent_client4.K8s)(import_kubernetes_fluent_client4.kind.ValidatingWebhookConfiguration).Apply(validateWebhook, { force });
1276
+ await (0, import_kubernetes_fluent_client3.K8s)(import_kubernetes_fluent_client3.kind.ValidatingWebhookConfiguration).Apply(validateWebhook, { force });
1300
1277
  } else {
1301
1278
  logger_default.info("Validating webhook not needed, removing if it exists");
1302
- await (0, import_kubernetes_fluent_client4.K8s)(import_kubernetes_fluent_client4.kind.ValidatingWebhookConfiguration).Delete(name2);
1279
+ await (0, import_kubernetes_fluent_client3.K8s)(import_kubernetes_fluent_client3.kind.ValidatingWebhookConfiguration).Delete(name2);
1303
1280
  }
1304
1281
  logger_default.info("Applying the Pepr Store CRD if it doesn't exist");
1305
- await (0, import_kubernetes_fluent_client4.K8s)(import_kubernetes_fluent_client4.kind.CustomResourceDefinition).Apply(peprStoreCRD, { force });
1282
+ await (0, import_kubernetes_fluent_client3.K8s)(import_kubernetes_fluent_client3.kind.CustomResourceDefinition).Apply(peprStoreCRD, { force });
1306
1283
  if (host) {
1307
1284
  return;
1308
1285
  }
1309
- const code = await import_fs3.promises.readFile(path);
1286
+ const code = await import_fs2.promises.readFile(path);
1310
1287
  const hash = import_crypto.default.createHash("sha256").update(code).digest("hex");
1311
1288
  if (code.length < 1) {
1312
1289
  throw new Error("No code provided");
@@ -1319,46 +1296,46 @@ async function setupRBAC(name2, capabilities, force, config) {
1319
1296
  const { rbacMode, rbac } = config;
1320
1297
  logger_default.info("Applying cluster role binding");
1321
1298
  const crb = clusterRoleBinding(name2);
1322
- await (0, import_kubernetes_fluent_client4.K8s)(import_kubernetes_fluent_client4.kind.ClusterRoleBinding).Apply(crb, { force });
1299
+ await (0, import_kubernetes_fluent_client3.K8s)(import_kubernetes_fluent_client3.kind.ClusterRoleBinding).Apply(crb, { force });
1323
1300
  logger_default.info("Applying cluster role");
1324
1301
  const cr = clusterRole(name2, capabilities, rbacMode, rbac);
1325
- await (0, import_kubernetes_fluent_client4.K8s)(import_kubernetes_fluent_client4.kind.ClusterRole).Apply(cr, { force });
1302
+ await (0, import_kubernetes_fluent_client3.K8s)(import_kubernetes_fluent_client3.kind.ClusterRole).Apply(cr, { force });
1326
1303
  logger_default.info("Applying service account");
1327
1304
  const sa = serviceAccount(name2);
1328
- await (0, import_kubernetes_fluent_client4.K8s)(import_kubernetes_fluent_client4.kind.ServiceAccount).Apply(sa, { force });
1305
+ await (0, import_kubernetes_fluent_client3.K8s)(import_kubernetes_fluent_client3.kind.ServiceAccount).Apply(sa, { force });
1329
1306
  logger_default.info("Applying store role");
1330
1307
  const role = storeRole(name2);
1331
- await (0, import_kubernetes_fluent_client4.K8s)(import_kubernetes_fluent_client4.kind.Role).Apply(role, { force });
1308
+ await (0, import_kubernetes_fluent_client3.K8s)(import_kubernetes_fluent_client3.kind.Role).Apply(role, { force });
1332
1309
  logger_default.info("Applying store role binding");
1333
1310
  const roleBinding = storeRoleBinding(name2);
1334
- await (0, import_kubernetes_fluent_client4.K8s)(import_kubernetes_fluent_client4.kind.RoleBinding).Apply(roleBinding, { force });
1311
+ await (0, import_kubernetes_fluent_client3.K8s)(import_kubernetes_fluent_client3.kind.RoleBinding).Apply(roleBinding, { force });
1335
1312
  }
1336
1313
  async function setupController(assets, code, hash, force) {
1337
1314
  const { name: name2 } = assets;
1338
1315
  logger_default.info("Applying module secret");
1339
1316
  const mod = moduleSecret(name2, code, hash);
1340
- await (0, import_kubernetes_fluent_client4.K8s)(import_kubernetes_fluent_client4.kind.Secret).Apply(mod, { force });
1317
+ await (0, import_kubernetes_fluent_client3.K8s)(import_kubernetes_fluent_client3.kind.Secret).Apply(mod, { force });
1341
1318
  logger_default.info("Applying controller service");
1342
1319
  const svc = service(name2);
1343
- await (0, import_kubernetes_fluent_client4.K8s)(import_kubernetes_fluent_client4.kind.Service).Apply(svc, { force });
1320
+ await (0, import_kubernetes_fluent_client3.K8s)(import_kubernetes_fluent_client3.kind.Service).Apply(svc, { force });
1344
1321
  logger_default.info("Applying TLS secret");
1345
1322
  const tls = tlsSecret(name2, assets.tls);
1346
- await (0, import_kubernetes_fluent_client4.K8s)(import_kubernetes_fluent_client4.kind.Secret).Apply(tls, { force });
1323
+ await (0, import_kubernetes_fluent_client3.K8s)(import_kubernetes_fluent_client3.kind.Secret).Apply(tls, { force });
1347
1324
  logger_default.info("Applying API token secret");
1348
1325
  const apiToken = apiTokenSecret(name2, assets.apiToken);
1349
- await (0, import_kubernetes_fluent_client4.K8s)(import_kubernetes_fluent_client4.kind.Secret).Apply(apiToken, { force });
1326
+ await (0, import_kubernetes_fluent_client3.K8s)(import_kubernetes_fluent_client3.kind.Secret).Apply(apiToken, { force });
1350
1327
  logger_default.info("Applying deployment");
1351
1328
  const dep = deployment(assets, hash, assets.buildTimestamp);
1352
- await (0, import_kubernetes_fluent_client4.K8s)(import_kubernetes_fluent_client4.kind.Deployment).Apply(dep, { force });
1329
+ await (0, import_kubernetes_fluent_client3.K8s)(import_kubernetes_fluent_client3.kind.Deployment).Apply(dep, { force });
1353
1330
  }
1354
1331
  async function setupWatcher(assets, hash, force) {
1355
1332
  const watchDeployment = watcher(assets, hash, assets.buildTimestamp);
1356
1333
  if (watchDeployment) {
1357
1334
  logger_default.info("Applying watcher deployment");
1358
- await (0, import_kubernetes_fluent_client4.K8s)(import_kubernetes_fluent_client4.kind.Deployment).Apply(watchDeployment, { force });
1335
+ await (0, import_kubernetes_fluent_client3.K8s)(import_kubernetes_fluent_client3.kind.Deployment).Apply(watchDeployment, { force });
1359
1336
  logger_default.info("Applying watcher service");
1360
1337
  const watchSvc = watcherService(assets.name);
1361
- await (0, import_kubernetes_fluent_client4.K8s)(import_kubernetes_fluent_client4.kind.Service).Apply(watchSvc, { force });
1338
+ await (0, import_kubernetes_fluent_client3.K8s)(import_kubernetes_fluent_client3.kind.Service).Apply(watchSvc, { force });
1362
1339
  }
1363
1340
  }
1364
1341
 
@@ -1390,7 +1367,7 @@ function loadCapabilities(path) {
1390
1367
  // src/lib/assets/yaml.ts
1391
1368
  var import_client_node = require("@kubernetes/client-node");
1392
1369
  var import_crypto2 = __toESM(require("crypto"));
1393
- var import_fs4 = require("fs");
1370
+ var import_fs3 = require("fs");
1394
1371
  async function overridesFile({ hash, name: name2, image, config, apiToken, capabilities }, path) {
1395
1372
  const rbacOverrides = clusterRole(name2, capabilities, config.rbacMode, config.rbac).rules;
1396
1373
  const overrides = {
@@ -1541,7 +1518,7 @@ async function overridesFile({ hash, name: name2, image, config, apiToken, capab
1541
1518
  }
1542
1519
  }
1543
1520
  };
1544
- await import_fs4.promises.writeFile(path, (0, import_client_node.dumpYaml)(overrides, { noRefs: true, forceQuotes: true }));
1521
+ await import_fs3.promises.writeFile(path, (0, import_client_node.dumpYaml)(overrides, { noRefs: true, forceQuotes: true }));
1545
1522
  }
1546
1523
  function zarfYaml({ name: name2, image, config }, path) {
1547
1524
  const zarfCfg = {
@@ -1598,7 +1575,7 @@ function zarfYamlChart({ name: name2, image, config }, path) {
1598
1575
  }
1599
1576
  async function allYaml(assets, imagePullSecret) {
1600
1577
  const { name: name2, tls, apiToken, path, config } = assets;
1601
- const code = await import_fs4.promises.readFile(path);
1578
+ const code = await import_fs3.promises.readFile(path);
1602
1579
  assets.hash = import_crypto2.default.createHash("sha256").update(code).digest("hex");
1603
1580
  const mutateWebhook = await webhookConfig(assets, "mutate", assets.config.webhookTimeout);
1604
1581
  const validateWebhook = await webhookConfig(assets, "validate", assets.config.webhookTimeout);
@@ -1884,6 +1861,73 @@ function serviceMonitorTemplate(name2) {
1884
1861
 
1885
1862
  // src/lib/assets/index.ts
1886
1863
  var import_fs5 = require("fs");
1864
+
1865
+ // src/lib/filesystemService.ts
1866
+ var import_fs4 = require("fs");
1867
+ async function createDirectoryIfNotExists(path) {
1868
+ try {
1869
+ await import_fs4.promises.access(path);
1870
+ } catch (error) {
1871
+ if (error.code === "ENOENT") {
1872
+ await import_fs4.promises.mkdir(path, { recursive: true });
1873
+ } else {
1874
+ throw error;
1875
+ }
1876
+ }
1877
+ }
1878
+
1879
+ // src/lib/assets/index.ts
1880
+ function toYaml(obj) {
1881
+ return (0, import_client_node2.dumpYaml)(obj, { noRefs: true });
1882
+ }
1883
+ function createWebhookYaml(assets, webhookConfiguration) {
1884
+ const yaml = toYaml(webhookConfiguration);
1885
+ return replaceString(
1886
+ replaceString(
1887
+ replaceString(yaml, assets.name, "{{ .Values.uuid }}"),
1888
+ assets.config.onError === "reject" ? "Fail" : "Ignore",
1889
+ "{{ .Values.admission.failurePolicy }}"
1890
+ ),
1891
+ `${assets.config.webhookTimeout}` || "10",
1892
+ "{{ .Values.admission.webhookTimeout }}"
1893
+ );
1894
+ }
1895
+ function helmLayout(basePath, unique) {
1896
+ const helm = {
1897
+ dirs: {
1898
+ chart: (0, import_path.resolve)(`${basePath}/${unique}-chart`)
1899
+ },
1900
+ files: {}
1901
+ };
1902
+ helm.dirs = {
1903
+ ...helm.dirs,
1904
+ charts: `${helm.dirs.chart}/charts`,
1905
+ tmpls: `${helm.dirs.chart}/templates`
1906
+ };
1907
+ helm.files = {
1908
+ ...helm.files,
1909
+ valuesYaml: `${helm.dirs.chart}/values.yaml`,
1910
+ chartYaml: `${helm.dirs.chart}/Chart.yaml`,
1911
+ namespaceYaml: `${helm.dirs.tmpls}/namespace.yaml`,
1912
+ watcherServiceYaml: `${helm.dirs.tmpls}/watcher-service.yaml`,
1913
+ admissionServiceYaml: `${helm.dirs.tmpls}/admission-service.yaml`,
1914
+ mutationWebhookYaml: `${helm.dirs.tmpls}/mutation-webhook.yaml`,
1915
+ validationWebhookYaml: `${helm.dirs.tmpls}/validation-webhook.yaml`,
1916
+ admissionDeploymentYaml: `${helm.dirs.tmpls}/admission-deployment.yaml`,
1917
+ admissionServiceMonitorYaml: `${helm.dirs.tmpls}/admission-service-monitor.yaml`,
1918
+ watcherDeploymentYaml: `${helm.dirs.tmpls}/watcher-deployment.yaml`,
1919
+ watcherServiceMonitorYaml: `${helm.dirs.tmpls}/watcher-service-monitor.yaml`,
1920
+ tlsSecretYaml: `${helm.dirs.tmpls}/tls-secret.yaml`,
1921
+ apiTokenSecretYaml: `${helm.dirs.tmpls}/api-token-secret.yaml`,
1922
+ moduleSecretYaml: `${helm.dirs.tmpls}/module-secret.yaml`,
1923
+ storeRoleYaml: `${helm.dirs.tmpls}/store-role.yaml`,
1924
+ storeRoleBindingYaml: `${helm.dirs.tmpls}/store-role-binding.yaml`,
1925
+ clusterRoleYaml: `${helm.dirs.tmpls}/cluster-role.yaml`,
1926
+ clusterRoleBindingYaml: `${helm.dirs.tmpls}/cluster-role-binding.yaml`,
1927
+ serviceAccountYaml: `${helm.dirs.tmpls}/service-account.yaml`
1928
+ };
1929
+ return helm;
1930
+ }
1887
1931
  var Assets = class {
1888
1932
  constructor(config, path, host) {
1889
1933
  this.config = config;
@@ -1921,82 +1965,48 @@ var Assets = class {
1921
1965
  }
1922
1966
  return allYaml(this, imagePullSecret);
1923
1967
  };
1968
+ /* eslint max-statements: ["warn", 21] */
1924
1969
  generateHelmChart = async (basePath) => {
1925
- const CHART_DIR = `${basePath}/${this.config.uuid}-chart`;
1926
- const CHAR_TEMPLATES_DIR = `${CHART_DIR}/templates`;
1927
- const valuesPath = (0, import_path.resolve)(CHART_DIR, `values.yaml`);
1928
- const chartPath = (0, import_path.resolve)(CHART_DIR, `Chart.yaml`);
1929
- const nsPath = (0, import_path.resolve)(CHAR_TEMPLATES_DIR, `namespace.yaml`);
1930
- const watcherSVCPath = (0, import_path.resolve)(CHAR_TEMPLATES_DIR, `watcher-service.yaml`);
1931
- const admissionSVCPath = (0, import_path.resolve)(CHAR_TEMPLATES_DIR, `admission-service.yaml`);
1932
- const mutationWebhookPath = (0, import_path.resolve)(CHAR_TEMPLATES_DIR, `mutation-webhook.yaml`);
1933
- const validationWebhookPath = (0, import_path.resolve)(CHAR_TEMPLATES_DIR, `validation-webhook.yaml`);
1934
- const admissionDeployPath = (0, import_path.resolve)(CHAR_TEMPLATES_DIR, `admission-deployment.yaml`);
1935
- const admissionServiceMonitorPath = (0, import_path.resolve)(CHAR_TEMPLATES_DIR, `admission-service-monitor.yaml`);
1936
- const watcherDeployPath = (0, import_path.resolve)(CHAR_TEMPLATES_DIR, `watcher-deployment.yaml`);
1937
- const watcherServiceMonitorPath = (0, import_path.resolve)(CHAR_TEMPLATES_DIR, `watcher-service-monitor.yaml`);
1938
- const tlsSecretPath = (0, import_path.resolve)(CHAR_TEMPLATES_DIR, `tls-secret.yaml`);
1939
- const apiTokenSecretPath = (0, import_path.resolve)(CHAR_TEMPLATES_DIR, `api-token-secret.yaml`);
1940
- const moduleSecretPath = (0, import_path.resolve)(CHAR_TEMPLATES_DIR, `module-secret.yaml`);
1941
- const storeRolePath = (0, import_path.resolve)(CHAR_TEMPLATES_DIR, `store-role.yaml`);
1942
- const storeRoleBindingPath = (0, import_path.resolve)(CHAR_TEMPLATES_DIR, `store-role-binding.yaml`);
1943
- const clusterRolePath = (0, import_path.resolve)(CHAR_TEMPLATES_DIR, `cluster-role.yaml`);
1944
- const clusterRoleBindingPath = (0, import_path.resolve)(CHAR_TEMPLATES_DIR, `cluster-role-binding.yaml`);
1945
- const serviceAccountPath = (0, import_path.resolve)(CHAR_TEMPLATES_DIR, `service-account.yaml`);
1970
+ const helm = helmLayout(basePath, this.config.uuid);
1946
1971
  try {
1947
- await createDirectoryIfNotExists(CHART_DIR);
1948
- await createDirectoryIfNotExists(`${CHART_DIR}/charts`);
1949
- await createDirectoryIfNotExists(`${CHAR_TEMPLATES_DIR}`);
1950
- await overridesFile(this, valuesPath);
1951
- await import_fs5.promises.writeFile(chartPath, dedent(chartYaml(this.config.uuid, this.config.description || "")));
1952
- await import_fs5.promises.writeFile(nsPath, dedent(nsTemplate()));
1972
+ await Promise.all(
1973
+ Object.values(helm.dirs).sort((l, r) => l.split("/").length - r.split("/").length).map(async (dir) => await createDirectoryIfNotExists(dir))
1974
+ );
1953
1975
  const code = await import_fs5.promises.readFile(this.path);
1954
- await import_fs5.promises.writeFile(watcherSVCPath, (0, import_client_node2.dumpYaml)(watcherService(this.name), { noRefs: true }));
1955
- await import_fs5.promises.writeFile(admissionSVCPath, (0, import_client_node2.dumpYaml)(service(this.name), { noRefs: true }));
1956
- await import_fs5.promises.writeFile(tlsSecretPath, (0, import_client_node2.dumpYaml)(tlsSecret(this.name, this.tls), { noRefs: true }));
1957
- await import_fs5.promises.writeFile(apiTokenSecretPath, (0, import_client_node2.dumpYaml)(apiTokenSecret(this.name, this.apiToken), { noRefs: true }));
1958
- await import_fs5.promises.writeFile(moduleSecretPath, (0, import_client_node2.dumpYaml)(moduleSecret(this.name, code, this.hash), { noRefs: true }));
1959
- await import_fs5.promises.writeFile(storeRolePath, (0, import_client_node2.dumpYaml)(storeRole(this.name), { noRefs: true }));
1960
- await import_fs5.promises.writeFile(storeRoleBindingPath, (0, import_client_node2.dumpYaml)(storeRoleBinding(this.name), { noRefs: true }));
1961
- await import_fs5.promises.writeFile(clusterRolePath, dedent(clusterRoleTemplate()));
1962
- await import_fs5.promises.writeFile(clusterRoleBindingPath, (0, import_client_node2.dumpYaml)(clusterRoleBinding(this.name), { noRefs: true }));
1963
- await import_fs5.promises.writeFile(serviceAccountPath, (0, import_client_node2.dumpYaml)(serviceAccount(this.name), { noRefs: true }));
1964
- const mutateWebhook = await webhookConfig(this, "mutate", this.config.webhookTimeout);
1965
- const validateWebhook = await webhookConfig(this, "validate", this.config.webhookTimeout);
1966
- const watchDeployment = watcher(this, this.hash, this.buildTimestamp);
1976
+ const pairs = [
1977
+ [helm.files.chartYaml, () => dedent(chartYaml(this.config.uuid, this.config.description || ""))],
1978
+ [helm.files.namespaceYaml, () => dedent(nsTemplate())],
1979
+ [helm.files.watcherServiceYaml, () => toYaml(watcherService(this.name))],
1980
+ [helm.files.admissionServiceYaml, () => toYaml(service(this.name))],
1981
+ [helm.files.tlsSecretYaml, () => toYaml(tlsSecret(this.name, this.tls))],
1982
+ [helm.files.apiTokenSecretYaml, () => toYaml(apiTokenSecret(this.name, this.apiToken))],
1983
+ [helm.files.storeRoleYaml, () => toYaml(storeRole(this.name))],
1984
+ [helm.files.storeRoleBindingYaml, () => toYaml(storeRoleBinding(this.name))],
1985
+ [helm.files.clusterRoleYaml, () => dedent(clusterRoleTemplate())],
1986
+ [helm.files.clusterRoleBindingYaml, () => toYaml(clusterRoleBinding(this.name))],
1987
+ [helm.files.serviceAccountYaml, () => toYaml(serviceAccount(this.name))],
1988
+ [helm.files.moduleSecretYaml, () => toYaml(moduleSecret(this.name, code, this.hash))]
1989
+ ];
1990
+ await Promise.all(pairs.map(async ([file, content]) => await import_fs5.promises.writeFile(file, content())));
1991
+ await overridesFile(this, helm.files.valuesYaml);
1992
+ const [mutateWebhook, validateWebhook] = await Promise.all([
1993
+ webhookConfig(this, "mutate", this.config.webhookTimeout),
1994
+ webhookConfig(this, "validate", this.config.webhookTimeout)
1995
+ ]);
1967
1996
  if (validateWebhook || mutateWebhook) {
1968
- await import_fs5.promises.writeFile(admissionDeployPath, dedent(admissionDeployTemplate(this.buildTimestamp)));
1969
- await import_fs5.promises.writeFile(admissionServiceMonitorPath, dedent(serviceMonitorTemplate("admission")));
1997
+ await import_fs5.promises.writeFile(helm.files.admissionDeploymentYaml, dedent(admissionDeployTemplate(this.buildTimestamp)));
1998
+ await import_fs5.promises.writeFile(helm.files.admissionServiceMonitorYaml, dedent(serviceMonitorTemplate("admission")));
1970
1999
  }
1971
2000
  if (mutateWebhook) {
1972
- const yamlMutateWebhook = (0, import_client_node2.dumpYaml)(mutateWebhook, { noRefs: true });
1973
- const mutateWebhookTemplate = replaceString(
1974
- replaceString(
1975
- replaceString(yamlMutateWebhook, this.name, "{{ .Values.uuid }}"),
1976
- this.config.onError === "reject" ? "Fail" : "Ignore",
1977
- "{{ .Values.admission.failurePolicy }}"
1978
- ),
1979
- `${this.config.webhookTimeout}` || "10",
1980
- "{{ .Values.admission.webhookTimeout }}"
1981
- );
1982
- await import_fs5.promises.writeFile(mutationWebhookPath, mutateWebhookTemplate);
2001
+ await import_fs5.promises.writeFile(helm.files.mutationWebhookYaml, createWebhookYaml(this, mutateWebhook));
1983
2002
  }
1984
2003
  if (validateWebhook) {
1985
- const yamlValidateWebhook = (0, import_client_node2.dumpYaml)(validateWebhook, { noRefs: true });
1986
- const validateWebhookTemplate = replaceString(
1987
- replaceString(
1988
- replaceString(yamlValidateWebhook, this.name, "{{ .Values.uuid }}"),
1989
- this.config.onError === "reject" ? "Fail" : "Ignore",
1990
- "{{ .Values.admission.failurePolicy }}"
1991
- ),
1992
- `${this.config.webhookTimeout}` || "10",
1993
- "{{ .Values.admission.webhookTimeout }}"
1994
- );
1995
- await import_fs5.promises.writeFile(validationWebhookPath, validateWebhookTemplate);
2004
+ await import_fs5.promises.writeFile(helm.files.validationWebhookYaml, createWebhookYaml(this, validateWebhook));
1996
2005
  }
2006
+ const watchDeployment = watcher(this, this.hash, this.buildTimestamp);
1997
2007
  if (watchDeployment) {
1998
- await import_fs5.promises.writeFile(watcherDeployPath, dedent(watcherDeployTemplate(this.buildTimestamp)));
1999
- await import_fs5.promises.writeFile(watcherServiceMonitorPath, dedent(serviceMonitorTemplate("watcher")));
2008
+ await import_fs5.promises.writeFile(helm.files.watcherDeploymentYaml, dedent(watcherDeployTemplate(this.buildTimestamp)));
2009
+ await import_fs5.promises.writeFile(helm.files.watcherServiceMonitorYaml, dedent(serviceMonitorTemplate("watcher")));
2000
2010
  }
2001
2011
  } catch (err) {
2002
2012
  console.error(`Error generating helm chart: ${err.message}`);
@@ -2211,8 +2221,8 @@ var hello_pepr_samples_default = [
2211
2221
  var gitIgnore = "# Ignore node_modules and Pepr build artifacts\nnode_modules\ndist\ninsecure*\n";
2212
2222
  var readmeMd = '# Pepr Module\n\nThis is a Pepr Module. [Pepr](https://github.com/defenseunicorns/pepr) is a type-safe Kubernetes middleware system.\n\nThe `capabilities` directory contains all the capabilities for this module. By default,\na capability is a single typescript file in the format of `capability-name.ts` that is\nimported in the root `pepr.ts` file as `import { HelloPepr } from "./capabilities/hello-pepr";`.\nBecause this is typescript, you can organize this however you choose, e.g. creating a sub-folder\nper-capability or common logic in shared files or folders.\n\nExample Structure:\n\n```\nModule Root\n\u251C\u2500\u2500 package.json\n\u251C\u2500\u2500 pepr.ts\n\u2514\u2500\u2500 capabilities\n \u251C\u2500\u2500 example-one.ts\n \u251C\u2500\u2500 example-three.ts\n \u2514\u2500\u2500 example-two.ts\n```\n';
2213
2223
  var peprTS = 'import { PeprModule } from "pepr";\n// cfg loads your pepr configuration from package.json\nimport cfg from "./package.json";\n\n// HelloPepr is a demo capability that is included with Pepr. Comment or delete the line below to remove it.\nimport { HelloPepr } from "./capabilities/hello-pepr";\n\n/**\n * This is the main entrypoint for this Pepr module. It is run when the module is started.\n * This is where you register your Pepr configurations and capabilities.\n */\nnew PeprModule(cfg, [\n // "HelloPepr" is a demo capability that is included with Pepr. Comment or delete the line below to remove it.\n HelloPepr,\n\n // Your additional capabilities go here\n]);\n';
2214
- var helloPeprTS = 'import {\n Capability,\n K8s,\n Log,\n PeprMutateRequest,\n RegisterKind,\n a,\n fetch,\n fetchStatus,\n kind,\n} from "pepr";\nimport nock from "nock";\n\n/**\n * The HelloPepr Capability is an example capability to demonstrate some general concepts of Pepr.\n * To test this capability you run `pepr dev`and then run the following command:\n * `kubectl apply -f capabilities/hello-pepr.samples.yaml`\n */\nexport const HelloPepr = new Capability({\n name: "hello-pepr",\n description: "A simple example capability to show how things work.",\n namespaces: ["pepr-demo", "pepr-demo-2"],\n});\n\n// Use the \'When\' function to create a new action, use \'Store\' to persist data\nconst { When, Store } = HelloPepr;\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (Namespace) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This action removes the label `remove-me` when a Namespace is created.\n * Note we don\'t need to specify the namespace here, because we\'ve already specified\n * it in the Capability definition above.\n */\nWhen(a.Namespace)\n .IsCreated()\n .Mutate(ns => ns.RemoveLabel("remove-me"));\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Watch Action with K8s SSA (Namespace) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This action watches for the `pepr-demo-2` namespace to be created, then creates a ConfigMap with\n * the name `pepr-ssa-demo` and adds the namespace UID to the ConfigMap data. Because Pepr uses\n * server-side apply for this operation, the ConfigMap will be created or updated if it already exists.\n */\nWhen(a.Namespace)\n .IsCreated()\n .WithName("pepr-demo-2")\n .Watch(async ns => {\n Log.info("Namespace pepr-demo-2 was created.");\n\n try {\n // Apply the ConfigMap using K8s server-side apply\n await K8s(kind.ConfigMap).Apply({\n metadata: {\n name: "pepr-ssa-demo",\n namespace: "pepr-demo-2",\n },\n data: {\n "ns-uid": ns.metadata.uid,\n },\n });\n } catch (error) {\n // You can use the Log object to log messages to the Pepr controller pod\n Log.error(error, "Failed to apply ConfigMap using server-side apply.");\n }\n\n // You can share data between actions using the Store, including between different types of actions\n Store.setItem("watch-data", "This data was stored by a Watch Action.");\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (CM Example 1) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This is a single action. They can be in the same file or put imported from other files.\n * In this example, when a ConfigMap is created with the name `example-1`, then add a label and annotation.\n *\n * Equivalent to manually running:\n * `kubectl label configmap example-1 pepr=was-here`\n * `kubectl annotate configmap example-1 pepr.dev=annotations-work-too`\n */\nWhen(a.ConfigMap)\n .IsCreated()\n .WithName("example-1")\n .Mutate(request => {\n request\n .SetLabel("pepr", "was-here")\n .SetAnnotation("pepr.dev", "annotations-work-too");\n\n // Use the Store to persist data between requests and Pepr controller pods\n Store.setItem("example-1", "was-here");\n\n // This data is written asynchronously and can be read back via `Store.getItem()` or `Store.subscribe()`\n Store.setItem("example-1-data", JSON.stringify(request.Raw.data));\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate & Validate Actions (CM Example 2) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This combines 3 different types of actions: \'Mutate\', \'Validate\', and \'Watch\'. The order\n * of the actions is required, but each action is optional. In this example, when a ConfigMap is created\n * with the name `example-2`, then add a label and annotation, validate that the ConfigMap has the label\n * `pepr`, and log the request.\n */\nWhen(a.ConfigMap)\n .IsCreated()\n .WithName("example-2")\n .Mutate(request => {\n // This Mutate Action will mutate the request before it is persisted to the cluster\n\n // Use `request.Merge()` to merge the new data with the existing data\n request.Merge({\n metadata: {\n labels: {\n pepr: "was-here",\n },\n annotations: {\n "pepr.dev": "annotations-work-too",\n },\n },\n });\n })\n .Validate(request => {\n // This Validate Action will validate the request before it is persisted to the cluster\n\n // Approve the request if the ConfigMap has the label \'pepr\'\n if (request.HasLabel("pepr")) {\n return request.Approve();\n }\n\n // Otherwise, deny the request with an error message (optional)\n return request.Deny("ConfigMap must have label \'pepr\'");\n })\n .Watch((cm, phase) => {\n // This Watch Action will watch the ConfigMap after it has been persisted to the cluster\n Log.info(cm, `ConfigMap was ${phase} with the name example-2`);\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (CM Example 2a) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This action shows a simple validation that will deny any ConfigMap that has the\n * annotation `evil`. Note that the `Deny()` function takes an optional second parameter that is a\n * user-defined status code to return.\n */\nWhen(a.ConfigMap)\n .IsCreated()\n .Validate(request => {\n if (request.HasAnnotation("evil")) {\n return request.Deny("No evil CM annotations allowed.", 400);\n }\n\n return request.Approve();\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (CM Example 3) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This action combines different styles. Unlike the previous actions, this one will look\n * for any ConfigMap in the `pepr-demo` namespace that has the label `change=by-label` during either\n * CREATE or UPDATE. Note that all conditions added such as `WithName()`, `WithLabel()`, `InNamespace()`,\n * are ANDs so all conditions must be true for the request to be processed.\n */\nWhen(a.ConfigMap)\n .IsCreatedOrUpdated()\n .WithLabel("change", "by-label")\n .Mutate(request => {\n // The K8s object e are going to mutate\n const cm = request.Raw;\n\n // Get the username and uid of the K8s request\n const { username, uid } = request.Request.userInfo;\n\n // Store some data about the request in the configmap\n cm.data["username"] = username;\n cm.data["uid"] = uid;\n\n // You can still mix other ways of making changes too\n request.SetAnnotation("pepr.dev", "making-waves");\n });\n\n// This action validates the label `change=by-label` is deleted\nWhen(a.ConfigMap)\n .IsDeleted()\n .WithLabel("change", "by-label")\n .Validate(request => {\n // Log and then always approve the request\n Log.info("CM with label \'change=by-label\' was deleted.");\n return request.Approve();\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (CM Example 4) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This action show how you can use the `Mutate()` function without an inline function.\n * This is useful if you want to keep your actions small and focused on a single task,\n * or if you want to reuse the same function in multiple actions.\n */\nWhen(a.ConfigMap).IsCreated().WithName("example-4").Mutate(example4Cb);\n\n// This function uses the complete type definition, but is not required.\nfunction example4Cb(cm: PeprMutateRequest<a.ConfigMap>) {\n cm.SetLabel("pepr.dev/first", "true");\n cm.SetLabel("pepr.dev/second", "true");\n cm.SetLabel("pepr.dev/third", "true");\n}\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (CM Example 4a) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This is the same as Example 4, except this only operates on a CM in the `pepr-demo-2` namespace.\n * Note because the Capability defines namespaces, the namespace specified here must be one of those.\n * Alternatively, you can remove the namespace from the Capability definition and specify it here.\n */\nWhen(a.ConfigMap)\n .IsCreated()\n .InNamespace("pepr-demo-2")\n .WithName("example-4a")\n .Mutate(example4Cb);\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (CM Example 5) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This action is a bit more complex. It will look for any ConfigMap in the `pepr-demo`\n * namespace that has the label `chuck-norris` during CREATE. When it finds one, it will fetch a\n * random Chuck Norris joke from the API and add it to the ConfigMap. This is a great example of how\n * you can use Pepr to make changes to your K8s objects based on external data.\n *\n * Note the use of the `async` keyword. This is required for any action that uses `await` or `fetch()`.\n *\n * Also note we are passing a type to the `fetch()` function. This is optional, but it will help you\n * avoid mistakes when working with the data returned from the API. You can also use the `as` keyword to\n * cast the data returned from the API.\n *\n * These are equivalent:\n * ```ts\n * const joke = await fetch<TheChuckNorrisJoke>("https://icanhazdadjoke.com/");\n * const joke = await fetch("https://icanhazdadjoke.com/") as TheChuckNorrisJoke;\n * ```\n *\n * Alternatively, you can drop the type completely:\n *\n * ```ts\n * fetch("https://icanhazdadjoke.com")\n * ```\n */\ninterface TheChuckNorrisJoke {\n id: string;\n joke: string;\n status: number;\n}\n\nWhen(a.ConfigMap)\n .IsCreatedOrUpdated()\n .WithLabel("chuck-norris")\n .Mutate(cm => cm.SetLabel("got-jokes", "true"))\n .Watch(async cm => {\n const jokeURL = "https://icanhazdadjoke.com/";\n\n // Set up Nock to mock the API calls globally with header matching\n nock(jokeURL).get("/").reply(200, {\n id: "R7UfaahVfFd",\n joke: "Funny joke goes here.",\n status: 200,\n });\n\n // Try/catch is not needed as a response object will always be returned\n const response = await fetch<TheChuckNorrisJoke>(jokeURL, {\n headers: {\n Accept: "application/json",\n },\n });\n\n // Instead, check the `response.ok` field\n if (response.ok) {\n const { joke } = response.data;\n // Add Joke to the Store\n await Store.setItemAndWait(jokeURL, joke);\n // Add the Chuck Norris joke to the configmap\n try {\n await K8s(kind.ConfigMap).Apply({\n metadata: {\n name: cm.metadata.name,\n namespace: cm.metadata.namespace,\n },\n data: {\n "chuck-says": Store.getItem(jokeURL),\n },\n });\n } catch (error) {\n Log.error(error, "Failed to apply ConfigMap using server-side apply.", {\n cm,\n });\n }\n }\n\n // You can also assert on different HTTP response codes\n if (response.status === fetchStatus.NOT_FOUND) {\n // Do something else\n return;\n }\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (Secret Base64 Handling) *\n * ---------------------------------------------------------------------------------------------------\n *\n * The K8s JS client provides incomplete support for base64 encoding/decoding handling for secrets,\n * unlike the GO client. To make this less painful, Pepr automatically handles base64 encoding/decoding\n * secret data before and after the action is executed.\n */\nWhen(a.Secret)\n .IsCreated()\n .WithName("secret-1")\n .Mutate(request => {\n const secret = request.Raw;\n\n // This will be encoded at the end of all processing back to base64: "Y2hhbmdlLXdpdGhvdXQtZW5jb2Rpbmc="\n secret.data.magic = "change-without-encoding";\n\n // You can modify the data directly, and it will be encoded at the end of all processing\n secret.data.example += " - modified by Pepr";\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (Untyped Custom Resource) *\n * ---------------------------------------------------------------------------------------------------\n *\n * Out of the box, Pepr supports all the standard Kubernetes objects. However, you can also create\n * your own types. This is useful if you are working with an Operator that creates custom resources.\n * There are two ways to do this, the first is to use the `When()` function with a `GenericKind`,\n * the second is to create a new class that extends `GenericKind` and use the `RegisterKind()` function.\n *\n * This example shows how to use the `When()` function with a `GenericKind`. Note that you\n * must specify the `group`, `version`, and `kind` of the object (if applicable). This is how Pepr knows\n * if the action should be triggered or not. Since we are using a `GenericKind`,\n * Pepr will not be able to provide any intellisense for the object, so you will need to refer to the\n * Kubernetes API documentation for the object you are working with.\n *\n * You will need to wait for the CRD in `hello-pepr.samples.yaml` to be created, then you can apply\n *\n * ```yaml\n * apiVersion: pepr.dev/v1\n * kind: Unicorn\n * metadata:\n * name: example-1\n * namespace: pepr-demo\n * spec:\n * message: replace-me\n * counter: 0\n * ```\n */\nWhen(a.GenericKind, {\n group: "pepr.dev",\n version: "v1",\n kind: "Unicorn",\n})\n .IsCreated()\n .WithName("example-1")\n .Mutate(request => {\n request.Merge({\n spec: {\n message: "Hello Pepr without type data!",\n counter: Math.random(),\n },\n });\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (Typed Custom Resource) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This example shows how to use the `RegisterKind()` function to create a new type. This is useful\n * if you are working with an Operator that creates custom resources and you want to have intellisense\n * for the object. Note that you must specify the `group`, `version`, and `kind` of the object (if applicable)\n * as this is how Pepr knows if the action should be triggered or not.\n *\n * Once you register a new Kind with Pepr, you can use the `When()` function with the new Kind. Ideally,\n * you should register custom Kinds at the top of your Capability file or Pepr Module so they are available\n * to all actions, but we are putting it here for demonstration purposes.\n *\n * You will need to wait for the CRD in `hello-pepr.samples.yaml` to be created, then you can apply\n *\n * ```yaml\n * apiVersion: pepr.dev/v1\n * kind: Unicorn\n * metadata:\n * name: example-2\n * namespace: pepr-demo\n * spec:\n * message: replace-me\n * counter: 0\n * ```*\n */\nclass UnicornKind extends a.GenericKind {\n spec: {\n /**\n * JSDoc comments can be added to explain more details about the field.\n *\n * @example\n * ```ts\n * request.Raw.spec.message = "Hello Pepr!";\n * ```\n * */\n message: string;\n counter: number;\n };\n}\n\nRegisterKind(UnicornKind, {\n group: "pepr.dev",\n version: "v1",\n kind: "Unicorn",\n});\n\nWhen(UnicornKind)\n .IsCreated()\n .WithName("example-2")\n .Mutate(request => {\n request.Merge({\n spec: {\n message: "Hello Pepr with type data!",\n counter: Math.random(),\n },\n });\n });\n\n/**\n * A callback function that is called once the Pepr Store is fully loaded.\n */\nStore.onReady(data => {\n Log.info(data, "Pepr Store Ready");\n});\n';
2215
- var packageJSON = { name: "pepr", description: "Kubernetes application engine", author: "Defense Unicorns", homepage: "https://github.com/defenseunicorns/pepr", license: "Apache-2.0", bin: "dist/cli.js", repository: "defenseunicorns/pepr", engines: { node: ">=18.0.0" }, files: ["/dist", "/src", "!src/**/*.test.ts", "!dist/**/*.test.d.ts*"], version: "0.40.1", main: "dist/lib.js", types: "dist/lib.d.ts", scripts: { ci: "npm ci", "gen-data-json": "node hack/build-template-data.js", prebuild: "rm -fr dist/* && npm run gen-data-json", version: "node scripts/set-version.js", build: "tsc && node build.mjs && npm pack", "build:image": "npm run build && docker buildx build --output type=docker --tag pepr:dev .", test: "npm run test:unit && npm run test:journey", "test:unit": "npm run gen-data-json && jest src --coverage --detectOpenHandles --coverageDirectory=./coverage --testPathIgnorePatterns='cosign.e2e.test.ts'", "test:journey": "npm run test:journey:k3d && npm run build && npm run test:journey:image && npm run test:journey:run", "test:journey:prep": "if [ ! -d ./pepr-upgrade-test ]; then git clone https://github.com/defenseunicorns/pepr-upgrade-test.git ; fi", "test:journey-wasm": "npm run test:journey:k3d && npm run build && npm run test:journey:image && npm run test:journey:run-wasm", "test:journey:k3d": "k3d cluster delete pepr-dev && k3d cluster create pepr-dev --k3s-arg '--debug@server:0' --wait && kubectl rollout status deployment -n kube-system", "test:journey:image": "docker buildx build --output type=docker --tag pepr:dev . && k3d image import pepr:dev -c pepr-dev", "test:journey:run": "jest --detectOpenHandles journey/entrypoint.test.ts && npm run test:journey:prep && npm run test:journey:upgrade", "test:journey:run-wasm": "jest --detectOpenHandles journey/entrypoint-wasm.test.ts", "test:journey:upgrade": "npm run test:journey:k3d && npm run test:journey:image && jest --detectOpenHandles journey/pepr-upgrade.test.ts", "format:check": "eslint src && prettier src --check", "format:fix": "eslint src --fix && prettier src --write", prepare: `if [ "$NODE_ENV" != 'production' ]; then husky; fi` }, dependencies: { "@types/ramda": "0.30.2", express: "4.21.1", "fast-json-patch": "3.1.1", "follow-redirects": "1.15.9", "http-status-codes": "^2.3.0", "json-pointer": "^0.6.2", "kubernetes-fluent-client": "3.3.4", pino: "9.5.0", "pino-pretty": "13.0.0", "prom-client": "15.1.3", ramda: "0.30.1", sigstore: "3.0.0" }, devDependencies: { "@commitlint/cli": "19.6.0", "@commitlint/config-conventional": "19.6.0", "@fast-check/jest": "^2.0.1", "@jest/globals": "29.7.0", "@types/eslint": "9.6.1", "@types/express": "5.0.0", "@types/follow-redirects": "1.14.4", "@types/json-pointer": "^1.0.34", "@types/node": "22.x.x", "@types/node-forge": "1.3.11", "@types/uuid": "10.0.0", "fast-check": "^3.19.0", husky: "^9.1.6", jest: "29.7.0", "js-yaml": "^4.1.0", nock: "^13.5.4", "ts-jest": "29.2.5" }, peerDependencies: { "@types/prompts": "2.4.9", "@typescript-eslint/eslint-plugin": "7.18.0", "@typescript-eslint/parser": "7.18.0", commander: "12.1.0", esbuild: "0.23.0", eslint: "8.57.0", "node-forge": "1.3.1", prettier: "3.3.3", prompts: "2.4.2", typescript: "5.3.3", uuid: "10.0.0" } };
2224
+ var helloPeprTS = 'import {\n Capability,\n K8s,\n Log,\n PeprMutateRequest,\n RegisterKind,\n a,\n fetch,\n fetchStatus,\n kind,\n} from "pepr";\nimport { MockAgent, setGlobalDispatcher } from "undici";\n\n/**\n * The HelloPepr Capability is an example capability to demonstrate some general concepts of Pepr.\n * To test this capability you run `pepr dev`and then run the following command:\n * `kubectl apply -f capabilities/hello-pepr.samples.yaml`\n */\nexport const HelloPepr = new Capability({\n name: "hello-pepr",\n description: "A simple example capability to show how things work.",\n namespaces: ["pepr-demo", "pepr-demo-2"],\n});\n\n// Use the \'When\' function to create a new action, use \'Store\' to persist data\nconst { When, Store } = HelloPepr;\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (Namespace) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This action removes the label `remove-me` when a Namespace is created.\n * Note we don\'t need to specify the namespace here, because we\'ve already specified\n * it in the Capability definition above.\n */\nWhen(a.Namespace)\n .IsCreated()\n .Mutate(ns => ns.RemoveLabel("remove-me"));\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Watch Action with K8s SSA (Namespace) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This action watches for the `pepr-demo-2` namespace to be created, then creates a ConfigMap with\n * the name `pepr-ssa-demo` and adds the namespace UID to the ConfigMap data. Because Pepr uses\n * server-side apply for this operation, the ConfigMap will be created or updated if it already exists.\n */\nWhen(a.Namespace)\n .IsCreated()\n .WithName("pepr-demo-2")\n .Watch(async ns => {\n Log.info("Namespace pepr-demo-2 was created.");\n\n try {\n // Apply the ConfigMap using K8s server-side apply\n await K8s(kind.ConfigMap).Apply({\n metadata: {\n name: "pepr-ssa-demo",\n namespace: "pepr-demo-2",\n },\n data: {\n "ns-uid": ns.metadata.uid,\n },\n });\n } catch (error) {\n // You can use the Log object to log messages to the Pepr controller pod\n Log.error(error, "Failed to apply ConfigMap using server-side apply.");\n }\n\n // You can share data between actions using the Store, including between different types of actions\n Store.setItem("watch-data", "This data was stored by a Watch Action.");\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (CM Example 1) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This is a single action. They can be in the same file or put imported from other files.\n * In this example, when a ConfigMap is created with the name `example-1`, then add a label and annotation.\n *\n * Equivalent to manually running:\n * `kubectl label configmap example-1 pepr=was-here`\n * `kubectl annotate configmap example-1 pepr.dev=annotations-work-too`\n */\nWhen(a.ConfigMap)\n .IsCreated()\n .WithName("example-1")\n .Mutate(request => {\n request\n .SetLabel("pepr", "was-here")\n .SetAnnotation("pepr.dev", "annotations-work-too");\n\n // Use the Store to persist data between requests and Pepr controller pods\n Store.setItem("example-1", "was-here");\n\n // This data is written asynchronously and can be read back via `Store.getItem()` or `Store.subscribe()`\n Store.setItem("example-1-data", JSON.stringify(request.Raw.data));\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate & Validate Actions (CM Example 2) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This combines 3 different types of actions: \'Mutate\', \'Validate\', and \'Watch\'. The order\n * of the actions is required, but each action is optional. In this example, when a ConfigMap is created\n * with the name `example-2`, then add a label and annotation, validate that the ConfigMap has the label\n * `pepr`, and log the request.\n */\nWhen(a.ConfigMap)\n .IsCreated()\n .WithName("example-2")\n .Mutate(request => {\n // This Mutate Action will mutate the request before it is persisted to the cluster\n\n // Use `request.Merge()` to merge the new data with the existing data\n request.Merge({\n metadata: {\n labels: {\n pepr: "was-here",\n },\n annotations: {\n "pepr.dev": "annotations-work-too",\n },\n },\n });\n })\n .Validate(request => {\n // This Validate Action will validate the request before it is persisted to the cluster\n\n // Approve the request if the ConfigMap has the label \'pepr\'\n if (request.HasLabel("pepr")) {\n return request.Approve();\n }\n\n // Otherwise, deny the request with an error message (optional)\n return request.Deny("ConfigMap must have label \'pepr\'");\n })\n .Watch((cm, phase) => {\n // This Watch Action will watch the ConfigMap after it has been persisted to the cluster\n Log.info(cm, `ConfigMap was ${phase} with the name example-2`);\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (CM Example 2a) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This action shows a simple validation that will deny any ConfigMap that has the\n * annotation `evil`. Note that the `Deny()` function takes an optional second parameter that is a\n * user-defined status code to return.\n */\nWhen(a.ConfigMap)\n .IsCreated()\n .Validate(request => {\n if (request.HasAnnotation("evil")) {\n return request.Deny("No evil CM annotations allowed.", 400);\n }\n\n return request.Approve();\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (CM Example 3) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This action combines different styles. Unlike the previous actions, this one will look\n * for any ConfigMap in the `pepr-demo` namespace that has the label `change=by-label` during either\n * CREATE or UPDATE. Note that all conditions added such as `WithName()`, `WithLabel()`, `InNamespace()`,\n * are ANDs so all conditions must be true for the request to be processed.\n */\nWhen(a.ConfigMap)\n .IsCreatedOrUpdated()\n .WithLabel("change", "by-label")\n .Mutate(request => {\n // The K8s object e are going to mutate\n const cm = request.Raw;\n\n // Get the username and uid of the K8s request\n const { username, uid } = request.Request.userInfo;\n\n // Store some data about the request in the configmap\n cm.data["username"] = username;\n cm.data["uid"] = uid;\n\n // You can still mix other ways of making changes too\n request.SetAnnotation("pepr.dev", "making-waves");\n });\n\n// This action validates the label `change=by-label` is deleted\nWhen(a.ConfigMap)\n .IsDeleted()\n .WithLabel("change", "by-label")\n .Validate(request => {\n // Log and then always approve the request\n Log.info("CM with label \'change=by-label\' was deleted.");\n return request.Approve();\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (CM Example 4) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This action show how you can use the `Mutate()` function without an inline function.\n * This is useful if you want to keep your actions small and focused on a single task,\n * or if you want to reuse the same function in multiple actions.\n */\nWhen(a.ConfigMap).IsCreated().WithName("example-4").Mutate(example4Cb);\n\n// This function uses the complete type definition, but is not required.\nfunction example4Cb(cm: PeprMutateRequest<a.ConfigMap>) {\n cm.SetLabel("pepr.dev/first", "true");\n cm.SetLabel("pepr.dev/second", "true");\n cm.SetLabel("pepr.dev/third", "true");\n}\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (CM Example 4a) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This is the same as Example 4, except this only operates on a CM in the `pepr-demo-2` namespace.\n * Note because the Capability defines namespaces, the namespace specified here must be one of those.\n * Alternatively, you can remove the namespace from the Capability definition and specify it here.\n */\nWhen(a.ConfigMap)\n .IsCreated()\n .InNamespace("pepr-demo-2")\n .WithName("example-4a")\n .Mutate(example4Cb);\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (CM Example 5) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This action is a bit more complex. It will look for any ConfigMap in the `pepr-demo`\n * namespace that has the label `chuck-norris` during CREATE. When it finds one, it will fetch a\n * random Chuck Norris joke from the API and add it to the ConfigMap. This is a great example of how\n * you can use Pepr to make changes to your K8s objects based on external data.\n *\n * Note the use of the `async` keyword. This is required for any action that uses `await` or `fetch()`.\n *\n * Also note we are passing a type to the `fetch()` function. This is optional, but it will help you\n * avoid mistakes when working with the data returned from the API. You can also use the `as` keyword to\n * cast the data returned from the API.\n *\n * These are equivalent:\n * ```ts\n * const joke = await fetch<TheChuckNorrisJoke>("https://icanhazdadjoke.com/");\n * const joke = await fetch("https://icanhazdadjoke.com/") as TheChuckNorrisJoke;\n * ```\n *\n * Alternatively, you can drop the type completely:\n *\n * ```ts\n * fetch("https://icanhazdadjoke.com")\n * ```\n */\ninterface TheChuckNorrisJoke {\n id: string;\n joke: string;\n status: number;\n}\n\nWhen(a.ConfigMap)\n .IsCreatedOrUpdated()\n .WithLabel("chuck-norris")\n .Mutate(cm => cm.SetLabel("got-jokes", "true"))\n .Watch(async cm => {\n const jokeURL = "https://icanhazdadjoke.com";\n\n const mockAgent: MockAgent = new MockAgent();\n setGlobalDispatcher(mockAgent);\n const mockClient = mockAgent.get(jokeURL);\n mockClient.intercept({ path: "/", method: "GET" }).reply(\n 200,\n {\n id: "R7UfaahVfFd",\n joke: "Funny joke goes here.",\n status: 200,\n },\n {\n headers: {\n "Content-Type": "application/json; charset=utf-8",\n },\n },\n );\n\n // Try/catch is not needed as a response object will always be returned\n const response = await fetch<TheChuckNorrisJoke>(jokeURL, {\n headers: {\n Accept: "application/json",\n },\n });\n\n // Instead, check the `response.ok` field\n if (response.ok) {\n const { joke } = response.data;\n // Add Joke to the Store\n await Store.setItemAndWait(jokeURL, joke);\n // Add the Chuck Norris joke to the configmap\n try {\n await K8s(kind.ConfigMap).Apply({\n metadata: {\n name: cm.metadata.name,\n namespace: cm.metadata.namespace,\n },\n data: {\n "chuck-says": Store.getItem(jokeURL),\n },\n });\n } catch (error) {\n Log.error(error, "Failed to apply ConfigMap using server-side apply.", {\n cm,\n });\n }\n }\n\n // You can also assert on different HTTP response codes\n if (response.status === fetchStatus.NOT_FOUND) {\n // Do something else\n return;\n }\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (Secret Base64 Handling) *\n * ---------------------------------------------------------------------------------------------------\n *\n * The K8s JS client provides incomplete support for base64 encoding/decoding handling for secrets,\n * unlike the GO client. To make this less painful, Pepr automatically handles base64 encoding/decoding\n * secret data before and after the action is executed.\n */\nWhen(a.Secret)\n .IsCreated()\n .WithName("secret-1")\n .Mutate(request => {\n const secret = request.Raw;\n\n // This will be encoded at the end of all processing back to base64: "Y2hhbmdlLXdpdGhvdXQtZW5jb2Rpbmc="\n secret.data.magic = "change-without-encoding";\n\n // You can modify the data directly, and it will be encoded at the end of all processing\n secret.data.example += " - modified by Pepr";\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (Untyped Custom Resource) *\n * ---------------------------------------------------------------------------------------------------\n *\n * Out of the box, Pepr supports all the standard Kubernetes objects. However, you can also create\n * your own types. This is useful if you are working with an Operator that creates custom resources.\n * There are two ways to do this, the first is to use the `When()` function with a `GenericKind`,\n * the second is to create a new class that extends `GenericKind` and use the `RegisterKind()` function.\n *\n * This example shows how to use the `When()` function with a `GenericKind`. Note that you\n * must specify the `group`, `version`, and `kind` of the object (if applicable). This is how Pepr knows\n * if the action should be triggered or not. Since we are using a `GenericKind`,\n * Pepr will not be able to provide any intellisense for the object, so you will need to refer to the\n * Kubernetes API documentation for the object you are working with.\n *\n * You will need to wait for the CRD in `hello-pepr.samples.yaml` to be created, then you can apply\n *\n * ```yaml\n * apiVersion: pepr.dev/v1\n * kind: Unicorn\n * metadata:\n * name: example-1\n * namespace: pepr-demo\n * spec:\n * message: replace-me\n * counter: 0\n * ```\n */\nWhen(a.GenericKind, {\n group: "pepr.dev",\n version: "v1",\n kind: "Unicorn",\n})\n .IsCreated()\n .WithName("example-1")\n .Mutate(request => {\n request.Merge({\n spec: {\n message: "Hello Pepr without type data!",\n counter: Math.random(),\n },\n });\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (Typed Custom Resource) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This example shows how to use the `RegisterKind()` function to create a new type. This is useful\n * if you are working with an Operator that creates custom resources and you want to have intellisense\n * for the object. Note that you must specify the `group`, `version`, and `kind` of the object (if applicable)\n * as this is how Pepr knows if the action should be triggered or not.\n *\n * Once you register a new Kind with Pepr, you can use the `When()` function with the new Kind. Ideally,\n * you should register custom Kinds at the top of your Capability file or Pepr Module so they are available\n * to all actions, but we are putting it here for demonstration purposes.\n *\n * You will need to wait for the CRD in `hello-pepr.samples.yaml` to be created, then you can apply\n *\n * ```yaml\n * apiVersion: pepr.dev/v1\n * kind: Unicorn\n * metadata:\n * name: example-2\n * namespace: pepr-demo\n * spec:\n * message: replace-me\n * counter: 0\n * ```*\n */\nclass UnicornKind extends a.GenericKind {\n spec: {\n /**\n * JSDoc comments can be added to explain more details about the field.\n *\n * @example\n * ```ts\n * request.Raw.spec.message = "Hello Pepr!";\n * ```\n * */\n message: string;\n counter: number;\n };\n}\n\nRegisterKind(UnicornKind, {\n group: "pepr.dev",\n version: "v1",\n kind: "Unicorn",\n});\n\nWhen(UnicornKind)\n .IsCreated()\n .WithName("example-2")\n .Mutate(request => {\n request.Merge({\n spec: {\n message: "Hello Pepr with type data!",\n counter: Math.random(),\n },\n });\n });\n\n/**\n * A callback function that is called once the Pepr Store is fully loaded.\n */\nStore.onReady(data => {\n Log.info(data, "Pepr Store Ready");\n});\n';
2225
+ var packageJSON = { name: "pepr", description: "Kubernetes application engine", author: "Defense Unicorns", homepage: "https://github.com/defenseunicorns/pepr", license: "Apache-2.0", bin: "dist/cli.js", repository: "defenseunicorns/pepr", engines: { node: ">=18.0.0" }, files: ["/dist", "/src", "!src/**/*.test.ts", "!dist/**/*.test.d.ts*"], version: "0.42.0", main: "dist/lib.js", types: "dist/lib.d.ts", scripts: { ci: "npm ci", "gen-data-json": "node hack/build-template-data.js", prebuild: "rm -fr dist/* && npm run gen-data-json", version: "node scripts/set-version.js", build: "tsc && node build.mjs && npm pack", "build:image": "npm run build && docker buildx build --output type=docker --tag pepr:dev .", test: "npm run test:unit && npm run test:journey", "test:unit": "npm run gen-data-json && jest src --coverage --detectOpenHandles --coverageDirectory=./coverage --testPathIgnorePatterns='cosign.e2e.test.ts'", "test:journey": "npm run test:journey:k3d && npm run build && npm run test:journey:image && npm run test:journey:run", "test:journey:prep": "if [ ! -d ./pepr-upgrade-test ]; then git clone https://github.com/defenseunicorns/pepr-upgrade-test.git ; fi", "test:journey-wasm": "npm run test:journey:k3d && npm run build && npm run test:journey:image && npm run test:journey:run-wasm", "test:journey:k3d": "k3d cluster delete pepr-dev && k3d cluster create pepr-dev --k3s-arg '--debug@server:0' --wait && kubectl rollout status deployment -n kube-system", "test:journey:image": "docker buildx build --output type=docker --tag pepr:dev . && k3d image import pepr:dev -c pepr-dev", "test:journey:run": "jest --detectOpenHandles journey/entrypoint.test.ts && npm run test:journey:prep && npm run test:journey:upgrade", "test:journey:run-wasm": "jest --detectOpenHandles journey/entrypoint-wasm.test.ts", "test:journey:upgrade": "npm run test:journey:k3d && npm run test:journey:image && jest --detectOpenHandles journey/pepr-upgrade.test.ts", "format:check": "eslint src && prettier src --check", "format:fix": "eslint src --fix && prettier src --write", prepare: `if [ "$NODE_ENV" != 'production' ]; then husky; fi` }, dependencies: { "@types/ramda": "0.30.2", express: "4.21.2", "fast-json-patch": "3.1.1", "follow-redirects": "1.15.9", "http-status-codes": "^2.3.0", "json-pointer": "^0.6.2", "kubernetes-fluent-client": "3.3.7", pino: "9.5.0", "pino-pretty": "13.0.0", "prom-client": "15.1.3", ramda: "0.30.1", sigstore: "3.0.0" }, devDependencies: { "@commitlint/cli": "19.6.0", "@commitlint/config-conventional": "19.6.0", "@fast-check/jest": "^2.0.1", "@jest/globals": "29.7.0", "@types/eslint": "9.6.1", "@types/express": "5.0.0", "@types/follow-redirects": "1.14.4", "@types/json-pointer": "^1.0.34", "@types/node": "22.x.x", "@types/node-forge": "1.3.11", "@types/uuid": "10.0.0", "fast-check": "^3.19.0", husky: "^9.1.6", jest: "29.7.0", "js-yaml": "^4.1.0", "ts-jest": "29.2.5", undici: "^7.0.1" }, peerDependencies: { "@types/prompts": "2.4.9", "@typescript-eslint/eslint-plugin": "7.18.0", "@typescript-eslint/parser": "7.18.0", commander: "12.1.0", esbuild: "0.23.0", eslint: "8.57.0", "node-forge": "1.3.1", prettier: "3.3.3", prompts: "2.4.2", typescript: "5.3.3", uuid: "10.0.0" } };
2216
2226
 
2217
2227
  // src/templates/pepr.code-snippets.json
2218
2228
  var pepr_code_snippets_default = {
@@ -2339,7 +2349,7 @@ function genPkgJSON(opts, pgkVerOverride) {
2339
2349
  },
2340
2350
  dependencies: {
2341
2351
  pepr: pgkVerOverride || version2,
2342
- nock: "13.5.4"
2352
+ undici: "^7.0.1"
2343
2353
  },
2344
2354
  devDependencies: {
2345
2355
  typescript
@@ -2717,6 +2727,45 @@ async function buildModule(reloader, entryPoint = peprTS2, embed = true) {
2717
2727
 
2718
2728
  // src/cli/deploy.ts
2719
2729
  var import_prompts = __toESM(require("prompts"));
2730
+
2731
+ // src/lib/deploymentChecks.ts
2732
+ var import_kubernetes_fluent_client4 = require("kubernetes-fluent-client");
2733
+ async function checkDeploymentStatus(namespace2) {
2734
+ const deployments = await (0, import_kubernetes_fluent_client4.K8s)(import_kubernetes_fluent_client4.kind.Deployment).InNamespace(namespace2).Get();
2735
+ let status = false;
2736
+ let readyCount = 0;
2737
+ for (const deployment2 of deployments.items) {
2738
+ const readyReplicas = deployment2.status?.readyReplicas ? deployment2.status?.readyReplicas : 0;
2739
+ if (deployment2.status?.readyReplicas !== deployment2.spec?.replicas) {
2740
+ logger_default.info(
2741
+ `Waiting for deployment ${deployment2.metadata?.name} rollout to finish: ${readyReplicas} of ${deployment2.spec?.replicas} replicas are available`
2742
+ );
2743
+ } else {
2744
+ logger_default.info(
2745
+ `Deployment ${deployment2.metadata?.name} rolled out: ${readyReplicas} of ${deployment2.spec?.replicas} replicas are available`
2746
+ );
2747
+ readyCount++;
2748
+ }
2749
+ }
2750
+ if (readyCount === deployments.items.length) {
2751
+ status = true;
2752
+ }
2753
+ return status;
2754
+ }
2755
+ async function namespaceDeploymentsReady(namespace2 = "pepr-system") {
2756
+ logger_default.info(`Checking ${namespace2} deployments status...`);
2757
+ let ready = false;
2758
+ while (!ready) {
2759
+ ready = await checkDeploymentStatus(namespace2);
2760
+ if (ready) {
2761
+ return ready;
2762
+ }
2763
+ await new Promise((resolve5) => setTimeout(resolve5, 1e3));
2764
+ }
2765
+ logger_default.info(`All ${namespace2} deployments are ready`);
2766
+ }
2767
+
2768
+ // src/cli/deploy.ts
2720
2769
  function deploy_default(program2) {
2721
2770
  program2.command("deploy").description("Deploy a Pepr Module").option("-i, --image [image]", "Override the image tag").option("--confirm", "Skip confirmation prompt").option("--pullSecret <name>", "Deploy imagePullSecret for Controller private registry").option("--docker-server <server>", "Docker server address").option("--docker-username <username>", "Docker registry username").option("--docker-email <email>", "Email for Docker registry").option("--docker-password <password>", "Password for Docker registry").option("--force", "Force deploy the module, override manager field").action(async (opts) => {
2722
2771
  let imagePullSecret;