pepr 0.13.0 → 0.13.2
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.
- package/dist/cli.js +54 -28
- package/dist/controller.js +1 -1
- package/dist/lib/assets/index.d.ts +5 -6
- package/dist/lib/assets/index.d.ts.map +1 -1
- package/dist/lib/assets/loader.d.ts +2 -8
- package/dist/lib/assets/loader.d.ts.map +1 -1
- package/dist/lib/capability.d.ts +4 -7
- package/dist/lib/capability.d.ts.map +1 -1
- package/dist/lib/controller.d.ts +3 -49
- package/dist/lib/controller.d.ts.map +1 -1
- package/dist/lib/errors.d.ts +12 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/k8s/types.d.ts +5 -1
- package/dist/lib/k8s/types.d.ts.map +1 -1
- package/dist/lib/metrics.d.ts +6 -12
- package/dist/lib/metrics.d.ts.map +1 -1
- package/dist/lib/module.d.ts +2 -2
- package/dist/lib/module.d.ts.map +1 -1
- package/dist/lib/mutate-processor.d.ts.map +1 -1
- package/dist/lib/mutate-request.d.ts +2 -2
- package/dist/lib/mutate-request.d.ts.map +1 -1
- package/dist/lib/types.d.ts +3 -9
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/validate-processor.d.ts.map +1 -1
- package/dist/lib/validate-request.d.ts +2 -2
- package/dist/lib/validate-request.d.ts.map +1 -1
- package/dist/lib.js +247 -191
- package/dist/lib.js.map +3 -3
- package/jest.config.json +4 -0
- package/journey/before.ts +21 -0
- package/journey/k8s.ts +81 -0
- package/journey/pepr-build.ts +69 -0
- package/journey/pepr-deploy.ts +133 -0
- package/journey/pepr-dev.ts +155 -0
- package/journey/pepr-format.ts +13 -0
- package/journey/pepr-init.ts +12 -0
- package/package.json +20 -21
- package/src/lib/assets/index.ts +15 -8
- package/src/lib/assets/loader.ts +4 -12
- package/src/lib/assets/webhooks.ts +2 -2
- package/src/lib/capability.ts +34 -32
- package/src/lib/controller.ts +111 -93
- package/src/lib/errors.ts +20 -0
- package/src/lib/k8s/types.ts +5 -1
- package/src/lib/metrics.ts +45 -32
- package/src/lib/module.ts +24 -10
- package/src/lib/mutate-processor.ts +5 -3
- package/src/lib/mutate-request.ts +22 -9
- package/src/lib/types.ts +4 -10
- package/src/lib/validate-processor.ts +8 -0
- package/src/lib/validate-request.ts +19 -7
package/dist/lib.js
CHANGED
|
@@ -611,27 +611,28 @@ var logger_default = Log;
|
|
|
611
611
|
|
|
612
612
|
// src/lib/capability.ts
|
|
613
613
|
var Capability = class {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
614
|
+
#name;
|
|
615
|
+
#description;
|
|
616
|
+
#namespaces;
|
|
617
|
+
#bindings = [];
|
|
618
618
|
get bindings() {
|
|
619
|
-
return this
|
|
619
|
+
return this.#bindings;
|
|
620
620
|
}
|
|
621
621
|
get name() {
|
|
622
|
-
return this
|
|
622
|
+
return this.#name;
|
|
623
623
|
}
|
|
624
624
|
get description() {
|
|
625
|
-
return this
|
|
625
|
+
return this.#description;
|
|
626
626
|
}
|
|
627
627
|
get namespaces() {
|
|
628
|
-
return this
|
|
628
|
+
return this.#namespaces || [];
|
|
629
629
|
}
|
|
630
630
|
constructor(cfg) {
|
|
631
|
-
this
|
|
632
|
-
this
|
|
633
|
-
this
|
|
634
|
-
|
|
631
|
+
this.#name = cfg.name;
|
|
632
|
+
this.#description = cfg.description;
|
|
633
|
+
this.#namespaces = cfg.namespaces;
|
|
634
|
+
this.When = this.When.bind(this);
|
|
635
|
+
logger_default.info(`Capability ${this.#name} registered`);
|
|
635
636
|
logger_default.debug(cfg);
|
|
636
637
|
}
|
|
637
638
|
/**
|
|
@@ -643,7 +644,7 @@ var Capability = class {
|
|
|
643
644
|
* @param kind if using a custom KubernetesObject not available in `a.*`, specify the GroupVersionKind
|
|
644
645
|
* @returns
|
|
645
646
|
*/
|
|
646
|
-
When
|
|
647
|
+
When(model, kind) {
|
|
647
648
|
const matchedKind = modelToGroupVersionKind(model.name);
|
|
648
649
|
if (!matchedKind && !kind) {
|
|
649
650
|
throw new Error(`Kind not specified for ${model.name}`);
|
|
@@ -659,7 +660,9 @@ var Capability = class {
|
|
|
659
660
|
annotations: {}
|
|
660
661
|
}
|
|
661
662
|
};
|
|
662
|
-
const
|
|
663
|
+
const bindings = this.#bindings;
|
|
664
|
+
const prefix = `${this.#name}: ${model.name}`;
|
|
665
|
+
const commonChain = { WithLabel, WithAnnotation, Mutate, Validate };
|
|
663
666
|
const isNotEmpty = (value) => Object.keys(value).length > 0;
|
|
664
667
|
const log = (message, cbString) => {
|
|
665
668
|
const filteredObj = (0, import_ramda.pickBy)(isNotEmpty, binding.filters);
|
|
@@ -667,27 +670,27 @@ var Capability = class {
|
|
|
667
670
|
logger_default.info(filteredObj, prefix);
|
|
668
671
|
logger_default.debug(cbString, prefix);
|
|
669
672
|
};
|
|
670
|
-
|
|
673
|
+
function Validate(validateCallback) {
|
|
671
674
|
if (!isWatchMode) {
|
|
672
675
|
log("Validate Action", validateCallback.toString());
|
|
673
|
-
|
|
676
|
+
bindings.push({
|
|
674
677
|
...binding,
|
|
675
678
|
isValidate: true,
|
|
676
679
|
validateCallback
|
|
677
680
|
});
|
|
678
681
|
}
|
|
679
|
-
}
|
|
680
|
-
|
|
682
|
+
}
|
|
683
|
+
function Mutate(mutateCallback) {
|
|
681
684
|
if (!isWatchMode) {
|
|
682
685
|
log("Mutate Action", mutateCallback.toString());
|
|
683
|
-
|
|
686
|
+
bindings.push({
|
|
684
687
|
...binding,
|
|
685
688
|
isMutate: true,
|
|
686
689
|
mutateCallback
|
|
687
690
|
});
|
|
688
691
|
}
|
|
689
692
|
return { Validate };
|
|
690
|
-
}
|
|
693
|
+
}
|
|
691
694
|
function InNamespace(...namespaces) {
|
|
692
695
|
logger_default.debug(`Add namespaces filter ${namespaces}`, prefix);
|
|
693
696
|
binding.filters.namespaces.push(...namespaces);
|
|
@@ -703,27 +706,26 @@ var Capability = class {
|
|
|
703
706
|
binding.filters.labels[key] = value;
|
|
704
707
|
return commonChain;
|
|
705
708
|
}
|
|
706
|
-
|
|
709
|
+
function WithAnnotation(key, value = "") {
|
|
707
710
|
logger_default.debug(`Add annotation filter ${key}=${value}`, prefix);
|
|
708
711
|
binding.filters.annotations[key] = value;
|
|
709
712
|
return commonChain;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
const bindEvent = (event) => {
|
|
713
|
+
}
|
|
714
|
+
function bindEvent(event) {
|
|
713
715
|
binding.event = event;
|
|
714
716
|
return {
|
|
715
717
|
...commonChain,
|
|
716
718
|
InNamespace,
|
|
717
719
|
WithName
|
|
718
720
|
};
|
|
719
|
-
}
|
|
721
|
+
}
|
|
720
722
|
return {
|
|
721
723
|
IsCreatedOrUpdated: () => bindEvent("CREATEORUPDATE" /* CreateOrUpdate */),
|
|
722
724
|
IsCreated: () => bindEvent("CREATE" /* Create */),
|
|
723
725
|
IsUpdated: () => bindEvent("UPDATE" /* Update */),
|
|
724
726
|
IsDeleted: () => bindEvent("DELETE" /* Delete */)
|
|
725
727
|
};
|
|
726
|
-
}
|
|
728
|
+
}
|
|
727
729
|
};
|
|
728
730
|
|
|
729
731
|
// src/lib/fetch.ts
|
|
@@ -778,15 +780,15 @@ var import_fs = __toESM(require("fs"));
|
|
|
778
780
|
var import_https = __toESM(require("https"));
|
|
779
781
|
|
|
780
782
|
// src/lib/metrics.ts
|
|
781
|
-
var import_prom_client = __toESM(require("prom-client"));
|
|
782
783
|
var import_perf_hooks = require("perf_hooks");
|
|
784
|
+
var import_prom_client = __toESM(require("prom-client"));
|
|
783
785
|
var loggingPrefix = "MetricsCollector";
|
|
784
786
|
var MetricsCollector = class {
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
787
|
+
#registry;
|
|
788
|
+
#counters = /* @__PURE__ */ new Map();
|
|
789
|
+
#summaries = /* @__PURE__ */ new Map();
|
|
790
|
+
#prefix;
|
|
791
|
+
#metricNames = {
|
|
790
792
|
errors: "errors",
|
|
791
793
|
alerts: "alerts",
|
|
792
794
|
mutate: "Mutate",
|
|
@@ -794,79 +796,98 @@ var MetricsCollector = class {
|
|
|
794
796
|
};
|
|
795
797
|
/**
|
|
796
798
|
* Creates a MetricsCollector instance with prefixed metrics.
|
|
797
|
-
* @param
|
|
799
|
+
* @param [prefix='pepr'] - The prefix for the metric names.
|
|
798
800
|
*/
|
|
799
801
|
constructor(prefix = "pepr") {
|
|
800
|
-
this
|
|
801
|
-
this
|
|
802
|
-
this.addCounter(this.
|
|
803
|
-
this.addCounter(this.
|
|
804
|
-
this.addSummary(this.
|
|
805
|
-
this.addSummary(this.
|
|
806
|
-
}
|
|
807
|
-
getMetricName(name) {
|
|
808
|
-
return `${this
|
|
809
|
-
}
|
|
810
|
-
addMetric(collection, MetricType, name, help) {
|
|
811
|
-
if (collection.has(this
|
|
802
|
+
this.#registry = new import_prom_client.Registry();
|
|
803
|
+
this.#prefix = prefix;
|
|
804
|
+
this.addCounter(this.#metricNames.errors, "Mutation/Validate errors encountered");
|
|
805
|
+
this.addCounter(this.#metricNames.alerts, "Mutation/Validate bad api token received");
|
|
806
|
+
this.addSummary(this.#metricNames.mutate, "Mutation operation summary");
|
|
807
|
+
this.addSummary(this.#metricNames.validate, "Validation operation summary");
|
|
808
|
+
}
|
|
809
|
+
#getMetricName(name) {
|
|
810
|
+
return `${this.#prefix}_${name}`;
|
|
811
|
+
}
|
|
812
|
+
#addMetric(collection, MetricType, name, help) {
|
|
813
|
+
if (collection.has(this.#getMetricName(name))) {
|
|
812
814
|
logger_default.debug(`Metric for ${name} already exists`, loggingPrefix);
|
|
813
815
|
return;
|
|
814
816
|
}
|
|
817
|
+
this.incCounter = this.incCounter.bind(this);
|
|
818
|
+
this.error = this.error.bind(this);
|
|
819
|
+
this.alert = this.alert.bind(this);
|
|
820
|
+
this.observeStart = this.observeStart.bind(this);
|
|
821
|
+
this.observeEnd = this.observeEnd.bind(this);
|
|
822
|
+
this.getMetrics = this.getMetrics.bind(this);
|
|
815
823
|
const metric = new MetricType({
|
|
816
|
-
name: this
|
|
824
|
+
name: this.#getMetricName(name),
|
|
817
825
|
help,
|
|
818
|
-
registers: [this
|
|
826
|
+
registers: [this.#registry]
|
|
819
827
|
});
|
|
820
|
-
collection.set(this
|
|
828
|
+
collection.set(this.#getMetricName(name), metric);
|
|
821
829
|
}
|
|
822
830
|
addCounter(name, help) {
|
|
823
|
-
this
|
|
831
|
+
this.#addMetric(this.#counters, import_prom_client.default.Counter, name, help);
|
|
824
832
|
}
|
|
825
833
|
addSummary(name, help) {
|
|
826
|
-
this
|
|
834
|
+
this.#addMetric(this.#summaries, import_prom_client.default.Summary, name, help);
|
|
827
835
|
}
|
|
828
836
|
incCounter(name) {
|
|
829
|
-
this.
|
|
837
|
+
this.#counters.get(this.#getMetricName(name))?.inc();
|
|
830
838
|
}
|
|
831
839
|
/**
|
|
832
840
|
* Increments the error counter.
|
|
833
841
|
*/
|
|
834
842
|
error() {
|
|
835
|
-
this.incCounter(this.
|
|
843
|
+
this.incCounter(this.#metricNames.errors);
|
|
836
844
|
}
|
|
837
845
|
/**
|
|
838
846
|
* Increments the alerts counter.
|
|
839
847
|
*/
|
|
840
848
|
alert() {
|
|
841
|
-
this.incCounter(this.
|
|
849
|
+
this.incCounter(this.#metricNames.alerts);
|
|
842
850
|
}
|
|
843
851
|
/**
|
|
844
852
|
* Returns the current timestamp from performance.now() method. Useful for start timing an operation.
|
|
845
|
-
* @returns
|
|
853
|
+
* @returns The timestamp.
|
|
846
854
|
*/
|
|
847
855
|
observeStart() {
|
|
848
856
|
return import_perf_hooks.performance.now();
|
|
849
857
|
}
|
|
850
858
|
/**
|
|
851
859
|
* Observes the duration since the provided start time and updates the summary.
|
|
852
|
-
* @param
|
|
853
|
-
* @param
|
|
860
|
+
* @param startTime - The start time.
|
|
861
|
+
* @param name - The metrics summary to increment.
|
|
854
862
|
*/
|
|
855
|
-
observeEnd(startTime, name = this.
|
|
856
|
-
this.
|
|
863
|
+
observeEnd(startTime, name = this.#metricNames.mutate) {
|
|
864
|
+
this.#summaries.get(this.#getMetricName(name))?.observe(import_perf_hooks.performance.now() - startTime);
|
|
857
865
|
}
|
|
858
866
|
/**
|
|
859
867
|
* Fetches the current metrics from the registry.
|
|
860
|
-
* @returns
|
|
868
|
+
* @returns The metrics.
|
|
861
869
|
*/
|
|
862
870
|
async getMetrics() {
|
|
863
|
-
return this.
|
|
871
|
+
return this.#registry.metrics();
|
|
864
872
|
}
|
|
865
873
|
};
|
|
866
874
|
|
|
867
875
|
// src/lib/mutate-processor.ts
|
|
868
876
|
var import_fast_json_patch = __toESM(require("fast-json-patch"));
|
|
869
877
|
|
|
878
|
+
// src/lib/errors.ts
|
|
879
|
+
var Errors = {
|
|
880
|
+
audit: "audit",
|
|
881
|
+
ignore: "ignore",
|
|
882
|
+
reject: "reject"
|
|
883
|
+
};
|
|
884
|
+
var ErrorList = Object.values(Errors);
|
|
885
|
+
function ValidateError(error = "") {
|
|
886
|
+
if (!ErrorList.includes(error)) {
|
|
887
|
+
throw new Error(`Invalid error: ${error}. Must be one of: ${ErrorList.join(", ")}`);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
870
891
|
// src/lib/filter.ts
|
|
871
892
|
function shouldSkipRequest(binding, req) {
|
|
872
893
|
const { group, kind, version } = binding.kind || {};
|
|
@@ -921,45 +942,53 @@ function shouldSkipRequest(binding, req) {
|
|
|
921
942
|
// src/lib/mutate-request.ts
|
|
922
943
|
var import_ramda2 = require("ramda");
|
|
923
944
|
var PeprMutateRequest = class {
|
|
924
|
-
/**
|
|
925
|
-
* Creates a new instance of the action class.
|
|
926
|
-
* @param input - The request object containing the Kubernetes resource to modify.
|
|
927
|
-
*/
|
|
928
|
-
constructor(_input) {
|
|
929
|
-
this._input = _input;
|
|
930
|
-
if (_input.operation.toUpperCase() === "DELETE" /* DELETE */) {
|
|
931
|
-
this.Raw = (0, import_ramda2.clone)(_input.oldObject);
|
|
932
|
-
} else {
|
|
933
|
-
this.Raw = (0, import_ramda2.clone)(_input.object);
|
|
934
|
-
}
|
|
935
|
-
if (!this.Raw) {
|
|
936
|
-
throw new Error("unable to load the request object into PeprRequest.RawP");
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
945
|
Raw;
|
|
946
|
+
#input;
|
|
940
947
|
get PermitSideEffects() {
|
|
941
|
-
return !this.
|
|
948
|
+
return !this.#input.dryRun;
|
|
942
949
|
}
|
|
943
950
|
/**
|
|
944
951
|
* Indicates whether the request is a dry run.
|
|
945
952
|
* @returns true if the request is a dry run, false otherwise.
|
|
946
953
|
*/
|
|
947
954
|
get IsDryRun() {
|
|
948
|
-
return this.
|
|
955
|
+
return this.#input.dryRun;
|
|
949
956
|
}
|
|
950
957
|
/**
|
|
951
958
|
* Provides access to the old resource in the request if available.
|
|
952
959
|
* @returns The old Kubernetes resource object or null if not available.
|
|
953
960
|
*/
|
|
954
961
|
get OldResource() {
|
|
955
|
-
return this.
|
|
962
|
+
return this.#input.oldObject;
|
|
956
963
|
}
|
|
957
964
|
/**
|
|
958
965
|
* Provides access to the request object.
|
|
959
966
|
* @returns The request object containing the Kubernetes resource.
|
|
960
967
|
*/
|
|
961
968
|
get Request() {
|
|
962
|
-
return this
|
|
969
|
+
return this.#input;
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Creates a new instance of the action class.
|
|
973
|
+
* @param input - The request object containing the Kubernetes resource to modify.
|
|
974
|
+
*/
|
|
975
|
+
constructor(input) {
|
|
976
|
+
this.#input = input;
|
|
977
|
+
this.Merge = this.Merge.bind(this);
|
|
978
|
+
this.SetLabel = this.SetLabel.bind(this);
|
|
979
|
+
this.SetAnnotation = this.SetAnnotation.bind(this);
|
|
980
|
+
this.RemoveLabel = this.RemoveLabel.bind(this);
|
|
981
|
+
this.RemoveAnnotation = this.RemoveAnnotation.bind(this);
|
|
982
|
+
this.HasLabel = this.HasLabel.bind(this);
|
|
983
|
+
this.HasAnnotation = this.HasAnnotation.bind(this);
|
|
984
|
+
if (input.operation.toUpperCase() === "DELETE" /* DELETE */) {
|
|
985
|
+
this.Raw = (0, import_ramda2.clone)(input.oldObject);
|
|
986
|
+
} else {
|
|
987
|
+
this.Raw = (0, import_ramda2.clone)(input.object);
|
|
988
|
+
}
|
|
989
|
+
if (!this.Raw) {
|
|
990
|
+
throw new Error("unable to load the request object into PeprRequest.RawP");
|
|
991
|
+
}
|
|
963
992
|
}
|
|
964
993
|
/**
|
|
965
994
|
* Deep merges the provided object with the current resource.
|
|
@@ -1126,11 +1155,13 @@ async function mutateProcessor(config, capabilities, req, reqMetadata) {
|
|
|
1126
1155
|
response.warnings = response.warnings || [];
|
|
1127
1156
|
response.warnings.push(`Action failed: ${e}`);
|
|
1128
1157
|
switch (config.onError) {
|
|
1129
|
-
case
|
|
1158
|
+
case Errors.reject:
|
|
1130
1159
|
logger_default.error(actionMetadata, `Action failed: ${e}`);
|
|
1131
1160
|
response.result = "Pepr module configured to reject on error";
|
|
1132
1161
|
return response;
|
|
1133
|
-
case
|
|
1162
|
+
case Errors.audit:
|
|
1163
|
+
response.auditAnnotations = response.auditAnnotations || {};
|
|
1164
|
+
response.auditAnnotations[Date.now()] = e;
|
|
1134
1165
|
break;
|
|
1135
1166
|
}
|
|
1136
1167
|
}
|
|
@@ -1163,35 +1194,40 @@ async function mutateProcessor(config, capabilities, req, reqMetadata) {
|
|
|
1163
1194
|
// src/lib/validate-request.ts
|
|
1164
1195
|
var import_ramda3 = require("ramda");
|
|
1165
1196
|
var PeprValidateRequest = class {
|
|
1166
|
-
/**
|
|
1167
|
-
* Creates a new instance of the Action class.
|
|
1168
|
-
* @param input - The request object containing the Kubernetes resource to modify.
|
|
1169
|
-
*/
|
|
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
|
-
}
|
|
1180
|
-
}
|
|
1181
1197
|
Raw;
|
|
1198
|
+
#input;
|
|
1182
1199
|
/**
|
|
1183
1200
|
* Provides access to the old resource in the request if available.
|
|
1184
1201
|
* @returns The old Kubernetes resource object or null if not available.
|
|
1185
1202
|
*/
|
|
1186
1203
|
get OldResource() {
|
|
1187
|
-
return this.
|
|
1204
|
+
return this.#input.oldObject;
|
|
1188
1205
|
}
|
|
1189
1206
|
/**
|
|
1190
1207
|
* Provides access to the request object.
|
|
1191
1208
|
* @returns The request object containing the Kubernetes resource.
|
|
1192
1209
|
*/
|
|
1193
1210
|
get Request() {
|
|
1194
|
-
return this
|
|
1211
|
+
return this.#input;
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Creates a new instance of the Action class.
|
|
1215
|
+
* @param input - The request object containing the Kubernetes resource to modify.
|
|
1216
|
+
*/
|
|
1217
|
+
constructor(input) {
|
|
1218
|
+
this.#input = input;
|
|
1219
|
+
this.HasLabel = this.HasLabel.bind(this);
|
|
1220
|
+
this.HasAnnotation = this.HasAnnotation.bind(this);
|
|
1221
|
+
this.Approve = this.Approve.bind(this);
|
|
1222
|
+
this.Deny = this.Deny.bind(this);
|
|
1223
|
+
if (input.operation.toUpperCase() === "DELETE" /* DELETE */) {
|
|
1224
|
+
this.Raw = (0, import_ramda3.clone)(input.oldObject);
|
|
1225
|
+
} else {
|
|
1226
|
+
this.Raw = (0, import_ramda3.clone)(input.object);
|
|
1227
|
+
}
|
|
1228
|
+
if (!this.Raw) {
|
|
1229
|
+
throw new Error("unable to load the request object into PeprRequest.RawP");
|
|
1230
|
+
}
|
|
1195
1231
|
}
|
|
1196
1232
|
/**
|
|
1197
1233
|
* Check if a label exists on the Kubernetes resource.
|
|
@@ -1245,6 +1281,10 @@ async function validateProcessor(capabilities, req, reqMetadata) {
|
|
|
1245
1281
|
allowed: true
|
|
1246
1282
|
// Assume it's allowed until a validation check fails
|
|
1247
1283
|
};
|
|
1284
|
+
const isSecret = req.kind.version == "v1" && req.kind.kind == "Secret";
|
|
1285
|
+
if (isSecret) {
|
|
1286
|
+
convertFromBase64Map(wrapped.Raw);
|
|
1287
|
+
}
|
|
1248
1288
|
logger_default.info(reqMetadata, `Processing validation request`);
|
|
1249
1289
|
for (const { name, bindings } of capabilities) {
|
|
1250
1290
|
const actionMetadata = { ...reqMetadata, name };
|
|
@@ -1282,40 +1322,39 @@ async function validateProcessor(capabilities, req, reqMetadata) {
|
|
|
1282
1322
|
}
|
|
1283
1323
|
|
|
1284
1324
|
// src/lib/controller.ts
|
|
1285
|
-
var Controller = class {
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1325
|
+
var Controller = class _Controller {
|
|
1326
|
+
// Track whether the server is running
|
|
1327
|
+
#running = false;
|
|
1328
|
+
// Metrics collector
|
|
1329
|
+
#metricsCollector = new MetricsCollector("pepr");
|
|
1330
|
+
// The token used to authenticate requests
|
|
1331
|
+
#token = "";
|
|
1332
|
+
// The express app instance
|
|
1333
|
+
#app = (0, import_express.default)();
|
|
1334
|
+
// Initialized with the constructor
|
|
1335
|
+
#config;
|
|
1336
|
+
#capabilities;
|
|
1337
|
+
#beforeHook;
|
|
1338
|
+
#afterHook;
|
|
1339
|
+
constructor(config, capabilities, beforeHook, afterHook) {
|
|
1340
|
+
this.#config = config;
|
|
1341
|
+
this.#capabilities = capabilities;
|
|
1342
|
+
this.#beforeHook = beforeHook;
|
|
1343
|
+
this.#afterHook = afterHook;
|
|
1344
|
+
this.startServer = this.startServer.bind(this);
|
|
1345
|
+
this.#app.use(_Controller.#logger);
|
|
1346
|
+
this.#app.use(import_express.default.json({ limit: "2mb" }));
|
|
1347
|
+
if (beforeHook) {
|
|
1348
|
+
logger_default.info(`Using beforeHook: ${beforeHook}`);
|
|
1295
1349
|
}
|
|
1296
|
-
if (
|
|
1297
|
-
logger_default.info(`Using afterHook: ${
|
|
1350
|
+
if (afterHook) {
|
|
1351
|
+
logger_default.info(`Using afterHook: ${afterHook}`);
|
|
1298
1352
|
}
|
|
1299
|
-
this
|
|
1353
|
+
this.#bindEndpoints();
|
|
1300
1354
|
}
|
|
1301
|
-
_app = (0, import_express.default)();
|
|
1302
|
-
_running = false;
|
|
1303
|
-
metricsCollector = new MetricsCollector("pepr");
|
|
1304
|
-
// The token used to authenticate requests
|
|
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
|
-
};
|
|
1316
1355
|
/** Start the webhook server */
|
|
1317
|
-
startServer
|
|
1318
|
-
if (this
|
|
1356
|
+
startServer(port) {
|
|
1357
|
+
if (this.#running) {
|
|
1319
1358
|
throw new Error("Cannot start Pepr module: Pepr module was not instantiated with deferStart=true");
|
|
1320
1359
|
}
|
|
1321
1360
|
const options = {
|
|
@@ -1323,16 +1362,16 @@ var Controller = class {
|
|
|
1323
1362
|
cert: import_fs.default.readFileSync(process.env.SSL_CERT_PATH || "/etc/certs/tls.crt")
|
|
1324
1363
|
};
|
|
1325
1364
|
if (!isWatchMode) {
|
|
1326
|
-
this
|
|
1327
|
-
logger_default.info(`Using API token: ${this
|
|
1328
|
-
if (!this
|
|
1365
|
+
this.#token = process.env.PEPR_API_TOKEN || import_fs.default.readFileSync("/app/api-token/value").toString().trim();
|
|
1366
|
+
logger_default.info(`Using API token: ${this.#token}`);
|
|
1367
|
+
if (!this.#token) {
|
|
1329
1368
|
throw new Error("API token not found");
|
|
1330
1369
|
}
|
|
1331
1370
|
}
|
|
1332
|
-
const server = import_https.default.createServer(options, this
|
|
1371
|
+
const server = import_https.default.createServer(options, this.#app).listen(port);
|
|
1333
1372
|
server.on("listening", () => {
|
|
1334
1373
|
logger_default.info(`Server listening on port ${port}`);
|
|
1335
|
-
this
|
|
1374
|
+
this.#running = true;
|
|
1336
1375
|
});
|
|
1337
1376
|
server.on("error", (e) => {
|
|
1338
1377
|
if (e.code === "EADDRINUSE") {
|
|
@@ -1352,28 +1391,16 @@ var Controller = class {
|
|
|
1352
1391
|
process.exit(0);
|
|
1353
1392
|
});
|
|
1354
1393
|
});
|
|
1355
|
-
}
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
res.on("finish", () => {
|
|
1366
|
-
const elapsedTime = Date.now() - startTime;
|
|
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);
|
|
1375
|
-
});
|
|
1376
|
-
next();
|
|
1394
|
+
}
|
|
1395
|
+
#bindEndpoints = () => {
|
|
1396
|
+
this.#app.get("/healthz", _Controller.#healthz);
|
|
1397
|
+
this.#app.get("/metrics", this.#metrics);
|
|
1398
|
+
if (isWatchMode) {
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
this.#app.use(["/mutate/:token", "/validate/:token"], this.#validateToken);
|
|
1402
|
+
this.#app.post("/mutate/:token", this.#admissionReq("Mutate"));
|
|
1403
|
+
this.#app.post("/validate/:token", this.#admissionReq("Validate"));
|
|
1377
1404
|
};
|
|
1378
1405
|
/**
|
|
1379
1406
|
* Validate the token in the request path
|
|
@@ -1383,40 +1410,26 @@ var Controller = class {
|
|
|
1383
1410
|
* @param next The next middleware function
|
|
1384
1411
|
* @returns
|
|
1385
1412
|
*/
|
|
1386
|
-
validateToken = (req, res, next) => {
|
|
1413
|
+
#validateToken = (req, res, next) => {
|
|
1387
1414
|
const { token } = req.params;
|
|
1388
|
-
if (token !== this
|
|
1415
|
+
if (token !== this.#token) {
|
|
1389
1416
|
const err = `Unauthorized: invalid token '${token.replace(/[^\w]/g, "_")}'`;
|
|
1390
1417
|
logger_default.warn(err);
|
|
1391
1418
|
res.status(401).send(err);
|
|
1392
|
-
this
|
|
1419
|
+
this.#metricsCollector.alert();
|
|
1393
1420
|
return;
|
|
1394
1421
|
}
|
|
1395
1422
|
next();
|
|
1396
1423
|
};
|
|
1397
|
-
/**
|
|
1398
|
-
* Health check endpoint handler
|
|
1399
|
-
*
|
|
1400
|
-
* @param req the incoming request
|
|
1401
|
-
* @param res the outgoing response
|
|
1402
|
-
*/
|
|
1403
|
-
healthz = (req, res) => {
|
|
1404
|
-
try {
|
|
1405
|
-
res.send("OK");
|
|
1406
|
-
} catch (err) {
|
|
1407
|
-
logger_default.error(err);
|
|
1408
|
-
res.status(500).send("Internal Server Error");
|
|
1409
|
-
}
|
|
1410
|
-
};
|
|
1411
1424
|
/**
|
|
1412
1425
|
* Metrics endpoint handler
|
|
1413
1426
|
*
|
|
1414
1427
|
* @param req the incoming request
|
|
1415
1428
|
* @param res the outgoing response
|
|
1416
1429
|
*/
|
|
1417
|
-
metrics = async (req, res) => {
|
|
1430
|
+
#metrics = async (req, res) => {
|
|
1418
1431
|
try {
|
|
1419
|
-
res.send(await this
|
|
1432
|
+
res.send(await this.#metricsCollector.getMetrics());
|
|
1420
1433
|
} catch (err) {
|
|
1421
1434
|
logger_default.error(err);
|
|
1422
1435
|
res.status(500).send("Internal Server Error");
|
|
@@ -1428,12 +1441,12 @@ var Controller = class {
|
|
|
1428
1441
|
* @param admissionKind the type of admission request
|
|
1429
1442
|
* @returns the request handler
|
|
1430
1443
|
*/
|
|
1431
|
-
admissionReq = (admissionKind) => {
|
|
1444
|
+
#admissionReq = (admissionKind) => {
|
|
1432
1445
|
return async (req, res) => {
|
|
1433
|
-
const startTime = this
|
|
1446
|
+
const startTime = this.#metricsCollector.observeStart();
|
|
1434
1447
|
try {
|
|
1435
1448
|
const request = req.body?.request || {};
|
|
1436
|
-
this
|
|
1449
|
+
this.#beforeHook && this.#beforeHook(request || {});
|
|
1437
1450
|
const name = request?.name ? `/${request.name}` : "";
|
|
1438
1451
|
const namespace = request?.namespace || "";
|
|
1439
1452
|
const gvk = request?.kind || { group: "", version: "", kind: "" };
|
|
@@ -1446,49 +1459,92 @@ var Controller = class {
|
|
|
1446
1459
|
logger_default.debug({ ...reqMetadata, request }, "Incoming request body");
|
|
1447
1460
|
let response;
|
|
1448
1461
|
if (admissionKind === "Mutate") {
|
|
1449
|
-
response = await mutateProcessor(this
|
|
1462
|
+
response = await mutateProcessor(this.#config, this.#capabilities, request, reqMetadata);
|
|
1450
1463
|
} else {
|
|
1451
|
-
response = await validateProcessor(this
|
|
1464
|
+
response = await validateProcessor(this.#capabilities, request, reqMetadata);
|
|
1452
1465
|
}
|
|
1453
|
-
this
|
|
1466
|
+
this.#afterHook && this.#afterHook(response);
|
|
1454
1467
|
logger_default.debug({ ...reqMetadata, response }, "Outgoing response");
|
|
1455
1468
|
res.send({
|
|
1456
1469
|
apiVersion: "admission.k8s.io/v1",
|
|
1457
1470
|
kind: "AdmissionReview",
|
|
1458
1471
|
response
|
|
1459
1472
|
});
|
|
1460
|
-
this
|
|
1473
|
+
this.#metricsCollector.observeEnd(startTime, admissionKind);
|
|
1461
1474
|
} catch (err) {
|
|
1462
1475
|
logger_default.error(err);
|
|
1463
1476
|
res.status(500).send("Internal Server Error");
|
|
1464
|
-
this
|
|
1477
|
+
this.#metricsCollector.error();
|
|
1465
1478
|
}
|
|
1466
1479
|
};
|
|
1467
1480
|
};
|
|
1481
|
+
/**
|
|
1482
|
+
* Middleware for logging requests
|
|
1483
|
+
*
|
|
1484
|
+
* @param req the incoming request
|
|
1485
|
+
* @param res the outgoing response
|
|
1486
|
+
* @param next the next middleware function
|
|
1487
|
+
*/
|
|
1488
|
+
static #logger(req, res, next) {
|
|
1489
|
+
const startTime = Date.now();
|
|
1490
|
+
res.on("finish", () => {
|
|
1491
|
+
const elapsedTime = Date.now() - startTime;
|
|
1492
|
+
const message = {
|
|
1493
|
+
uid: req.body?.request?.uid,
|
|
1494
|
+
method: req.method,
|
|
1495
|
+
url: req.originalUrl,
|
|
1496
|
+
status: res.statusCode,
|
|
1497
|
+
duration: `${elapsedTime} ms`
|
|
1498
|
+
};
|
|
1499
|
+
res.statusCode >= 300 ? logger_default.warn(message) : logger_default.info(message);
|
|
1500
|
+
});
|
|
1501
|
+
next();
|
|
1502
|
+
}
|
|
1503
|
+
/**
|
|
1504
|
+
* Health check endpoint handler
|
|
1505
|
+
*
|
|
1506
|
+
* @param req the incoming request
|
|
1507
|
+
* @param res the outgoing response
|
|
1508
|
+
*/
|
|
1509
|
+
static #healthz(req, res) {
|
|
1510
|
+
try {
|
|
1511
|
+
res.send("OK");
|
|
1512
|
+
} catch (err) {
|
|
1513
|
+
logger_default.error(err);
|
|
1514
|
+
res.status(500).send("Internal Server Error");
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1468
1517
|
};
|
|
1469
1518
|
|
|
1470
1519
|
// src/lib/module.ts
|
|
1471
1520
|
var PeprModule = class {
|
|
1472
|
-
|
|
1521
|
+
#controller;
|
|
1473
1522
|
/**
|
|
1474
1523
|
* Create a new Pepr runtime
|
|
1475
1524
|
*
|
|
1476
1525
|
* @param config The configuration for the Pepr runtime
|
|
1477
1526
|
* @param capabilities The capabilities to be loaded into the Pepr runtime
|
|
1478
|
-
* @param
|
|
1527
|
+
* @param opts Options for the Pepr runtime
|
|
1479
1528
|
*/
|
|
1480
1529
|
constructor({ description, pepr }, capabilities = [], opts = {}) {
|
|
1481
1530
|
const config = (0, import_ramda4.clone)(pepr);
|
|
1482
1531
|
config.description = description;
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
throw new Error(`Invalid onErrors value: ${config.onError}`);
|
|
1486
|
-
}
|
|
1532
|
+
ValidateError(config.onError);
|
|
1533
|
+
this.start = this.start.bind(this);
|
|
1487
1534
|
if (process.env.PEPR_MODE === "build" && process.send) {
|
|
1488
|
-
|
|
1535
|
+
const exportedCapabilities = [];
|
|
1536
|
+
for (const capability of capabilities) {
|
|
1537
|
+
exportedCapabilities.push({
|
|
1538
|
+
name: capability.name,
|
|
1539
|
+
description: capability.description,
|
|
1540
|
+
namespaces: capability.namespaces,
|
|
1541
|
+
bindings: capability.bindings
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
process.send(exportedCapabilities);
|
|
1489
1545
|
return;
|
|
1490
1546
|
}
|
|
1491
|
-
this
|
|
1547
|
+
this.#controller = new Controller(config, capabilities, opts.beforeHook, opts.afterHook);
|
|
1492
1548
|
if (opts.deferStart) {
|
|
1493
1549
|
return;
|
|
1494
1550
|
}
|
|
@@ -1501,7 +1557,7 @@ var PeprModule = class {
|
|
|
1501
1557
|
* @param port
|
|
1502
1558
|
*/
|
|
1503
1559
|
start(port = 3e3) {
|
|
1504
|
-
this.
|
|
1560
|
+
this.#controller.startServer(port);
|
|
1505
1561
|
}
|
|
1506
1562
|
};
|
|
1507
1563
|
// Annotate the CommonJS export names for ESM import in node:
|