pepr 0.12.1 → 0.13.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 (90) hide show
  1. package/CODE_OF_CONDUCT.md +83 -0
  2. package/CONTRIBUTING.md +70 -0
  3. package/README.md +28 -30
  4. package/dist/cli.js +644 -679
  5. package/dist/controller.js +13 -81
  6. package/dist/lib/assets/deploy.d.ts +3 -0
  7. package/dist/lib/assets/deploy.d.ts.map +1 -0
  8. package/dist/lib/assets/index.d.ts +18 -0
  9. package/dist/lib/assets/index.d.ts.map +1 -0
  10. package/dist/lib/assets/loader.d.ts +14 -0
  11. package/dist/lib/assets/loader.d.ts.map +1 -0
  12. package/dist/lib/assets/networking.d.ts +6 -0
  13. package/dist/lib/assets/networking.d.ts.map +1 -0
  14. package/dist/lib/assets/pods.d.ts +8 -0
  15. package/dist/lib/assets/pods.d.ts.map +1 -0
  16. package/dist/lib/assets/rbac.d.ts +11 -0
  17. package/dist/lib/assets/rbac.d.ts.map +1 -0
  18. package/dist/lib/assets/webhooks.d.ts +6 -0
  19. package/dist/lib/assets/webhooks.d.ts.map +1 -0
  20. package/dist/lib/assets/yaml.d.ts +4 -0
  21. package/dist/lib/assets/yaml.d.ts.map +1 -0
  22. package/dist/lib/capability.d.ts +1 -3
  23. package/dist/lib/capability.d.ts.map +1 -1
  24. package/dist/lib/controller.d.ts +45 -10
  25. package/dist/lib/controller.d.ts.map +1 -1
  26. package/dist/lib/filter.d.ts +1 -1
  27. package/dist/lib/filter.d.ts.map +1 -1
  28. package/dist/lib/k8s/index.d.ts +2 -1
  29. package/dist/lib/k8s/index.d.ts.map +1 -1
  30. package/dist/lib/k8s/kinds.d.ts.map +1 -1
  31. package/dist/lib/k8s/types.d.ts +13 -13
  32. package/dist/lib/k8s/types.d.ts.map +1 -1
  33. package/dist/lib/k8s/upstream.d.ts +2 -2
  34. package/dist/lib/k8s/upstream.d.ts.map +1 -1
  35. package/dist/lib/logger.d.ts +8 -54
  36. package/dist/lib/logger.d.ts.map +1 -1
  37. package/dist/lib/metrics.d.ts +11 -4
  38. package/dist/lib/metrics.d.ts.map +1 -1
  39. package/dist/lib/module.d.ts +2 -2
  40. package/dist/lib/module.d.ts.map +1 -1
  41. package/dist/lib/mutate-processor.d.ts +5 -0
  42. package/dist/lib/mutate-processor.d.ts.map +1 -0
  43. package/dist/lib/{request.d.ts → mutate-request.d.ts} +5 -5
  44. package/dist/lib/mutate-request.d.ts.map +1 -0
  45. package/dist/lib/types.d.ts +45 -46
  46. package/dist/lib/types.d.ts.map +1 -1
  47. package/dist/lib/utils.d.ts.map +1 -1
  48. package/dist/lib/validate-processor.d.ts +4 -0
  49. package/dist/lib/validate-processor.d.ts.map +1 -0
  50. package/dist/lib/validate-request.d.ts +54 -0
  51. package/dist/lib/validate-request.d.ts.map +1 -0
  52. package/dist/lib.d.ts +3 -2
  53. package/dist/lib.d.ts.map +1 -1
  54. package/dist/lib.js +510 -306
  55. package/dist/lib.js.map +4 -4
  56. package/package.json +15 -12
  57. package/src/cli.ts +2 -11
  58. package/src/lib/assets/deploy.ts +179 -0
  59. package/src/lib/assets/index.ts +46 -0
  60. package/src/lib/assets/loader.ts +49 -0
  61. package/src/lib/assets/networking.ts +58 -0
  62. package/src/lib/assets/pods.ts +148 -0
  63. package/src/lib/assets/rbac.ts +57 -0
  64. package/src/lib/assets/webhooks.ts +139 -0
  65. package/src/lib/assets/yaml.ts +75 -0
  66. package/src/lib/capability.ts +54 -44
  67. package/src/lib/controller.ts +171 -89
  68. package/src/lib/fetch.ts +1 -1
  69. package/src/lib/filter.ts +1 -3
  70. package/src/lib/k8s/index.ts +4 -1
  71. package/src/lib/k8s/kinds.ts +40 -0
  72. package/src/lib/k8s/types.ts +16 -14
  73. package/src/lib/k8s/upstream.ts +5 -1
  74. package/src/lib/logger.ts +14 -125
  75. package/src/lib/metrics.ts +67 -23
  76. package/src/lib/module.ts +13 -11
  77. package/src/lib/{processor.ts → mutate-processor.ts} +37 -28
  78. package/src/lib/{request.ts → mutate-request.ts} +4 -4
  79. package/src/lib/types.ts +51 -51
  80. package/src/lib/utils.ts +9 -7
  81. package/src/lib/validate-processor.ts +68 -0
  82. package/src/lib/validate-request.ts +94 -0
  83. package/src/lib.ts +4 -2
  84. package/src/runtime/controller.ts +1 -1
  85. package/dist/lib/k8s/webhook.d.ts +0 -37
  86. package/dist/lib/k8s/webhook.d.ts.map +0 -1
  87. package/dist/lib/processor.d.ts +0 -5
  88. package/dist/lib/processor.d.ts.map +0 -1
  89. package/dist/lib/request.d.ts.map +0 -1
  90. package/src/lib/k8s/webhook.ts +0 -643
package/dist/lib.js CHANGED
@@ -33,8 +33,9 @@ __export(lib_exports, {
33
33
  Capability: () => Capability,
34
34
  Log: () => logger_default,
35
35
  PeprModule: () => PeprModule,
36
- PeprRequest: () => PeprRequest,
36
+ PeprMutateRequest: () => PeprMutateRequest,
37
37
  PeprUtils: () => utils_exports,
38
+ PeprValidateRequest: () => PeprValidateRequest,
38
39
  R: () => R,
39
40
  RegisterKind: () => RegisterKind,
40
41
  a: () => upstream_exports,
@@ -48,55 +49,63 @@ var k8s = __toESM(require("@kubernetes/client-node"));
48
49
  var import_http_status_codes2 = require("http-status-codes");
49
50
  var R = __toESM(require("ramda"));
50
51
 
52
+ // src/lib/capability.ts
53
+ var import_ramda = require("ramda");
54
+
51
55
  // src/lib/k8s/upstream.ts
52
56
  var upstream_exports = {};
53
57
  __export(upstream_exports, {
54
- APIService: () => import_client_node.V1APIService,
55
- CSIDriver: () => import_client_node.V1CSIDriver,
56
- CSIStorageCapacity: () => import_client_node.V1CSIStorageCapacity,
57
- CertificateSigningRequest: () => import_client_node.V1CertificateSigningRequest,
58
- ConfigMap: () => import_client_node.V1ConfigMap,
59
- ControllerRevision: () => import_client_node.V1ControllerRevision,
60
- CronJob: () => import_client_node.V1CronJob,
61
- CustomResourceDefinition: () => import_client_node.V1CustomResourceDefinition,
62
- DaemonSet: () => import_client_node.V1DaemonSet,
63
- Deployment: () => import_client_node.V1Deployment,
64
- EndpointSlice: () => import_client_node.V1EndpointSlice,
58
+ APIService: () => import_client_node2.V1APIService,
59
+ CSIDriver: () => import_client_node2.V1CSIDriver,
60
+ CSIStorageCapacity: () => import_client_node2.V1CSIStorageCapacity,
61
+ CertificateSigningRequest: () => import_client_node2.V1CertificateSigningRequest,
62
+ ClusterRole: () => import_client_node2.V1ClusterRole,
63
+ ClusterRoleBinding: () => import_client_node2.V1ClusterRoleBinding,
64
+ ConfigMap: () => import_client_node2.V1ConfigMap,
65
+ ControllerRevision: () => import_client_node2.V1ControllerRevision,
66
+ CronJob: () => import_client_node2.V1CronJob,
67
+ CustomResourceDefinition: () => import_client_node2.V1CustomResourceDefinition,
68
+ DaemonSet: () => import_client_node2.V1DaemonSet,
69
+ Deployment: () => import_client_node2.V1Deployment,
70
+ EndpointSlice: () => import_client_node2.V1EndpointSlice,
65
71
  GenericKind: () => GenericKind,
66
- HorizontalPodAutoscaler: () => import_client_node.V1HorizontalPodAutoscaler,
67
- Ingress: () => import_client_node.V1Ingress,
68
- IngressClass: () => import_client_node.V1IngressClass,
69
- Job: () => import_client_node.V1Job,
70
- LimitRange: () => import_client_node.V1LimitRange,
71
- LocalSubjectAccessReview: () => import_client_node.V1LocalSubjectAccessReview,
72
- MutatingWebhookConfiguration: () => import_client_node.V1MutatingWebhookConfiguration,
73
- Namespace: () => import_client_node.V1Namespace,
74
- NetworkPolicy: () => import_client_node.V1NetworkPolicy,
75
- Node: () => import_client_node.V1Node,
76
- PersistentVolume: () => import_client_node.V1PersistentVolume,
77
- PersistentVolumeClaim: () => import_client_node.V1PersistentVolumeClaim,
78
- Pod: () => import_client_node.V1Pod,
79
- PodDisruptionBudget: () => import_client_node.V1PodDisruptionBudget,
80
- PodTemplate: () => import_client_node.V1PodTemplate,
81
- ReplicaSet: () => import_client_node.V1ReplicaSet,
82
- ReplicationController: () => import_client_node.V1ReplicationController,
83
- ResourceQuota: () => import_client_node.V1ResourceQuota,
84
- RuntimeClass: () => import_client_node.V1RuntimeClass,
85
- Secret: () => import_client_node.V1Secret,
86
- SelfSubjectAccessReview: () => import_client_node.V1SelfSubjectAccessReview,
87
- SelfSubjectRulesReview: () => import_client_node.V1SelfSubjectRulesReview,
88
- Service: () => import_client_node.V1Service,
89
- ServiceAccount: () => import_client_node.V1ServiceAccount,
90
- StatefulSet: () => import_client_node.V1StatefulSet,
91
- StorageClass: () => import_client_node.V1StorageClass,
92
- SubjectAccessReview: () => import_client_node.V1SubjectAccessReview,
93
- TokenReview: () => import_client_node.V1TokenReview,
94
- ValidatingWebhookConfiguration: () => import_client_node.V1ValidatingWebhookConfiguration,
95
- VolumeAttachment: () => import_client_node.V1VolumeAttachment
72
+ HorizontalPodAutoscaler: () => import_client_node2.V1HorizontalPodAutoscaler,
73
+ Ingress: () => import_client_node2.V1Ingress,
74
+ IngressClass: () => import_client_node2.V1IngressClass,
75
+ Job: () => import_client_node2.V1Job,
76
+ LimitRange: () => import_client_node2.V1LimitRange,
77
+ LocalSubjectAccessReview: () => import_client_node2.V1LocalSubjectAccessReview,
78
+ MutatingWebhookConfiguration: () => import_client_node2.V1MutatingWebhookConfiguration,
79
+ Namespace: () => import_client_node2.V1Namespace,
80
+ NetworkPolicy: () => import_client_node2.V1NetworkPolicy,
81
+ Node: () => import_client_node2.V1Node,
82
+ PersistentVolume: () => import_client_node2.V1PersistentVolume,
83
+ PersistentVolumeClaim: () => import_client_node2.V1PersistentVolumeClaim,
84
+ Pod: () => import_client_node2.V1Pod,
85
+ PodDisruptionBudget: () => import_client_node2.V1PodDisruptionBudget,
86
+ PodTemplate: () => import_client_node2.V1PodTemplate,
87
+ ReplicaSet: () => import_client_node2.V1ReplicaSet,
88
+ ReplicationController: () => import_client_node2.V1ReplicationController,
89
+ ResourceQuota: () => import_client_node2.V1ResourceQuota,
90
+ Role: () => import_client_node2.V1Role,
91
+ RoleBinding: () => import_client_node2.V1RoleBinding,
92
+ RuntimeClass: () => import_client_node2.V1RuntimeClass,
93
+ Secret: () => import_client_node2.V1Secret,
94
+ SelfSubjectAccessReview: () => import_client_node2.V1SelfSubjectAccessReview,
95
+ SelfSubjectRulesReview: () => import_client_node2.V1SelfSubjectRulesReview,
96
+ Service: () => import_client_node2.V1Service,
97
+ ServiceAccount: () => import_client_node2.V1ServiceAccount,
98
+ StatefulSet: () => import_client_node2.V1StatefulSet,
99
+ StorageClass: () => import_client_node2.V1StorageClass,
100
+ SubjectAccessReview: () => import_client_node2.V1SubjectAccessReview,
101
+ TokenReview: () => import_client_node2.V1TokenReview,
102
+ ValidatingWebhookConfiguration: () => import_client_node2.V1ValidatingWebhookConfiguration,
103
+ VolumeAttachment: () => import_client_node2.V1VolumeAttachment
96
104
  });
97
- var import_client_node = require("@kubernetes/client-node");
105
+ var import_client_node2 = require("@kubernetes/client-node");
98
106
 
99
107
  // src/lib/k8s/types.ts
108
+ var import_client_node = require("@kubernetes/client-node");
100
109
  var GenericKind = class {
101
110
  apiVersion;
102
111
  kind;
@@ -105,6 +114,46 @@ var GenericKind = class {
105
114
 
106
115
  // src/lib/k8s/kinds.ts
107
116
  var gvkMap = {
117
+ /**
118
+ * Represents a K8s ClusterRole resource.
119
+ * ClusterRole is a set of permissions that can be bound to a user or group in a cluster-wide scope.
120
+ * @see {@link https://kubernetes.io/docs/reference/access-authn-authz/rbac/#role-and-clusterrole}
121
+ */
122
+ V1ClusterRole: {
123
+ kind: "ClusterRole",
124
+ version: "v1",
125
+ group: "rbac.authorization.k8s.io"
126
+ },
127
+ /**
128
+ * Represents a K8s ClusterRoleBinding resource.
129
+ * ClusterRoleBinding binds a ClusterRole to a user or group in a cluster-wide scope.
130
+ * @see {@link https://kubernetes.io/docs/reference/access-authn-authz/rbac/#rolebinding-and-clusterrolebinding}
131
+ */
132
+ V1ClusterRoleBinding: {
133
+ kind: "ClusterRoleBinding",
134
+ version: "v1",
135
+ group: "rbac.authorization.k8s.io"
136
+ },
137
+ /**
138
+ * Represents a K8s Role resource.
139
+ * Role is a set of permissions that can be bound to a user or group in a namespace scope.
140
+ * @see {@link https://kubernetes.io/docs/reference/access-authn-authz/rbac/#role-and-clusterrole}
141
+ */
142
+ V1Role: {
143
+ kind: "Role",
144
+ version: "v1",
145
+ group: "rbac.authorization.k8s.io"
146
+ },
147
+ /**
148
+ * Represents a K8s RoleBinding resource.
149
+ * RoleBinding binds a Role to a user or group in a namespace scope.
150
+ * @see {@link https://kubernetes.io/docs/reference/access-authn-authz/rbac/#rolebinding-and-clusterrolebinding}
151
+ */
152
+ V1RoleBinding: {
153
+ kind: "RoleBinding",
154
+ version: "v1",
155
+ group: "rbac.authorization.k8s.io"
156
+ },
108
157
  /**
109
158
  * Represents a K8s ConfigMap resource.
110
159
  * ConfigMap holds configuration data for pods to consume.
@@ -539,89 +588,24 @@ var RegisterKind = (model, groupVersionKind) => {
539
588
  gvkMap[name] = groupVersionKind;
540
589
  };
541
590
 
591
+ // src/lib/k8s/index.ts
592
+ var isWatchMode = process.env.PEPR_WATCH_MODE === "true";
593
+
542
594
  // src/lib/logger.ts
543
- var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
544
- LogLevel2[LogLevel2["debug"] = 0] = "debug";
545
- LogLevel2[LogLevel2["info"] = 1] = "info";
546
- LogLevel2[LogLevel2["warn"] = 2] = "warn";
547
- LogLevel2[LogLevel2["error"] = 3] = "error";
548
- return LogLevel2;
549
- })(LogLevel || {});
550
- var Logger = class {
551
- _logLevel;
552
- /**
553
- * Create a new logger instance.
554
- * @param logLevel - The minimum log level to log messages for.
555
- */
556
- constructor(logLevel) {
557
- this._logLevel = logLevel;
558
- }
559
- /**
560
- * Change the log level of the logger.
561
- * @param logLevel - The log level to log the message at.
562
- */
563
- SetLogLevel(logLevel) {
564
- this._logLevel = LogLevel[logLevel];
565
- this.debug(`Log level set to ${logLevel}`);
566
- }
567
- /**
568
- * Log a debug message.
569
- * @param message - The message to log.
570
- */
571
- debug(message, prefix) {
572
- this.log(0 /* debug */, message, prefix);
573
- }
574
- /**
575
- * Log an info message.
576
- * @param message - The message to log.
577
- */
578
- info(message, prefix) {
579
- this.log(1 /* info */, message, prefix);
580
- }
581
- /**
582
- * Log a warning message.
583
- * @param message - The message to log.
584
- */
585
- warn(message, prefix) {
586
- this.log(2 /* warn */, message, prefix);
587
- }
588
- /**
589
- * Log an error message.
590
- * @param message - The message to log.
591
- */
592
- error(message, prefix) {
593
- this.log(3 /* error */, message, prefix);
594
- }
595
- /**
596
- * Log a message at the specified log level.
597
- * @param logLevel - The log level of the message.
598
- * @param message - The message to log.
599
- */
600
- log(logLevel, message, callerPrefix = "") {
601
- const color = {
602
- [0 /* debug */]: "\x1B[30m" /* FgBlack */,
603
- [1 /* info */]: "\x1B[36m" /* FgCyan */,
604
- [2 /* warn */]: "\x1B[33m" /* FgYellow */,
605
- [3 /* error */]: "\x1B[31m" /* FgRed */
606
- };
607
- if (logLevel >= this._logLevel) {
608
- let prefix = "[" + LogLevel[logLevel] + "] " + callerPrefix;
609
- prefix = this.colorize(prefix, color[logLevel]);
610
- if (typeof message !== "string") {
611
- console.log(prefix);
612
- console.debug("%o", message);
613
- } else {
614
- console.log(prefix + " " + message);
615
- }
616
- }
617
- }
618
- colorize(text, color) {
619
- return color + text + "\x1B[0m" /* Reset */;
595
+ var import_pino = require("pino");
596
+ var isPrettyLog = process.env.PEPR_PRETTY_LOGS === "true";
597
+ var pretty = {
598
+ target: "pino-pretty",
599
+ options: {
600
+ colorize: true
620
601
  }
621
602
  };
622
- var Log = new Logger(1 /* info */);
603
+ var transport = isPrettyLog ? pretty : void 0;
604
+ var Log = (0, import_pino.pino)({
605
+ transport
606
+ });
623
607
  if (process.env.LOG_LEVEL) {
624
- Log.SetLogLevel(process.env.LOG_LEVEL);
608
+ Log.level = process.env.LOG_LEVEL;
625
609
  }
626
610
  var logger_default = Log;
627
611
 
@@ -630,8 +614,6 @@ var Capability = class {
630
614
  _name;
631
615
  _description;
632
616
  _namespaces;
633
- // Currently everything is considered a mutation
634
- _mutateOrValidate = "mutate" /* mutate */;
635
617
  _bindings = [];
636
618
  get bindings() {
637
619
  return this._bindings;
@@ -645,9 +627,6 @@ var Capability = class {
645
627
  get namespaces() {
646
628
  return this._namespaces || [];
647
629
  }
648
- get mutateOrValidate() {
649
- return this._mutateOrValidate;
650
- }
651
630
  constructor(cfg) {
652
631
  this._name = cfg.name;
653
632
  this._description = cfg.description;
@@ -678,52 +657,63 @@ var Capability = class {
678
657
  namespaces: [],
679
658
  labels: {},
680
659
  annotations: {}
681
- },
682
- callback: () => void 0
660
+ }
683
661
  };
684
662
  const prefix = `${this._name}: ${model.name}`;
685
- logger_default.info(`Binding created`, prefix);
686
- const Then = (cb) => {
687
- logger_default.info(`Binding action created`, prefix);
688
- logger_default.debug(cb.toString(), prefix);
689
- this._bindings.push({
690
- ...binding,
691
- callback: cb
692
- });
693
- return { Then };
663
+ const isNotEmpty = (value) => Object.keys(value).length > 0;
664
+ const log = (message, cbString) => {
665
+ const filteredObj = (0, import_ramda.pickBy)(isNotEmpty, binding.filters);
666
+ logger_default.info(`${message} configured for ${binding.event}`, prefix);
667
+ logger_default.info(filteredObj, prefix);
668
+ logger_default.debug(cbString, prefix);
694
669
  };
695
- const ThenSet = (merge) => {
696
- Then((req) => req.Merge(merge));
697
- return { Then };
670
+ const Validate = (validateCallback) => {
671
+ if (!isWatchMode) {
672
+ log("Validate Action", validateCallback.toString());
673
+ this._bindings.push({
674
+ ...binding,
675
+ isValidate: true,
676
+ validateCallback
677
+ });
678
+ }
679
+ };
680
+ const Mutate = (mutateCallback) => {
681
+ if (!isWatchMode) {
682
+ log("Mutate Action", mutateCallback.toString());
683
+ this._bindings.push({
684
+ ...binding,
685
+ isMutate: true,
686
+ mutateCallback
687
+ });
688
+ }
689
+ return { Validate };
698
690
  };
699
691
  function InNamespace(...namespaces) {
700
692
  logger_default.debug(`Add namespaces filter ${namespaces}`, prefix);
701
693
  binding.filters.namespaces.push(...namespaces);
702
- return { WithLabel, WithAnnotation, WithName, Then, ThenSet };
694
+ return { ...commonChain, WithName };
703
695
  }
704
696
  function WithName(name) {
705
697
  logger_default.debug(`Add name filter ${name}`, prefix);
706
698
  binding.filters.name = name;
707
- return { WithLabel, WithAnnotation, Then, ThenSet };
699
+ return commonChain;
708
700
  }
709
701
  function WithLabel(key, value = "") {
710
702
  logger_default.debug(`Add label filter ${key}=${value}`, prefix);
711
703
  binding.filters.labels[key] = value;
712
- return { WithLabel, WithAnnotation, Then, ThenSet };
704
+ return commonChain;
713
705
  }
714
706
  const WithAnnotation = (key, value = "") => {
715
707
  logger_default.debug(`Add annotation filter ${key}=${value}`, prefix);
716
708
  binding.filters.annotations[key] = value;
717
- return { WithLabel, WithAnnotation, Then, ThenSet };
709
+ return commonChain;
718
710
  };
711
+ const commonChain = { WithLabel, WithAnnotation, Mutate, Validate };
719
712
  const bindEvent = (event) => {
720
713
  binding.event = event;
721
714
  return {
715
+ ...commonChain,
722
716
  InNamespace,
723
- Then,
724
- ThenSet,
725
- WithAnnotation,
726
- WithLabel,
727
717
  WithName
728
718
  };
729
719
  };
@@ -743,7 +733,7 @@ var fetchRaw = import_node_fetch.default;
743
733
  async function fetch(url, init) {
744
734
  let data = void 0;
745
735
  try {
746
- logger_default.debug(`Fetching ${url}`);
736
+ logger_default.debug(init, `Fetching ${url}`);
747
737
  const resp = await fetchRaw(url, init);
748
738
  const contentType = resp.headers.get("content-type") || "";
749
739
  if (resp.ok) {
@@ -780,14 +770,101 @@ async function fetch(url, init) {
780
770
  }
781
771
 
782
772
  // src/lib/module.ts
783
- var import_ramda2 = require("ramda");
773
+ var import_ramda4 = require("ramda");
784
774
 
785
775
  // src/lib/controller.ts
786
776
  var import_express = __toESM(require("express"));
787
777
  var import_fs = __toESM(require("fs"));
788
778
  var import_https = __toESM(require("https"));
789
779
 
790
- // src/lib/processor.ts
780
+ // src/lib/metrics.ts
781
+ var import_prom_client = __toESM(require("prom-client"));
782
+ var import_perf_hooks = require("perf_hooks");
783
+ var loggingPrefix = "MetricsCollector";
784
+ var MetricsCollector = class {
785
+ _registry;
786
+ _counters = /* @__PURE__ */ new Map();
787
+ _summaries = /* @__PURE__ */ new Map();
788
+ _prefix;
789
+ _metricNames = {
790
+ errors: "errors",
791
+ alerts: "alerts",
792
+ mutate: "Mutate",
793
+ validate: "Validate"
794
+ };
795
+ /**
796
+ * Creates a MetricsCollector instance with prefixed metrics.
797
+ * @param {string} [prefix='pepr'] - The prefix for the metric names.
798
+ */
799
+ constructor(prefix = "pepr") {
800
+ this._registry = new import_prom_client.Registry();
801
+ this._prefix = prefix;
802
+ this.addCounter(this._metricNames.errors, "Mutation/Validate errors encountered");
803
+ this.addCounter(this._metricNames.alerts, "Mutation/Validate bad api token received");
804
+ this.addSummary(this._metricNames.mutate, "Mutation operation summary");
805
+ this.addSummary(this._metricNames.validate, "Validation operation summary");
806
+ }
807
+ getMetricName(name) {
808
+ return `${this._prefix}_${name}`;
809
+ }
810
+ addMetric(collection, MetricType, name, help) {
811
+ if (collection.has(this.getMetricName(name))) {
812
+ logger_default.debug(`Metric for ${name} already exists`, loggingPrefix);
813
+ return;
814
+ }
815
+ const metric = new MetricType({
816
+ name: this.getMetricName(name),
817
+ help,
818
+ registers: [this._registry]
819
+ });
820
+ collection.set(this.getMetricName(name), metric);
821
+ }
822
+ addCounter(name, help) {
823
+ this.addMetric(this._counters, import_prom_client.default.Counter, name, help);
824
+ }
825
+ addSummary(name, help) {
826
+ this.addMetric(this._summaries, import_prom_client.default.Summary, name, help);
827
+ }
828
+ incCounter(name) {
829
+ this._counters.get(this.getMetricName(name))?.inc();
830
+ }
831
+ /**
832
+ * Increments the error counter.
833
+ */
834
+ error() {
835
+ this.incCounter(this._metricNames.errors);
836
+ }
837
+ /**
838
+ * Increments the alerts counter.
839
+ */
840
+ alert() {
841
+ this.incCounter(this._metricNames.alerts);
842
+ }
843
+ /**
844
+ * Returns the current timestamp from performance.now() method. Useful for start timing an operation.
845
+ * @returns {number} The timestamp.
846
+ */
847
+ observeStart() {
848
+ return import_perf_hooks.performance.now();
849
+ }
850
+ /**
851
+ * Observes the duration since the provided start time and updates the summary.
852
+ * @param {number} startTime - The start time.
853
+ * @param {string} name - The metrics summary to increment.
854
+ */
855
+ observeEnd(startTime, name = this._metricNames.mutate) {
856
+ this._summaries.get(this.getMetricName(name))?.observe(import_perf_hooks.performance.now() - startTime);
857
+ }
858
+ /**
859
+ * Fetches the current metrics from the registry.
860
+ * @returns {Promise<string>} The metrics.
861
+ */
862
+ async getMetrics() {
863
+ return this._registry.metrics();
864
+ }
865
+ };
866
+
867
+ // src/lib/mutate-processor.ts
791
868
  var import_fast_json_patch = __toESM(require("fast-json-patch"));
792
869
 
793
870
  // src/lib/filter.ts
@@ -797,7 +874,6 @@ function shouldSkipRequest(binding, req) {
797
874
  const operation = req.operation.toUpperCase();
798
875
  const srcObject = operation === "DELETE" /* DELETE */ ? req.oldObject : req.object;
799
876
  const { metadata } = srcObject || {};
800
- console.log(metadata);
801
877
  if (!binding.event.includes(operation) && !binding.event.includes("*" /* Any */)) {
802
878
  return true;
803
879
  }
@@ -842,19 +918,19 @@ function shouldSkipRequest(binding, req) {
842
918
  return false;
843
919
  }
844
920
 
845
- // src/lib/request.ts
846
- var import_ramda = require("ramda");
847
- var PeprRequest = class {
921
+ // src/lib/mutate-request.ts
922
+ var import_ramda2 = require("ramda");
923
+ var PeprMutateRequest = class {
848
924
  /**
849
- * Creates a new instance of the Action class.
925
+ * Creates a new instance of the action class.
850
926
  * @param input - The request object containing the Kubernetes resource to modify.
851
927
  */
852
928
  constructor(_input) {
853
929
  this._input = _input;
854
930
  if (_input.operation.toUpperCase() === "DELETE" /* DELETE */) {
855
- this.Raw = (0, import_ramda.clone)(_input.oldObject);
931
+ this.Raw = (0, import_ramda2.clone)(_input.oldObject);
856
932
  } else {
857
- this.Raw = (0, import_ramda.clone)(_input.object);
933
+ this.Raw = (0, import_ramda2.clone)(_input.object);
858
934
  }
859
935
  if (!this.Raw) {
860
936
  throw new Error("unable to load the request object into PeprRequest.RawP");
@@ -891,13 +967,13 @@ var PeprRequest = class {
891
967
  * @param obj - The object to merge with the current resource.
892
968
  */
893
969
  Merge(obj) {
894
- this.Raw = (0, import_ramda.mergeDeepRight)(this.Raw, obj);
970
+ this.Raw = (0, import_ramda2.mergeDeepRight)(this.Raw, obj);
895
971
  }
896
972
  /**
897
973
  * Updates a label on the Kubernetes resource.
898
974
  * @param key - The key of the label to update.
899
975
  * @param value - The value of the label.
900
- * @returns The current Action instance for method chaining.
976
+ * @returns The current action instance for method chaining.
901
977
  */
902
978
  SetLabel(key, value) {
903
979
  const ref = this.Raw;
@@ -910,7 +986,7 @@ var PeprRequest = class {
910
986
  * Updates an annotation on the Kubernetes resource.
911
987
  * @param key - The key of the annotation to update.
912
988
  * @param value - The value of the annotation.
913
- * @returns The current Action instance for method chaining.
989
+ * @returns The current action instance for method chaining.
914
990
  */
915
991
  SetAnnotation(key, value) {
916
992
  const ref = this.Raw;
@@ -982,11 +1058,15 @@ function convertFromBase64Map(obj) {
982
1058
  const skip = [];
983
1059
  obj.data = obj.data ?? {};
984
1060
  for (const key in obj.data) {
985
- const decoded = base64Decode(obj.data[key]);
986
- if (isAscii.test(decoded)) {
987
- obj.data[key] = decoded;
1061
+ if (obj.data[key] == void 0) {
1062
+ obj.data[key] = "";
988
1063
  } else {
989
- skip.push(key);
1064
+ const decoded = base64Decode(obj.data[key]);
1065
+ if (isAscii.test(decoded)) {
1066
+ obj.data[key] = decoded;
1067
+ } else {
1068
+ skip.push(key);
1069
+ }
990
1070
  }
991
1071
  }
992
1072
  logger_default.debug(`Non-ascii data detected in keys: ${skip}, skipping automatic base64 decoding`);
@@ -999,30 +1079,33 @@ function base64Encode(data) {
999
1079
  return Buffer.from(data).toString("base64");
1000
1080
  }
1001
1081
 
1002
- // src/lib/processor.ts
1003
- async function processor(config, capabilities, req, parentPrefix) {
1004
- const wrapped = new PeprRequest(req);
1082
+ // src/lib/mutate-processor.ts
1083
+ async function mutateProcessor(config, capabilities, req, reqMetadata) {
1084
+ const wrapped = new PeprMutateRequest(req);
1005
1085
  const response = {
1006
1086
  uid: req.uid,
1007
1087
  warnings: [],
1008
1088
  allowed: false
1009
1089
  };
1010
- let matchedCapabilityAction = false;
1090
+ let matchedAction = false;
1011
1091
  let skipDecode = [];
1012
1092
  const isSecret = req.kind.version == "v1" && req.kind.kind == "Secret";
1013
1093
  if (isSecret) {
1014
1094
  skipDecode = convertFromBase64Map(wrapped.Raw);
1015
1095
  }
1016
- logger_default.info(`Processing request`, parentPrefix);
1096
+ logger_default.info(reqMetadata, `Processing request`);
1017
1097
  for (const { name, bindings } of capabilities) {
1018
- const prefix = `${parentPrefix} ${name}:`;
1098
+ const actionMetadata = { ...reqMetadata, name };
1019
1099
  for (const action of bindings) {
1100
+ if (!action.mutateCallback) {
1101
+ continue;
1102
+ }
1020
1103
  if (shouldSkipRequest(action, req)) {
1021
1104
  continue;
1022
1105
  }
1023
- const label = action.callback.name;
1024
- logger_default.info(`Processing matched action ${label}`, prefix);
1025
- matchedCapabilityAction = true;
1106
+ const label = action.mutateCallback.name;
1107
+ logger_default.info(actionMetadata, `Processing matched action ${label}`);
1108
+ matchedAction = true;
1026
1109
  const updateStatus = (status) => {
1027
1110
  if (req.operation == "DELETE") {
1028
1111
  return;
@@ -1034,26 +1117,28 @@ async function processor(config, capabilities, req, parentPrefix) {
1034
1117
  };
1035
1118
  updateStatus("started");
1036
1119
  try {
1037
- await action.callback(wrapped);
1038
- logger_default.info(`Action succeeded`, prefix);
1120
+ await action.mutateCallback(wrapped);
1121
+ logger_default.info(actionMetadata, `Action succeeded`);
1039
1122
  updateStatus("succeeded");
1040
1123
  } catch (e) {
1124
+ logger_default.warn(actionMetadata, `Action failed: ${e}`);
1125
+ updateStatus("warning");
1041
1126
  response.warnings = response.warnings || [];
1042
1127
  response.warnings.push(`Action failed: ${e}`);
1043
- if (config.onError) {
1044
- logger_default.error(`Action failed: ${e}`, prefix);
1045
- response.result = "Pepr module configured to reject on error";
1046
- return response;
1047
- } else {
1048
- logger_default.warn(`Action failed: ${e}`, prefix);
1049
- updateStatus("warning");
1128
+ switch (config.onError) {
1129
+ case "reject":
1130
+ logger_default.error(actionMetadata, `Action failed: ${e}`);
1131
+ response.result = "Pepr module configured to reject on error";
1132
+ return response;
1133
+ case "audit":
1134
+ break;
1050
1135
  }
1051
1136
  }
1052
1137
  }
1053
1138
  }
1054
1139
  response.allowed = true;
1055
- if (!matchedCapabilityAction) {
1056
- logger_default.info(`No matching capability action found`, parentPrefix);
1140
+ if (!matchedAction) {
1141
+ logger_default.info(reqMetadata, `No matching actions found`);
1057
1142
  return response;
1058
1143
  }
1059
1144
  if (req.operation == "DELETE") {
@@ -1066,126 +1151,192 @@ async function processor(config, capabilities, req, parentPrefix) {
1066
1151
  const patches = import_fast_json_patch.default.compare(req.object, transformed);
1067
1152
  if (patches.length > 0) {
1068
1153
  response.patchType = "JSONPatch";
1069
- response.patch = Buffer.from(JSON.stringify(patches)).toString("base64");
1154
+ response.patch = base64Encode(JSON.stringify(patches));
1070
1155
  }
1071
1156
  if (response.warnings && response.warnings.length < 1) {
1072
1157
  delete response.warnings;
1073
1158
  }
1074
- logger_default.debug(patches, parentPrefix);
1159
+ logger_default.debug({ ...reqMetadata, patches }, `Patches generated`);
1075
1160
  return response;
1076
1161
  }
1077
1162
 
1078
- // src/lib/metrics.ts
1079
- var import_prom_client = __toESM(require("prom-client"));
1080
- var import_perf_hooks = require("perf_hooks");
1081
- var MetricsCollector = class {
1082
- _registry;
1083
- _errors;
1084
- _alerts;
1085
- _summary;
1163
+ // src/lib/validate-request.ts
1164
+ var import_ramda3 = require("ramda");
1165
+ var PeprValidateRequest = class {
1086
1166
  /**
1087
- * Creates a MetricsCollector instance with prefixed metrics.
1088
- * @param {string} [prefix='pepr'] - The prefix for the metric names.
1167
+ * Creates a new instance of the Action class.
1168
+ * @param input - The request object containing the Kubernetes resource to modify.
1089
1169
  */
1090
- constructor(prefix = "pepr") {
1091
- this._registry = new import_prom_client.default.Registry();
1092
- this._errors = new import_prom_client.default.Counter({
1093
- name: `${prefix}_errors`,
1094
- help: "error counter",
1095
- registers: [this._registry]
1096
- });
1097
- this._alerts = new import_prom_client.default.Counter({
1098
- name: `${prefix}_alerts`,
1099
- help: "alerts counter",
1100
- registers: [this._registry]
1101
- });
1102
- this._summary = new import_prom_client.default.Summary({
1103
- name: `${prefix}_summary`,
1104
- help: "summary",
1105
- registers: [this._registry]
1106
- });
1170
+ constructor(_input) {
1171
+ this._input = _input;
1172
+ if (_input.operation.toUpperCase() === "DELETE" /* DELETE */) {
1173
+ this.Raw = (0, import_ramda3.clone)(_input.oldObject);
1174
+ } else {
1175
+ this.Raw = (0, import_ramda3.clone)(_input.object);
1176
+ }
1177
+ if (!this.Raw) {
1178
+ throw new Error("unable to load the request object into PeprRequest.RawP");
1179
+ }
1107
1180
  }
1181
+ Raw;
1108
1182
  /**
1109
- * Increments the error counter.
1183
+ * Provides access to the old resource in the request if available.
1184
+ * @returns The old Kubernetes resource object or null if not available.
1110
1185
  */
1111
- error() {
1112
- this._errors.inc();
1186
+ get OldResource() {
1187
+ return this._input.oldObject;
1113
1188
  }
1114
1189
  /**
1115
- * Increments the alerts counter.
1190
+ * Provides access to the request object.
1191
+ * @returns The request object containing the Kubernetes resource.
1116
1192
  */
1117
- alert() {
1118
- this._alerts.inc();
1193
+ get Request() {
1194
+ return this._input;
1119
1195
  }
1120
1196
  /**
1121
- * Returns the current timestamp from performance.now() method. Useful for start timing an operation.
1122
- * @returns {number} The timestamp.
1197
+ * Check if a label exists on the Kubernetes resource.
1198
+ *
1199
+ * @param key the label key to check
1200
+ * @returns
1123
1201
  */
1124
- observeStart() {
1125
- return import_perf_hooks.performance.now();
1202
+ HasLabel(key) {
1203
+ return this.Raw.metadata?.labels?.[key] !== void 0;
1126
1204
  }
1127
1205
  /**
1128
- * Observes the duration since the provided start time and updates the summary.
1129
- * @param {number} startTime - The start time.
1206
+ * Check if an annotation exists on the Kubernetes resource.
1207
+ *
1208
+ * @param key the annotation key to check
1209
+ * @returns
1130
1210
  */
1131
- observeEnd(startTime) {
1132
- this._summary.observe(import_perf_hooks.performance.now() - startTime);
1211
+ HasAnnotation(key) {
1212
+ return this.Raw.metadata?.annotations?.[key] !== void 0;
1133
1213
  }
1134
1214
  /**
1135
- * Fetches the current metrics from the registry.
1136
- * @returns {Promise<string>} The metrics.
1215
+ * Create a validation response that allows the request.
1216
+ *
1217
+ * @returns The validation response.
1137
1218
  */
1138
- async getMetrics() {
1139
- return this._registry.metrics();
1219
+ Approve() {
1220
+ return {
1221
+ allowed: true
1222
+ };
1223
+ }
1224
+ /**
1225
+ * Create a validation response that denies the request.
1226
+ *
1227
+ * @param statusMessage Optional status message to return to the user.
1228
+ * @param statusCode Optional status code to return to the user.
1229
+ * @returns The validation response.
1230
+ */
1231
+ Deny(statusMessage, statusCode) {
1232
+ return {
1233
+ allowed: false,
1234
+ statusCode,
1235
+ statusMessage
1236
+ };
1140
1237
  }
1141
1238
  };
1142
1239
 
1240
+ // src/lib/validate-processor.ts
1241
+ async function validateProcessor(capabilities, req, reqMetadata) {
1242
+ const wrapped = new PeprValidateRequest(req);
1243
+ const response = {
1244
+ uid: req.uid,
1245
+ allowed: true
1246
+ // Assume it's allowed until a validation check fails
1247
+ };
1248
+ logger_default.info(reqMetadata, `Processing validation request`);
1249
+ for (const { name, bindings } of capabilities) {
1250
+ const actionMetadata = { ...reqMetadata, name };
1251
+ for (const action of bindings) {
1252
+ if (!action.validateCallback) {
1253
+ continue;
1254
+ }
1255
+ if (shouldSkipRequest(action, req)) {
1256
+ continue;
1257
+ }
1258
+ const label = action.validateCallback.name;
1259
+ logger_default.info(actionMetadata, `Processing matched action ${label}`);
1260
+ try {
1261
+ const resp = await action.validateCallback(wrapped);
1262
+ response.allowed = resp.allowed;
1263
+ if (resp.statusCode || resp.statusMessage) {
1264
+ response.status = {
1265
+ code: resp.statusCode || 400,
1266
+ message: resp.statusMessage || `Validation failed for ${name}`
1267
+ };
1268
+ }
1269
+ logger_default.info(actionMetadata, `Validation Action completed: ${resp.allowed ? "allowed" : "denied"}`);
1270
+ } catch (e) {
1271
+ logger_default.error(actionMetadata, `Action failed: ${e}`);
1272
+ response.allowed = false;
1273
+ response.status = {
1274
+ code: 500,
1275
+ message: `Action failed with error: ${e}`
1276
+ };
1277
+ return response;
1278
+ }
1279
+ }
1280
+ }
1281
+ return response;
1282
+ }
1283
+
1143
1284
  // src/lib/controller.ts
1144
1285
  var Controller = class {
1145
- constructor(config, capabilities, beforeHook, afterHook) {
1146
- this.config = config;
1147
- this.capabilities = capabilities;
1148
- this.beforeHook = beforeHook;
1149
- this.afterHook = afterHook;
1150
- this.app.use(this.logger);
1151
- this.app.use(import_express.default.json({ limit: "2mb" }));
1152
- this.app.get("/healthz", this.healthz);
1153
- this.app.get("/metrics", this.metrics);
1154
- this.app.post("/mutate/:token", this.mutate);
1155
- if (beforeHook) {
1156
- console.info(`Using beforeHook: ${beforeHook}`);
1286
+ constructor(_config, _capabilities, _beforeHook, _afterHook) {
1287
+ this._config = _config;
1288
+ this._capabilities = _capabilities;
1289
+ this._beforeHook = _beforeHook;
1290
+ this._afterHook = _afterHook;
1291
+ this._app.use(this.logger);
1292
+ this._app.use(import_express.default.json({ limit: "2mb" }));
1293
+ if (_beforeHook) {
1294
+ logger_default.info(`Using beforeHook: ${_beforeHook}`);
1157
1295
  }
1158
- if (afterHook) {
1159
- console.info(`Using afterHook: ${afterHook}`);
1296
+ if (_afterHook) {
1297
+ logger_default.info(`Using afterHook: ${_afterHook}`);
1160
1298
  }
1299
+ this.bindEndpoints();
1161
1300
  }
1162
- app = (0, import_express.default)();
1163
- running = false;
1301
+ _app = (0, import_express.default)();
1302
+ _running = false;
1164
1303
  metricsCollector = new MetricsCollector("pepr");
1165
1304
  // The token used to authenticate requests
1166
- token = "";
1305
+ _token = "";
1306
+ bindEndpoints = () => {
1307
+ this._app.get("/healthz", this.healthz);
1308
+ this._app.get("/metrics", this.metrics);
1309
+ if (isWatchMode) {
1310
+ return;
1311
+ }
1312
+ this._app.use(["/mutate/:token", "/validate/:token"], this.validateToken);
1313
+ this._app.post("/mutate/:token", this.admissionReq("Mutate"));
1314
+ this._app.post("/validate/:token", this.admissionReq("Validate"));
1315
+ };
1167
1316
  /** Start the webhook server */
1168
1317
  startServer = (port) => {
1169
- if (this.running) {
1318
+ if (this._running) {
1170
1319
  throw new Error("Cannot start Pepr module: Pepr module was not instantiated with deferStart=true");
1171
1320
  }
1172
1321
  const options = {
1173
1322
  key: import_fs.default.readFileSync(process.env.SSL_KEY_PATH || "/etc/certs/tls.key"),
1174
1323
  cert: import_fs.default.readFileSync(process.env.SSL_CERT_PATH || "/etc/certs/tls.crt")
1175
1324
  };
1176
- this.token = process.env.PEPR_API_TOKEN || import_fs.default.readFileSync("/app/api-token/value").toString().trim();
1177
- console.info(`Using API token: ${this.token}`);
1178
- if (!this.token) {
1179
- throw new Error("API token not found");
1325
+ if (!isWatchMode) {
1326
+ this._token = process.env.PEPR_API_TOKEN || import_fs.default.readFileSync("/app/api-token/value").toString().trim();
1327
+ logger_default.info(`Using API token: ${this._token}`);
1328
+ if (!this._token) {
1329
+ throw new Error("API token not found");
1330
+ }
1180
1331
  }
1181
- const server = import_https.default.createServer(options, this.app).listen(port);
1332
+ const server = import_https.default.createServer(options, this._app).listen(port);
1182
1333
  server.on("listening", () => {
1183
- console.log(`Server listening on port ${port}`);
1184
- this.running = true;
1334
+ logger_default.info(`Server listening on port ${port}`);
1335
+ this._running = true;
1185
1336
  });
1186
1337
  server.on("error", (e) => {
1187
1338
  if (e.code === "EADDRINUSE") {
1188
- console.log(
1339
+ logger_default.warn(
1189
1340
  `Address in use, retrying in 2 seconds. If this persists, ensure ${port} is not in use, e.g. "lsof -i :${port}"`
1190
1341
  );
1191
1342
  setTimeout(() => {
@@ -1195,80 +1346,128 @@ var Controller = class {
1195
1346
  }
1196
1347
  });
1197
1348
  process.on("SIGTERM", () => {
1198
- console.log("Received SIGTERM, closing server");
1349
+ logger_default.info("Received SIGTERM, closing server");
1199
1350
  server.close(() => {
1200
- console.log("Server closed");
1351
+ logger_default.info("Server closed");
1201
1352
  process.exit(0);
1202
1353
  });
1203
1354
  });
1204
1355
  };
1356
+ /**
1357
+ * Middleware for logging requests
1358
+ *
1359
+ * @param req the incoming request
1360
+ * @param res the outgoing response
1361
+ * @param next the next middleware function
1362
+ */
1205
1363
  logger = (req, res, next) => {
1206
1364
  const startTime = Date.now();
1207
1365
  res.on("finish", () => {
1208
- const now = (/* @__PURE__ */ new Date()).toISOString();
1209
1366
  const elapsedTime = Date.now() - startTime;
1210
- const message = `[${now}] ${req.method} ${req.originalUrl} [${res.statusCode}] ${elapsedTime} ms
1211
- `;
1212
- res.statusCode >= 400 ? console.error(message) : console.info(message);
1367
+ const message = {
1368
+ uid: req.body?.request?.uid,
1369
+ method: req.method,
1370
+ url: req.originalUrl,
1371
+ status: res.statusCode,
1372
+ duration: `${elapsedTime} ms`
1373
+ };
1374
+ res.statusCode >= 300 ? logger_default.warn(message) : logger_default.info(message);
1213
1375
  });
1214
1376
  next();
1215
1377
  };
1378
+ /**
1379
+ * Validate the token in the request path
1380
+ *
1381
+ * @param req The incoming request
1382
+ * @param res The outgoing response
1383
+ * @param next The next middleware function
1384
+ * @returns
1385
+ */
1386
+ validateToken = (req, res, next) => {
1387
+ const { token } = req.params;
1388
+ if (token !== this._token) {
1389
+ const err = `Unauthorized: invalid token '${token.replace(/[^\w]/g, "_")}'`;
1390
+ logger_default.warn(err);
1391
+ res.status(401).send(err);
1392
+ this.metricsCollector.alert();
1393
+ return;
1394
+ }
1395
+ next();
1396
+ };
1397
+ /**
1398
+ * Health check endpoint handler
1399
+ *
1400
+ * @param req the incoming request
1401
+ * @param res the outgoing response
1402
+ */
1216
1403
  healthz = (req, res) => {
1217
1404
  try {
1218
1405
  res.send("OK");
1219
1406
  } catch (err) {
1220
- console.error(err);
1407
+ logger_default.error(err);
1221
1408
  res.status(500).send("Internal Server Error");
1222
1409
  }
1223
1410
  };
1411
+ /**
1412
+ * Metrics endpoint handler
1413
+ *
1414
+ * @param req the incoming request
1415
+ * @param res the outgoing response
1416
+ */
1224
1417
  metrics = async (req, res) => {
1225
1418
  try {
1226
1419
  res.send(await this.metricsCollector.getMetrics());
1227
1420
  } catch (err) {
1228
- console.error(err);
1421
+ logger_default.error(err);
1229
1422
  res.status(500).send("Internal Server Error");
1230
1423
  }
1231
1424
  };
1232
- mutate = async (req, res) => {
1233
- const startTime = this.metricsCollector.observeStart();
1234
- try {
1235
- const { token } = req.params;
1236
- if (token !== this.token) {
1237
- const err = `Unauthorized: invalid token '${token.replace(/[^\w]/g, "_")}'`;
1238
- console.warn(err);
1239
- res.status(401).send(err);
1240
- this.metricsCollector.alert();
1241
- return;
1425
+ /**
1426
+ * Admission request handler for both mutate and validate requests
1427
+ *
1428
+ * @param admissionKind the type of admission request
1429
+ * @returns the request handler
1430
+ */
1431
+ admissionReq = (admissionKind) => {
1432
+ return async (req, res) => {
1433
+ const startTime = this.metricsCollector.observeStart();
1434
+ try {
1435
+ const request = req.body?.request || {};
1436
+ this._beforeHook && this._beforeHook(request || {});
1437
+ const name = request?.name ? `/${request.name}` : "";
1438
+ const namespace = request?.namespace || "";
1439
+ const gvk = request?.kind || { group: "", version: "", kind: "" };
1440
+ const reqMetadata = {
1441
+ uid: request.uid,
1442
+ namespace,
1443
+ name
1444
+ };
1445
+ logger_default.info({ ...reqMetadata, gvk, operation: request.operation, admissionKind }, "Incoming request");
1446
+ logger_default.debug({ ...reqMetadata, request }, "Incoming request body");
1447
+ let response;
1448
+ if (admissionKind === "Mutate") {
1449
+ response = await mutateProcessor(this._config, this._capabilities, request, reqMetadata);
1450
+ } else {
1451
+ response = await validateProcessor(this._capabilities, request, reqMetadata);
1452
+ }
1453
+ this._afterHook && this._afterHook(response);
1454
+ logger_default.debug({ ...reqMetadata, response }, "Outgoing response");
1455
+ res.send({
1456
+ apiVersion: "admission.k8s.io/v1",
1457
+ kind: "AdmissionReview",
1458
+ response
1459
+ });
1460
+ this.metricsCollector.observeEnd(startTime, admissionKind);
1461
+ } catch (err) {
1462
+ logger_default.error(err);
1463
+ res.status(500).send("Internal Server Error");
1464
+ this.metricsCollector.error();
1242
1465
  }
1243
- const request = req.body?.request || {};
1244
- this.beforeHook && this.beforeHook(request || {});
1245
- const name = request?.name ? `/${request.name}` : "";
1246
- const namespace = request?.namespace || "";
1247
- const gvk = request?.kind || { group: "", version: "", kind: "" };
1248
- const prefix = `${request.uid} ${namespace}${name}`;
1249
- logger_default.info(`Mutate [${request.operation}] ${gvk.group}/${gvk.version}/${gvk.kind}`, prefix);
1250
- const response = await processor(this.config, this.capabilities, request, prefix);
1251
- this.afterHook && this.afterHook(response);
1252
- logger_default.debug(response, prefix);
1253
- res.send({
1254
- apiVersion: "admission.k8s.io/v1",
1255
- kind: "AdmissionReview",
1256
- response
1257
- });
1258
- this.metricsCollector.observeEnd(startTime);
1259
- } catch (err) {
1260
- console.error(err);
1261
- res.status(500).send("Internal Server Error");
1262
- this.metricsCollector.error();
1263
- }
1466
+ };
1264
1467
  };
1265
1468
  };
1266
1469
 
1267
1470
  // src/lib/module.ts
1268
- var alwaysIgnore = {
1269
- namespaces: ["kube-system", "pepr-system"],
1270
- labels: [{ "pepr.dev": "ignore" }]
1271
- };
1272
1471
  var PeprModule = class {
1273
1472
  _controller;
1274
1473
  /**
@@ -1279,10 +1478,14 @@ var PeprModule = class {
1279
1478
  * @param _deferStart (optional) If set to `true`, the Pepr runtime will not be started automatically. This can be used to start the Pepr runtime manually with `start()`.
1280
1479
  */
1281
1480
  constructor({ description, pepr }, capabilities = [], opts = {}) {
1282
- const config = (0, import_ramda2.mergeDeepWith)(import_ramda2.concat, pepr, alwaysIgnore);
1481
+ const config = (0, import_ramda4.clone)(pepr);
1283
1482
  config.description = description;
1284
- if (process.env.PEPR_MODE === "build") {
1285
- process.send?.({ capabilities });
1483
+ const validOnErrors = ["ignore", "warn", "fail"];
1484
+ if (!validOnErrors.includes(config.onError || "")) {
1485
+ throw new Error(`Invalid onErrors value: ${config.onError}`);
1486
+ }
1487
+ if (process.env.PEPR_MODE === "build" && process.send) {
1488
+ process.send(capabilities);
1286
1489
  return;
1287
1490
  }
1288
1491
  this._controller = new Controller(config, capabilities, opts.beforeHook, opts.afterHook);
@@ -1306,8 +1509,9 @@ var PeprModule = class {
1306
1509
  Capability,
1307
1510
  Log,
1308
1511
  PeprModule,
1309
- PeprRequest,
1512
+ PeprMutateRequest,
1310
1513
  PeprUtils,
1514
+ PeprValidateRequest,
1311
1515
  R,
1312
1516
  RegisterKind,
1313
1517
  a,