pepr 0.7.0 → 0.8.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.
package/README.md CHANGED
@@ -40,10 +40,20 @@ When(a.ConfigMap)
40
40
  });
41
41
  ```
42
42
 
43
+ ## Prerequisites
44
+
45
+ - [Node.js](https://nodejs.org/en/) v18.0.0+.
46
+
47
+ > _Recommend installing with [NVM](https://github.com/nvm-sh/nvm) or [NVM for Windows](https://github.com/coreybutler/nvm-windows) to avoid permission issues when installing the Pepr CLI globally._
48
+
49
+ - Recommended (optional) tools:
50
+ - [Visual Studio Code](https://code.visualstudio.com/) for inline debugging and [Pepr Capabilities](#capability) creation.
51
+ - A Kubernetes cluster for `pepr dev`. Pepr modules include `npm run k3d-setup` if you want to test locally with [K3d](https://k3d.io/) and [Docker](https://www.docker.com/).
52
+
43
53
  ## Wow too many words! tl;dr;
44
54
 
45
55
  ```bash
46
- # Install Pepr (you can also use npx)
56
+ # Install Pepr globally. If this command requires sudo, see the Prerequisites section to install Node.js with NVM or NVM for Windows.
47
57
  npm i -g pepr
48
58
 
49
59
  # Initialize a new Pepr Module
@@ -95,6 +105,7 @@ For example, a CapabilityAction could be responsible for adding a specific label
95
105
  See [CapabilityActions](./docs/actions.md) for more details.
96
106
 
97
107
  ## Logical Pepr Flow
108
+
98
109
  ![Module Diagram](./.images/modules.svg)
99
110
 
100
111
  ## TypeScript
package/dist/cli.js CHANGED
@@ -268,13 +268,18 @@ var Webhook = class {
268
268
  this.name = `pepr-${config.uuid}`;
269
269
  this.image = `ghcr.io/defenseunicorns/pepr/controller:v${config.version}`;
270
270
  this._tls = genTLS(this.host || `${this.name}.pepr-system.svc`);
271
+ this._apiToken = import_crypto.default.randomBytes(32).toString("hex");
271
272
  }
272
273
  name;
273
274
  _tls;
275
+ _apiToken;
274
276
  image;
275
277
  get tls() {
276
278
  return this._tls;
277
279
  }
280
+ get apiToken() {
281
+ return this._apiToken;
282
+ }
278
283
  /** Generate the pepr-system namespace */
279
284
  namespace() {
280
285
  return {
@@ -334,6 +339,20 @@ var Webhook = class {
334
339
  }
335
340
  };
336
341
  }
342
+ apiTokenSecret() {
343
+ return {
344
+ apiVersion: "v1",
345
+ kind: "Secret",
346
+ metadata: {
347
+ name: `${this.name}-api-token`,
348
+ namespace: "pepr-system"
349
+ },
350
+ type: "Opaque",
351
+ data: {
352
+ value: Buffer.from(this._apiToken).toString("base64")
353
+ }
354
+ };
355
+ }
337
356
  tlsSecret() {
338
357
  return {
339
358
  apiVersion: "v1",
@@ -418,13 +437,14 @@ var Webhook = class {
418
437
  const clientConfig = {
419
438
  caBundle: this._tls.ca
420
439
  };
440
+ const mutatePath = `/mutate/${this._apiToken}`;
421
441
  if (this.host) {
422
- clientConfig.url = `https://${this.host}:3000/mutate`;
442
+ clientConfig.url = `https://${this.host}:3000${mutatePath}`;
423
443
  } else {
424
444
  clientConfig.service = {
425
445
  name: this.name,
426
446
  namespace: "pepr-system",
427
- path: "/mutate"
447
+ path: mutatePath
428
448
  };
429
449
  }
430
450
  const rules = await this.generateWebhookRules(path);
@@ -514,6 +534,11 @@ var Webhook = class {
514
534
  mountPath: "/etc/certs",
515
535
  readOnly: true
516
536
  },
537
+ {
538
+ name: "api-token",
539
+ mountPath: "/app/api-token",
540
+ readOnly: true
541
+ },
517
542
  {
518
543
  name: "module",
519
544
  mountPath: `/app/load`,
@@ -529,6 +554,12 @@ var Webhook = class {
529
554
  secretName: `${this.name}-tls`
530
555
  }
531
556
  },
557
+ {
558
+ name: "api-token",
559
+ secret: {
560
+ secretName: `${this.name}-api-token`
561
+ }
562
+ },
532
563
  {
533
564
  name: "module",
534
565
  secret: {
@@ -541,44 +572,6 @@ var Webhook = class {
541
572
  }
542
573
  };
543
574
  }
544
- /** Only permit the kube-system ns ingress access to the controller */
545
- networkPolicy() {
546
- return {
547
- apiVersion: "networking.k8s.io/v1",
548
- kind: "NetworkPolicy",
549
- metadata: {
550
- name: this.name,
551
- namespace: "pepr-system"
552
- },
553
- spec: {
554
- podSelector: {
555
- matchLabels: {
556
- app: this.name
557
- }
558
- },
559
- policyTypes: ["Ingress"],
560
- ingress: [
561
- {
562
- from: [
563
- {
564
- namespaceSelector: {
565
- matchLabels: {
566
- "kubernetes.io/metadata.name": "kube-system"
567
- }
568
- }
569
- }
570
- ],
571
- ports: [
572
- {
573
- protocol: "TCP",
574
- port: 443
575
- }
576
- ]
577
- }
578
- ]
579
- }
580
- };
581
- }
582
575
  service() {
583
576
  return {
584
577
  apiVersion: "v1",
@@ -647,10 +640,10 @@ var Webhook = class {
647
640
  const webhook = await this.mutatingWebhook(path);
648
641
  const resources = [
649
642
  this.namespace(),
650
- this.networkPolicy(),
651
643
  this.clusterRole(),
652
644
  this.clusterRoleBinding(),
653
645
  this.serviceAccount(),
646
+ this.apiTokenSecret(),
654
647
  this.tlsSecret(),
655
648
  webhook,
656
649
  this.deployment(hash),
@@ -695,16 +688,6 @@ var Webhook = class {
695
688
  const hash = import_crypto.default.createHash("sha256").update(code).digest("hex");
696
689
  const appsApi = kubeConfig.makeApiClient(import_client_node.AppsV1Api);
697
690
  const rbacApi = kubeConfig.makeApiClient(import_client_node.RbacAuthorizationV1Api);
698
- const networkApi = kubeConfig.makeApiClient(import_client_node.NetworkingV1Api);
699
- const networkPolicy = this.networkPolicy();
700
- try {
701
- logger_default.info("Checking for network policy");
702
- await networkApi.readNamespacedNetworkPolicy(networkPolicy.metadata?.name ?? "", namespace);
703
- } catch (e) {
704
- logger_default.debug(e instanceof import_client_node.HttpError ? e.body : e);
705
- logger_default.info("Creating network policy");
706
- await networkApi.createNamespacedNetworkPolicy(namespace, networkPolicy);
707
- }
708
691
  const crb = this.clusterRoleBinding();
709
692
  try {
710
693
  logger_default.info("Creating cluster role binding");
@@ -769,6 +752,16 @@ var Webhook = class {
769
752
  await coreV1Api.deleteNamespacedSecret(tls.metadata?.name ?? "", namespace);
770
753
  await coreV1Api.createNamespacedSecret(namespace, tls);
771
754
  }
755
+ const apiToken = this.apiTokenSecret();
756
+ try {
757
+ logger_default.info("Creating API token secret");
758
+ await coreV1Api.createNamespacedSecret(namespace, apiToken);
759
+ } catch (e) {
760
+ logger_default.debug(e instanceof import_client_node.HttpError ? e.body : e);
761
+ logger_default.info("Removing and re-creating API token secret");
762
+ await coreV1Api.deleteNamespacedSecret(apiToken.metadata?.name ?? "", namespace);
763
+ await coreV1Api.createNamespacedSecret(namespace, apiToken);
764
+ }
772
765
  const dep = this.deployment(hash);
773
766
  try {
774
767
  logger_default.info("Creating deployment");
@@ -970,8 +963,8 @@ var hello_pepr_samples_default = [
970
963
  var gitIgnore = "# Ignore node_modules and Pepr build artifacts\nnode_modules\ndist\ninsecure*\n";
971
964
  var readmeMd = '# Pepr Module\n\nThis is a Pepr Module. [Pepr](https://github.com/defenseunicorns/pepr) is a Kubernetes transformation system\nwritten in Typescript.\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';
972
965
  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';
973
- var helloPeprTS = 'import {\n Capability,\n PeprRequest,\n RegisterKind,\n a,\n fetch,\n fetchStatus,\n} from "pepr";\n\n/**\n * The HelloPepr Capability is an example capability to demonstrate some general concepts of Pepr.\n * To test this capability you can run `pepr dev` or `npm start` 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 Capability Action\nconst { When } = HelloPepr;\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (Namespace) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This Capability 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 .Then(ns => ns.RemoveLabel("remove-me"));\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (CM Example 1) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This is a single Capability 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 .Then(request =>\n request\n .SetLabel("pepr", "was-here")\n .SetAnnotation("pepr.dev", "annotations-work-too")\n );\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (CM Example 2) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This Capability Action does the exact same changes for example-2, except this time it uses\n * the `.ThenSet()` feature. You can stack multiple `.Then()` calls, but only a single `.ThenSet()`\n */\nWhen(a.ConfigMap)\n .IsCreated()\n .WithName("example-2")\n .ThenSet({\n metadata: {\n labels: {\n pepr: "was-here",\n },\n annotations: {\n "pepr.dev": "annotations-work-too",\n },\n },\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (CM Example 3) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This Capability 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 .Then(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/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (CM Example 4) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This Capability Action show how you can use the `Then()` function to make multiple changes to the\n * same object from different functions. This is useful if you want to keep your Capability Actions\n * small and focused on a single task, or if you want to reuse the same function in multiple\n * Capability Actions.\n *\n * Note that the order of the `.Then()` calls matters. The first call will be executed first,\n * then the second, and so on. Also note the functions are not called until the Capability Action\n * is triggered.\n */\nWhen(a.ConfigMap)\n .IsCreated()\n .WithName("example-4")\n .Then(cm => cm.SetLabel("pepr.dev/first", "true"))\n .Then(addSecond)\n .Then(addThird);\n\n//This function uses the complete type definition, but is not required.\nfunction addSecond(cm: PeprRequest<a.ConfigMap>) {\n cm.SetLabel("pepr.dev/second", "true");\n}\n\n// This function has no type definition, so you won\'t have intellisense in the function body.\nfunction addThird(cm) {\n cm.SetLabel("pepr.dev/third", "true");\n}\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY 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 .Then(cm => cm.SetLabel("pepr.dev/first", "true"))\n .Then(addSecond)\n .Then(addThird);\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (CM Example 5) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This Capability 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 Capability 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://api.chucknorris.io/jokes/random?category=dev");\n * const joke = await fetch("https://api.chucknorris.io/jokes/random?category=dev") as TheChuckNorrisJoke;\n * ```\n *\n * Alternatively, you can drop the type completely:\n *\n * ```ts\n * fetch("https://api.chucknorris.io/jokes/random?category=dev")\n * ```\n */\ninterface TheChuckNorrisJoke {\n icon_url: string;\n id: string;\n url: string;\n value: string;\n}\n\nWhen(a.ConfigMap)\n .IsCreated()\n .WithLabel("chuck-norris")\n .Then(async change => {\n // Try/catch is not needed as a response object will always be returned\n const response = await fetch<TheChuckNorrisJoke>(\n "https://api.chucknorris.io/jokes/random?category=dev"\n );\n\n // Instead, check the `response.ok` field\n if (response.ok) {\n // Add the Chuck Norris joke to the configmap\n change.Raw.data["chuck-says"] = response.data.value;\n return;\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 * CAPABILITY 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 Capability Action is executed.\n */\nWhen(a.Secret)\n .IsCreated()\n .WithName("secret-1")\n .Then(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 * CAPABILITY 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 Capability 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 ot 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 .ThenSet({\n spec: {\n message: "Hello Pepr without type data!",\n counter: Math.random(),\n },\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY 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 Capability 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 Capability Actions, but we are putting it here for demonstration purposes.\n *\n * You will need ot 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 .ThenSet({\n spec: {\n message: "Hello Pepr now with type data!",\n counter: Math.random(),\n },\n });\n';
974
- 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" }, version: "0.7.0", main: "dist/lib.js", types: "dist/lib.d.ts", scripts: { prebuild: "rm -fr dist/* && node hack/build-template-data.js", build: "tsc && node build.mjs", test: "npm run test:unit && npm run test:e2e", "test:unit": "npm run build && tsc -p tsconfig.tests.json && ava dist/**/*.test.js", "test:e2e": "npm run test:e2e:k3d && npm run test:e2e:build && npm run test:e2e:image && npm run test:e2e:run", "test:e2e:k3d": "k3d cluster delete pepr-dev && k3d cluster create pepr-dev --k3s-arg '--debug@server:0'", "test:e2e:build": "npm run build && npm pack && npm uninstall pepr -g && npm install -g pepr-0.0.0-development.tgz && pepr", "test:e2e:image": "docker buildx build --tag pepr:dev . && k3d image import pepr:dev -c pepr-dev", "test:e2e:run": "ava hack/e2e.test.mjs --sequential --timeout=2m", "format:check": "eslint src && prettier src --check", "format:fix": "eslint src --fix && prettier src --write" }, dependencies: { "@kubernetes/client-node": "0.18.1", express: "4.18.2", "fast-json-patch": "3.1.1", "http-status-codes": "2.2.0", "node-fetch": "2.6.11", ramda: "0.29.0" }, devDependencies: { "@types/eslint": "8.40.0", "@types/express": "4.17.17", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.2", "@types/prettier": "2.7.3", "@types/prompts": "2.4.4", "@types/ramda": "0.29.2", "@types/uuid": "9.0.1", ava: "5.3.0", nock: "13.3.1" }, peerDependencies: { "@typescript-eslint/eslint-plugin": "5.59.7", "@typescript-eslint/parser": "5.59.7", commander: "10.0.1", esbuild: "0.17.19", eslint: "8.41.0", "node-forge": "1.3.1", prettier: "2.8.8", prompts: "2.4.2", typescript: "5.0.4", uuid: "9.0.0" }, ava: { failFast: true, verbose: true } };
966
+ var helloPeprTS = 'import {\n Capability,\n PeprRequest,\n RegisterKind,\n a,\n fetch,\n fetchStatus,\n} from "pepr";\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 Capability Action\nconst { When } = HelloPepr;\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (Namespace) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This Capability 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 .Then(ns => ns.RemoveLabel("remove-me"));\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (CM Example 1) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This is a single Capability 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 .Then(request =>\n request\n .SetLabel("pepr", "was-here")\n .SetAnnotation("pepr.dev", "annotations-work-too")\n );\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (CM Example 2) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This Capability Action does the exact same changes for example-2, except this time it uses\n * the `.ThenSet()` feature. You can stack multiple `.Then()` calls, but only a single `.ThenSet()`\n */\nWhen(a.ConfigMap)\n .IsCreated()\n .WithName("example-2")\n .ThenSet({\n metadata: {\n labels: {\n pepr: "was-here",\n },\n annotations: {\n "pepr.dev": "annotations-work-too",\n },\n },\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (CM Example 3) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This Capability 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 .Then(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/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (CM Example 4) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This Capability Action show how you can use the `Then()` function to make multiple changes to the\n * same object from different functions. This is useful if you want to keep your Capability Actions\n * small and focused on a single task, or if you want to reuse the same function in multiple\n * Capability Actions.\n *\n * Note that the order of the `.Then()` calls matters. The first call will be executed first,\n * then the second, and so on. Also note the functions are not called until the Capability Action\n * is triggered.\n */\nWhen(a.ConfigMap)\n .IsCreated()\n .WithName("example-4")\n .Then(cm => cm.SetLabel("pepr.dev/first", "true"))\n .Then(addSecond)\n .Then(addThird);\n\n//This function uses the complete type definition, but is not required.\nfunction addSecond(cm: PeprRequest<a.ConfigMap>) {\n cm.SetLabel("pepr.dev/second", "true");\n}\n\n// This function has no type definition, so you won\'t have intellisense in the function body.\nfunction addThird(cm) {\n cm.SetLabel("pepr.dev/third", "true");\n}\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY 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 .Then(cm => cm.SetLabel("pepr.dev/first", "true"))\n .Then(addSecond)\n .Then(addThird);\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY ACTION (CM Example 5) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This Capability 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 Capability 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://api.chucknorris.io/jokes/random?category=dev");\n * const joke = await fetch("https://api.chucknorris.io/jokes/random?category=dev") as TheChuckNorrisJoke;\n * ```\n *\n * Alternatively, you can drop the type completely:\n *\n * ```ts\n * fetch("https://api.chucknorris.io/jokes/random?category=dev")\n * ```\n */\ninterface TheChuckNorrisJoke {\n icon_url: string;\n id: string;\n url: string;\n value: string;\n}\n\nWhen(a.ConfigMap)\n .IsCreated()\n .WithLabel("chuck-norris")\n .Then(async change => {\n // Try/catch is not needed as a response object will always be returned\n const response = await fetch<TheChuckNorrisJoke>(\n "https://api.chucknorris.io/jokes/random?category=dev"\n );\n\n // Instead, check the `response.ok` field\n if (response.ok) {\n // Add the Chuck Norris joke to the configmap\n change.Raw.data["chuck-says"] = response.data.value;\n return;\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 * CAPABILITY 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 Capability Action is executed.\n */\nWhen(a.Secret)\n .IsCreated()\n .WithName("secret-1")\n .Then(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 * CAPABILITY 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 Capability 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 ot 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 .ThenSet({\n spec: {\n message: "Hello Pepr without type data!",\n counter: Math.random(),\n },\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * CAPABILITY 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 Capability 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 Capability Actions, but we are putting it here for demonstration purposes.\n *\n * You will need ot 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 .ThenSet({\n spec: {\n message: "Hello Pepr now with type data!",\n counter: Math.random(),\n },\n });\n';
967
+ 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" }, version: "0.8.0", main: "dist/lib.js", types: "dist/lib.d.ts", scripts: { prebuild: "rm -fr dist/* && node hack/build-template-data.js", build: "tsc && node build.mjs", test: "npm run test:unit && npm run test:e2e", "test:unit": "npm run build && tsc -p tsconfig.tests.json && ava dist/**/*.test.js", "test:e2e": "npm run test:e2e:k3d && npm run test:e2e:build && npm run test:e2e:image && npm run test:e2e:run", "test:e2e:k3d": "k3d cluster delete pepr-dev && k3d cluster create pepr-dev --k3s-arg '--debug@server:0'", "test:e2e:build": "npm run build && npm pack && npm uninstall pepr -g && npm install -g pepr-0.0.0-development.tgz && pepr", "test:e2e:image": "docker buildx build --tag pepr:dev . && k3d image import pepr:dev -c pepr-dev", "test:e2e:run": "ava hack/e2e.test.mjs --sequential --timeout=2m", "format:check": "eslint src && prettier src --check", "format:fix": "eslint src --fix && prettier src --write" }, dependencies: { "@kubernetes/client-node": "0.18.1", express: "4.18.2", "fast-json-patch": "3.1.1", "http-status-codes": "2.2.0", "node-fetch": "2.6.11", ramda: "0.29.0" }, devDependencies: { "@types/eslint": "8.40.2", "@types/express": "4.17.17", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.2", "@types/prettier": "2.7.3", "@types/prompts": "2.4.4", "@types/ramda": "0.29.2", "@types/uuid": "9.0.2", ava: "5.3.0", nock: "13.3.1" }, peerDependencies: { "@typescript-eslint/eslint-plugin": "5.59.7", "@typescript-eslint/parser": "5.59.7", commander: "10.0.1", esbuild: "0.17.19", eslint: "8.41.0", "node-forge": "1.3.1", prettier: "2.8.8", prompts: "2.4.2", typescript: "5.0.4", uuid: "9.0.0" }, ava: { failFast: true, verbose: true } };
975
968
 
976
969
  // src/cli/init/templates/pepr.code-snippets.json
977
970
  var pepr_code_snippets_default = {
@@ -1054,6 +1047,9 @@ function genPkgJSON(opts, pgkVerOverride) {
1054
1047
  version: "0.0.1",
1055
1048
  description: opts.description,
1056
1049
  keywords: ["pepr", "k8s", "policy-engine", "pepr-module", "security"],
1050
+ engines: {
1051
+ node: ">=18.0.0"
1052
+ },
1057
1053
  pepr: {
1058
1054
  name: opts.name.trim(),
1059
1055
  uuid: pgkVerOverride ? "static-test" : uuid,
@@ -1064,8 +1060,7 @@ function genPkgJSON(opts, pgkVerOverride) {
1064
1060
  }
1065
1061
  },
1066
1062
  scripts: {
1067
- "k3d-setup": scripts["test:e2e:k3d"],
1068
- start: "pepr dev"
1063
+ "k3d-setup": scripts["test:e2e:k3d"]
1069
1064
  },
1070
1065
  dependencies: {
1071
1066
  pepr: pgkVerOverride || version
@@ -1309,6 +1304,7 @@ function dev_default(program2) {
1309
1304
  env: {
1310
1305
  ...process.env,
1311
1306
  LOG_LEVEL: "debug",
1307
+ PEPR_API_TOKEN: webhook.apiToken,
1312
1308
  SSL_KEY_PATH: "insecure-tls.key",
1313
1309
  SSL_CERT_PATH: "insecure-tls.crt"
1314
1310
  }
@@ -1573,12 +1569,41 @@ function update_default(program2) {
1573
1569
  });
1574
1570
  }
1575
1571
 
1572
+ // src/lib.ts
1573
+ var import_client_node4 = __toESM(require("@kubernetes/client-node"));
1574
+ var import_http_status_codes2 = require("http-status-codes");
1575
+ var utils = __toESM(require("ramda"));
1576
+
1577
+ // src/lib/k8s/upstream.ts
1578
+ var import_client_node3 = require("@kubernetes/client-node");
1579
+
1580
+ // src/lib/fetch.ts
1581
+ var import_http_status_codes = require("http-status-codes");
1582
+ var import_node_fetch = __toESM(require("node-fetch"));
1583
+
1584
+ // src/lib/module.ts
1585
+ var import_ramda3 = require("ramda");
1586
+
1587
+ // src/lib/controller.ts
1588
+ var import_express = __toESM(require("express"));
1589
+
1590
+ // src/lib/processor.ts
1591
+ var import_fast_json_patch = __toESM(require("fast-json-patch"));
1592
+
1593
+ // src/lib/request.ts
1594
+ var import_ramda2 = require("ramda");
1595
+
1576
1596
  // src/cli.ts
1577
1597
  var program = new RootCmd();
1578
1598
  program.version(version).description(`Pepr Kubernetes Thingy (v${version})`).action(() => {
1579
1599
  if (program.args.length < 1) {
1580
1600
  console.log(banner);
1581
1601
  program.help();
1602
+ } else {
1603
+ logger_default.error(`Invalid command '${program.args.join(" ")}'
1604
+ `);
1605
+ program.outputHelp();
1606
+ process.exitCode = 1;
1582
1607
  }
1583
1608
  });
1584
1609
  init_default(program);
@@ -116,7 +116,7 @@ if (process.env.LOG_LEVEL) {
116
116
  var logger_default = Log;
117
117
 
118
118
  // src/cli/init/templates/data.json
119
- 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" }, version: "0.7.0", main: "dist/lib.js", types: "dist/lib.d.ts", scripts: { prebuild: "rm -fr dist/* && node hack/build-template-data.js", build: "tsc && node build.mjs", test: "npm run test:unit && npm run test:e2e", "test:unit": "npm run build && tsc -p tsconfig.tests.json && ava dist/**/*.test.js", "test:e2e": "npm run test:e2e:k3d && npm run test:e2e:build && npm run test:e2e:image && npm run test:e2e:run", "test:e2e:k3d": "k3d cluster delete pepr-dev && k3d cluster create pepr-dev --k3s-arg '--debug@server:0'", "test:e2e:build": "npm run build && npm pack && npm uninstall pepr -g && npm install -g pepr-0.0.0-development.tgz && pepr", "test:e2e:image": "docker buildx build --tag pepr:dev . && k3d image import pepr:dev -c pepr-dev", "test:e2e:run": "ava hack/e2e.test.mjs --sequential --timeout=2m", "format:check": "eslint src && prettier src --check", "format:fix": "eslint src --fix && prettier src --write" }, dependencies: { "@kubernetes/client-node": "0.18.1", express: "4.18.2", "fast-json-patch": "3.1.1", "http-status-codes": "2.2.0", "node-fetch": "2.6.11", ramda: "0.29.0" }, devDependencies: { "@types/eslint": "8.40.0", "@types/express": "4.17.17", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.2", "@types/prettier": "2.7.3", "@types/prompts": "2.4.4", "@types/ramda": "0.29.2", "@types/uuid": "9.0.1", ava: "5.3.0", nock: "13.3.1" }, peerDependencies: { "@typescript-eslint/eslint-plugin": "5.59.7", "@typescript-eslint/parser": "5.59.7", commander: "10.0.1", esbuild: "0.17.19", eslint: "8.41.0", "node-forge": "1.3.1", prettier: "2.8.8", prompts: "2.4.2", typescript: "5.0.4", uuid: "9.0.0" }, ava: { failFast: true, verbose: true } };
119
+ 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" }, version: "0.8.0", main: "dist/lib.js", types: "dist/lib.d.ts", scripts: { prebuild: "rm -fr dist/* && node hack/build-template-data.js", build: "tsc && node build.mjs", test: "npm run test:unit && npm run test:e2e", "test:unit": "npm run build && tsc -p tsconfig.tests.json && ava dist/**/*.test.js", "test:e2e": "npm run test:e2e:k3d && npm run test:e2e:build && npm run test:e2e:image && npm run test:e2e:run", "test:e2e:k3d": "k3d cluster delete pepr-dev && k3d cluster create pepr-dev --k3s-arg '--debug@server:0'", "test:e2e:build": "npm run build && npm pack && npm uninstall pepr -g && npm install -g pepr-0.0.0-development.tgz && pepr", "test:e2e:image": "docker buildx build --tag pepr:dev . && k3d image import pepr:dev -c pepr-dev", "test:e2e:run": "ava hack/e2e.test.mjs --sequential --timeout=2m", "format:check": "eslint src && prettier src --check", "format:fix": "eslint src --fix && prettier src --write" }, dependencies: { "@kubernetes/client-node": "0.18.1", express: "4.18.2", "fast-json-patch": "3.1.1", "http-status-codes": "2.2.0", "node-fetch": "2.6.11", ramda: "0.29.0" }, devDependencies: { "@types/eslint": "8.40.2", "@types/express": "4.17.17", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.2", "@types/prettier": "2.7.3", "@types/prompts": "2.4.4", "@types/ramda": "0.29.2", "@types/uuid": "9.0.2", ava: "5.3.0", nock: "13.3.1" }, peerDependencies: { "@typescript-eslint/eslint-plugin": "5.59.7", "@typescript-eslint/parser": "5.59.7", commander: "10.0.1", esbuild: "0.17.19", eslint: "8.41.0", "node-forge": "1.3.1", prettier: "2.8.8", prompts: "2.4.2", typescript: "5.0.4", uuid: "9.0.0" }, ava: { failFast: true, verbose: true } };
120
120
 
121
121
  // src/runtime/controller.ts
122
122
  var { version } = packageJSON;
@@ -8,6 +8,7 @@ export declare class Controller {
8
8
  private readonly afterHook?;
9
9
  private readonly app;
10
10
  private running;
11
+ private token;
11
12
  constructor(config: ModuleConfig, capabilities: Capability[], beforeHook?: ((req: Request) => void) | undefined, afterHook?: ((res: Response) => void) | undefined);
12
13
  /** Start the webhook server */
13
14
  startServer: (port: number) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/lib/controller.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAGvC,qBAAa,UAAU;IAKnB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IAC5B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;IAP7B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAa;IACjC,OAAO,CAAC,OAAO,CAAS;gBAGL,MAAM,EAAE,YAAY,EACpB,YAAY,EAAE,UAAU,EAAE,EAC1B,UAAU,CAAC,SAAQ,OAAO,KAAK,IAAI,aAAA,EACnC,SAAS,CAAC,SAAQ,QAAQ,KAAK,IAAI,aAAA;IAuBtD,+BAA+B;IACxB,WAAW,SAAU,MAAM,UA0ChC;IAEF,OAAO,CAAC,MAAM,CAYZ;IAEF,OAAO,CAAC,OAAO,CAOb;IAEF,OAAO,CAAC,MAAM,CAiCZ;CACH"}
1
+ {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/lib/controller.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGhD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,qBAAa,UAAU;IAQnB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IAC5B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;IAV7B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAa;IACjC,OAAO,CAAC,OAAO,CAAS;IAGxB,OAAO,CAAC,KAAK,CAAM;gBAGA,MAAM,EAAE,YAAY,EACpB,YAAY,EAAE,UAAU,EAAE,EAC1B,UAAU,CAAC,SAAQ,OAAO,KAAK,IAAI,aAAA,EACnC,SAAS,CAAC,SAAQ,QAAQ,KAAK,IAAI,aAAA;IAuBtD,+BAA+B;IACxB,WAAW,SAAU,MAAM,UAkDhC;IAEF,OAAO,CAAC,MAAM,CAYZ;IAEF,OAAO,CAAC,OAAO,CAOb;IAEF,OAAO,CAAC,MAAM,CA0CZ;CACH"}
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import { V1ClusterRole, V1ClusterRoleBinding, V1Deployment, V1MutatingWebhookConfiguration, V1Namespace, V1NetworkPolicy, V1RuleWithOperations, V1Secret, V1Service, V1ServiceAccount } from "@kubernetes/client-node";
2
+ import { V1ClusterRole, V1ClusterRoleBinding, V1Deployment, V1MutatingWebhookConfiguration, V1Namespace, V1RuleWithOperations, V1Secret, V1Service, V1ServiceAccount } from "@kubernetes/client-node";
3
3
  import { ModuleConfig } from "../types";
4
4
  import { TLSOut } from "./tls";
5
5
  export declare class Webhook {
@@ -7,8 +7,10 @@ export declare class Webhook {
7
7
  private readonly host?;
8
8
  private name;
9
9
  private _tls;
10
+ private _apiToken;
10
11
  image: string;
11
12
  get tls(): TLSOut;
13
+ get apiToken(): string;
12
14
  constructor(config: ModuleConfig, host?: string | undefined);
13
15
  /** Generate the pepr-system namespace */
14
16
  namespace(): V1Namespace;
@@ -21,12 +23,11 @@ export declare class Webhook {
21
23
  clusterRole(): V1ClusterRole;
22
24
  clusterRoleBinding(): V1ClusterRoleBinding;
23
25
  serviceAccount(): V1ServiceAccount;
26
+ apiTokenSecret(): V1Secret;
24
27
  tlsSecret(): V1Secret;
25
28
  generateWebhookRules(path: string): Promise<V1RuleWithOperations[]>;
26
29
  mutatingWebhook(path: string, timeoutSeconds?: number): Promise<V1MutatingWebhookConfiguration>;
27
30
  deployment(hash: string): V1Deployment;
28
- /** Only permit the kube-system ns ingress access to the controller */
29
- networkPolicy(): V1NetworkPolicy;
30
31
  service(): V1Service;
31
32
  moduleSecret(data: Buffer, hash: string): V1Secret;
32
33
  zarfYaml(path: string): string;
@@ -1 +1 @@
1
- {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../../src/lib/k8s/webhook.ts"],"names":[],"mappings":";AAGA,OAAO,EASL,aAAa,EACb,oBAAoB,EACpB,YAAY,EAEZ,8BAA8B,EAC9B,WAAW,EACX,eAAe,EACf,oBAAoB,EACpB,QAAQ,EACR,SAAS,EACT,gBAAgB,EAEjB,MAAM,yBAAyB,CAAC;AAQjC,OAAO,EAA6B,YAAY,EAAE,MAAM,UAAU,CAAC;AACnE,OAAO,EAAE,MAAM,EAAU,MAAM,OAAO,CAAC;AAQvC,qBAAa,OAAO;IAUN,OAAO,CAAC,QAAQ,CAAC,MAAM;IAAgB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;IATzE,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,IAAI,CAAS;IAEd,KAAK,EAAE,MAAM,CAAC;IAErB,IAAW,GAAG,IAAI,MAAM,CAEvB;gBAE4B,MAAM,EAAE,YAAY,EAAmB,IAAI,CAAC,oBAAQ;IASjF,yCAAyC;IACzC,SAAS,IAAI,WAAW;IAQxB;;;;;OAKG;IACH,WAAW,IAAI,aAAa;IAgB5B,kBAAkB,IAAI,oBAAoB;IAqB1C,cAAc,IAAI,gBAAgB;IAWlC,SAAS,IAAI,QAAQ;IAgBrB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAwF7D,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,SAAK,GAAG,OAAO,CAAC,8BAA8B,CAAC;IAyDjG,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY;IAyFtC,sEAAsE;IACtE,aAAa,IAAI,eAAe;IAsChC,OAAO,IAAI,SAAS;IAsBpB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ;IAkBlD,QAAQ,CAAC,IAAI,EAAE,MAAM;IA2Bf,OAAO,CAAC,IAAI,EAAE,MAAM;IAyBpB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM;CA6InD"}
1
+ {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../../src/lib/k8s/webhook.ts"],"names":[],"mappings":";AAGA,OAAO,EAQL,aAAa,EACb,oBAAoB,EACpB,YAAY,EAEZ,8BAA8B,EAC9B,WAAW,EACX,oBAAoB,EACpB,QAAQ,EACR,SAAS,EACT,gBAAgB,EAEjB,MAAM,yBAAyB,CAAC;AAQjC,OAAO,EAA6B,YAAY,EAAE,MAAM,UAAU,CAAC;AACnE,OAAO,EAAE,MAAM,EAAU,MAAM,OAAO,CAAC;AAQvC,qBAAa,OAAO;IAeN,OAAO,CAAC,QAAQ,CAAC,MAAM;IAAgB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;IAdzE,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,SAAS,CAAS;IAEnB,KAAK,EAAE,MAAM,CAAC;IAErB,IAAW,GAAG,IAAI,MAAM,CAEvB;IAED,IAAW,QAAQ,IAAI,MAAM,CAE5B;gBAE4B,MAAM,EAAE,YAAY,EAAmB,IAAI,CAAC,oBAAQ;IAYjF,yCAAyC;IACzC,SAAS,IAAI,WAAW;IAQxB;;;;;OAKG;IACH,WAAW,IAAI,aAAa;IAgB5B,kBAAkB,IAAI,oBAAoB;IAqB1C,cAAc,IAAI,gBAAgB;IAWlC,cAAc,IAAI,QAAQ;IAe1B,SAAS,IAAI,QAAQ;IAgBrB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAwF7D,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,SAAK,GAAG,OAAO,CAAC,8BAA8B,CAAC;IA4DjG,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY;IAoGtC,OAAO,IAAI,SAAS;IAsBpB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ;IAkBlD,QAAQ,CAAC,IAAI,EAAE,MAAM;IA2Bf,OAAO,CAAC,IAAI,EAAE,MAAM;IAyBpB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM;CA6InD"}
package/dist/lib.js CHANGED
@@ -1072,7 +1072,7 @@ var Controller = class {
1072
1072
  this.app.use(this.logger);
1073
1073
  this.app.use(import_express.default.json({ limit: "2mb" }));
1074
1074
  this.app.get("/healthz", this.healthz);
1075
- this.app.post("/mutate", this.mutate);
1075
+ this.app.post("/mutate/:token", this.mutate);
1076
1076
  if (beforeHook) {
1077
1077
  console.info(`Using beforeHook: ${beforeHook}`);
1078
1078
  }
@@ -1082,6 +1082,8 @@ var Controller = class {
1082
1082
  }
1083
1083
  app = (0, import_express.default)();
1084
1084
  running = false;
1085
+ // The token used to authenticate requests
1086
+ token = "";
1085
1087
  /** Start the webhook server */
1086
1088
  startServer = (port) => {
1087
1089
  if (this.running) {
@@ -1091,6 +1093,11 @@ var Controller = class {
1091
1093
  key: import_fs.default.readFileSync(process.env.SSL_KEY_PATH || "/etc/certs/tls.key"),
1092
1094
  cert: import_fs.default.readFileSync(process.env.SSL_CERT_PATH || "/etc/certs/tls.crt")
1093
1095
  };
1096
+ this.token = process.env.PEPR_API_TOKEN || import_fs.default.readFileSync("/app/api-token/value").toString().trim();
1097
+ console.info(`Using API token: ${this.token}`);
1098
+ if (!this.token) {
1099
+ throw new Error("API token not found");
1100
+ }
1094
1101
  const server = import_https.default.createServer(options, this.app).listen(port);
1095
1102
  server.on("listening", () => {
1096
1103
  console.log(`Server listening on port ${port}`);
@@ -1136,6 +1143,13 @@ var Controller = class {
1136
1143
  };
1137
1144
  mutate = async (req, res) => {
1138
1145
  try {
1146
+ const { token } = req.params;
1147
+ if (token !== this.token) {
1148
+ const err = `Unauthorized: invalid token '${token.replace(/[^\w]/g, "_")}'`;
1149
+ console.warn(err);
1150
+ res.status(401).send(err);
1151
+ return;
1152
+ }
1139
1153
  const request = req.body?.request || {};
1140
1154
  this.beforeHook && this.beforeHook(request || {});
1141
1155
  const name = request?.name ? `/${request.name}` : "";
package/dist/lib.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/lib.ts", "../src/lib/k8s/upstream.ts", "../src/lib/k8s/types.ts", "../src/lib/k8s/kinds.ts", "../src/lib/logger.ts", "../src/lib/capability.ts", "../src/lib/fetch.ts", "../src/lib/module.ts", "../src/lib/controller.ts", "../src/lib/processor.ts", "../src/lib/filter.ts", "../src/lib/request.ts", "../src/lib/utils.ts"],
4
- "sourcesContent": ["import k8s from \"@kubernetes/client-node\";\nimport { StatusCodes as fetchStatus } from \"http-status-codes\";\nimport * as utils from \"ramda\";\nimport { Capability } from \"./lib/capability\";\nimport { fetch, fetchRaw } from \"./lib/fetch\";\nimport { RegisterKind, a } from \"./lib/k8s/index\";\nimport Log from \"./lib/logger\";\nimport { PeprModule } from \"./lib/module\";\nimport { PeprRequest } from \"./lib/request\";\nimport * as PeprUtils from \"./lib/utils\";\n\n// Import type information for external packages\nimport type * as KubernetesClientNode from \"@kubernetes/client-node\";\nimport type * as RamdaUtils from \"ramda\";\n\nexport {\n a,\n /** PeprModule is used to setup a complete Pepr Module: `new PeprModule(cfg, {...capabilities})` */\n PeprModule,\n PeprRequest,\n PeprUtils,\n RegisterKind,\n Capability,\n Log,\n utils,\n fetch,\n fetchRaw,\n fetchStatus,\n k8s,\n\n // Export the imported type information for external packages\n RamdaUtils,\n KubernetesClientNode,\n};\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\n/** a is a collection of K8s types to be used within a CapabilityAction: `When(a.Configmap)` */\nexport {\n V1APIService as APIService,\n V1CertificateSigningRequest as CertificateSigningRequest,\n V1ConfigMap as ConfigMap,\n V1ControllerRevision as ControllerRevision,\n V1CronJob as CronJob,\n V1CSIDriver as CSIDriver,\n V1CSIStorageCapacity as CSIStorageCapacity,\n V1CustomResourceDefinition as CustomResourceDefinition,\n V1DaemonSet as DaemonSet,\n V1Deployment as Deployment,\n V1EndpointSlice as EndpointSlice,\n V1HorizontalPodAutoscaler as HorizontalPodAutoscaler,\n V1Ingress as Ingress,\n V1IngressClass as IngressClass,\n V1Job as Job,\n V1LimitRange as LimitRange,\n V1LocalSubjectAccessReview as LocalSubjectAccessReview,\n V1MutatingWebhookConfiguration as MutatingWebhookConfiguration,\n V1Namespace as Namespace,\n V1NetworkPolicy as NetworkPolicy,\n V1Node as Node,\n V1PersistentVolume as PersistentVolume,\n V1PersistentVolumeClaim as PersistentVolumeClaim,\n V1Pod as Pod,\n V1PodDisruptionBudget as PodDisruptionBudget,\n V1PodTemplate as PodTemplate,\n V1ReplicaSet as ReplicaSet,\n V1ReplicationController as ReplicationController,\n V1ResourceQuota as ResourceQuota,\n V1RuntimeClass as RuntimeClass,\n V1Secret as Secret,\n V1SelfSubjectAccessReview as SelfSubjectAccessReview,\n V1SelfSubjectRulesReview as SelfSubjectRulesReview,\n V1Service as Service,\n V1ServiceAccount as ServiceAccount,\n V1StatefulSet as StatefulSet,\n V1StorageClass as StorageClass,\n V1SubjectAccessReview as SubjectAccessReview,\n V1TokenReview as TokenReview,\n V1ValidatingWebhookConfiguration as ValidatingWebhookConfiguration,\n V1VolumeAttachment as VolumeAttachment,\n} from \"@kubernetes/client-node\";\n\nexport { GenericKind } from \"./types\";\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { V1ListMeta, V1ObjectMeta } from \"@kubernetes/client-node\";\n\nexport enum Operation {\n CREATE = \"CREATE\",\n UPDATE = \"UPDATE\",\n DELETE = \"DELETE\",\n CONNECT = \"CONNECT\",\n}\n\nexport interface KubernetesObject {\n apiVersion?: string;\n kind?: string;\n metadata?: V1ObjectMeta;\n}\nexport interface KubernetesListObject<T extends KubernetesObject> {\n apiVersion?: string;\n kind?: string;\n metadata?: V1ListMeta;\n items: T[];\n}\n\n/**\n * GenericKind is a generic Kubernetes object that can be used to represent any Kubernetes object\n * that is not explicitly supported by Pepr. This can be used on its own or as a base class for\n * other types. See the examples in `HelloPepr.ts` for more information.\n */\nexport class GenericKind {\n apiVersion?: string;\n kind?: string;\n metadata?: V1ObjectMeta;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [key: string]: any;\n}\n\n/**\n * GroupVersionKind unambiguously identifies a kind. It doesn't anonymously include GroupVersion\n * to avoid automatic coercion. It doesn't use a GroupVersion to avoid custom marshalling\n **/\nexport interface GroupVersionKind {\n /** The K8s resource kind, e..g \"Pod\". */\n readonly kind: string;\n readonly group: string;\n readonly version?: string;\n /** Optional, override the plural name for use in Webhook rules generation */\n readonly plural?: string;\n}\n\n/**\n * GroupVersionResource unambiguously identifies a resource. It doesn't anonymously include GroupVersion\n * to avoid automatic coercion. It doesn't use a GroupVersion to avoid custom marshalling\n */\nexport interface GroupVersionResource {\n readonly group: string;\n readonly version: string;\n readonly resource: string;\n}\n\n/**\n * A Kubernetes admission request to be processed by a capability.\n */\nexport interface Request<T = KubernetesObject> {\n /** UID is an identifier for the individual request/response. */\n readonly uid: string;\n\n /** Kind is the fully-qualified type of object being submitted (for example, v1.Pod or autoscaling.v1.Scale) */\n readonly kind: GroupVersionKind;\n\n /** Resource is the fully-qualified resource being requested (for example, v1.pods) */\n readonly resource: GroupVersionResource;\n\n /** SubResource is the sub-resource being requested, if any (for example, \"status\" or \"scale\") */\n readonly subResource?: string;\n\n /** RequestKind is the fully-qualified type of the original API request (for example, v1.Pod or autoscaling.v1.Scale). */\n readonly requestKind?: GroupVersionKind;\n\n /** RequestResource is the fully-qualified resource of the original API request (for example, v1.pods). */\n readonly requestResource?: GroupVersionResource;\n\n /** RequestSubResource is the sub-resource of the original API request, if any (for example, \"status\" or \"scale\"). */\n readonly requestSubResource?: string;\n\n /**\n * Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and\n * rely on the server to generate the name. If that is the case, this method will return the empty string.\n */\n readonly name: string;\n\n /** Namespace is the namespace associated with the request (if any). */\n readonly namespace?: string;\n\n /**\n * Operation is the operation being performed. This may be different than the operation\n * requested. e.g. a patch can result in either a CREATE or UPDATE Operation.\n */\n readonly operation: Operation;\n\n /** UserInfo is information about the requesting user */\n readonly userInfo: {\n /** The name that uniquely identifies this user among all active users. */\n username?: string;\n\n /**\n * A unique value that identifies this user across time. If this user is deleted\n * and another user by the same name is added, they will have different UIDs.\n */\n uid?: string;\n\n /** The names of groups this user is a part of. */\n groups?: string[];\n\n /** Any additional information provided by the authenticator. */\n extra?: {\n [key: string]: string[];\n };\n };\n\n /** Object is the object from the incoming request prior to default values being applied */\n readonly object: T;\n\n /** OldObject is the existing object. Only populated for UPDATE requests. */\n readonly oldObject?: T;\n\n /** DryRun indicates that modifications will definitely not be persisted for this request. Defaults to false. */\n readonly dryRun?: boolean;\n\n /**\n * Options contains the options for the operation being performed.\n * e.g. `meta.k8s.io/v1.DeleteOptions` or `meta.k8s.io/v1.CreateOptions`. This may be\n * different than the options the caller provided. e.g. for a patch request the performed\n * Operation might be a CREATE, in which case the Options will a\n * `meta.k8s.io/v1.CreateOptions` even though the caller provided `meta.k8s.io/v1.PatchOptions`.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly options?: any;\n}\n\nexport interface Response {\n /** UID is an identifier for the individual request/response. This must be copied over from the corresponding AdmissionRequest. */\n uid: string;\n\n /** Allowed indicates whether or not the admission request was permitted. */\n allowed: boolean;\n\n /** Result contains extra details into why an admission request was denied. This field IS NOT consulted in any way if \"Allowed\" is \"true\". */\n result?: string;\n\n /** The patch body. Currently we only support \"JSONPatch\" which implements RFC 6902. */\n patch?: string;\n\n /** The type of Patch. Currently we only allow \"JSONPatch\". */\n patchType?: \"JSONPatch\";\n\n /** AuditAnnotations is an unstructured key value map set by remote admission controller (e.g. error=image-blacklisted). */\n auditAnnotations?: {\n [key: string]: string;\n };\n\n /** warnings is a list of warning messages to return to the requesting API client. */\n warnings?: string[];\n}\n\nexport type WebhookIgnore = {\n /**\n * List of Kubernetes namespaces to always ignore.\n * Any resources in these namespaces will be ignored by Pepr.\n *\n * Note: `kube-system` and `pepr-system` are always ignored.\n */\n namespaces?: string[];\n /**\n * List of Kubernetes labels to always ignore.\n * Any resources with these labels will be ignored by Pepr.\n *\n * The example below will ignore any resources with the label `my-label=ulta-secret`:\n * ```\n * alwaysIgnore:\n * labels: [{ \"my-label\": \"ultra-secret\" }]\n * ```\n */\n labels?: Record<string, string>[];\n};\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { GenericClass } from \"../types\";\nimport { GroupVersionKind } from \"./types\";\n\nexport const gvkMap: Record<string, GroupVersionKind> = {\n /**\n * Represents a K8s ConfigMap resource.\n * ConfigMap holds configuration data for pods to consume.\n * @see {@link https://kubernetes.io/docs/concepts/configuration/configmap/}\n */\n V1ConfigMap: {\n kind: \"ConfigMap\",\n version: \"v1\",\n group: \"\",\n },\n\n /**\n * Represents a K8s Endpoints resource.\n * Endpoints expose a service's IP addresses and ports to other resources.\n * @see {@link https://kubernetes.io/docs/concepts/services-networking/service/#endpoints}\n */\n V1Endpoint: {\n kind: \"Endpoints\",\n version: \"v1\",\n group: \"\",\n plural: \"endpoints\",\n },\n\n /**\n * Represents a K8s LimitRange resource.\n * LimitRange enforces constraints on the resource consumption of objects in a namespace.\n * @see {@link https://kubernetes.io/docs/concepts/policy/limit-range/}\n */\n V1LimitRange: {\n kind: \"LimitRange\",\n version: \"v1\",\n group: \"\",\n },\n\n /**\n * Represents a K8s Namespace resource.\n * Namespace is a way to divide cluster resources between multiple users.\n * @see {@link https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/}\n */\n V1Namespace: {\n kind: \"Namespace\",\n version: \"v1\",\n group: \"\",\n },\n\n /**\n * Represents a K8s Node resource.\n * Node is a worker machine in Kubernetes.\n * @see {@link https://kubernetes.io/docs/concepts/architecture/nodes/}\n */\n V1Node: {\n kind: \"Node\",\n version: \"v1\",\n group: \"\",\n },\n\n /**\n * Represents a K8s PersistentVolumeClaim resource.\n * PersistentVolumeClaim is a user's request for and claim to a persistent volume.\n * @see {@link https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistentvolumeclaims}\n */\n V1PersistentVolumeClaim: {\n kind: \"PersistentVolumeClaim\",\n version: \"v1\",\n group: \"\",\n },\n\n /**\n * Represents a K8s PersistentVolume resource.\n * PersistentVolume is a piece of storage in the cluster that has been provisioned by an administrator.\n * @see {@link https://kubernetes.io/docs/concepts/storage/persistent-volumes/}\n */\n V1PersistentVolume: {\n kind: \"PersistentVolume\",\n version: \"v1\",\n group: \"\",\n },\n\n /**\n * Represents a K8s Pod resource.\n * Pod is the smallest and simplest unit in the Kubernetes object model.\n * @see {@link https://kubernetes.io/docs/concepts/workloads/pods/}\n */\n V1Pod: {\n kind: \"Pod\",\n version: \"v1\",\n group: \"\",\n },\n /**\n * Represents a K8s PodTemplate resource.\n * PodTemplate is an object that describes the pod that will be created from a higher level abstraction.\n * @see {@link https://kubernetes.io/docs/concepts/workloads/controllers/#pod-template}\n */\n V1PodTemplate: {\n kind: \"PodTemplate\",\n version: \"v1\",\n group: \"\",\n },\n\n /**\n * Represents a K8s ReplicationController resource.\n * ReplicationController ensures that a specified number of pod replicas are running at any given time.\n * @see {@link https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/}\n */\n V1ReplicationController: {\n kind: \"ReplicationController\",\n version: \"v1\",\n group: \"\",\n },\n\n /**\n * Represents a K8s ResourceQuota resource.\n * ResourceQuota provides constraints that limit resource consumption per namespace.\n * @see {@link https://kubernetes.io/docs/concepts/policy/resource-quotas/}\n */\n V1ResourceQuota: {\n kind: \"ResourceQuota\",\n version: \"v1\",\n group: \"\",\n },\n\n /**\n * Represents a K8s Secret resource.\n * Secret holds secret data of a certain type.\n * @see {@link https://kubernetes.io/docs/concepts/configuration/secret/}\n */\n V1Secret: {\n kind: \"Secret\",\n version: \"v1\",\n group: \"\",\n },\n\n /**\n * Represents a K8s ServiceAccount resource.\n * ServiceAccount is an identity that processes in a pod can use to access the Kubernetes API.\n * @see {@link https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/}\n */\n V1ServiceAccount: {\n kind: \"ServiceAccount\",\n version: \"v1\",\n group: \"\",\n },\n\n /**\n * Represents a K8s Service resource.\n * Service is an abstraction which defines a logical set of Pods and a policy by which to access them.\n * @see {@link https://kubernetes.io/docs/concepts/services-networking/service/}\n */\n V1Service: {\n kind: \"Service\",\n version: \"v1\",\n group: \"\",\n },\n\n /**\n * Represents a K8s MutatingWebhookConfiguration resource.\n * MutatingWebhookConfiguration configures a mutating admission webhook.\n * @see {@link https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#configure-admission-webhooks-on-the-fly}\n */\n V1MutatingWebhookConfiguration: {\n kind: \"MutatingWebhookConfiguration\",\n version: \"v1\",\n group: \"admissionregistration.k8s.io\",\n },\n\n /**\n * Represents a K8s ValidatingWebhookConfiguration resource.\n * ValidatingWebhookConfiguration configures a validating admission webhook.\n * @see {@link https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#configure-admission-webhooks-on-the-fly}\n */\n V1ValidatingWebhookConfiguration: {\n kind: \"ValidatingWebhookConfiguration\",\n version: \"v1\",\n group: \"admissionregistration.k8s.io\",\n },\n /**\n * Represents a K8s CustomResourceDefinition resource.\n * CustomResourceDefinition is a custom resource in a Kubernetes cluster.\n * @see {@link https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/}\n */\n V1CustomResourceDefinition: {\n kind: \"CustomResourceDefinition\",\n version: \"v1\",\n group: \"apiextensions.k8s.io\",\n },\n\n /**\n * Represents a K8s APIService resource.\n * APIService represents a server for a particular API version and group.\n * @see {@link https://kubernetes.io/docs/tasks/access-kubernetes-api/setup-extension-api-server/}\n */\n V1APIService: {\n kind: \"APIService\",\n version: \"v1\",\n group: \"apiregistration.k8s.io\",\n },\n\n /**\n * Represents a K8s ControllerRevision resource.\n * ControllerRevision is used to manage the history of a StatefulSet or DaemonSet.\n * @see {@link https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#revision-history}\n */\n V1ControllerRevision: {\n kind: \"ControllerRevision\",\n version: \"v1\",\n group: \"apps\",\n },\n\n /**\n * Represents a K8s DaemonSet resource.\n * DaemonSet ensures that all (or some) nodes run a copy of a Pod.\n * @see {@link https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/}\n */\n V1DaemonSet: {\n kind: \"DaemonSet\",\n version: \"v1\",\n group: \"apps\",\n },\n\n /**\n * Represents a K8s Deployment resource.\n * Deployment provides declarative updates for Pods and ReplicaSets.\n * @see {@link https://kubernetes.io/docs/concepts/workloads/controllers/deployment/}\n */\n V1Deployment: {\n kind: \"Deployment\",\n version: \"v1\",\n group: \"apps\",\n },\n\n /**\n * Represents a K8s ReplicaSet resource.\n * ReplicaSet ensures that a specified number of pod replicas are running at any given time.\n * @see {@link https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/}\n */\n V1ReplicaSet: {\n kind: \"ReplicaSet\",\n version: \"v1\",\n group: \"apps\",\n },\n\n /**\n * Represents a K8s StatefulSet resource.\n * StatefulSet is used to manage stateful applications.\n * @see {@link https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/}\n */\n V1StatefulSet: {\n kind: \"StatefulSet\",\n version: \"v1\",\n group: \"apps\",\n },\n\n /**\n * Represents a K8s TokenReview resource.\n * TokenReview attempts to authenticate a token to a known user.\n * @see {@link https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#tokenreview-v1-authentication-k8s-io}\n */\n V1TokenReview: {\n kind: \"TokenReview\",\n version: \"v1\",\n group: \"authentication.k8s.io\",\n },\n\n /**\n * Represents a K8s LocalSubjectAccessReview resource.\n * LocalSubjectAccessReview checks whether a specific user can perform a specific action in a specific namespace.\n * @see {@link https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#localsubjectaccessreview-v1-authorization-k8s-io}\n */\n V1LocalSubjectAccessReview: {\n kind: \"LocalSubjectAccessReview\",\n version: \"v1\",\n group: \"authorization.k8s.io\",\n },\n\n /**\n * Represents a K8s SelfSubjectAccessReview resource.\n * SelfSubjectAccessReview checks whether the current user can perform a specific action.\n * @see {@link https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#selfsubjectaccessreview-v1-authorization-k8s-io}\n */\n V1SelfSubjectAccessReview: {\n kind: \"SelfSubjectAccessReview\",\n version: \"v1\",\n group: \"authorization.k8s.io\",\n },\n\n /**\n * Represents a K8s SelfSubjectRulesReview resource.\n * SelfSubjectRulesReview lists the permissions a specific user has within a namespace.\n * @see {@link https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#selfsubjectrulesreview-v1-authorization-k8s-io}\n */\n V1SelfSubjectRulesReview: {\n kind: \"SelfSubjectRulesReview\",\n version: \"v1\",\n group: \"authorization.k8s.io\",\n },\n\n /**\n * Represents a K8s SubjectAccessReview resource.\n * SubjectAccessReview checks whether a specific user can perform a specific action.\n * @see {@link https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#subjectaccessreview-v1-authorization-k8s-io}\n */\n V1SubjectAccessReview: {\n kind: \"SubjectAccessReview\",\n version: \"v1\",\n group: \"authorization.k8s.io\",\n },\n\n /**\n * Represents a K8s HorizontalPodAutoscaler resource.\n * HorizontalPodAutoscaler automatically scales the number of Pods in a replication controller, deployment, or replica set.\n * @see {@link https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/}\n */\n V1HorizontalPodAutoscaler: {\n kind: \"HorizontalPodAutoscaler\",\n version: \"v2\",\n group: \"autoscaling\",\n },\n\n /**\n * Represents a K8s CronJob resource.\n * CronJob manages time-based jobs, specifically those that run periodically and complete after a successful execution.\n * @see {@link https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/}\n */\n V1CronJob: {\n kind: \"CronJob\",\n version: \"v1\",\n group: \"batch\",\n },\n\n /**\n * Represents a K8s Job resource.\n * Job represents the configuration of a single job.\n * @see {@link https://kubernetes.io/docs/concepts/workloads/controllers/job/}\n */\n V1Job: {\n kind: \"Job\",\n version: \"v1\",\n group: \"batch\",\n },\n\n /**\n * Represents a K8s CertificateSigningRequest resource.\n * CertificateSigningRequest represents a certificate signing request.\n * @see {@link https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/}\n */\n V1CertificateSigningRequest: {\n kind: \"CertificateSigningRequest\",\n version: \"v1\",\n group: \"certificates.k8s.io\",\n },\n\n /**\n * Represents a K8s EndpointSlice resource.\n * EndpointSlice represents a scalable set of network endpoints for a Kubernetes Service.\n * @see {@link https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/}\n */\n V1EndpointSlice: {\n kind: \"EndpointSlice\",\n version: \"v1\",\n group: \"discovery.k8s.io\",\n },\n\n /**\n * Represents a K8s IngressClass resource.\n * IngressClass represents the class of the Ingress, referenced by the Ingress spec.\n * @see {@link https://kubernetes.io/docs/concepts/services-networking/ingress/}\n */\n V1IngressClass: {\n kind: \"IngressClass\",\n version: \"v1\",\n group: \"networking.k8s.io\",\n },\n\n /**\n * Represents a K8s Ingress resource.\n * Ingress exposes HTTP and HTTPS routes from outside the cluster to services within the cluster.\n * @see {@link https://kubernetes.io/docs/concepts/services-networking/ingress/}\n */\n V1Ingress: {\n kind: \"Ingress\",\n version: \"v1\",\n group: \"networking.k8s.io\",\n plural: \"ingresses\",\n },\n\n /**\n * Represents a K8s NetworkPolicy resource.\n * NetworkPolicy defines a set of rules for how pods communicate with each other.\n * @see {@link https://kubernetes.io/docs/concepts/services-networking/network-policies/}\n */\n V1NetworkPolicy: {\n kind: \"NetworkPolicy\",\n version: \"v1\",\n group: \"networking.k8s.io\",\n },\n\n /**\n * Represents a K8s RuntimeClass resource.\n * RuntimeClass is a cluster-scoped resource that surfaces container runtime properties to the control plane.\n * @see {@link https://kubernetes.io/docs/concepts/containers/runtime-class/}\n */\n V1RuntimeClass: {\n kind: \"RuntimeClass\",\n version: \"v1\",\n group: \"node.k8s.io\",\n },\n\n /**\n * Represents a K8s PodDisruptionBudget resource.\n * PodDisruptionBudget is an API object that limits the number of pods of a replicated application that are down simultaneously.\n * @see {@link https://kubernetes.io/docs/concepts/workloads/pods/disruptions/}\n */\n V1PodDisruptionBudget: {\n kind: \"PodDisruptionBudget\",\n version: \"v1\",\n group: \"policy\",\n },\n\n /**\n * Represents a K8s VolumeAttachment resource.\n * VolumeAttachment captures the intent to attach or detach the specified volume to/from the specified node.\n * @see {@link https://kubernetes.io/docs/concepts/storage/storage-classes/}\n */\n V1VolumeAttachment: {\n kind: \"VolumeAttachment\",\n version: \"v1\",\n group: \"storage.k8s.io\",\n },\n\n /**\n * Represents a K8s CSIDriver resource.\n * CSIDriver captures information about a Container Storage Interface (CSI) volume driver.\n * @see {@link https://kubernetes.io/docs/concepts/storage/volumes/}\n */\n V1CSIDriver: {\n kind: \"CSIDriver\",\n version: \"v1\",\n group: \"storage.k8s.io\",\n },\n\n /**\n * Represents a K8s CSIStorageCapacity resource.\n * CSIStorageCapacity stores the reported storage capacity of a CSI node or storage class.\n * @see {@link https://kubernetes.io/docs/concepts/storage/csi/}\n */\n V1CSIStorageCapacity: {\n kind: \"CSIStorageCapacity\",\n version: \"v1\",\n group: \"storage.k8s.io\",\n },\n\n /**\n * Represents a K8s StorageClass resource.\n * StorageClass is a cluster-scoped resource that provides a way for administrators to describe the classes of storage they offer.\n * @see {@link https://kubernetes.io/docs/concepts/storage/storage-classes/}\n */\n V1StorageClass: {\n kind: \"StorageClass\",\n version: \"v1\",\n group: \"storage.k8s.io\",\n },\n};\n\nexport function modelToGroupVersionKind(key: string): GroupVersionKind {\n return gvkMap[key];\n}\n\n/**\n * Registers a new model and GroupVersionKind with Pepr for use with `When(a.<Kind>)`\n *\n * @param model Used to match the GroupVersionKind and define the type-data for the request\n * @param groupVersionKind Contains the match parameters to determine the request should be handled\n */\nexport const RegisterKind = (model: GenericClass, groupVersionKind: GroupVersionKind) => {\n const name = model.name;\n\n // Do not allow overwriting existing GVKs\n if (gvkMap[name]) {\n throw new Error(`GVK ${name} already registered`);\n }\n\n // Set the GVK\n gvkMap[name] = groupVersionKind;\n};\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\n/**\n * Enumeration representing different logging levels.\n */\nexport enum LogLevel {\n debug = 0,\n info = 1,\n warn = 2,\n error = 3,\n}\n\nenum ConsoleColors {\n Reset = \"\\x1b[0m\",\n Bright = \"\\x1b[1m\",\n Dim = \"\\x1b[2m\",\n Underscore = \"\\x1b[4m\",\n Blink = \"\\x1b[5m\",\n Reverse = \"\\x1b[7m\",\n Hidden = \"\\x1b[8m\",\n\n FgBlack = \"\\x1b[30m\",\n FgRed = \"\\x1b[31m\",\n FgGreen = \"\\x1b[32m\",\n FgYellow = \"\\x1b[33m\",\n FgBlue = \"\\x1b[34m\",\n FgMagenta = \"\\x1b[35m\",\n FgCyan = \"\\x1b[36m\",\n FgWhite = \"\\x1b[37m\",\n\n BgBlack = \"\\x1b[40m\",\n BgRed = \"\\x1b[41m\",\n BgGreen = \"\\x1b[42m\",\n BgYellow = \"\\x1b[43m\",\n BgBlue = \"\\x1b[44m\",\n BgMagenta = \"\\x1b[45m\",\n BgCyan = \"\\x1b[46m\",\n BgWhite = \"\\x1b[47m\",\n}\n\n/**\n * Simple logger class that logs messages at different log levels.\n */\nexport class Logger {\n private _logLevel: LogLevel;\n\n /**\n * Create a new logger instance.\n * @param logLevel - The minimum log level to log messages for.\n */\n constructor(logLevel: LogLevel) {\n this._logLevel = logLevel;\n }\n\n /**\n * Change the log level of the logger.\n * @param logLevel - The log level to log the message at.\n */\n public SetLogLevel(logLevel: string): void {\n this._logLevel = LogLevel[logLevel as keyof typeof LogLevel];\n this.debug(`Log level set to ${logLevel}`);\n }\n\n /**\n * Log a debug message.\n * @param message - The message to log.\n */\n public debug<T>(message: T, prefix?: string): void {\n this.log(LogLevel.debug, message, prefix);\n }\n\n /**\n * Log an info message.\n * @param message - The message to log.\n */\n public info<T>(message: T, prefix?: string): void {\n this.log(LogLevel.info, message, prefix);\n }\n\n /**\n * Log a warning message.\n * @param message - The message to log.\n */\n public warn<T>(message: T, prefix?: string): void {\n this.log(LogLevel.warn, message, prefix);\n }\n\n /**\n * Log an error message.\n * @param message - The message to log.\n */\n public error<T>(message: T, prefix?: string): void {\n this.log(LogLevel.error, message, prefix);\n }\n\n /**\n * Log a message at the specified log level.\n * @param logLevel - The log level of the message.\n * @param message - The message to log.\n */\n private log<T>(logLevel: LogLevel, message: T, callerPrefix = \"\"): void {\n const color = {\n [LogLevel.debug]: ConsoleColors.FgBlack,\n [LogLevel.info]: ConsoleColors.FgCyan,\n [LogLevel.warn]: ConsoleColors.FgYellow,\n [LogLevel.error]: ConsoleColors.FgRed,\n };\n\n if (logLevel >= this._logLevel) {\n // Prefix the message with the colored log level.\n let prefix = \"[\" + LogLevel[logLevel] + \"]\\t\" + callerPrefix;\n\n prefix = this.colorize(prefix, color[logLevel]);\n\n // If the message is not a string, use the debug method to log the object.\n if (typeof message !== \"string\") {\n console.log(prefix);\n console.debug(\"%o\", message);\n } else {\n console.log(prefix + \"\\t\" + message);\n }\n }\n }\n\n private colorize(text: string, color: ConsoleColors): string {\n return color + text + ConsoleColors.Reset;\n }\n}\n\n/** Log is an instance of Logger used to generate log entries. */\nconst Log = new Logger(LogLevel.info);\nif (process.env.LOG_LEVEL) {\n Log.SetLogLevel(process.env.LOG_LEVEL);\n}\nexport default Log;\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { modelToGroupVersionKind } from \"./k8s/index\";\nimport { GroupVersionKind } from \"./k8s/types\";\nimport logger from \"./logger\";\nimport {\n BindToAction,\n Binding,\n BindingFilter,\n BindingWithName,\n CapabilityAction,\n CapabilityCfg,\n DeepPartial,\n Event,\n GenericClass,\n HookPhase,\n WhenSelector,\n} from \"./types\";\n\n/**\n * A capability is a unit of functionality that can be registered with the Pepr runtime.\n */\nexport class Capability implements CapabilityCfg {\n private _name: string;\n private _description: string;\n private _namespaces?: string[] | undefined;\n\n // Currently everything is considered a mutation\n private _mutateOrValidate = HookPhase.mutate;\n\n private _bindings: Binding[] = [];\n\n get bindings(): Binding[] {\n return this._bindings;\n }\n\n get name() {\n return this._name;\n }\n\n get description() {\n return this._description;\n }\n\n get namespaces() {\n return this._namespaces || [];\n }\n\n get mutateOrValidate() {\n return this._mutateOrValidate;\n }\n\n constructor(cfg: CapabilityCfg) {\n this._name = cfg.name;\n this._description = cfg.description;\n this._namespaces = cfg.namespaces;\n logger.info(`Capability ${this._name} registered`);\n logger.debug(cfg);\n }\n\n /**\n * The When method is used to register a capability action to be executed when a Kubernetes resource is\n * processed by Pepr. The action will be executed if the resource matches the specified kind and any\n * filters that are applied.\n *\n * @param model the KubernetesObject model to match\n * @param kind if using a custom KubernetesObject not available in `a.*`, specify the GroupVersionKind\n * @returns\n */\n When = <T extends GenericClass>(model: T, kind?: GroupVersionKind): WhenSelector<T> => {\n const matchedKind = modelToGroupVersionKind(model.name);\n\n // If the kind is not specified and the model is not a KubernetesObject, throw an error\n if (!matchedKind && !kind) {\n throw new Error(`Kind not specified for ${model.name}`);\n }\n\n const binding: Binding = {\n // If the kind is not specified, use the matched kind from the model\n kind: kind || matchedKind,\n event: Event.Any,\n filters: {\n name: \"\",\n namespaces: [],\n labels: {},\n annotations: {},\n },\n callback: () => undefined,\n };\n\n const prefix = `${this._name}: ${model.name}`;\n\n logger.info(`Binding created`, prefix);\n\n const Then = (cb: CapabilityAction<T>): BindToAction<T> => {\n logger.info(`Binding action created`, prefix);\n logger.debug(cb.toString(), prefix);\n // Push the binding to the list of bindings for this capability as a new BindingAction\n // with the callback function to preserve\n this._bindings.push({\n ...binding,\n callback: cb,\n });\n\n // Now only allow adding actions to the same binding\n return { Then };\n };\n\n const ThenSet = (merge: DeepPartial<InstanceType<T>>): BindToAction<T> => {\n // Add the new action to the binding\n Then(req => req.Merge(merge));\n\n return { Then };\n };\n\n function InNamespace(...namespaces: string[]): BindingWithName<T> {\n logger.debug(`Add namespaces filter ${namespaces}`, prefix);\n binding.filters.namespaces.push(...namespaces);\n return { WithLabel, WithAnnotation, WithName, Then, ThenSet };\n }\n\n function WithName(name: string): BindingFilter<T> {\n logger.debug(`Add name filter ${name}`, prefix);\n binding.filters.name = name;\n return { WithLabel, WithAnnotation, Then, ThenSet };\n }\n\n function WithLabel(key: string, value = \"\"): BindingFilter<T> {\n logger.debug(`Add label filter ${key}=${value}`, prefix);\n binding.filters.labels[key] = value;\n return { WithLabel, WithAnnotation, Then, ThenSet };\n }\n\n const WithAnnotation = (key: string, value = \"\"): BindingFilter<T> => {\n logger.debug(`Add annotation filter ${key}=${value}`, prefix);\n binding.filters.annotations[key] = value;\n return { WithLabel, WithAnnotation, Then, ThenSet };\n };\n\n const bindEvent = (event: Event) => {\n binding.event = event;\n return {\n InNamespace,\n Then,\n ThenSet,\n WithAnnotation,\n WithLabel,\n WithName,\n };\n };\n\n return {\n IsCreatedOrUpdated: () => bindEvent(Event.CreateOrUpdate),\n IsCreated: () => bindEvent(Event.Create),\n IsUpdated: () => bindEvent(Event.Update),\n IsDeleted: () => bindEvent(Event.Delete),\n };\n };\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { StatusCodes } from \"http-status-codes\";\nimport f, { FetchError, RequestInfo, RequestInit } from \"node-fetch\";\nimport logger from \"./logger\";\n\nexport const fetchRaw = f;\n\nexport type FetchResponse<T> = {\n data: T;\n ok: boolean;\n status: number;\n statusText: string;\n};\n\n/**\n * Perform an async HTTP call and return the parsed JSON response, optionally\n * as a specific type.\n *\n * @example\n * ```ts\n * fetch<string[]>(\"https://example.com/api/foo\");\n * ```\n *\n * @param url The URL or Request object to fetch\n * @param init Additional options for the request\n * @returns\n */\nexport async function fetch<T>(url: URL | RequestInfo, init?: RequestInit): Promise<FetchResponse<T>> {\n let data = undefined as unknown as T;\n try {\n logger.debug(`Fetching ${url}`);\n\n const resp = await fetchRaw(url, init);\n const contentType = resp.headers.get(\"content-type\") || \"\";\n\n if (resp.ok) {\n // Parse the response as JSON if the content type is JSON\n if (contentType.includes(\"application/json\")) {\n data = await resp.json();\n } else {\n // Otherwise, return however the response was read\n data = (await resp.text()) as unknown as T;\n }\n }\n\n return {\n data,\n ok: resp.ok,\n status: resp.status,\n statusText: resp.statusText,\n };\n } catch (e) {\n if (e instanceof FetchError) {\n logger.debug(`Fetch failed: ${e instanceof Error ? e.message : e}`);\n\n // Parse the error code from the FetchError or default to 400 (Bad Request)\n const status = parseInt(e.code || \"400\");\n\n return {\n data,\n ok: false,\n status,\n statusText: e.message,\n };\n }\n\n return {\n data,\n ok: false,\n status: StatusCodes.BAD_REQUEST,\n statusText: \"Unknown error\",\n };\n }\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { concat, mergeDeepWith } from \"ramda\";\n\nimport { Capability } from \"./capability\";\nimport { Controller } from \"./controller\";\nimport { Request, Response } from \"./k8s/types\";\nimport { ModuleConfig } from \"./types\";\n\nconst alwaysIgnore = {\n namespaces: [\"kube-system\", \"pepr-system\"],\n labels: [{ \"pepr.dev\": \"ignore\" }],\n};\n\nexport type PackageJSON = {\n description: string;\n pepr: ModuleConfig;\n};\n\nexport type PeprModuleOptions = {\n deferStart?: boolean;\n\n /** A user-defined callback to pre-process or intercept a Pepr request from K8s immediately before it is processed */\n beforeHook?: (req: Request) => void;\n\n /** A user-defined callback to post-process or intercept a Pepr response just before it is returned to K8s */\n afterHook?: (res: Response) => void;\n};\n\nexport class PeprModule {\n private _controller!: Controller;\n\n /**\n * Create a new Pepr runtime\n *\n * @param config The configuration for the Pepr runtime\n * @param capabilities The capabilities to be loaded into the Pepr runtime\n * @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()`.\n */\n constructor({ description, pepr }: PackageJSON, capabilities: Capability[] = [], opts: PeprModuleOptions = {}) {\n const config: ModuleConfig = mergeDeepWith(concat, pepr, alwaysIgnore);\n config.description = description;\n\n // Handle build mode\n if (process.env.PEPR_MODE === \"build\") {\n process.send?.({ capabilities });\n return;\n }\n\n this._controller = new Controller(config, capabilities, opts.beforeHook, opts.afterHook);\n\n // Stop processing if deferStart is set to true\n if (opts.deferStart) {\n return;\n }\n\n this.start();\n }\n\n /**\n * Start the Pepr runtime manually.\n * Normally this is called automatically when the Pepr module is instantiated, but can be called manually if `deferStart` is set to `true` in the constructor.\n *\n * @param port\n */\n start(port = 3000) {\n this._controller.startServer(port);\n }\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport express from \"express\";\nimport fs from \"fs\";\nimport https from \"https\";\nimport { Capability } from \"./capability\";\nimport { Request, Response } from \"./k8s/types\";\nimport { processor } from \"./processor\";\nimport { ModuleConfig } from \"./types\";\nimport Log from \"./logger\";\n\nexport class Controller {\n private readonly app = express();\n private running = false;\n\n constructor(\n private readonly config: ModuleConfig,\n private readonly capabilities: Capability[],\n private readonly beforeHook?: (req: Request) => void,\n private readonly afterHook?: (res: Response) => void\n ) {\n // Middleware for logging requests\n this.app.use(this.logger);\n\n // Middleware for parsing JSON, limit to 2mb vs 100K for K8s compatibility\n this.app.use(express.json({ limit: \"2mb\" }));\n\n // Health check endpoint\n this.app.get(\"/healthz\", this.healthz);\n\n // Mutate endpoint\n this.app.post(\"/mutate\", this.mutate);\n\n if (beforeHook) {\n console.info(`Using beforeHook: ${beforeHook}`);\n }\n\n if (afterHook) {\n console.info(`Using afterHook: ${afterHook}`);\n }\n }\n\n /** Start the webhook server */\n public startServer = (port: number) => {\n if (this.running) {\n throw new Error(\"Cannot start Pepr module: Pepr module was not instantiated with deferStart=true\");\n }\n\n // Load SSL certificate and key\n const options = {\n key: fs.readFileSync(process.env.SSL_KEY_PATH || \"/etc/certs/tls.key\"),\n cert: fs.readFileSync(process.env.SSL_CERT_PATH || \"/etc/certs/tls.crt\"),\n };\n\n // Create HTTPS server\n const server = https.createServer(options, this.app).listen(port);\n\n // Handle server listening event\n server.on(\"listening\", () => {\n console.log(`Server listening on port ${port}`);\n // Track that the server is running\n this.running = true;\n });\n\n // Handle EADDRINUSE errors\n server.on(\"error\", (e: { code: string }) => {\n if (e.code === \"EADDRINUSE\") {\n console.log(\n `Address in use, retrying in 2 seconds. If this persists, ensure ${port} is not in use, e.g. \"lsof -i :${port}\"`\n );\n setTimeout(() => {\n server.close();\n server.listen(port);\n }, 2000);\n }\n });\n\n // Listen for the SIGTERM signal and gracefully close the server\n process.on(\"SIGTERM\", () => {\n console.log(\"Received SIGTERM, closing server\");\n server.close(() => {\n console.log(\"Server closed\");\n process.exit(0);\n });\n });\n };\n\n private logger = (req: express.Request, res: express.Response, next: express.NextFunction) => {\n const startTime = Date.now();\n\n res.on(\"finish\", () => {\n const now = new Date().toISOString();\n const elapsedTime = Date.now() - startTime;\n const message = `[${now}] ${req.method} ${req.originalUrl} [${res.statusCode}] ${elapsedTime} ms\\n`;\n\n res.statusCode >= 400 ? console.error(message) : console.info(message);\n });\n\n next();\n };\n\n private healthz = (req: express.Request, res: express.Response) => {\n try {\n res.send(\"OK\");\n } catch (err) {\n console.error(err);\n res.status(500).send(\"Internal Server Error\");\n }\n };\n\n private mutate = async (req: express.Request, res: express.Response) => {\n try {\n const request: Request = req.body?.request || ({} as Request);\n\n // Run the before hook if it exists\n this.beforeHook && this.beforeHook(request || {});\n\n const name = request?.name ? `/${request.name}` : \"\";\n const namespace = request?.namespace || \"\";\n const gvk = request?.kind || { group: \"\", version: \"\", kind: \"\" };\n const prefix = `${request.uid} ${namespace}${name}`;\n\n Log.info(`Mutate request: ${gvk.group}/${gvk.version}/${gvk.kind}`, prefix);\n\n // Process the request\n const response = await processor(this.config, this.capabilities, request, prefix);\n\n // Run the after hook if it exists\n this.afterHook && this.afterHook(response);\n\n // Log the response\n Log.debug(response, prefix);\n\n // Send a no prob bob response\n res.send({\n apiVersion: \"admission.k8s.io/v1\",\n kind: \"AdmissionReview\",\n response,\n });\n } catch (err) {\n console.error(err);\n res.status(500).send(\"Internal Server Error\");\n }\n };\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport jsonPatch from \"fast-json-patch\";\n\nimport { Capability } from \"./capability\";\nimport { shouldSkipRequest } from \"./filter\";\nimport { Request, Response } from \"./k8s/types\";\nimport { Secret } from \"./k8s/upstream\";\nimport Log from \"./logger\";\nimport { PeprRequest } from \"./request\";\nimport { ModuleConfig } from \"./types\";\nimport { convertFromBase64Map, convertToBase64Map } from \"./utils\";\n\nexport async function processor(\n config: ModuleConfig,\n capabilities: Capability[],\n req: Request,\n parentPrefix: string\n): Promise<Response> {\n const wrapped = new PeprRequest(req);\n const response: Response = {\n uid: req.uid,\n warnings: [],\n allowed: false,\n };\n\n // Track whether any capability matched the request\n let matchedCapabilityAction = false;\n\n // Track data fields that should be skipped during decoding\n let skipDecode: string[] = [];\n\n // If the resource is a secret, decode the data\n const isSecret = req.kind.version == \"v1\" && req.kind.kind == \"Secret\";\n if (isSecret) {\n skipDecode = convertFromBase64Map(wrapped.Raw as unknown as Secret);\n }\n\n Log.info(`Processing request`, parentPrefix);\n\n for (const { name, bindings } of capabilities) {\n const prefix = `${parentPrefix} ${name}:`;\n\n for (const action of bindings) {\n // Continue to the next action without doing anything if this one should be skipped\n if (shouldSkipRequest(action, req)) {\n continue;\n }\n\n const label = action.callback.name;\n Log.info(`Processing matched action ${label}`, prefix);\n\n matchedCapabilityAction = true;\n\n // Add annotations to the request to indicate that the capability started processing\n // this will allow tracking of failed mutations that were permitted to continue\n const updateStatus = (status: string) => {\n // Only update the status if the request is a CREATE or UPDATE (we don't use CONNECT)\n if (req.operation == \"DELETE\") {\n return;\n }\n\n const identifier = `${config.uuid}.pepr.dev/${name}`;\n wrapped.Raw.metadata = wrapped.Raw.metadata || {};\n wrapped.Raw.metadata.annotations = wrapped.Raw.metadata.annotations || {};\n wrapped.Raw.metadata.annotations[identifier] = status;\n };\n\n updateStatus(\"started\");\n\n try {\n // Run the action\n await action.callback(wrapped);\n\n Log.info(`Action succeeded`, prefix);\n\n // Add annotations to the request to indicate that the capability succeeded\n updateStatus(\"succeeded\");\n } catch (e) {\n // Annoying ts false positive\n response.warnings = response.warnings || [];\n response.warnings.push(`Action failed: ${e}`);\n\n // If errors are not allowed, note the failure in the Response\n if (config.onError) {\n Log.error(`Action failed: ${e}`, prefix);\n response.result = \"Pepr module configured to reject on error\";\n return response;\n } else {\n Log.warn(`Action failed: ${e}`, prefix);\n updateStatus(\"warning\");\n }\n }\n }\n }\n\n // If we've made it this far, the request is allowed\n response.allowed = true;\n\n // If no capability matched the request, exit early\n if (!matchedCapabilityAction) {\n Log.info(`No matching capability action found`, parentPrefix);\n return response;\n }\n\n const transformed = wrapped.Raw;\n\n // Post-process the Secret requests to convert it back to the original format\n if (isSecret) {\n convertToBase64Map(transformed as unknown as Secret, skipDecode);\n }\n\n // Compare the original request to the modified request to get the patches\n const patches = jsonPatch.compare(req.object, transformed);\n\n // Only add the patch if there are patches to apply\n if (patches.length > 0) {\n response.patchType = \"JSONPatch\";\n // Webhook must be base64-encoded\n // https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#response\n response.patch = Buffer.from(JSON.stringify(patches)).toString(\"base64\");\n }\n\n // Remove the warnings array if it's empty\n if (response.warnings && response.warnings.length < 1) {\n delete response.warnings;\n }\n\n Log.debug(patches, parentPrefix);\n\n return response;\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { Request } from \"./k8s/types\";\nimport logger from \"./logger\";\nimport { Binding, Event } from \"./types\";\n\n/**\n * shouldSkipRequest determines if a request should be skipped based on the binding filters.\n *\n * @param binding the capability action binding\n * @param req the incoming request\n * @returns\n */\nexport function shouldSkipRequest(binding: Binding, req: Request) {\n const { group, kind, version } = binding.kind || {};\n const { namespaces, labels, annotations, name } = binding.filters || {};\n const { metadata } = req.object || {};\n\n // Test for matching operation\n if (!binding.event.includes(req.operation) && !binding.event.includes(Event.Any)) {\n return true;\n }\n\n // Test name first, since it's the most specific\n if (name && name !== req.name) {\n return true;\n }\n\n // Test for matching kinds\n if (kind !== req.kind.kind) {\n return true;\n }\n\n // Test for matching groups\n if (group && group !== req.kind.group) {\n return true;\n }\n\n // Test for matching versions\n if (version && version !== req.kind.version) {\n return true;\n }\n\n // Test for matching namespaces\n if (namespaces.length && !namespaces.includes(req.namespace || \"\")) {\n logger.debug(\"Namespace does not match\");\n return true;\n }\n\n // Test for matching labels\n for (const [key, value] of Object.entries(labels)) {\n const testKey = metadata?.labels?.[key];\n\n // First check if the label exists\n if (!testKey) {\n logger.debug(`Label ${key} does not exist`);\n return true;\n }\n\n // Then check if the value matches, if specified\n if (value && testKey !== value) {\n logger.debug(`${testKey} does not match ${value}`);\n return true;\n }\n }\n\n // Test for matching annotations\n for (const [key, value] of Object.entries(annotations)) {\n const testKey = metadata?.annotations?.[key];\n\n // First check if the annotation exists\n if (!testKey) {\n logger.debug(`Annotation ${key} does not exist`);\n return true;\n }\n\n // Then check if the value matches, if specified\n if (value && testKey !== value) {\n logger.debug(`${testKey} does not match ${value}`);\n return true;\n }\n }\n\n // No failed filters, so we should not skip this request\n return false;\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { clone, mergeDeepRight } from \"ramda\";\n\nimport { KubernetesObject, Request } from \"./k8s/types\";\nimport { DeepPartial } from \"./types\";\n\n/**\n * The RequestWrapper class provides methods to modify Kubernetes objects in the context\n * of a mutating webhook request.\n */\nexport class PeprRequest<T extends KubernetesObject> {\n public Raw: T;\n\n get PermitSideEffects() {\n return !this._input.dryRun;\n }\n\n /**\n * Indicates whether the request is a dry run.\n * @returns true if the request is a dry run, false otherwise.\n */\n get IsDryRun() {\n return this._input.dryRun;\n }\n\n /**\n * Provides access to the old resource in the request if available.\n * @returns The old Kubernetes resource object or null if not available.\n */\n get OldResource() {\n return this._input.oldObject;\n }\n\n /**\n * Provides access to the request object.\n * @returns The request object containing the Kubernetes resource.\n */\n get Request() {\n return this._input;\n }\n\n /**\n * Creates a new instance of the Action class.\n * @param input - The request object containing the Kubernetes resource to modify.\n */\n constructor(private _input: Request<T>) {\n // Deep clone the object to prevent mutation of the original object\n this.Raw = clone(_input.object);\n }\n\n /**\n * Deep merges the provided object with the current resource.\n *\n * @param obj - The object to merge with the current resource.\n */\n Merge(obj: DeepPartial<T>) {\n this.Raw = mergeDeepRight(this.Raw, obj) as unknown as T;\n }\n\n /**\n * Updates a label on the Kubernetes resource.\n * @param key - The key of the label to update.\n * @param value - The value of the label.\n * @returns The current Action instance for method chaining.\n */\n SetLabel(key: string, value: string) {\n const ref = this.Raw;\n\n ref.metadata = ref.metadata ?? {};\n ref.metadata.labels = ref.metadata.labels ?? {};\n ref.metadata.labels[key] = value;\n\n return this;\n }\n\n /**\n * Updates an annotation on the Kubernetes resource.\n * @param key - The key of the annotation to update.\n * @param value - The value of the annotation.\n * @returns The current Action instance for method chaining.\n */\n SetAnnotation(key: string, value: string) {\n const ref = this.Raw;\n\n ref.metadata = ref.metadata ?? {};\n ref.metadata.annotations = ref.metadata.annotations ?? {};\n ref.metadata.annotations[key] = value;\n\n return this;\n }\n\n /**\n * Removes a label from the Kubernetes resource.\n * @param key - The key of the label to remove.\n * @returns The current Action instance for method chaining.\n */\n RemoveLabel(key: string) {\n if (this.Raw.metadata?.labels?.[key]) {\n delete this.Raw.metadata.labels[key];\n }\n return this;\n }\n\n /**\n * Removes an annotation from the Kubernetes resource.\n * @param key - The key of the annotation to remove.\n * @returns The current Action instance for method chaining.\n */\n RemoveAnnotation(key: string) {\n if (this.Raw.metadata?.annotations?.[key]) {\n delete this.Raw.metadata.annotations[key];\n }\n return this;\n }\n\n /**\n * Check if a label exists on the Kubernetes resource.\n *\n * @param key the label key to check\n * @returns\n */\n HasLabel(key: string) {\n return this.Raw?.metadata?.labels?.[key] !== undefined;\n }\n\n /**\n * Check if an annotation exists on the Kubernetes resource.\n *\n * @param key the annotation key to check\n * @returns\n */\n HasAnnotation(key: string) {\n return this.Raw?.metadata?.annotations?.[key] !== undefined;\n }\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport Log from \"./logger\";\n\n/** Test if a string is ascii or not */\nexport const isAscii = /^[\\s\\x20-\\x7E]*$/;\n\n/**\n * Encode all ascii values in a map to base64\n * @param obj The object to encode\n * @param skip A list of keys to skip encoding\n */\nexport function convertToBase64Map(obj: { data?: Record<string, string> }, skip: string[]) {\n obj.data = obj.data ?? {};\n for (const key in obj.data) {\n const value = obj.data[key];\n // Only encode ascii values\n obj.data[key] = skip.includes(key) ? value : base64Encode(value);\n }\n}\n\n/**\n * Decode all ascii values in a map from base64 to utf-8\n * @param obj The object to decode\n * @returns A list of keys that were skipped\n */\nexport function convertFromBase64Map(obj: { data?: Record<string, string> }) {\n const skip: string[] = [];\n\n obj.data = obj.data ?? {};\n for (const key in obj.data) {\n const decoded = base64Decode(obj.data[key]);\n if (isAscii.test(decoded)) {\n // Only decode ascii values\n obj.data[key] = decoded;\n } else {\n skip.push(key);\n }\n }\n\n Log.debug(`Non-ascii data detected in keys: ${skip}, skipping automatic base64 decoding`);\n\n return skip;\n}\n\n/** Decode a base64 string */\nexport function base64Decode(data: string) {\n return Buffer.from(data, \"base64\").toString(\"utf-8\");\n}\n\n/** Encode a string to base64 */\nexport function base64Encode(data: string) {\n return Buffer.from(data).toString(\"base64\");\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+CAAAA;AAAA,EAAA,+BAAAC;AAAA,EAAA;AAAA;AAAA;AAAA,IAAAC,sBAAgB;AAChB,IAAAC,4BAA2C;AAC3C,YAAuB;;;ACFvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,yBA0CO;;;ACjBA,IAAM,cAAN,MAAkB;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAGF;;;AC7BO,IAAM,SAA2C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMtD,aAAa;AAAA,IACX,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YAAY;AAAA,IACV,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,cAAc;AAAA,IACZ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAa;AAAA,IACX,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,yBAAyB;AAAA,IACvB,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,oBAAoB;AAAA,IAClB,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,eAAe;AAAA,IACb,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,yBAAyB;AAAA,IACvB,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAiB;AAAA,IACf,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAU;AAAA,IACR,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,kBAAkB;AAAA,IAChB,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,WAAW;AAAA,IACT,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,gCAAgC;AAAA,IAC9B,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,kCAAkC;AAAA,IAChC,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,4BAA4B;AAAA,IAC1B,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,cAAc;AAAA,IACZ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,sBAAsB;AAAA,IACpB,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAa;AAAA,IACX,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,cAAc;AAAA,IACZ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,cAAc;AAAA,IACZ,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,eAAe;AAAA,IACb,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,eAAe;AAAA,IACb,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,4BAA4B;AAAA,IAC1B,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,2BAA2B;AAAA,IACzB,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,0BAA0B;AAAA,IACxB,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,uBAAuB;AAAA,IACrB,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,2BAA2B;AAAA,IACzB,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,WAAW;AAAA,IACT,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,6BAA6B;AAAA,IAC3B,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAiB;AAAA,IACf,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,gBAAgB;AAAA,IACd,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,WAAW;AAAA,IACT,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAiB;AAAA,IACf,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,gBAAgB;AAAA,IACd,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,uBAAuB;AAAA,IACrB,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,oBAAoB;AAAA,IAClB,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAa;AAAA,IACX,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,sBAAsB;AAAA,IACpB,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,gBAAgB;AAAA,IACd,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AACF;AAEO,SAAS,wBAAwB,KAA+B;AACrE,SAAO,OAAO,GAAG;AACnB;AAQO,IAAM,eAAe,CAAC,OAAqB,qBAAuC;AACvF,QAAM,OAAO,MAAM;AAGnB,MAAI,OAAO,IAAI,GAAG;AAChB,UAAM,IAAI,MAAM,OAAO,yBAAyB;AAAA,EAClD;AAGA,SAAO,IAAI,IAAI;AACjB;;;ACpeO,IAAK,WAAL,kBAAKC,cAAL;AACL,EAAAA,oBAAA,WAAQ,KAAR;AACA,EAAAA,oBAAA,UAAO,KAAP;AACA,EAAAA,oBAAA,UAAO,KAAP;AACA,EAAAA,oBAAA,WAAQ,KAAR;AAJU,SAAAA;AAAA,GAAA;AAsCL,IAAM,SAAN,MAAa;AAAA,EACV;AAAA;AAAA;AAAA;AAAA;AAAA,EAMR,YAAY,UAAoB;AAC9B,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,YAAY,UAAwB;AACzC,SAAK,YAAY,SAAS,QAAiC;AAC3D,SAAK,MAAM,oBAAoB,UAAU;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,MAAS,SAAY,QAAuB;AACjD,SAAK,IAAI,eAAgB,SAAS,MAAM;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,KAAQ,SAAY,QAAuB;AAChD,SAAK,IAAI,cAAe,SAAS,MAAM;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,KAAQ,SAAY,QAAuB;AAChD,SAAK,IAAI,cAAe,SAAS,MAAM;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,MAAS,SAAY,QAAuB;AACjD,SAAK,IAAI,eAAgB,SAAS,MAAM;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,IAAO,UAAoB,SAAY,eAAe,IAAU;AACtE,UAAM,QAAQ;AAAA,MACZ,CAAC,aAAc,GAAG;AAAA,MAClB,CAAC,YAAa,GAAG;AAAA,MACjB,CAAC,YAAa,GAAG;AAAA,MACjB,CAAC,aAAc,GAAG;AAAA,IACpB;AAEA,QAAI,YAAY,KAAK,WAAW;AAE9B,UAAI,SAAS,MAAM,SAAS,QAAQ,IAAI,OAAQ;AAEhD,eAAS,KAAK,SAAS,QAAQ,MAAM,QAAQ,CAAC;AAG9C,UAAI,OAAO,YAAY,UAAU;AAC/B,gBAAQ,IAAI,MAAM;AAClB,gBAAQ,MAAM,MAAM,OAAO;AAAA,MAC7B,OAAO;AACL,gBAAQ,IAAI,SAAS,MAAO,OAAO;AAAA,MACrC;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,SAAS,MAAc,OAA8B;AAC3D,WAAO,QAAQ,OAAO;AAAA,EACxB;AACF;AAGA,IAAM,MAAM,IAAI,OAAO,YAAa;AACpC,IAAI,QAAQ,IAAI,WAAW;AACzB,MAAI,YAAY,QAAQ,IAAI,SAAS;AACvC;AACA,IAAO,iBAAQ;;;AChHR,IAAM,aAAN,MAA0C;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA;AAAA,EAEA,YAAuB,CAAC;AAAA,EAEhC,IAAI,WAAsB;AACxB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,OAAO;AACT,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,cAAc;AAChB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,aAAa;AACf,WAAO,KAAK,eAAe,CAAC;AAAA,EAC9B;AAAA,EAEA,IAAI,mBAAmB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,YAAY,KAAoB;AAC9B,SAAK,QAAQ,IAAI;AACjB,SAAK,eAAe,IAAI;AACxB,SAAK,cAAc,IAAI;AACvB,mBAAO,KAAK,cAAc,KAAK,kBAAkB;AACjD,mBAAO,MAAM,GAAG;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,OAAO,CAAyB,OAAU,SAA6C;AACrF,UAAM,cAAc,wBAAwB,MAAM,IAAI;AAGtD,QAAI,CAAC,eAAe,CAAC,MAAM;AACzB,YAAM,IAAI,MAAM,0BAA0B,MAAM,MAAM;AAAA,IACxD;AAEA,UAAM,UAAmB;AAAA;AAAA,MAEvB,MAAM,QAAQ;AAAA,MACd;AAAA,MACA,SAAS;AAAA,QACP,MAAM;AAAA,QACN,YAAY,CAAC;AAAA,QACb,QAAQ,CAAC;AAAA,QACT,aAAa,CAAC;AAAA,MAChB;AAAA,MACA,UAAU,MAAM;AAAA,IAClB;AAEA,UAAM,SAAS,GAAG,KAAK,UAAU,MAAM;AAEvC,mBAAO,KAAK,mBAAmB,MAAM;AAErC,UAAM,OAAO,CAAC,OAA6C;AACzD,qBAAO,KAAK,0BAA0B,MAAM;AAC5C,qBAAO,MAAM,GAAG,SAAS,GAAG,MAAM;AAGlC,WAAK,UAAU,KAAK;AAAA,QAClB,GAAG;AAAA,QACH,UAAU;AAAA,MACZ,CAAC;AAGD,aAAO,EAAE,KAAK;AAAA,IAChB;AAEA,UAAM,UAAU,CAAC,UAAyD;AAExE,WAAK,SAAO,IAAI,MAAM,KAAK,CAAC;AAE5B,aAAO,EAAE,KAAK;AAAA,IAChB;AAEA,aAAS,eAAe,YAA0C;AAChE,qBAAO,MAAM,yBAAyB,cAAc,MAAM;AAC1D,cAAQ,QAAQ,WAAW,KAAK,GAAG,UAAU;AAC7C,aAAO,EAAE,WAAW,gBAAgB,UAAU,MAAM,QAAQ;AAAA,IAC9D;AAEA,aAAS,SAAS,MAAgC;AAChD,qBAAO,MAAM,mBAAmB,QAAQ,MAAM;AAC9C,cAAQ,QAAQ,OAAO;AACvB,aAAO,EAAE,WAAW,gBAAgB,MAAM,QAAQ;AAAA,IACpD;AAEA,aAAS,UAAU,KAAa,QAAQ,IAAsB;AAC5D,qBAAO,MAAM,oBAAoB,OAAO,SAAS,MAAM;AACvD,cAAQ,QAAQ,OAAO,GAAG,IAAI;AAC9B,aAAO,EAAE,WAAW,gBAAgB,MAAM,QAAQ;AAAA,IACpD;AAEA,UAAM,iBAAiB,CAAC,KAAa,QAAQ,OAAyB;AACpE,qBAAO,MAAM,yBAAyB,OAAO,SAAS,MAAM;AAC5D,cAAQ,QAAQ,YAAY,GAAG,IAAI;AACnC,aAAO,EAAE,WAAW,gBAAgB,MAAM,QAAQ;AAAA,IACpD;AAEA,UAAM,YAAY,CAAC,UAAiB;AAClC,cAAQ,QAAQ;AAChB,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,MACL,oBAAoB,MAAM,+CAA8B;AAAA,MACxD,WAAW,MAAM,+BAAsB;AAAA,MACvC,WAAW,MAAM,+BAAsB;AAAA,MACvC,WAAW,MAAM,+BAAsB;AAAA,IACzC;AAAA,EACF;AACF;;;AC5JA,+BAA4B;AAC5B,wBAAwD;AAGjD,IAAM,WAAW,kBAAAC;AAsBxB,eAAsB,MAAS,KAAwB,MAA+C;AACpG,MAAI,OAAO;AACX,MAAI;AACF,mBAAO,MAAM,YAAY,KAAK;AAE9B,UAAM,OAAO,MAAM,SAAS,KAAK,IAAI;AACrC,UAAM,cAAc,KAAK,QAAQ,IAAI,cAAc,KAAK;AAExD,QAAI,KAAK,IAAI;AAEX,UAAI,YAAY,SAAS,kBAAkB,GAAG;AAC5C,eAAO,MAAM,KAAK,KAAK;AAAA,MACzB,OAAO;AAEL,eAAQ,MAAM,KAAK,KAAK;AAAA,MAC1B;AAAA,IACF;AAEA,WAAO;AAAA,MACL;AAAA,MACA,IAAI,KAAK;AAAA,MACT,QAAQ,KAAK;AAAA,MACb,YAAY,KAAK;AAAA,IACnB;AAAA,EACF,SAAS,GAAP;AACA,QAAI,aAAa,8BAAY;AAC3B,qBAAO,MAAM,iBAAiB,aAAa,QAAQ,EAAE,UAAU,GAAG;AAGlE,YAAM,SAAS,SAAS,EAAE,QAAQ,KAAK;AAEvC,aAAO;AAAA,QACL;AAAA,QACA,IAAI;AAAA,QACJ;AAAA,QACA,YAAY,EAAE;AAAA,MAChB;AAAA,IACF;AAEA,WAAO;AAAA,MACL;AAAA,MACA,IAAI;AAAA,MACJ,QAAQ,qCAAY;AAAA,MACpB,YAAY;AAAA,IACd;AAAA,EACF;AACF;;;ACxEA,IAAAC,gBAAsC;;;ACAtC,qBAAoB;AACpB,gBAAe;AACf,mBAAkB;;;ACFlB,6BAAsB;;;ACWf,SAAS,kBAAkB,SAAkB,KAAc;AAChE,QAAM,EAAE,OAAO,MAAM,QAAQ,IAAI,QAAQ,QAAQ,CAAC;AAClD,QAAM,EAAE,YAAY,QAAQ,aAAa,KAAK,IAAI,QAAQ,WAAW,CAAC;AACtE,QAAM,EAAE,SAAS,IAAI,IAAI,UAAU,CAAC;AAGpC,MAAI,CAAC,QAAQ,MAAM,SAAS,IAAI,SAAS,KAAK,CAAC,QAAQ,MAAM,sBAAkB,GAAG;AAChF,WAAO;AAAA,EACT;AAGA,MAAI,QAAQ,SAAS,IAAI,MAAM;AAC7B,WAAO;AAAA,EACT;AAGA,MAAI,SAAS,IAAI,KAAK,MAAM;AAC1B,WAAO;AAAA,EACT;AAGA,MAAI,SAAS,UAAU,IAAI,KAAK,OAAO;AACrC,WAAO;AAAA,EACT;AAGA,MAAI,WAAW,YAAY,IAAI,KAAK,SAAS;AAC3C,WAAO;AAAA,EACT;AAGA,MAAI,WAAW,UAAU,CAAC,WAAW,SAAS,IAAI,aAAa,EAAE,GAAG;AAClE,mBAAO,MAAM,0BAA0B;AACvC,WAAO;AAAA,EACT;AAGA,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,UAAM,UAAU,UAAU,SAAS,GAAG;AAGtC,QAAI,CAAC,SAAS;AACZ,qBAAO,MAAM,SAAS,oBAAoB;AAC1C,aAAO;AAAA,IACT;AAGA,QAAI,SAAS,YAAY,OAAO;AAC9B,qBAAO,MAAM,GAAG,0BAA0B,OAAO;AACjD,aAAO;AAAA,IACT;AAAA,EACF;AAGA,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,WAAW,GAAG;AACtD,UAAM,UAAU,UAAU,cAAc,GAAG;AAG3C,QAAI,CAAC,SAAS;AACZ,qBAAO,MAAM,cAAc,oBAAoB;AAC/C,aAAO;AAAA,IACT;AAGA,QAAI,SAAS,YAAY,OAAO;AAC9B,qBAAO,MAAM,GAAG,0BAA0B,OAAO;AACjD,aAAO;AAAA,IACT;AAAA,EACF;AAGA,SAAO;AACT;;;ACnFA,mBAAsC;AAS/B,IAAM,cAAN,MAA8C;AAAA;AAAA;AAAA;AAAA;AAAA,EAmCnD,YAAoB,QAAoB;AAApB;AAElB,SAAK,UAAM,oBAAM,OAAO,MAAM;AAAA,EAChC;AAAA,EArCO;AAAA,EAEP,IAAI,oBAAoB;AACtB,WAAO,CAAC,KAAK,OAAO;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,WAAW;AACb,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,cAAc;AAChB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,UAAU;AACZ,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,MAAM,KAAqB;AACzB,SAAK,UAAM,6BAAe,KAAK,KAAK,GAAG;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,SAAS,KAAa,OAAe;AACnC,UAAM,MAAM,KAAK;AAEjB,QAAI,WAAW,IAAI,YAAY,CAAC;AAChC,QAAI,SAAS,SAAS,IAAI,SAAS,UAAU,CAAC;AAC9C,QAAI,SAAS,OAAO,GAAG,IAAI;AAE3B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,cAAc,KAAa,OAAe;AACxC,UAAM,MAAM,KAAK;AAEjB,QAAI,WAAW,IAAI,YAAY,CAAC;AAChC,QAAI,SAAS,cAAc,IAAI,SAAS,eAAe,CAAC;AACxD,QAAI,SAAS,YAAY,GAAG,IAAI;AAEhC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YAAY,KAAa;AACvB,QAAI,KAAK,IAAI,UAAU,SAAS,GAAG,GAAG;AACpC,aAAO,KAAK,IAAI,SAAS,OAAO,GAAG;AAAA,IACrC;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAiB,KAAa;AAC5B,QAAI,KAAK,IAAI,UAAU,cAAc,GAAG,GAAG;AACzC,aAAO,KAAK,IAAI,SAAS,YAAY,GAAG;AAAA,IAC1C;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,SAAS,KAAa;AACpB,WAAO,KAAK,KAAK,UAAU,SAAS,GAAG,MAAM;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,cAAc,KAAa;AACzB,WAAO,KAAK,KAAK,UAAU,cAAc,GAAG,MAAM;AAAA,EACpD;AACF;;;ACxIA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAMO,IAAM,UAAU;AAOhB,SAAS,mBAAmB,KAAwC,MAAgB;AACzF,MAAI,OAAO,IAAI,QAAQ,CAAC;AACxB,aAAW,OAAO,IAAI,MAAM;AAC1B,UAAM,QAAQ,IAAI,KAAK,GAAG;AAE1B,QAAI,KAAK,GAAG,IAAI,KAAK,SAAS,GAAG,IAAI,QAAQ,aAAa,KAAK;AAAA,EACjE;AACF;AAOO,SAAS,qBAAqB,KAAwC;AAC3E,QAAM,OAAiB,CAAC;AAExB,MAAI,OAAO,IAAI,QAAQ,CAAC;AACxB,aAAW,OAAO,IAAI,MAAM;AAC1B,UAAM,UAAU,aAAa,IAAI,KAAK,GAAG,CAAC;AAC1C,QAAI,QAAQ,KAAK,OAAO,GAAG;AAEzB,UAAI,KAAK,GAAG,IAAI;AAAA,IAClB,OAAO;AACL,WAAK,KAAK,GAAG;AAAA,IACf;AAAA,EACF;AAEA,iBAAI,MAAM,oCAAoC,0CAA0C;AAExF,SAAO;AACT;AAGO,SAAS,aAAa,MAAc;AACzC,SAAO,OAAO,KAAK,MAAM,QAAQ,EAAE,SAAS,OAAO;AACrD;AAGO,SAAS,aAAa,MAAc;AACzC,SAAO,OAAO,KAAK,IAAI,EAAE,SAAS,QAAQ;AAC5C;;;AHxCA,eAAsB,UACpB,QACA,cACA,KACA,cACmB;AACnB,QAAM,UAAU,IAAI,YAAY,GAAG;AACnC,QAAM,WAAqB;AAAA,IACzB,KAAK,IAAI;AAAA,IACT,UAAU,CAAC;AAAA,IACX,SAAS;AAAA,EACX;AAGA,MAAI,0BAA0B;AAG9B,MAAI,aAAuB,CAAC;AAG5B,QAAM,WAAW,IAAI,KAAK,WAAW,QAAQ,IAAI,KAAK,QAAQ;AAC9D,MAAI,UAAU;AACZ,iBAAa,qBAAqB,QAAQ,GAAwB;AAAA,EACpE;AAEA,iBAAI,KAAK,sBAAsB,YAAY;AAE3C,aAAW,EAAE,MAAM,SAAS,KAAK,cAAc;AAC7C,UAAM,SAAS,GAAG,gBAAgB;AAElC,eAAW,UAAU,UAAU;AAE7B,UAAI,kBAAkB,QAAQ,GAAG,GAAG;AAClC;AAAA,MACF;AAEA,YAAM,QAAQ,OAAO,SAAS;AAC9B,qBAAI,KAAK,6BAA6B,SAAS,MAAM;AAErD,gCAA0B;AAI1B,YAAM,eAAe,CAAC,WAAmB;AAEvC,YAAI,IAAI,aAAa,UAAU;AAC7B;AAAA,QACF;AAEA,cAAM,aAAa,GAAG,OAAO,iBAAiB;AAC9C,gBAAQ,IAAI,WAAW,QAAQ,IAAI,YAAY,CAAC;AAChD,gBAAQ,IAAI,SAAS,cAAc,QAAQ,IAAI,SAAS,eAAe,CAAC;AACxE,gBAAQ,IAAI,SAAS,YAAY,UAAU,IAAI;AAAA,MACjD;AAEA,mBAAa,SAAS;AAEtB,UAAI;AAEF,cAAM,OAAO,SAAS,OAAO;AAE7B,uBAAI,KAAK,oBAAoB,MAAM;AAGnC,qBAAa,WAAW;AAAA,MAC1B,SAAS,GAAP;AAEA,iBAAS,WAAW,SAAS,YAAY,CAAC;AAC1C,iBAAS,SAAS,KAAK,kBAAkB,GAAG;AAG5C,YAAI,OAAO,SAAS;AAClB,yBAAI,MAAM,kBAAkB,KAAK,MAAM;AACvC,mBAAS,SAAS;AAClB,iBAAO;AAAA,QACT,OAAO;AACL,yBAAI,KAAK,kBAAkB,KAAK,MAAM;AACtC,uBAAa,SAAS;AAAA,QACxB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,WAAS,UAAU;AAGnB,MAAI,CAAC,yBAAyB;AAC5B,mBAAI,KAAK,uCAAuC,YAAY;AAC5D,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,QAAQ;AAG5B,MAAI,UAAU;AACZ,uBAAmB,aAAkC,UAAU;AAAA,EACjE;AAGA,QAAM,UAAU,uBAAAC,QAAU,QAAQ,IAAI,QAAQ,WAAW;AAGzD,MAAI,QAAQ,SAAS,GAAG;AACtB,aAAS,YAAY;AAGrB,aAAS,QAAQ,OAAO,KAAK,KAAK,UAAU,OAAO,CAAC,EAAE,SAAS,QAAQ;AAAA,EACzE;AAGA,MAAI,SAAS,YAAY,SAAS,SAAS,SAAS,GAAG;AACrD,WAAO,SAAS;AAAA,EAClB;AAEA,iBAAI,MAAM,SAAS,YAAY;AAE/B,SAAO;AACT;;;ADxHO,IAAM,aAAN,MAAiB;AAAA,EAItB,YACmB,QACA,cACA,YACA,WACjB;AAJiB;AACA;AACA;AACA;AAGjB,SAAK,IAAI,IAAI,KAAK,MAAM;AAGxB,SAAK,IAAI,IAAI,eAAAC,QAAQ,KAAK,EAAE,OAAO,MAAM,CAAC,CAAC;AAG3C,SAAK,IAAI,IAAI,YAAY,KAAK,OAAO;AAGrC,SAAK,IAAI,KAAK,WAAW,KAAK,MAAM;AAEpC,QAAI,YAAY;AACd,cAAQ,KAAK,qBAAqB,YAAY;AAAA,IAChD;AAEA,QAAI,WAAW;AACb,cAAQ,KAAK,oBAAoB,WAAW;AAAA,IAC9C;AAAA,EACF;AAAA,EA5BiB,UAAM,eAAAA,SAAQ;AAAA,EACvB,UAAU;AAAA;AAAA,EA8BX,cAAc,CAAC,SAAiB;AACrC,QAAI,KAAK,SAAS;AAChB,YAAM,IAAI,MAAM,iFAAiF;AAAA,IACnG;AAGA,UAAM,UAAU;AAAA,MACd,KAAK,UAAAC,QAAG,aAAa,QAAQ,IAAI,gBAAgB,oBAAoB;AAAA,MACrE,MAAM,UAAAA,QAAG,aAAa,QAAQ,IAAI,iBAAiB,oBAAoB;AAAA,IACzE;AAGA,UAAM,SAAS,aAAAC,QAAM,aAAa,SAAS,KAAK,GAAG,EAAE,OAAO,IAAI;AAGhE,WAAO,GAAG,aAAa,MAAM;AAC3B,cAAQ,IAAI,4BAA4B,MAAM;AAE9C,WAAK,UAAU;AAAA,IACjB,CAAC;AAGD,WAAO,GAAG,SAAS,CAAC,MAAwB;AAC1C,UAAI,EAAE,SAAS,cAAc;AAC3B,gBAAQ;AAAA,UACN,mEAAmE,sCAAsC;AAAA,QAC3G;AACA,mBAAW,MAAM;AACf,iBAAO,MAAM;AACb,iBAAO,OAAO,IAAI;AAAA,QACpB,GAAG,GAAI;AAAA,MACT;AAAA,IACF,CAAC;AAGD,YAAQ,GAAG,WAAW,MAAM;AAC1B,cAAQ,IAAI,kCAAkC;AAC9C,aAAO,MAAM,MAAM;AACjB,gBAAQ,IAAI,eAAe;AAC3B,gBAAQ,KAAK,CAAC;AAAA,MAChB,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEQ,SAAS,CAAC,KAAsB,KAAuB,SAA+B;AAC5F,UAAM,YAAY,KAAK,IAAI;AAE3B,QAAI,GAAG,UAAU,MAAM;AACrB,YAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,YAAM,cAAc,KAAK,IAAI,IAAI;AACjC,YAAM,UAAU,IAAI,QAAQ,IAAI,UAAU,IAAI,gBAAgB,IAAI,eAAe;AAAA;AAEjF,UAAI,cAAc,MAAM,QAAQ,MAAM,OAAO,IAAI,QAAQ,KAAK,OAAO;AAAA,IACvE,CAAC;AAED,SAAK;AAAA,EACP;AAAA,EAEQ,UAAU,CAAC,KAAsB,QAA0B;AACjE,QAAI;AACF,UAAI,KAAK,IAAI;AAAA,IACf,SAAS,KAAP;AACA,cAAQ,MAAM,GAAG;AACjB,UAAI,OAAO,GAAG,EAAE,KAAK,uBAAuB;AAAA,IAC9C;AAAA,EACF;AAAA,EAEQ,SAAS,OAAO,KAAsB,QAA0B;AACtE,QAAI;AACF,YAAM,UAAmB,IAAI,MAAM,WAAY,CAAC;AAGhD,WAAK,cAAc,KAAK,WAAW,WAAW,CAAC,CAAC;AAEhD,YAAM,OAAO,SAAS,OAAO,IAAI,QAAQ,SAAS;AAClD,YAAM,YAAY,SAAS,aAAa;AACxC,YAAM,MAAM,SAAS,QAAQ,EAAE,OAAO,IAAI,SAAS,IAAI,MAAM,GAAG;AAChE,YAAM,SAAS,GAAG,QAAQ,OAAO,YAAY;AAE7C,qBAAI,KAAK,mBAAmB,IAAI,SAAS,IAAI,WAAW,IAAI,QAAQ,MAAM;AAG1E,YAAM,WAAW,MAAM,UAAU,KAAK,QAAQ,KAAK,cAAc,SAAS,MAAM;AAGhF,WAAK,aAAa,KAAK,UAAU,QAAQ;AAGzC,qBAAI,MAAM,UAAU,MAAM;AAG1B,UAAI,KAAK;AAAA,QACP,YAAY;AAAA,QACZ,MAAM;AAAA,QACN;AAAA,MACF,CAAC;AAAA,IACH,SAAS,KAAP;AACA,cAAQ,MAAM,GAAG;AACjB,UAAI,OAAO,GAAG,EAAE,KAAK,uBAAuB;AAAA,IAC9C;AAAA,EACF;AACF;;;ADvIA,IAAM,eAAe;AAAA,EACnB,YAAY,CAAC,eAAe,aAAa;AAAA,EACzC,QAAQ,CAAC,EAAE,YAAY,SAAS,CAAC;AACnC;AAiBO,IAAM,aAAN,MAAiB;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASR,YAAY,EAAE,aAAa,KAAK,GAAgB,eAA6B,CAAC,GAAG,OAA0B,CAAC,GAAG;AAC7G,UAAM,aAAuB,6BAAc,sBAAQ,MAAM,YAAY;AACrE,WAAO,cAAc;AAGrB,QAAI,QAAQ,IAAI,cAAc,SAAS;AACrC,cAAQ,OAAO,EAAE,aAAa,CAAC;AAC/B;AAAA,IACF;AAEA,SAAK,cAAc,IAAI,WAAW,QAAQ,cAAc,KAAK,YAAY,KAAK,SAAS;AAGvF,QAAI,KAAK,YAAY;AACnB;AAAA,IACF;AAEA,SAAK,MAAM;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,OAAO,KAAM;AACjB,SAAK,YAAY,YAAY,IAAI;AAAA,EACnC;AACF;",
4
+ "sourcesContent": ["import k8s from \"@kubernetes/client-node\";\nimport { StatusCodes as fetchStatus } from \"http-status-codes\";\nimport * as utils from \"ramda\";\nimport { Capability } from \"./lib/capability\";\nimport { fetch, fetchRaw } from \"./lib/fetch\";\nimport { RegisterKind, a } from \"./lib/k8s/index\";\nimport Log from \"./lib/logger\";\nimport { PeprModule } from \"./lib/module\";\nimport { PeprRequest } from \"./lib/request\";\nimport * as PeprUtils from \"./lib/utils\";\n\n// Import type information for external packages\nimport type * as KubernetesClientNode from \"@kubernetes/client-node\";\nimport type * as RamdaUtils from \"ramda\";\n\nexport {\n a,\n /** PeprModule is used to setup a complete Pepr Module: `new PeprModule(cfg, {...capabilities})` */\n PeprModule,\n PeprRequest,\n PeprUtils,\n RegisterKind,\n Capability,\n Log,\n utils,\n fetch,\n fetchRaw,\n fetchStatus,\n k8s,\n\n // Export the imported type information for external packages\n RamdaUtils,\n KubernetesClientNode,\n};\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\n/** a is a collection of K8s types to be used within a CapabilityAction: `When(a.Configmap)` */\nexport {\n V1APIService as APIService,\n V1CertificateSigningRequest as CertificateSigningRequest,\n V1ConfigMap as ConfigMap,\n V1ControllerRevision as ControllerRevision,\n V1CronJob as CronJob,\n V1CSIDriver as CSIDriver,\n V1CSIStorageCapacity as CSIStorageCapacity,\n V1CustomResourceDefinition as CustomResourceDefinition,\n V1DaemonSet as DaemonSet,\n V1Deployment as Deployment,\n V1EndpointSlice as EndpointSlice,\n V1HorizontalPodAutoscaler as HorizontalPodAutoscaler,\n V1Ingress as Ingress,\n V1IngressClass as IngressClass,\n V1Job as Job,\n V1LimitRange as LimitRange,\n V1LocalSubjectAccessReview as LocalSubjectAccessReview,\n V1MutatingWebhookConfiguration as MutatingWebhookConfiguration,\n V1Namespace as Namespace,\n V1NetworkPolicy as NetworkPolicy,\n V1Node as Node,\n V1PersistentVolume as PersistentVolume,\n V1PersistentVolumeClaim as PersistentVolumeClaim,\n V1Pod as Pod,\n V1PodDisruptionBudget as PodDisruptionBudget,\n V1PodTemplate as PodTemplate,\n V1ReplicaSet as ReplicaSet,\n V1ReplicationController as ReplicationController,\n V1ResourceQuota as ResourceQuota,\n V1RuntimeClass as RuntimeClass,\n V1Secret as Secret,\n V1SelfSubjectAccessReview as SelfSubjectAccessReview,\n V1SelfSubjectRulesReview as SelfSubjectRulesReview,\n V1Service as Service,\n V1ServiceAccount as ServiceAccount,\n V1StatefulSet as StatefulSet,\n V1StorageClass as StorageClass,\n V1SubjectAccessReview as SubjectAccessReview,\n V1TokenReview as TokenReview,\n V1ValidatingWebhookConfiguration as ValidatingWebhookConfiguration,\n V1VolumeAttachment as VolumeAttachment,\n} from \"@kubernetes/client-node\";\n\nexport { GenericKind } from \"./types\";\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { V1ListMeta, V1ObjectMeta } from \"@kubernetes/client-node\";\n\nexport enum Operation {\n CREATE = \"CREATE\",\n UPDATE = \"UPDATE\",\n DELETE = \"DELETE\",\n CONNECT = \"CONNECT\",\n}\n\nexport interface KubernetesObject {\n apiVersion?: string;\n kind?: string;\n metadata?: V1ObjectMeta;\n}\nexport interface KubernetesListObject<T extends KubernetesObject> {\n apiVersion?: string;\n kind?: string;\n metadata?: V1ListMeta;\n items: T[];\n}\n\n/**\n * GenericKind is a generic Kubernetes object that can be used to represent any Kubernetes object\n * that is not explicitly supported by Pepr. This can be used on its own or as a base class for\n * other types. See the examples in `HelloPepr.ts` for more information.\n */\nexport class GenericKind {\n apiVersion?: string;\n kind?: string;\n metadata?: V1ObjectMeta;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [key: string]: any;\n}\n\n/**\n * GroupVersionKind unambiguously identifies a kind. It doesn't anonymously include GroupVersion\n * to avoid automatic coercion. It doesn't use a GroupVersion to avoid custom marshalling\n **/\nexport interface GroupVersionKind {\n /** The K8s resource kind, e..g \"Pod\". */\n readonly kind: string;\n readonly group: string;\n readonly version?: string;\n /** Optional, override the plural name for use in Webhook rules generation */\n readonly plural?: string;\n}\n\n/**\n * GroupVersionResource unambiguously identifies a resource. It doesn't anonymously include GroupVersion\n * to avoid automatic coercion. It doesn't use a GroupVersion to avoid custom marshalling\n */\nexport interface GroupVersionResource {\n readonly group: string;\n readonly version: string;\n readonly resource: string;\n}\n\n/**\n * A Kubernetes admission request to be processed by a capability.\n */\nexport interface Request<T = KubernetesObject> {\n /** UID is an identifier for the individual request/response. */\n readonly uid: string;\n\n /** Kind is the fully-qualified type of object being submitted (for example, v1.Pod or autoscaling.v1.Scale) */\n readonly kind: GroupVersionKind;\n\n /** Resource is the fully-qualified resource being requested (for example, v1.pods) */\n readonly resource: GroupVersionResource;\n\n /** SubResource is the sub-resource being requested, if any (for example, \"status\" or \"scale\") */\n readonly subResource?: string;\n\n /** RequestKind is the fully-qualified type of the original API request (for example, v1.Pod or autoscaling.v1.Scale). */\n readonly requestKind?: GroupVersionKind;\n\n /** RequestResource is the fully-qualified resource of the original API request (for example, v1.pods). */\n readonly requestResource?: GroupVersionResource;\n\n /** RequestSubResource is the sub-resource of the original API request, if any (for example, \"status\" or \"scale\"). */\n readonly requestSubResource?: string;\n\n /**\n * Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and\n * rely on the server to generate the name. If that is the case, this method will return the empty string.\n */\n readonly name: string;\n\n /** Namespace is the namespace associated with the request (if any). */\n readonly namespace?: string;\n\n /**\n * Operation is the operation being performed. This may be different than the operation\n * requested. e.g. a patch can result in either a CREATE or UPDATE Operation.\n */\n readonly operation: Operation;\n\n /** UserInfo is information about the requesting user */\n readonly userInfo: {\n /** The name that uniquely identifies this user among all active users. */\n username?: string;\n\n /**\n * A unique value that identifies this user across time. If this user is deleted\n * and another user by the same name is added, they will have different UIDs.\n */\n uid?: string;\n\n /** The names of groups this user is a part of. */\n groups?: string[];\n\n /** Any additional information provided by the authenticator. */\n extra?: {\n [key: string]: string[];\n };\n };\n\n /** Object is the object from the incoming request prior to default values being applied */\n readonly object: T;\n\n /** OldObject is the existing object. Only populated for UPDATE requests. */\n readonly oldObject?: T;\n\n /** DryRun indicates that modifications will definitely not be persisted for this request. Defaults to false. */\n readonly dryRun?: boolean;\n\n /**\n * Options contains the options for the operation being performed.\n * e.g. `meta.k8s.io/v1.DeleteOptions` or `meta.k8s.io/v1.CreateOptions`. This may be\n * different than the options the caller provided. e.g. for a patch request the performed\n * Operation might be a CREATE, in which case the Options will a\n * `meta.k8s.io/v1.CreateOptions` even though the caller provided `meta.k8s.io/v1.PatchOptions`.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly options?: any;\n}\n\nexport interface Response {\n /** UID is an identifier for the individual request/response. This must be copied over from the corresponding AdmissionRequest. */\n uid: string;\n\n /** Allowed indicates whether or not the admission request was permitted. */\n allowed: boolean;\n\n /** Result contains extra details into why an admission request was denied. This field IS NOT consulted in any way if \"Allowed\" is \"true\". */\n result?: string;\n\n /** The patch body. Currently we only support \"JSONPatch\" which implements RFC 6902. */\n patch?: string;\n\n /** The type of Patch. Currently we only allow \"JSONPatch\". */\n patchType?: \"JSONPatch\";\n\n /** AuditAnnotations is an unstructured key value map set by remote admission controller (e.g. error=image-blacklisted). */\n auditAnnotations?: {\n [key: string]: string;\n };\n\n /** warnings is a list of warning messages to return to the requesting API client. */\n warnings?: string[];\n}\n\nexport type WebhookIgnore = {\n /**\n * List of Kubernetes namespaces to always ignore.\n * Any resources in these namespaces will be ignored by Pepr.\n *\n * Note: `kube-system` and `pepr-system` are always ignored.\n */\n namespaces?: string[];\n /**\n * List of Kubernetes labels to always ignore.\n * Any resources with these labels will be ignored by Pepr.\n *\n * The example below will ignore any resources with the label `my-label=ulta-secret`:\n * ```\n * alwaysIgnore:\n * labels: [{ \"my-label\": \"ultra-secret\" }]\n * ```\n */\n labels?: Record<string, string>[];\n};\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { GenericClass } from \"../types\";\nimport { GroupVersionKind } from \"./types\";\n\nexport const gvkMap: Record<string, GroupVersionKind> = {\n /**\n * Represents a K8s ConfigMap resource.\n * ConfigMap holds configuration data for pods to consume.\n * @see {@link https://kubernetes.io/docs/concepts/configuration/configmap/}\n */\n V1ConfigMap: {\n kind: \"ConfigMap\",\n version: \"v1\",\n group: \"\",\n },\n\n /**\n * Represents a K8s Endpoints resource.\n * Endpoints expose a service's IP addresses and ports to other resources.\n * @see {@link https://kubernetes.io/docs/concepts/services-networking/service/#endpoints}\n */\n V1Endpoint: {\n kind: \"Endpoints\",\n version: \"v1\",\n group: \"\",\n plural: \"endpoints\",\n },\n\n /**\n * Represents a K8s LimitRange resource.\n * LimitRange enforces constraints on the resource consumption of objects in a namespace.\n * @see {@link https://kubernetes.io/docs/concepts/policy/limit-range/}\n */\n V1LimitRange: {\n kind: \"LimitRange\",\n version: \"v1\",\n group: \"\",\n },\n\n /**\n * Represents a K8s Namespace resource.\n * Namespace is a way to divide cluster resources between multiple users.\n * @see {@link https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/}\n */\n V1Namespace: {\n kind: \"Namespace\",\n version: \"v1\",\n group: \"\",\n },\n\n /**\n * Represents a K8s Node resource.\n * Node is a worker machine in Kubernetes.\n * @see {@link https://kubernetes.io/docs/concepts/architecture/nodes/}\n */\n V1Node: {\n kind: \"Node\",\n version: \"v1\",\n group: \"\",\n },\n\n /**\n * Represents a K8s PersistentVolumeClaim resource.\n * PersistentVolumeClaim is a user's request for and claim to a persistent volume.\n * @see {@link https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistentvolumeclaims}\n */\n V1PersistentVolumeClaim: {\n kind: \"PersistentVolumeClaim\",\n version: \"v1\",\n group: \"\",\n },\n\n /**\n * Represents a K8s PersistentVolume resource.\n * PersistentVolume is a piece of storage in the cluster that has been provisioned by an administrator.\n * @see {@link https://kubernetes.io/docs/concepts/storage/persistent-volumes/}\n */\n V1PersistentVolume: {\n kind: \"PersistentVolume\",\n version: \"v1\",\n group: \"\",\n },\n\n /**\n * Represents a K8s Pod resource.\n * Pod is the smallest and simplest unit in the Kubernetes object model.\n * @see {@link https://kubernetes.io/docs/concepts/workloads/pods/}\n */\n V1Pod: {\n kind: \"Pod\",\n version: \"v1\",\n group: \"\",\n },\n /**\n * Represents a K8s PodTemplate resource.\n * PodTemplate is an object that describes the pod that will be created from a higher level abstraction.\n * @see {@link https://kubernetes.io/docs/concepts/workloads/controllers/#pod-template}\n */\n V1PodTemplate: {\n kind: \"PodTemplate\",\n version: \"v1\",\n group: \"\",\n },\n\n /**\n * Represents a K8s ReplicationController resource.\n * ReplicationController ensures that a specified number of pod replicas are running at any given time.\n * @see {@link https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/}\n */\n V1ReplicationController: {\n kind: \"ReplicationController\",\n version: \"v1\",\n group: \"\",\n },\n\n /**\n * Represents a K8s ResourceQuota resource.\n * ResourceQuota provides constraints that limit resource consumption per namespace.\n * @see {@link https://kubernetes.io/docs/concepts/policy/resource-quotas/}\n */\n V1ResourceQuota: {\n kind: \"ResourceQuota\",\n version: \"v1\",\n group: \"\",\n },\n\n /**\n * Represents a K8s Secret resource.\n * Secret holds secret data of a certain type.\n * @see {@link https://kubernetes.io/docs/concepts/configuration/secret/}\n */\n V1Secret: {\n kind: \"Secret\",\n version: \"v1\",\n group: \"\",\n },\n\n /**\n * Represents a K8s ServiceAccount resource.\n * ServiceAccount is an identity that processes in a pod can use to access the Kubernetes API.\n * @see {@link https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/}\n */\n V1ServiceAccount: {\n kind: \"ServiceAccount\",\n version: \"v1\",\n group: \"\",\n },\n\n /**\n * Represents a K8s Service resource.\n * Service is an abstraction which defines a logical set of Pods and a policy by which to access them.\n * @see {@link https://kubernetes.io/docs/concepts/services-networking/service/}\n */\n V1Service: {\n kind: \"Service\",\n version: \"v1\",\n group: \"\",\n },\n\n /**\n * Represents a K8s MutatingWebhookConfiguration resource.\n * MutatingWebhookConfiguration configures a mutating admission webhook.\n * @see {@link https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#configure-admission-webhooks-on-the-fly}\n */\n V1MutatingWebhookConfiguration: {\n kind: \"MutatingWebhookConfiguration\",\n version: \"v1\",\n group: \"admissionregistration.k8s.io\",\n },\n\n /**\n * Represents a K8s ValidatingWebhookConfiguration resource.\n * ValidatingWebhookConfiguration configures a validating admission webhook.\n * @see {@link https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#configure-admission-webhooks-on-the-fly}\n */\n V1ValidatingWebhookConfiguration: {\n kind: \"ValidatingWebhookConfiguration\",\n version: \"v1\",\n group: \"admissionregistration.k8s.io\",\n },\n /**\n * Represents a K8s CustomResourceDefinition resource.\n * CustomResourceDefinition is a custom resource in a Kubernetes cluster.\n * @see {@link https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/}\n */\n V1CustomResourceDefinition: {\n kind: \"CustomResourceDefinition\",\n version: \"v1\",\n group: \"apiextensions.k8s.io\",\n },\n\n /**\n * Represents a K8s APIService resource.\n * APIService represents a server for a particular API version and group.\n * @see {@link https://kubernetes.io/docs/tasks/access-kubernetes-api/setup-extension-api-server/}\n */\n V1APIService: {\n kind: \"APIService\",\n version: \"v1\",\n group: \"apiregistration.k8s.io\",\n },\n\n /**\n * Represents a K8s ControllerRevision resource.\n * ControllerRevision is used to manage the history of a StatefulSet or DaemonSet.\n * @see {@link https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#revision-history}\n */\n V1ControllerRevision: {\n kind: \"ControllerRevision\",\n version: \"v1\",\n group: \"apps\",\n },\n\n /**\n * Represents a K8s DaemonSet resource.\n * DaemonSet ensures that all (or some) nodes run a copy of a Pod.\n * @see {@link https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/}\n */\n V1DaemonSet: {\n kind: \"DaemonSet\",\n version: \"v1\",\n group: \"apps\",\n },\n\n /**\n * Represents a K8s Deployment resource.\n * Deployment provides declarative updates for Pods and ReplicaSets.\n * @see {@link https://kubernetes.io/docs/concepts/workloads/controllers/deployment/}\n */\n V1Deployment: {\n kind: \"Deployment\",\n version: \"v1\",\n group: \"apps\",\n },\n\n /**\n * Represents a K8s ReplicaSet resource.\n * ReplicaSet ensures that a specified number of pod replicas are running at any given time.\n * @see {@link https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/}\n */\n V1ReplicaSet: {\n kind: \"ReplicaSet\",\n version: \"v1\",\n group: \"apps\",\n },\n\n /**\n * Represents a K8s StatefulSet resource.\n * StatefulSet is used to manage stateful applications.\n * @see {@link https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/}\n */\n V1StatefulSet: {\n kind: \"StatefulSet\",\n version: \"v1\",\n group: \"apps\",\n },\n\n /**\n * Represents a K8s TokenReview resource.\n * TokenReview attempts to authenticate a token to a known user.\n * @see {@link https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#tokenreview-v1-authentication-k8s-io}\n */\n V1TokenReview: {\n kind: \"TokenReview\",\n version: \"v1\",\n group: \"authentication.k8s.io\",\n },\n\n /**\n * Represents a K8s LocalSubjectAccessReview resource.\n * LocalSubjectAccessReview checks whether a specific user can perform a specific action in a specific namespace.\n * @see {@link https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#localsubjectaccessreview-v1-authorization-k8s-io}\n */\n V1LocalSubjectAccessReview: {\n kind: \"LocalSubjectAccessReview\",\n version: \"v1\",\n group: \"authorization.k8s.io\",\n },\n\n /**\n * Represents a K8s SelfSubjectAccessReview resource.\n * SelfSubjectAccessReview checks whether the current user can perform a specific action.\n * @see {@link https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#selfsubjectaccessreview-v1-authorization-k8s-io}\n */\n V1SelfSubjectAccessReview: {\n kind: \"SelfSubjectAccessReview\",\n version: \"v1\",\n group: \"authorization.k8s.io\",\n },\n\n /**\n * Represents a K8s SelfSubjectRulesReview resource.\n * SelfSubjectRulesReview lists the permissions a specific user has within a namespace.\n * @see {@link https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#selfsubjectrulesreview-v1-authorization-k8s-io}\n */\n V1SelfSubjectRulesReview: {\n kind: \"SelfSubjectRulesReview\",\n version: \"v1\",\n group: \"authorization.k8s.io\",\n },\n\n /**\n * Represents a K8s SubjectAccessReview resource.\n * SubjectAccessReview checks whether a specific user can perform a specific action.\n * @see {@link https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#subjectaccessreview-v1-authorization-k8s-io}\n */\n V1SubjectAccessReview: {\n kind: \"SubjectAccessReview\",\n version: \"v1\",\n group: \"authorization.k8s.io\",\n },\n\n /**\n * Represents a K8s HorizontalPodAutoscaler resource.\n * HorizontalPodAutoscaler automatically scales the number of Pods in a replication controller, deployment, or replica set.\n * @see {@link https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/}\n */\n V1HorizontalPodAutoscaler: {\n kind: \"HorizontalPodAutoscaler\",\n version: \"v2\",\n group: \"autoscaling\",\n },\n\n /**\n * Represents a K8s CronJob resource.\n * CronJob manages time-based jobs, specifically those that run periodically and complete after a successful execution.\n * @see {@link https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/}\n */\n V1CronJob: {\n kind: \"CronJob\",\n version: \"v1\",\n group: \"batch\",\n },\n\n /**\n * Represents a K8s Job resource.\n * Job represents the configuration of a single job.\n * @see {@link https://kubernetes.io/docs/concepts/workloads/controllers/job/}\n */\n V1Job: {\n kind: \"Job\",\n version: \"v1\",\n group: \"batch\",\n },\n\n /**\n * Represents a K8s CertificateSigningRequest resource.\n * CertificateSigningRequest represents a certificate signing request.\n * @see {@link https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/}\n */\n V1CertificateSigningRequest: {\n kind: \"CertificateSigningRequest\",\n version: \"v1\",\n group: \"certificates.k8s.io\",\n },\n\n /**\n * Represents a K8s EndpointSlice resource.\n * EndpointSlice represents a scalable set of network endpoints for a Kubernetes Service.\n * @see {@link https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/}\n */\n V1EndpointSlice: {\n kind: \"EndpointSlice\",\n version: \"v1\",\n group: \"discovery.k8s.io\",\n },\n\n /**\n * Represents a K8s IngressClass resource.\n * IngressClass represents the class of the Ingress, referenced by the Ingress spec.\n * @see {@link https://kubernetes.io/docs/concepts/services-networking/ingress/}\n */\n V1IngressClass: {\n kind: \"IngressClass\",\n version: \"v1\",\n group: \"networking.k8s.io\",\n },\n\n /**\n * Represents a K8s Ingress resource.\n * Ingress exposes HTTP and HTTPS routes from outside the cluster to services within the cluster.\n * @see {@link https://kubernetes.io/docs/concepts/services-networking/ingress/}\n */\n V1Ingress: {\n kind: \"Ingress\",\n version: \"v1\",\n group: \"networking.k8s.io\",\n plural: \"ingresses\",\n },\n\n /**\n * Represents a K8s NetworkPolicy resource.\n * NetworkPolicy defines a set of rules for how pods communicate with each other.\n * @see {@link https://kubernetes.io/docs/concepts/services-networking/network-policies/}\n */\n V1NetworkPolicy: {\n kind: \"NetworkPolicy\",\n version: \"v1\",\n group: \"networking.k8s.io\",\n },\n\n /**\n * Represents a K8s RuntimeClass resource.\n * RuntimeClass is a cluster-scoped resource that surfaces container runtime properties to the control plane.\n * @see {@link https://kubernetes.io/docs/concepts/containers/runtime-class/}\n */\n V1RuntimeClass: {\n kind: \"RuntimeClass\",\n version: \"v1\",\n group: \"node.k8s.io\",\n },\n\n /**\n * Represents a K8s PodDisruptionBudget resource.\n * PodDisruptionBudget is an API object that limits the number of pods of a replicated application that are down simultaneously.\n * @see {@link https://kubernetes.io/docs/concepts/workloads/pods/disruptions/}\n */\n V1PodDisruptionBudget: {\n kind: \"PodDisruptionBudget\",\n version: \"v1\",\n group: \"policy\",\n },\n\n /**\n * Represents a K8s VolumeAttachment resource.\n * VolumeAttachment captures the intent to attach or detach the specified volume to/from the specified node.\n * @see {@link https://kubernetes.io/docs/concepts/storage/storage-classes/}\n */\n V1VolumeAttachment: {\n kind: \"VolumeAttachment\",\n version: \"v1\",\n group: \"storage.k8s.io\",\n },\n\n /**\n * Represents a K8s CSIDriver resource.\n * CSIDriver captures information about a Container Storage Interface (CSI) volume driver.\n * @see {@link https://kubernetes.io/docs/concepts/storage/volumes/}\n */\n V1CSIDriver: {\n kind: \"CSIDriver\",\n version: \"v1\",\n group: \"storage.k8s.io\",\n },\n\n /**\n * Represents a K8s CSIStorageCapacity resource.\n * CSIStorageCapacity stores the reported storage capacity of a CSI node or storage class.\n * @see {@link https://kubernetes.io/docs/concepts/storage/csi/}\n */\n V1CSIStorageCapacity: {\n kind: \"CSIStorageCapacity\",\n version: \"v1\",\n group: \"storage.k8s.io\",\n },\n\n /**\n * Represents a K8s StorageClass resource.\n * StorageClass is a cluster-scoped resource that provides a way for administrators to describe the classes of storage they offer.\n * @see {@link https://kubernetes.io/docs/concepts/storage/storage-classes/}\n */\n V1StorageClass: {\n kind: \"StorageClass\",\n version: \"v1\",\n group: \"storage.k8s.io\",\n },\n};\n\nexport function modelToGroupVersionKind(key: string): GroupVersionKind {\n return gvkMap[key];\n}\n\n/**\n * Registers a new model and GroupVersionKind with Pepr for use with `When(a.<Kind>)`\n *\n * @param model Used to match the GroupVersionKind and define the type-data for the request\n * @param groupVersionKind Contains the match parameters to determine the request should be handled\n */\nexport const RegisterKind = (model: GenericClass, groupVersionKind: GroupVersionKind) => {\n const name = model.name;\n\n // Do not allow overwriting existing GVKs\n if (gvkMap[name]) {\n throw new Error(`GVK ${name} already registered`);\n }\n\n // Set the GVK\n gvkMap[name] = groupVersionKind;\n};\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\n/**\n * Enumeration representing different logging levels.\n */\nexport enum LogLevel {\n debug = 0,\n info = 1,\n warn = 2,\n error = 3,\n}\n\nenum ConsoleColors {\n Reset = \"\\x1b[0m\",\n Bright = \"\\x1b[1m\",\n Dim = \"\\x1b[2m\",\n Underscore = \"\\x1b[4m\",\n Blink = \"\\x1b[5m\",\n Reverse = \"\\x1b[7m\",\n Hidden = \"\\x1b[8m\",\n\n FgBlack = \"\\x1b[30m\",\n FgRed = \"\\x1b[31m\",\n FgGreen = \"\\x1b[32m\",\n FgYellow = \"\\x1b[33m\",\n FgBlue = \"\\x1b[34m\",\n FgMagenta = \"\\x1b[35m\",\n FgCyan = \"\\x1b[36m\",\n FgWhite = \"\\x1b[37m\",\n\n BgBlack = \"\\x1b[40m\",\n BgRed = \"\\x1b[41m\",\n BgGreen = \"\\x1b[42m\",\n BgYellow = \"\\x1b[43m\",\n BgBlue = \"\\x1b[44m\",\n BgMagenta = \"\\x1b[45m\",\n BgCyan = \"\\x1b[46m\",\n BgWhite = \"\\x1b[47m\",\n}\n\n/**\n * Simple logger class that logs messages at different log levels.\n */\nexport class Logger {\n private _logLevel: LogLevel;\n\n /**\n * Create a new logger instance.\n * @param logLevel - The minimum log level to log messages for.\n */\n constructor(logLevel: LogLevel) {\n this._logLevel = logLevel;\n }\n\n /**\n * Change the log level of the logger.\n * @param logLevel - The log level to log the message at.\n */\n public SetLogLevel(logLevel: string): void {\n this._logLevel = LogLevel[logLevel as keyof typeof LogLevel];\n this.debug(`Log level set to ${logLevel}`);\n }\n\n /**\n * Log a debug message.\n * @param message - The message to log.\n */\n public debug<T>(message: T, prefix?: string): void {\n this.log(LogLevel.debug, message, prefix);\n }\n\n /**\n * Log an info message.\n * @param message - The message to log.\n */\n public info<T>(message: T, prefix?: string): void {\n this.log(LogLevel.info, message, prefix);\n }\n\n /**\n * Log a warning message.\n * @param message - The message to log.\n */\n public warn<T>(message: T, prefix?: string): void {\n this.log(LogLevel.warn, message, prefix);\n }\n\n /**\n * Log an error message.\n * @param message - The message to log.\n */\n public error<T>(message: T, prefix?: string): void {\n this.log(LogLevel.error, message, prefix);\n }\n\n /**\n * Log a message at the specified log level.\n * @param logLevel - The log level of the message.\n * @param message - The message to log.\n */\n private log<T>(logLevel: LogLevel, message: T, callerPrefix = \"\"): void {\n const color = {\n [LogLevel.debug]: ConsoleColors.FgBlack,\n [LogLevel.info]: ConsoleColors.FgCyan,\n [LogLevel.warn]: ConsoleColors.FgYellow,\n [LogLevel.error]: ConsoleColors.FgRed,\n };\n\n if (logLevel >= this._logLevel) {\n // Prefix the message with the colored log level.\n let prefix = \"[\" + LogLevel[logLevel] + \"]\\t\" + callerPrefix;\n\n prefix = this.colorize(prefix, color[logLevel]);\n\n // If the message is not a string, use the debug method to log the object.\n if (typeof message !== \"string\") {\n console.log(prefix);\n console.debug(\"%o\", message);\n } else {\n console.log(prefix + \"\\t\" + message);\n }\n }\n }\n\n private colorize(text: string, color: ConsoleColors): string {\n return color + text + ConsoleColors.Reset;\n }\n}\n\n/** Log is an instance of Logger used to generate log entries. */\nconst Log = new Logger(LogLevel.info);\nif (process.env.LOG_LEVEL) {\n Log.SetLogLevel(process.env.LOG_LEVEL);\n}\nexport default Log;\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { modelToGroupVersionKind } from \"./k8s/index\";\nimport { GroupVersionKind } from \"./k8s/types\";\nimport logger from \"./logger\";\nimport {\n BindToAction,\n Binding,\n BindingFilter,\n BindingWithName,\n CapabilityAction,\n CapabilityCfg,\n DeepPartial,\n Event,\n GenericClass,\n HookPhase,\n WhenSelector,\n} from \"./types\";\n\n/**\n * A capability is a unit of functionality that can be registered with the Pepr runtime.\n */\nexport class Capability implements CapabilityCfg {\n private _name: string;\n private _description: string;\n private _namespaces?: string[] | undefined;\n\n // Currently everything is considered a mutation\n private _mutateOrValidate = HookPhase.mutate;\n\n private _bindings: Binding[] = [];\n\n get bindings(): Binding[] {\n return this._bindings;\n }\n\n get name() {\n return this._name;\n }\n\n get description() {\n return this._description;\n }\n\n get namespaces() {\n return this._namespaces || [];\n }\n\n get mutateOrValidate() {\n return this._mutateOrValidate;\n }\n\n constructor(cfg: CapabilityCfg) {\n this._name = cfg.name;\n this._description = cfg.description;\n this._namespaces = cfg.namespaces;\n logger.info(`Capability ${this._name} registered`);\n logger.debug(cfg);\n }\n\n /**\n * The When method is used to register a capability action to be executed when a Kubernetes resource is\n * processed by Pepr. The action will be executed if the resource matches the specified kind and any\n * filters that are applied.\n *\n * @param model the KubernetesObject model to match\n * @param kind if using a custom KubernetesObject not available in `a.*`, specify the GroupVersionKind\n * @returns\n */\n When = <T extends GenericClass>(model: T, kind?: GroupVersionKind): WhenSelector<T> => {\n const matchedKind = modelToGroupVersionKind(model.name);\n\n // If the kind is not specified and the model is not a KubernetesObject, throw an error\n if (!matchedKind && !kind) {\n throw new Error(`Kind not specified for ${model.name}`);\n }\n\n const binding: Binding = {\n // If the kind is not specified, use the matched kind from the model\n kind: kind || matchedKind,\n event: Event.Any,\n filters: {\n name: \"\",\n namespaces: [],\n labels: {},\n annotations: {},\n },\n callback: () => undefined,\n };\n\n const prefix = `${this._name}: ${model.name}`;\n\n logger.info(`Binding created`, prefix);\n\n const Then = (cb: CapabilityAction<T>): BindToAction<T> => {\n logger.info(`Binding action created`, prefix);\n logger.debug(cb.toString(), prefix);\n // Push the binding to the list of bindings for this capability as a new BindingAction\n // with the callback function to preserve\n this._bindings.push({\n ...binding,\n callback: cb,\n });\n\n // Now only allow adding actions to the same binding\n return { Then };\n };\n\n const ThenSet = (merge: DeepPartial<InstanceType<T>>): BindToAction<T> => {\n // Add the new action to the binding\n Then(req => req.Merge(merge));\n\n return { Then };\n };\n\n function InNamespace(...namespaces: string[]): BindingWithName<T> {\n logger.debug(`Add namespaces filter ${namespaces}`, prefix);\n binding.filters.namespaces.push(...namespaces);\n return { WithLabel, WithAnnotation, WithName, Then, ThenSet };\n }\n\n function WithName(name: string): BindingFilter<T> {\n logger.debug(`Add name filter ${name}`, prefix);\n binding.filters.name = name;\n return { WithLabel, WithAnnotation, Then, ThenSet };\n }\n\n function WithLabel(key: string, value = \"\"): BindingFilter<T> {\n logger.debug(`Add label filter ${key}=${value}`, prefix);\n binding.filters.labels[key] = value;\n return { WithLabel, WithAnnotation, Then, ThenSet };\n }\n\n const WithAnnotation = (key: string, value = \"\"): BindingFilter<T> => {\n logger.debug(`Add annotation filter ${key}=${value}`, prefix);\n binding.filters.annotations[key] = value;\n return { WithLabel, WithAnnotation, Then, ThenSet };\n };\n\n const bindEvent = (event: Event) => {\n binding.event = event;\n return {\n InNamespace,\n Then,\n ThenSet,\n WithAnnotation,\n WithLabel,\n WithName,\n };\n };\n\n return {\n IsCreatedOrUpdated: () => bindEvent(Event.CreateOrUpdate),\n IsCreated: () => bindEvent(Event.Create),\n IsUpdated: () => bindEvent(Event.Update),\n IsDeleted: () => bindEvent(Event.Delete),\n };\n };\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { StatusCodes } from \"http-status-codes\";\nimport f, { FetchError, RequestInfo, RequestInit } from \"node-fetch\";\nimport logger from \"./logger\";\n\nexport const fetchRaw = f;\n\nexport type FetchResponse<T> = {\n data: T;\n ok: boolean;\n status: number;\n statusText: string;\n};\n\n/**\n * Perform an async HTTP call and return the parsed JSON response, optionally\n * as a specific type.\n *\n * @example\n * ```ts\n * fetch<string[]>(\"https://example.com/api/foo\");\n * ```\n *\n * @param url The URL or Request object to fetch\n * @param init Additional options for the request\n * @returns\n */\nexport async function fetch<T>(url: URL | RequestInfo, init?: RequestInit): Promise<FetchResponse<T>> {\n let data = undefined as unknown as T;\n try {\n logger.debug(`Fetching ${url}`);\n\n const resp = await fetchRaw(url, init);\n const contentType = resp.headers.get(\"content-type\") || \"\";\n\n if (resp.ok) {\n // Parse the response as JSON if the content type is JSON\n if (contentType.includes(\"application/json\")) {\n data = await resp.json();\n } else {\n // Otherwise, return however the response was read\n data = (await resp.text()) as unknown as T;\n }\n }\n\n return {\n data,\n ok: resp.ok,\n status: resp.status,\n statusText: resp.statusText,\n };\n } catch (e) {\n if (e instanceof FetchError) {\n logger.debug(`Fetch failed: ${e instanceof Error ? e.message : e}`);\n\n // Parse the error code from the FetchError or default to 400 (Bad Request)\n const status = parseInt(e.code || \"400\");\n\n return {\n data,\n ok: false,\n status,\n statusText: e.message,\n };\n }\n\n return {\n data,\n ok: false,\n status: StatusCodes.BAD_REQUEST,\n statusText: \"Unknown error\",\n };\n }\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { concat, mergeDeepWith } from \"ramda\";\n\nimport { Capability } from \"./capability\";\nimport { Controller } from \"./controller\";\nimport { Request, Response } from \"./k8s/types\";\nimport { ModuleConfig } from \"./types\";\n\nconst alwaysIgnore = {\n namespaces: [\"kube-system\", \"pepr-system\"],\n labels: [{ \"pepr.dev\": \"ignore\" }],\n};\n\nexport type PackageJSON = {\n description: string;\n pepr: ModuleConfig;\n};\n\nexport type PeprModuleOptions = {\n deferStart?: boolean;\n\n /** A user-defined callback to pre-process or intercept a Pepr request from K8s immediately before it is processed */\n beforeHook?: (req: Request) => void;\n\n /** A user-defined callback to post-process or intercept a Pepr response just before it is returned to K8s */\n afterHook?: (res: Response) => void;\n};\n\nexport class PeprModule {\n private _controller!: Controller;\n\n /**\n * Create a new Pepr runtime\n *\n * @param config The configuration for the Pepr runtime\n * @param capabilities The capabilities to be loaded into the Pepr runtime\n * @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()`.\n */\n constructor({ description, pepr }: PackageJSON, capabilities: Capability[] = [], opts: PeprModuleOptions = {}) {\n const config: ModuleConfig = mergeDeepWith(concat, pepr, alwaysIgnore);\n config.description = description;\n\n // Handle build mode\n if (process.env.PEPR_MODE === \"build\") {\n process.send?.({ capabilities });\n return;\n }\n\n this._controller = new Controller(config, capabilities, opts.beforeHook, opts.afterHook);\n\n // Stop processing if deferStart is set to true\n if (opts.deferStart) {\n return;\n }\n\n this.start();\n }\n\n /**\n * Start the Pepr runtime manually.\n * Normally this is called automatically when the Pepr module is instantiated, but can be called manually if `deferStart` is set to `true` in the constructor.\n *\n * @param port\n */\n start(port = 3000) {\n this._controller.startServer(port);\n }\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport express from \"express\";\nimport fs from \"fs\";\nimport https from \"https\";\n\nimport { Capability } from \"./capability\";\nimport { Request, Response } from \"./k8s/types\";\nimport Log from \"./logger\";\nimport { processor } from \"./processor\";\nimport { ModuleConfig } from \"./types\";\n\nexport class Controller {\n private readonly app = express();\n private running = false;\n\n // The token used to authenticate requests\n private token = \"\";\n\n constructor(\n private readonly config: ModuleConfig,\n private readonly capabilities: Capability[],\n private readonly beforeHook?: (req: Request) => void,\n private readonly afterHook?: (res: Response) => void\n ) {\n // Middleware for logging requests\n this.app.use(this.logger);\n\n // Middleware for parsing JSON, limit to 2mb vs 100K for K8s compatibility\n this.app.use(express.json({ limit: \"2mb\" }));\n\n // Health check endpoint\n this.app.get(\"/healthz\", this.healthz);\n\n // Mutate endpoint\n this.app.post(\"/mutate/:token\", this.mutate);\n\n if (beforeHook) {\n console.info(`Using beforeHook: ${beforeHook}`);\n }\n\n if (afterHook) {\n console.info(`Using afterHook: ${afterHook}`);\n }\n }\n\n /** Start the webhook server */\n public startServer = (port: number) => {\n if (this.running) {\n throw new Error(\"Cannot start Pepr module: Pepr module was not instantiated with deferStart=true\");\n }\n\n // Load SSL certificate and key\n const options = {\n key: fs.readFileSync(process.env.SSL_KEY_PATH || \"/etc/certs/tls.key\"),\n cert: fs.readFileSync(process.env.SSL_CERT_PATH || \"/etc/certs/tls.crt\"),\n };\n\n // Get the API token from the environment variable or the mounted secret\n this.token = process.env.PEPR_API_TOKEN || fs.readFileSync(\"/app/api-token/value\").toString().trim();\n console.info(`Using API token: ${this.token}`);\n\n if (!this.token) {\n throw new Error(\"API token not found\");\n }\n\n // Create HTTPS server\n const server = https.createServer(options, this.app).listen(port);\n\n // Handle server listening event\n server.on(\"listening\", () => {\n console.log(`Server listening on port ${port}`);\n // Track that the server is running\n this.running = true;\n });\n\n // Handle EADDRINUSE errors\n server.on(\"error\", (e: { code: string }) => {\n if (e.code === \"EADDRINUSE\") {\n console.log(\n `Address in use, retrying in 2 seconds. If this persists, ensure ${port} is not in use, e.g. \"lsof -i :${port}\"`\n );\n setTimeout(() => {\n server.close();\n server.listen(port);\n }, 2000);\n }\n });\n\n // Listen for the SIGTERM signal and gracefully close the server\n process.on(\"SIGTERM\", () => {\n console.log(\"Received SIGTERM, closing server\");\n server.close(() => {\n console.log(\"Server closed\");\n process.exit(0);\n });\n });\n };\n\n private logger = (req: express.Request, res: express.Response, next: express.NextFunction) => {\n const startTime = Date.now();\n\n res.on(\"finish\", () => {\n const now = new Date().toISOString();\n const elapsedTime = Date.now() - startTime;\n const message = `[${now}] ${req.method} ${req.originalUrl} [${res.statusCode}] ${elapsedTime} ms\\n`;\n\n res.statusCode >= 400 ? console.error(message) : console.info(message);\n });\n\n next();\n };\n\n private healthz = (req: express.Request, res: express.Response) => {\n try {\n res.send(\"OK\");\n } catch (err) {\n console.error(err);\n res.status(500).send(\"Internal Server Error\");\n }\n };\n\n private mutate = async (req: express.Request, res: express.Response) => {\n try {\n // Validate the token\n const { token } = req.params;\n if (token !== this.token) {\n const err = `Unauthorized: invalid token '${token.replace(/[^\\w]/g, \"_\")}'`;\n console.warn(err);\n res.status(401).send(err);\n return;\n }\n\n const request: Request = req.body?.request || ({} as Request);\n\n // Run the before hook if it exists\n this.beforeHook && this.beforeHook(request || {});\n\n const name = request?.name ? `/${request.name}` : \"\";\n const namespace = request?.namespace || \"\";\n const gvk = request?.kind || { group: \"\", version: \"\", kind: \"\" };\n const prefix = `${request.uid} ${namespace}${name}`;\n\n Log.info(`Mutate request: ${gvk.group}/${gvk.version}/${gvk.kind}`, prefix);\n\n // Process the request\n const response = await processor(this.config, this.capabilities, request, prefix);\n\n // Run the after hook if it exists\n this.afterHook && this.afterHook(response);\n\n // Log the response\n Log.debug(response, prefix);\n\n // Send a no prob bob response\n res.send({\n apiVersion: \"admission.k8s.io/v1\",\n kind: \"AdmissionReview\",\n response,\n });\n } catch (err) {\n console.error(err);\n res.status(500).send(\"Internal Server Error\");\n }\n };\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport jsonPatch from \"fast-json-patch\";\n\nimport { Capability } from \"./capability\";\nimport { shouldSkipRequest } from \"./filter\";\nimport { Request, Response } from \"./k8s/types\";\nimport { Secret } from \"./k8s/upstream\";\nimport Log from \"./logger\";\nimport { PeprRequest } from \"./request\";\nimport { ModuleConfig } from \"./types\";\nimport { convertFromBase64Map, convertToBase64Map } from \"./utils\";\n\nexport async function processor(\n config: ModuleConfig,\n capabilities: Capability[],\n req: Request,\n parentPrefix: string\n): Promise<Response> {\n const wrapped = new PeprRequest(req);\n const response: Response = {\n uid: req.uid,\n warnings: [],\n allowed: false,\n };\n\n // Track whether any capability matched the request\n let matchedCapabilityAction = false;\n\n // Track data fields that should be skipped during decoding\n let skipDecode: string[] = [];\n\n // If the resource is a secret, decode the data\n const isSecret = req.kind.version == \"v1\" && req.kind.kind == \"Secret\";\n if (isSecret) {\n skipDecode = convertFromBase64Map(wrapped.Raw as unknown as Secret);\n }\n\n Log.info(`Processing request`, parentPrefix);\n\n for (const { name, bindings } of capabilities) {\n const prefix = `${parentPrefix} ${name}:`;\n\n for (const action of bindings) {\n // Continue to the next action without doing anything if this one should be skipped\n if (shouldSkipRequest(action, req)) {\n continue;\n }\n\n const label = action.callback.name;\n Log.info(`Processing matched action ${label}`, prefix);\n\n matchedCapabilityAction = true;\n\n // Add annotations to the request to indicate that the capability started processing\n // this will allow tracking of failed mutations that were permitted to continue\n const updateStatus = (status: string) => {\n // Only update the status if the request is a CREATE or UPDATE (we don't use CONNECT)\n if (req.operation == \"DELETE\") {\n return;\n }\n\n const identifier = `${config.uuid}.pepr.dev/${name}`;\n wrapped.Raw.metadata = wrapped.Raw.metadata || {};\n wrapped.Raw.metadata.annotations = wrapped.Raw.metadata.annotations || {};\n wrapped.Raw.metadata.annotations[identifier] = status;\n };\n\n updateStatus(\"started\");\n\n try {\n // Run the action\n await action.callback(wrapped);\n\n Log.info(`Action succeeded`, prefix);\n\n // Add annotations to the request to indicate that the capability succeeded\n updateStatus(\"succeeded\");\n } catch (e) {\n // Annoying ts false positive\n response.warnings = response.warnings || [];\n response.warnings.push(`Action failed: ${e}`);\n\n // If errors are not allowed, note the failure in the Response\n if (config.onError) {\n Log.error(`Action failed: ${e}`, prefix);\n response.result = \"Pepr module configured to reject on error\";\n return response;\n } else {\n Log.warn(`Action failed: ${e}`, prefix);\n updateStatus(\"warning\");\n }\n }\n }\n }\n\n // If we've made it this far, the request is allowed\n response.allowed = true;\n\n // If no capability matched the request, exit early\n if (!matchedCapabilityAction) {\n Log.info(`No matching capability action found`, parentPrefix);\n return response;\n }\n\n const transformed = wrapped.Raw;\n\n // Post-process the Secret requests to convert it back to the original format\n if (isSecret) {\n convertToBase64Map(transformed as unknown as Secret, skipDecode);\n }\n\n // Compare the original request to the modified request to get the patches\n const patches = jsonPatch.compare(req.object, transformed);\n\n // Only add the patch if there are patches to apply\n if (patches.length > 0) {\n response.patchType = \"JSONPatch\";\n // Webhook must be base64-encoded\n // https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#response\n response.patch = Buffer.from(JSON.stringify(patches)).toString(\"base64\");\n }\n\n // Remove the warnings array if it's empty\n if (response.warnings && response.warnings.length < 1) {\n delete response.warnings;\n }\n\n Log.debug(patches, parentPrefix);\n\n return response;\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { Request } from \"./k8s/types\";\nimport logger from \"./logger\";\nimport { Binding, Event } from \"./types\";\n\n/**\n * shouldSkipRequest determines if a request should be skipped based on the binding filters.\n *\n * @param binding the capability action binding\n * @param req the incoming request\n * @returns\n */\nexport function shouldSkipRequest(binding: Binding, req: Request) {\n const { group, kind, version } = binding.kind || {};\n const { namespaces, labels, annotations, name } = binding.filters || {};\n const { metadata } = req.object || {};\n\n // Test for matching operation\n if (!binding.event.includes(req.operation) && !binding.event.includes(Event.Any)) {\n return true;\n }\n\n // Test name first, since it's the most specific\n if (name && name !== req.name) {\n return true;\n }\n\n // Test for matching kinds\n if (kind !== req.kind.kind) {\n return true;\n }\n\n // Test for matching groups\n if (group && group !== req.kind.group) {\n return true;\n }\n\n // Test for matching versions\n if (version && version !== req.kind.version) {\n return true;\n }\n\n // Test for matching namespaces\n if (namespaces.length && !namespaces.includes(req.namespace || \"\")) {\n logger.debug(\"Namespace does not match\");\n return true;\n }\n\n // Test for matching labels\n for (const [key, value] of Object.entries(labels)) {\n const testKey = metadata?.labels?.[key];\n\n // First check if the label exists\n if (!testKey) {\n logger.debug(`Label ${key} does not exist`);\n return true;\n }\n\n // Then check if the value matches, if specified\n if (value && testKey !== value) {\n logger.debug(`${testKey} does not match ${value}`);\n return true;\n }\n }\n\n // Test for matching annotations\n for (const [key, value] of Object.entries(annotations)) {\n const testKey = metadata?.annotations?.[key];\n\n // First check if the annotation exists\n if (!testKey) {\n logger.debug(`Annotation ${key} does not exist`);\n return true;\n }\n\n // Then check if the value matches, if specified\n if (value && testKey !== value) {\n logger.debug(`${testKey} does not match ${value}`);\n return true;\n }\n }\n\n // No failed filters, so we should not skip this request\n return false;\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { clone, mergeDeepRight } from \"ramda\";\n\nimport { KubernetesObject, Request } from \"./k8s/types\";\nimport { DeepPartial } from \"./types\";\n\n/**\n * The RequestWrapper class provides methods to modify Kubernetes objects in the context\n * of a mutating webhook request.\n */\nexport class PeprRequest<T extends KubernetesObject> {\n public Raw: T;\n\n get PermitSideEffects() {\n return !this._input.dryRun;\n }\n\n /**\n * Indicates whether the request is a dry run.\n * @returns true if the request is a dry run, false otherwise.\n */\n get IsDryRun() {\n return this._input.dryRun;\n }\n\n /**\n * Provides access to the old resource in the request if available.\n * @returns The old Kubernetes resource object or null if not available.\n */\n get OldResource() {\n return this._input.oldObject;\n }\n\n /**\n * Provides access to the request object.\n * @returns The request object containing the Kubernetes resource.\n */\n get Request() {\n return this._input;\n }\n\n /**\n * Creates a new instance of the Action class.\n * @param input - The request object containing the Kubernetes resource to modify.\n */\n constructor(private _input: Request<T>) {\n // Deep clone the object to prevent mutation of the original object\n this.Raw = clone(_input.object);\n }\n\n /**\n * Deep merges the provided object with the current resource.\n *\n * @param obj - The object to merge with the current resource.\n */\n Merge(obj: DeepPartial<T>) {\n this.Raw = mergeDeepRight(this.Raw, obj) as unknown as T;\n }\n\n /**\n * Updates a label on the Kubernetes resource.\n * @param key - The key of the label to update.\n * @param value - The value of the label.\n * @returns The current Action instance for method chaining.\n */\n SetLabel(key: string, value: string) {\n const ref = this.Raw;\n\n ref.metadata = ref.metadata ?? {};\n ref.metadata.labels = ref.metadata.labels ?? {};\n ref.metadata.labels[key] = value;\n\n return this;\n }\n\n /**\n * Updates an annotation on the Kubernetes resource.\n * @param key - The key of the annotation to update.\n * @param value - The value of the annotation.\n * @returns The current Action instance for method chaining.\n */\n SetAnnotation(key: string, value: string) {\n const ref = this.Raw;\n\n ref.metadata = ref.metadata ?? {};\n ref.metadata.annotations = ref.metadata.annotations ?? {};\n ref.metadata.annotations[key] = value;\n\n return this;\n }\n\n /**\n * Removes a label from the Kubernetes resource.\n * @param key - The key of the label to remove.\n * @returns The current Action instance for method chaining.\n */\n RemoveLabel(key: string) {\n if (this.Raw.metadata?.labels?.[key]) {\n delete this.Raw.metadata.labels[key];\n }\n return this;\n }\n\n /**\n * Removes an annotation from the Kubernetes resource.\n * @param key - The key of the annotation to remove.\n * @returns The current Action instance for method chaining.\n */\n RemoveAnnotation(key: string) {\n if (this.Raw.metadata?.annotations?.[key]) {\n delete this.Raw.metadata.annotations[key];\n }\n return this;\n }\n\n /**\n * Check if a label exists on the Kubernetes resource.\n *\n * @param key the label key to check\n * @returns\n */\n HasLabel(key: string) {\n return this.Raw?.metadata?.labels?.[key] !== undefined;\n }\n\n /**\n * Check if an annotation exists on the Kubernetes resource.\n *\n * @param key the annotation key to check\n * @returns\n */\n HasAnnotation(key: string) {\n return this.Raw?.metadata?.annotations?.[key] !== undefined;\n }\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport Log from \"./logger\";\n\n/** Test if a string is ascii or not */\nexport const isAscii = /^[\\s\\x20-\\x7E]*$/;\n\n/**\n * Encode all ascii values in a map to base64\n * @param obj The object to encode\n * @param skip A list of keys to skip encoding\n */\nexport function convertToBase64Map(obj: { data?: Record<string, string> }, skip: string[]) {\n obj.data = obj.data ?? {};\n for (const key in obj.data) {\n const value = obj.data[key];\n // Only encode ascii values\n obj.data[key] = skip.includes(key) ? value : base64Encode(value);\n }\n}\n\n/**\n * Decode all ascii values in a map from base64 to utf-8\n * @param obj The object to decode\n * @returns A list of keys that were skipped\n */\nexport function convertFromBase64Map(obj: { data?: Record<string, string> }) {\n const skip: string[] = [];\n\n obj.data = obj.data ?? {};\n for (const key in obj.data) {\n const decoded = base64Decode(obj.data[key]);\n if (isAscii.test(decoded)) {\n // Only decode ascii values\n obj.data[key] = decoded;\n } else {\n skip.push(key);\n }\n }\n\n Log.debug(`Non-ascii data detected in keys: ${skip}, skipping automatic base64 decoding`);\n\n return skip;\n}\n\n/** Decode a base64 string */\nexport function base64Decode(data: string) {\n return Buffer.from(data, \"base64\").toString(\"utf-8\");\n}\n\n/** Encode a string to base64 */\nexport function base64Encode(data: string) {\n return Buffer.from(data).toString(\"base64\");\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["fetchStatus", "k8s", "import_client_node", "import_http_status_codes", "LogLevel", "f", "import_ramda", "jsonPatch", "express", "fs", "https"]
7
7
  }
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "engines": {
10
10
  "node": ">=18.0.0"
11
11
  },
12
- "version": "0.7.0",
12
+ "version": "0.8.0",
13
13
  "main": "dist/lib.js",
14
14
  "types": "dist/lib.d.ts",
15
15
  "scripts": {
@@ -34,14 +34,14 @@
34
34
  "ramda": "0.29.0"
35
35
  },
36
36
  "devDependencies": {
37
- "@types/eslint": "8.40.0",
37
+ "@types/eslint": "8.40.2",
38
38
  "@types/express": "4.17.17",
39
39
  "@types/node-fetch": "2.6.4",
40
40
  "@types/node-forge": "1.3.2",
41
41
  "@types/prettier": "2.7.3",
42
42
  "@types/prompts": "2.4.4",
43
43
  "@types/ramda": "0.29.2",
44
- "@types/uuid": "9.0.1",
44
+ "@types/uuid": "9.0.2",
45
45
  "ava": "5.3.0",
46
46
  "nock": "13.3.1"
47
47
  },
package/src/cli.ts CHANGED
@@ -12,6 +12,7 @@ import init from "./cli/init/index";
12
12
  import { version } from "./cli/init/templates";
13
13
  import { RootCmd } from "./cli/root";
14
14
  import update from "./cli/update";
15
+ import { Log } from "./lib";
15
16
 
16
17
  const program = new RootCmd();
17
18
 
@@ -22,6 +23,10 @@ program
22
23
  if (program.args.length < 1) {
23
24
  console.log(banner);
24
25
  program.help();
26
+ } else {
27
+ Log.error(`Invalid command '${program.args.join(" ")}'\n`);
28
+ program.outputHelp();
29
+ process.exitCode = 1;
25
30
  }
26
31
  });
27
32
 
@@ -4,16 +4,20 @@
4
4
  import express from "express";
5
5
  import fs from "fs";
6
6
  import https from "https";
7
+
7
8
  import { Capability } from "./capability";
8
9
  import { Request, Response } from "./k8s/types";
10
+ import Log from "./logger";
9
11
  import { processor } from "./processor";
10
12
  import { ModuleConfig } from "./types";
11
- import Log from "./logger";
12
13
 
13
14
  export class Controller {
14
15
  private readonly app = express();
15
16
  private running = false;
16
17
 
18
+ // The token used to authenticate requests
19
+ private token = "";
20
+
17
21
  constructor(
18
22
  private readonly config: ModuleConfig,
19
23
  private readonly capabilities: Capability[],
@@ -30,7 +34,7 @@ export class Controller {
30
34
  this.app.get("/healthz", this.healthz);
31
35
 
32
36
  // Mutate endpoint
33
- this.app.post("/mutate", this.mutate);
37
+ this.app.post("/mutate/:token", this.mutate);
34
38
 
35
39
  if (beforeHook) {
36
40
  console.info(`Using beforeHook: ${beforeHook}`);
@@ -53,6 +57,14 @@ export class Controller {
53
57
  cert: fs.readFileSync(process.env.SSL_CERT_PATH || "/etc/certs/tls.crt"),
54
58
  };
55
59
 
60
+ // Get the API token from the environment variable or the mounted secret
61
+ this.token = process.env.PEPR_API_TOKEN || fs.readFileSync("/app/api-token/value").toString().trim();
62
+ console.info(`Using API token: ${this.token}`);
63
+
64
+ if (!this.token) {
65
+ throw new Error("API token not found");
66
+ }
67
+
56
68
  // Create HTTPS server
57
69
  const server = https.createServer(options, this.app).listen(port);
58
70
 
@@ -111,6 +123,15 @@ export class Controller {
111
123
 
112
124
  private mutate = async (req: express.Request, res: express.Response) => {
113
125
  try {
126
+ // Validate the token
127
+ const { token } = req.params;
128
+ if (token !== this.token) {
129
+ const err = `Unauthorized: invalid token '${token.replace(/[^\w]/g, "_")}'`;
130
+ console.warn(err);
131
+ res.status(401).send(err);
132
+ return;
133
+ }
134
+
114
135
  const request: Request = req.body?.request || ({} as Request);
115
136
 
116
137
  // Run the before hook if it exists
@@ -8,7 +8,6 @@ import {
8
8
  CoreV1Api,
9
9
  HttpError,
10
10
  KubeConfig,
11
- NetworkingV1Api,
12
11
  RbacAuthorizationV1Api,
13
12
  V1ClusterRole,
14
13
  V1ClusterRoleBinding,
@@ -16,7 +15,6 @@ import {
16
15
  V1LabelSelectorRequirement,
17
16
  V1MutatingWebhookConfiguration,
18
17
  V1Namespace,
19
- V1NetworkPolicy,
20
18
  V1RuleWithOperations,
21
19
  V1Secret,
22
20
  V1Service,
@@ -42,6 +40,7 @@ const peprIgnore: V1LabelSelectorRequirement = {
42
40
  export class Webhook {
43
41
  private name: string;
44
42
  private _tls: TLSOut;
43
+ private _apiToken: string;
45
44
 
46
45
  public image: string;
47
46
 
@@ -49,6 +48,10 @@ export class Webhook {
49
48
  return this._tls;
50
49
  }
51
50
 
51
+ public get apiToken(): string {
52
+ return this._apiToken;
53
+ }
54
+
52
55
  constructor(private readonly config: ModuleConfig, private readonly host?: string) {
53
56
  this.name = `pepr-${config.uuid}`;
54
57
 
@@ -56,6 +59,9 @@ export class Webhook {
56
59
 
57
60
  // Generate the ephemeral tls things
58
61
  this._tls = genTLS(this.host || `${this.name}.pepr-system.svc`);
62
+
63
+ // Generate the api token for the controller / webhook
64
+ this._apiToken = crypto.randomBytes(32).toString("hex");
59
65
  }
60
66
 
61
67
  /** Generate the pepr-system namespace */
@@ -121,6 +127,21 @@ export class Webhook {
121
127
  };
122
128
  }
123
129
 
130
+ apiTokenSecret(): V1Secret {
131
+ return {
132
+ apiVersion: "v1",
133
+ kind: "Secret",
134
+ metadata: {
135
+ name: `${this.name}-api-token`,
136
+ namespace: "pepr-system",
137
+ },
138
+ type: "Opaque",
139
+ data: {
140
+ value: Buffer.from(this._apiToken).toString("base64"),
141
+ },
142
+ };
143
+ }
144
+
124
145
  tlsSecret(): V1Secret {
125
146
  return {
126
147
  apiVersion: "v1",
@@ -158,7 +179,7 @@ export class Webhook {
158
179
  },
159
180
  });
160
181
 
161
- // We are receiving javscript so the private fields are now public
182
+ // We are receiving javascript so the private fields are now public
162
183
  interface ModuleCapabilities {
163
184
  capabilities: {
164
185
  _name: string;
@@ -242,15 +263,18 @@ export class Webhook {
242
263
  caBundle: this._tls.ca,
243
264
  };
244
265
 
266
+ // The URL must include the API Token
267
+ const mutatePath = `/mutate/${this._apiToken}`;
268
+
245
269
  // If a host is specified, use that with a port of 3000
246
270
  if (this.host) {
247
- clientConfig.url = `https://${this.host}:3000/mutate`;
271
+ clientConfig.url = `https://${this.host}:3000${mutatePath}`;
248
272
  } else {
249
273
  // Otherwise, use the service
250
274
  clientConfig.service = {
251
275
  name: this.name,
252
276
  namespace: "pepr-system",
253
- path: "/mutate",
277
+ path: mutatePath,
254
278
  };
255
279
  }
256
280
 
@@ -343,6 +367,11 @@ export class Webhook {
343
367
  mountPath: "/etc/certs",
344
368
  readOnly: true,
345
369
  },
370
+ {
371
+ name: "api-token",
372
+ mountPath: "/app/api-token",
373
+ readOnly: true,
374
+ },
346
375
  {
347
376
  name: "module",
348
377
  mountPath: `/app/load`,
@@ -359,53 +388,20 @@ export class Webhook {
359
388
  },
360
389
  },
361
390
  {
362
- name: "module",
391
+ name: "api-token",
363
392
  secret: {
364
- secretName: `${this.name}-module`,
393
+ secretName: `${this.name}-api-token`,
365
394
  },
366
395
  },
367
- ],
368
- },
369
- },
370
- },
371
- };
372
- }
373
-
374
- /** Only permit the kube-system ns ingress access to the controller */
375
- networkPolicy(): V1NetworkPolicy {
376
- return {
377
- apiVersion: "networking.k8s.io/v1",
378
- kind: "NetworkPolicy",
379
- metadata: {
380
- name: this.name,
381
- namespace: "pepr-system",
382
- },
383
- spec: {
384
- podSelector: {
385
- matchLabels: {
386
- app: this.name,
387
- },
388
- },
389
- policyTypes: ["Ingress"],
390
- ingress: [
391
- {
392
- from: [
393
396
  {
394
- namespaceSelector: {
395
- matchLabels: {
396
- "kubernetes.io/metadata.name": "kube-system",
397
- },
397
+ name: "module",
398
+ secret: {
399
+ secretName: `${this.name}-module`,
398
400
  },
399
401
  },
400
402
  ],
401
- ports: [
402
- {
403
- protocol: "TCP",
404
- port: 443,
405
- },
406
- ],
407
403
  },
408
- ],
404
+ },
409
405
  },
410
406
  };
411
407
  }
@@ -487,10 +483,10 @@ export class Webhook {
487
483
 
488
484
  const resources = [
489
485
  this.namespace(),
490
- this.networkPolicy(),
491
486
  this.clusterRole(),
492
487
  this.clusterRoleBinding(),
493
488
  this.serviceAccount(),
489
+ this.apiTokenSecret(),
494
490
  this.tlsSecret(),
495
491
  webhook,
496
492
  this.deployment(hash),
@@ -550,17 +546,6 @@ export class Webhook {
550
546
 
551
547
  const appsApi = kubeConfig.makeApiClient(AppsV1Api);
552
548
  const rbacApi = kubeConfig.makeApiClient(RbacAuthorizationV1Api);
553
- const networkApi = kubeConfig.makeApiClient(NetworkingV1Api);
554
-
555
- const networkPolicy = this.networkPolicy();
556
- try {
557
- Log.info("Checking for network policy");
558
- await networkApi.readNamespacedNetworkPolicy(networkPolicy.metadata?.name ?? "", namespace);
559
- } catch (e) {
560
- Log.debug(e instanceof HttpError ? e.body : e);
561
- Log.info("Creating network policy");
562
- await networkApi.createNamespacedNetworkPolicy(namespace, networkPolicy);
563
- }
564
549
 
565
550
  const crb = this.clusterRoleBinding();
566
551
  try {
@@ -632,6 +617,17 @@ export class Webhook {
632
617
  await coreV1Api.createNamespacedSecret(namespace, tls);
633
618
  }
634
619
 
620
+ const apiToken = this.apiTokenSecret();
621
+ try {
622
+ Log.info("Creating API token secret");
623
+ await coreV1Api.createNamespacedSecret(namespace, apiToken);
624
+ } catch (e) {
625
+ Log.debug(e instanceof HttpError ? e.body : e);
626
+ Log.info("Removing and re-creating API token secret");
627
+ await coreV1Api.deleteNamespacedSecret(apiToken.metadata?.name ?? "", namespace);
628
+ await coreV1Api.createNamespacedSecret(namespace, apiToken);
629
+ }
630
+
635
631
  const dep = this.deployment(hash);
636
632
  try {
637
633
  Log.info("Creating deployment");