pepr 0.34.0 → 0.34.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1962,8 +1962,8 @@ var hello_pepr_samples_default = [
1962
1962
  var gitIgnore = "# Ignore node_modules and Pepr build artifacts\nnode_modules\ndist\ninsecure*\n";
1963
1963
  var readmeMd = '# Pepr Module\n\nThis is a Pepr Module. [Pepr](https://github.com/defenseunicorns/pepr) is a type-safe Kubernetes middleware system.\n\nThe `capabilities` directory contains all the capabilities for this module. By default,\na capability is a single typescript file in the format of `capability-name.ts` that is\nimported in the root `pepr.ts` file as `import { HelloPepr } from "./capabilities/hello-pepr";`.\nBecause this is typescript, you can organize this however you choose, e.g. creating a sub-folder\nper-capability or common logic in shared files or folders.\n\nExample Structure:\n\n```\nModule Root\n\u251C\u2500\u2500 package.json\n\u251C\u2500\u2500 pepr.ts\n\u2514\u2500\u2500 capabilities\n \u251C\u2500\u2500 example-one.ts\n \u251C\u2500\u2500 example-three.ts\n \u2514\u2500\u2500 example-two.ts\n```\n';
1964
1964
  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';
1965
- var helloPeprTS = 'import {\n Capability,\n K8s,\n Log,\n PeprMutateRequest,\n RegisterKind,\n a,\n fetch,\n fetchStatus,\n kind,\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 action, use \'Store\' to persist data\nconst { When, Store } = HelloPepr;\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (Namespace) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This action removes the label `remove-me` when a Namespace is created.\n * Note we don\'t need to specify the namespace here, because we\'ve already specified\n * it in the Capability definition above.\n */\nWhen(a.Namespace)\n .IsCreated()\n .Mutate(ns => ns.RemoveLabel("remove-me"));\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Watch Action with K8s SSA (Namespace) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This action watches for the `pepr-demo-2` namespace to be created, then creates a ConfigMap with\n * the name `pepr-ssa-demo` and adds the namespace UID to the ConfigMap data. Because Pepr uses\n * server-side apply for this operation, the ConfigMap will be created or updated if it already exists.\n */\nWhen(a.Namespace)\n .IsCreated()\n .WithName("pepr-demo-2")\n .Watch(async ns => {\n Log.info("Namespace pepr-demo-2 was created.");\n\n try {\n // Apply the ConfigMap using K8s server-side apply\n await K8s(kind.ConfigMap).Apply({\n metadata: {\n name: "pepr-ssa-demo",\n namespace: "pepr-demo-2",\n },\n data: {\n "ns-uid": ns.metadata.uid,\n },\n });\n } catch (error) {\n // You can use the Log object to log messages to the Pepr controller pod\n Log.error(error, "Failed to apply ConfigMap using server-side apply.");\n }\n\n // You can share data between actions using the Store, including between different types of actions\n Store.setItem("watch-data", "This data was stored by a Watch Action.");\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (CM Example 1) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This is a single action. They can be in the same file or put imported from other files.\n * In this example, when a ConfigMap is created with the name `example-1`, then add a label and annotation.\n *\n * Equivalent to manually running:\n * `kubectl label configmap example-1 pepr=was-here`\n * `kubectl annotate configmap example-1 pepr.dev=annotations-work-too`\n */\nWhen(a.ConfigMap)\n .IsCreated()\n .WithName("example-1")\n .Mutate(request => {\n request\n .SetLabel("pepr", "was-here")\n .SetAnnotation("pepr.dev", "annotations-work-too");\n\n // Use the Store to persist data between requests and Pepr controller pods\n Store.setItem("example-1", "was-here");\n\n // This data is written asynchronously and can be read back via `Store.getItem()` or `Store.subscribe()`\n Store.setItem("example-1-data", JSON.stringify(request.Raw.data));\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate & Validate Actions (CM Example 2) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This combines 3 different types of actions: \'Mutate\', \'Validate\', and \'Watch\'. The order\n * of the actions is required, but each action is optional. In this example, when a ConfigMap is created\n * with the name `example-2`, then add a label and annotation, validate that the ConfigMap has the label\n * `pepr`, and log the request.\n */\nWhen(a.ConfigMap)\n .IsCreated()\n .WithName("example-2")\n .Mutate(request => {\n // This Mutate Action will mutate the request before it is persisted to the cluster\n\n // Use `request.Merge()` to merge the new data with the existing data\n request.Merge({\n metadata: {\n labels: {\n pepr: "was-here",\n },\n annotations: {\n "pepr.dev": "annotations-work-too",\n },\n },\n });\n })\n .Validate(request => {\n // This Validate Action will validate the request before it is persisted to the cluster\n\n // Approve the request if the ConfigMap has the label \'pepr\'\n if (request.HasLabel("pepr")) {\n return request.Approve();\n }\n\n // Otherwise, deny the request with an error message (optional)\n return request.Deny("ConfigMap must have label \'pepr\'");\n })\n .Watch((cm, phase) => {\n // This Watch Action will watch the ConfigMap after it has been persisted to the cluster\n Log.info(cm, `ConfigMap was ${phase} with the name example-2`);\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (CM Example 2a) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This action shows a simple validation that will deny any ConfigMap that has the\n * annotation `evil`. Note that the `Deny()` function takes an optional second parameter that is a\n * user-defined status code to return.\n */\nWhen(a.ConfigMap)\n .IsCreated()\n .Validate(request => {\n if (request.HasAnnotation("evil")) {\n return request.Deny("No evil CM annotations allowed.", 400);\n }\n\n return request.Approve();\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (CM Example 3) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This action combines different styles. Unlike the previous actions, this one will look\n * for any ConfigMap in the `pepr-demo` namespace that has the label `change=by-label` during either\n * CREATE or UPDATE. Note that all conditions added such as `WithName()`, `WithLabel()`, `InNamespace()`,\n * are ANDs so all conditions must be true for the request to be processed.\n */\nWhen(a.ConfigMap)\n .IsCreatedOrUpdated()\n .WithLabel("change", "by-label")\n .Mutate(request => {\n // The K8s object e are going to mutate\n const cm = request.Raw;\n\n // Get the username and uid of the K8s request\n const { username, uid } = request.Request.userInfo;\n\n // Store some data about the request in the configmap\n cm.data["username"] = username;\n cm.data["uid"] = uid;\n\n // You can still mix other ways of making changes too\n request.SetAnnotation("pepr.dev", "making-waves");\n });\n\n// This action validates the label `change=by-label` is deleted\nWhen(a.ConfigMap)\n .IsDeleted()\n .WithLabel("change", "by-label")\n .Validate(request => {\n // Log and then always approve the request\n Log.info("CM with label \'change=by-label\' was deleted.");\n return request.Approve();\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (CM Example 4) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This action show how you can use the `Mutate()` function without an inline function.\n * This is useful if you want to keep your actions small and focused on a single task,\n * or if you want to reuse the same function in multiple actions.\n */\nWhen(a.ConfigMap).IsCreated().WithName("example-4").Mutate(example4Cb);\n\n// This function uses the complete type definition, but is not required.\nfunction example4Cb(cm: PeprMutateRequest<a.ConfigMap>) {\n cm.SetLabel("pepr.dev/first", "true");\n cm.SetLabel("pepr.dev/second", "true");\n cm.SetLabel("pepr.dev/third", "true");\n}\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (CM Example 4a) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This is the same as Example 4, except this only operates on a CM in the `pepr-demo-2` namespace.\n * Note because the Capability defines namespaces, the namespace specified here must be one of those.\n * Alternatively, you can remove the namespace from the Capability definition and specify it here.\n */\nWhen(a.ConfigMap)\n .IsCreated()\n .InNamespace("pepr-demo-2")\n .WithName("example-4a")\n .Mutate(example4Cb);\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (CM Example 5) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This action is a bit more complex. It will look for any ConfigMap in the `pepr-demo`\n * namespace that has the label `chuck-norris` during CREATE. When it finds one, it will fetch a\n * random Chuck Norris joke from the API and add it to the ConfigMap. This is a great example of how\n * you can use Pepr to make changes to your K8s objects based on external data.\n *\n * Note the use of the `async` keyword. This is required for any action that uses `await` or `fetch()`.\n *\n * Also note we are passing a type to the `fetch()` function. This is optional, but it will help you\n * avoid mistakes when working with the data returned from the API. You can also use the `as` keyword to\n * cast the data returned from the API.\n *\n * These are equivalent:\n * ```ts\n * const joke = await fetch<TheChuckNorrisJoke>("https://icanhazdadjoke.com/");\n * const joke = await fetch("https://icanhazdadjoke.com/") as TheChuckNorrisJoke;\n * ```\n *\n * Alternatively, you can drop the type completely:\n *\n * ```ts\n * fetch("https://icanhazdadjoke.com")\n * ```\n */\ninterface TheChuckNorrisJoke {\n id: string;\n joke: string;\n status: number;\n}\n\nWhen(a.ConfigMap)\n .IsCreated()\n .WithLabel("chuck-norris")\n .Mutate(async change => {\n // Try/catch is not needed as a response object will always be returned\n const response = await fetch<TheChuckNorrisJoke>(\n "https://icanhazdadjoke.com/",\n {\n headers: {\n Accept: "application/json",\n },\n },\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.joke;\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 * Mutate Action (Secret Base64 Handling) *\n * ---------------------------------------------------------------------------------------------------\n *\n * The K8s JS client provides incomplete support for base64 encoding/decoding handling for secrets,\n * unlike the GO client. To make this less painful, Pepr automatically handles base64 encoding/decoding\n * secret data before and after the action is executed.\n */\nWhen(a.Secret)\n .IsCreated()\n .WithName("secret-1")\n .Mutate(request => {\n const secret = request.Raw;\n\n // This will be encoded at the end of all processing back to base64: "Y2hhbmdlLXdpdGhvdXQtZW5jb2Rpbmc="\n secret.data.magic = "change-without-encoding";\n\n // You can modify the data directly, and it will be encoded at the end of all processing\n secret.data.example += " - modified by Pepr";\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (Untyped Custom Resource) *\n * ---------------------------------------------------------------------------------------------------\n *\n * Out of the box, Pepr supports all the standard Kubernetes objects. However, you can also create\n * your own types. This is useful if you are working with an Operator that creates custom resources.\n * There are two ways to do this, the first is to use the `When()` function with a `GenericKind`,\n * the second is to create a new class that extends `GenericKind` and use the `RegisterKind()` function.\n *\n * This example shows how to use the `When()` function with a `GenericKind`. Note that you\n * must specify the `group`, `version`, and `kind` of the object (if applicable). This is how Pepr knows\n * if the action should be triggered or not. Since we are using a `GenericKind`,\n * Pepr will not be able to provide any intellisense for the object, so you will need to refer to the\n * Kubernetes API documentation for the object you are working with.\n *\n * You will need to wait for the CRD in `hello-pepr.samples.yaml` to be created, then you can apply\n *\n * ```yaml\n * apiVersion: pepr.dev/v1\n * kind: Unicorn\n * metadata:\n * name: example-1\n * namespace: pepr-demo\n * spec:\n * message: replace-me\n * counter: 0\n * ```\n */\nWhen(a.GenericKind, {\n group: "pepr.dev",\n version: "v1",\n kind: "Unicorn",\n})\n .IsCreated()\n .WithName("example-1")\n .Mutate(request => {\n request.Merge({\n spec: {\n message: "Hello Pepr without type data!",\n counter: Math.random(),\n },\n });\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (Typed Custom Resource) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This example shows how to use the `RegisterKind()` function to create a new type. This is useful\n * if you are working with an Operator that creates custom resources and you want to have intellisense\n * for the object. Note that you must specify the `group`, `version`, and `kind` of the object (if applicable)\n * as this is how Pepr knows if the action should be triggered or not.\n *\n * Once you register a new Kind with Pepr, you can use the `When()` function with the new Kind. Ideally,\n * you should register custom Kinds at the top of your Capability file or Pepr Module so they are available\n * to all actions, but we are putting it here for demonstration purposes.\n *\n * You will need to wait for the CRD in `hello-pepr.samples.yaml` to be created, then you can apply\n *\n * ```yaml\n * apiVersion: pepr.dev/v1\n * kind: Unicorn\n * metadata:\n * name: example-2\n * namespace: pepr-demo\n * spec:\n * message: replace-me\n * counter: 0\n * ```*\n */\nclass UnicornKind extends a.GenericKind {\n spec: {\n /**\n * JSDoc comments can be added to explain more details about the field.\n *\n * @example\n * ```ts\n * request.Raw.spec.message = "Hello Pepr!";\n * ```\n * */\n message: string;\n counter: number;\n };\n}\n\nRegisterKind(UnicornKind, {\n group: "pepr.dev",\n version: "v1",\n kind: "Unicorn",\n});\n\nWhen(UnicornKind)\n .IsCreated()\n .WithName("example-2")\n .Mutate(request => {\n request.Merge({\n spec: {\n message: "Hello Pepr with type data!",\n counter: Math.random(),\n },\n });\n });\n\n/**\n * A callback function that is called once the Pepr Store is fully loaded.\n */\nStore.onReady(data => {\n Log.info(data, "Pepr Store Ready");\n});\n';
1966
- var packageJSON = { name: "pepr", description: "Kubernetes application engine", author: "Defense Unicorns", homepage: "https://github.com/defenseunicorns/pepr", license: "Apache-2.0", bin: "dist/cli.js", repository: "defenseunicorns/pepr", engines: { node: ">=18.0.0" }, files: ["/dist", "/src"], version: "0.34.0", main: "dist/lib.js", types: "dist/lib.d.ts", scripts: { ci: "npm ci", "gen-data-json": "node hack/build-template-data.js", prebuild: "rm -fr dist/* && npm run gen-data-json", version: "node scripts/set-version.js", build: "tsc && node build.mjs", "build:image": "npm run build && docker buildx build --tag pepr:dev .", test: "npm run test:unit && npm run test:journey", "test:unit": "npm run gen-data-json && jest src --coverage --detectOpenHandles --coverageDirectory=./coverage", "test:journey": "npm run test:journey:k3d && npm run test:journey:build && npm run test:journey:image && npm run test:journey:run", "test:journey:prep": "if [ ! -d ./pepr-upgrade-test ]; then git clone https://github.com/defenseunicorns/pepr-upgrade-test.git ; fi", "test:journey-wasm": "npm run test:journey:k3d && npm run test:journey:build && npm run test:journey:image && npm run test:journey:run-wasm", "test:journey:k3d": "k3d cluster delete pepr-dev && k3d cluster create pepr-dev --k3s-arg '--debug@server:0' --wait && kubectl rollout status deployment -n kube-system", "test:journey:build": "npm run build && npm pack", "test:journey:image": "docker buildx build --tag pepr:dev . && k3d image import pepr:dev -c pepr-dev", "test:journey:run": "jest --detectOpenHandles journey/entrypoint.test.ts && npm run test:journey:prep && npm run test:journey:upgrade", "test:journey:run-wasm": "jest --detectOpenHandles journey/entrypoint-wasm.test.ts", "test:journey:upgrade": "npm run test:journey:k3d && npm run test:journey:image && jest --detectOpenHandles journey/pepr-upgrade.test.ts", "format:check": "eslint src && prettier src --check", "format:fix": "eslint src --fix && prettier src --write" }, dependencies: { "@types/ramda": "0.30.1", express: "4.19.2", "fast-json-patch": "3.1.1", "json-pointer": "^0.6.2", "kubernetes-fluent-client": "3.0.1", pino: "9.3.2", "pino-pretty": "11.2.2", "prom-client": "15.1.3", ramda: "0.30.1" }, devDependencies: { "@commitlint/cli": "19.3.0", "@commitlint/config-conventional": "19.2.2", "@fast-check/jest": "^2.0.1", "@jest/globals": "29.7.0", "@types/eslint": "9.6.0", "@types/express": "4.17.21", "@types/json-pointer": "^1.0.34", "@types/node": "22.x.x", "@types/node-forge": "1.3.11", "@types/prompts": "2.4.9", "@types/uuid": "10.0.0", "fast-check": "^3.19.0", jest: "29.7.0", "js-yaml": "^4.1.0", nock: "13.5.4", "ts-jest": "29.2.4" }, peerDependencies: { "@typescript-eslint/eslint-plugin": "7.18.0", "@typescript-eslint/parser": "7.18.0", commander: "12.1.0", esbuild: "0.23.0", eslint: "8.57.0", "node-forge": "1.3.1", prettier: "3.3.3", prompts: "2.4.2", typescript: "5.3.3", uuid: "10.0.0" } };
1965
+ var helloPeprTS = 'import {\n Capability,\n K8s,\n Log,\n PeprMutateRequest,\n RegisterKind,\n a,\n fetch,\n fetchStatus,\n kind,\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 action, use \'Store\' to persist data\nconst { When, Store } = HelloPepr;\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (Namespace) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This action removes the label `remove-me` when a Namespace is created.\n * Note we don\'t need to specify the namespace here, because we\'ve already specified\n * it in the Capability definition above.\n */\nWhen(a.Namespace)\n .IsCreated()\n .Mutate(ns => ns.RemoveLabel("remove-me"));\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Watch Action with K8s SSA (Namespace) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This action watches for the `pepr-demo-2` namespace to be created, then creates a ConfigMap with\n * the name `pepr-ssa-demo` and adds the namespace UID to the ConfigMap data. Because Pepr uses\n * server-side apply for this operation, the ConfigMap will be created or updated if it already exists.\n */\nWhen(a.Namespace)\n .IsCreated()\n .WithName("pepr-demo-2")\n .Watch(async ns => {\n Log.info("Namespace pepr-demo-2 was created.");\n\n try {\n // Apply the ConfigMap using K8s server-side apply\n await K8s(kind.ConfigMap).Apply({\n metadata: {\n name: "pepr-ssa-demo",\n namespace: "pepr-demo-2",\n },\n data: {\n "ns-uid": ns.metadata.uid,\n },\n });\n } catch (error) {\n // You can use the Log object to log messages to the Pepr controller pod\n Log.error(error, "Failed to apply ConfigMap using server-side apply.");\n }\n\n // You can share data between actions using the Store, including between different types of actions\n Store.setItem("watch-data", "This data was stored by a Watch Action.");\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (CM Example 1) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This is a single action. They can be in the same file or put imported from other files.\n * In this example, when a ConfigMap is created with the name `example-1`, then add a label and annotation.\n *\n * Equivalent to manually running:\n * `kubectl label configmap example-1 pepr=was-here`\n * `kubectl annotate configmap example-1 pepr.dev=annotations-work-too`\n */\nWhen(a.ConfigMap)\n .IsCreated()\n .WithName("example-1")\n .Mutate(request => {\n request\n .SetLabel("pepr", "was-here")\n .SetAnnotation("pepr.dev", "annotations-work-too");\n\n // Use the Store to persist data between requests and Pepr controller pods\n Store.setItem("example-1", "was-here");\n\n // This data is written asynchronously and can be read back via `Store.getItem()` or `Store.subscribe()`\n Store.setItem("example-1-data", JSON.stringify(request.Raw.data));\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate & Validate Actions (CM Example 2) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This combines 3 different types of actions: \'Mutate\', \'Validate\', and \'Watch\'. The order\n * of the actions is required, but each action is optional. In this example, when a ConfigMap is created\n * with the name `example-2`, then add a label and annotation, validate that the ConfigMap has the label\n * `pepr`, and log the request.\n */\nWhen(a.ConfigMap)\n .IsCreated()\n .WithName("example-2")\n .Mutate(request => {\n // This Mutate Action will mutate the request before it is persisted to the cluster\n\n // Use `request.Merge()` to merge the new data with the existing data\n request.Merge({\n metadata: {\n labels: {\n pepr: "was-here",\n },\n annotations: {\n "pepr.dev": "annotations-work-too",\n },\n },\n });\n })\n .Validate(request => {\n // This Validate Action will validate the request before it is persisted to the cluster\n\n // Approve the request if the ConfigMap has the label \'pepr\'\n if (request.HasLabel("pepr")) {\n return request.Approve();\n }\n\n // Otherwise, deny the request with an error message (optional)\n return request.Deny("ConfigMap must have label \'pepr\'");\n })\n .Watch((cm, phase) => {\n // This Watch Action will watch the ConfigMap after it has been persisted to the cluster\n Log.info(cm, `ConfigMap was ${phase} with the name example-2`);\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (CM Example 2a) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This action shows a simple validation that will deny any ConfigMap that has the\n * annotation `evil`. Note that the `Deny()` function takes an optional second parameter that is a\n * user-defined status code to return.\n */\nWhen(a.ConfigMap)\n .IsCreated()\n .Validate(request => {\n if (request.HasAnnotation("evil")) {\n return request.Deny("No evil CM annotations allowed.", 400);\n }\n\n return request.Approve();\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (CM Example 3) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This action combines different styles. Unlike the previous actions, this one will look\n * for any ConfigMap in the `pepr-demo` namespace that has the label `change=by-label` during either\n * CREATE or UPDATE. Note that all conditions added such as `WithName()`, `WithLabel()`, `InNamespace()`,\n * are ANDs so all conditions must be true for the request to be processed.\n */\nWhen(a.ConfigMap)\n .IsCreatedOrUpdated()\n .WithLabel("change", "by-label")\n .Mutate(request => {\n // The K8s object e are going to mutate\n const cm = request.Raw;\n\n // Get the username and uid of the K8s request\n const { username, uid } = request.Request.userInfo;\n\n // Store some data about the request in the configmap\n cm.data["username"] = username;\n cm.data["uid"] = uid;\n\n // You can still mix other ways of making changes too\n request.SetAnnotation("pepr.dev", "making-waves");\n });\n\n// This action validates the label `change=by-label` is deleted\nWhen(a.ConfigMap)\n .IsDeleted()\n .WithLabel("change", "by-label")\n .Validate(request => {\n // Log and then always approve the request\n Log.info("CM with label \'change=by-label\' was deleted.");\n return request.Approve();\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (CM Example 4) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This action show how you can use the `Mutate()` function without an inline function.\n * This is useful if you want to keep your actions small and focused on a single task,\n * or if you want to reuse the same function in multiple actions.\n */\nWhen(a.ConfigMap).IsCreated().WithName("example-4").Mutate(example4Cb);\n\n// This function uses the complete type definition, but is not required.\nfunction example4Cb(cm: PeprMutateRequest<a.ConfigMap>) {\n cm.SetLabel("pepr.dev/first", "true");\n cm.SetLabel("pepr.dev/second", "true");\n cm.SetLabel("pepr.dev/third", "true");\n}\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (CM Example 4a) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This is the same as Example 4, except this only operates on a CM in the `pepr-demo-2` namespace.\n * Note because the Capability defines namespaces, the namespace specified here must be one of those.\n * Alternatively, you can remove the namespace from the Capability definition and specify it here.\n */\nWhen(a.ConfigMap)\n .IsCreated()\n .InNamespace("pepr-demo-2")\n .WithName("example-4a")\n .Mutate(example4Cb);\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (CM Example 5) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This action is a bit more complex. It will look for any ConfigMap in the `pepr-demo`\n * namespace that has the label `chuck-norris` during CREATE. When it finds one, it will fetch a\n * random Chuck Norris joke from the API and add it to the ConfigMap. This is a great example of how\n * you can use Pepr to make changes to your K8s objects based on external data.\n *\n * Note the use of the `async` keyword. This is required for any action that uses `await` or `fetch()`.\n *\n * Also note we are passing a type to the `fetch()` function. This is optional, but it will help you\n * avoid mistakes when working with the data returned from the API. You can also use the `as` keyword to\n * cast the data returned from the API.\n *\n * These are equivalent:\n * ```ts\n * const joke = await fetch<TheChuckNorrisJoke>("https://icanhazdadjoke.com/");\n * const joke = await fetch("https://icanhazdadjoke.com/") as TheChuckNorrisJoke;\n * ```\n *\n * Alternatively, you can drop the type completely:\n *\n * ```ts\n * fetch("https://icanhazdadjoke.com")\n * ```\n */\ninterface TheChuckNorrisJoke {\n id: string;\n joke: string;\n status: number;\n}\n\nWhen(a.ConfigMap)\n .IsCreatedOrUpdated()\n .WithLabel("chuck-norris")\n .Mutate(cm => cm.SetLabel("got-jokes", "true"))\n .Watch(async cm => {\n const jokeURL = "https://icanhazdadjoke.com/";\n // Try/catch is not needed as a response object will always be returned\n const response = await fetch<TheChuckNorrisJoke>(jokeURL, {\n headers: {\n Accept: "application/json",\n },\n });\n\n // Instead, check the `response.ok` field\n if (response.ok) {\n const { joke } = response.data;\n // Add Joke to the Store\n await Store.setItemAndWait(jokeURL, joke);\n // Add the Chuck Norris joke to the configmap\n try {\n await K8s(kind.ConfigMap).Apply({\n metadata: {\n name: cm.metadata.name,\n namespace: cm.metadata.namespace,\n },\n data: {\n "chuck-says": Store.getItem(jokeURL),\n },\n });\n } catch (error) {\n Log.error(error, "Failed to apply ConfigMap using server-side apply.", {\n cm,\n });\n }\n }\n\n // You can also assert on different HTTP response codes\n if (response.status === fetchStatus.NOT_FOUND) {\n // Do something else\n return;\n }\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (Secret Base64 Handling) *\n * ---------------------------------------------------------------------------------------------------\n *\n * The K8s JS client provides incomplete support for base64 encoding/decoding handling for secrets,\n * unlike the GO client. To make this less painful, Pepr automatically handles base64 encoding/decoding\n * secret data before and after the action is executed.\n */\nWhen(a.Secret)\n .IsCreated()\n .WithName("secret-1")\n .Mutate(request => {\n const secret = request.Raw;\n\n // This will be encoded at the end of all processing back to base64: "Y2hhbmdlLXdpdGhvdXQtZW5jb2Rpbmc="\n secret.data.magic = "change-without-encoding";\n\n // You can modify the data directly, and it will be encoded at the end of all processing\n secret.data.example += " - modified by Pepr";\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (Untyped Custom Resource) *\n * ---------------------------------------------------------------------------------------------------\n *\n * Out of the box, Pepr supports all the standard Kubernetes objects. However, you can also create\n * your own types. This is useful if you are working with an Operator that creates custom resources.\n * There are two ways to do this, the first is to use the `When()` function with a `GenericKind`,\n * the second is to create a new class that extends `GenericKind` and use the `RegisterKind()` function.\n *\n * This example shows how to use the `When()` function with a `GenericKind`. Note that you\n * must specify the `group`, `version`, and `kind` of the object (if applicable). This is how Pepr knows\n * if the action should be triggered or not. Since we are using a `GenericKind`,\n * Pepr will not be able to provide any intellisense for the object, so you will need to refer to the\n * Kubernetes API documentation for the object you are working with.\n *\n * You will need to wait for the CRD in `hello-pepr.samples.yaml` to be created, then you can apply\n *\n * ```yaml\n * apiVersion: pepr.dev/v1\n * kind: Unicorn\n * metadata:\n * name: example-1\n * namespace: pepr-demo\n * spec:\n * message: replace-me\n * counter: 0\n * ```\n */\nWhen(a.GenericKind, {\n group: "pepr.dev",\n version: "v1",\n kind: "Unicorn",\n})\n .IsCreated()\n .WithName("example-1")\n .Mutate(request => {\n request.Merge({\n spec: {\n message: "Hello Pepr without type data!",\n counter: Math.random(),\n },\n });\n });\n\n/**\n * ---------------------------------------------------------------------------------------------------\n * Mutate Action (Typed Custom Resource) *\n * ---------------------------------------------------------------------------------------------------\n *\n * This example shows how to use the `RegisterKind()` function to create a new type. This is useful\n * if you are working with an Operator that creates custom resources and you want to have intellisense\n * for the object. Note that you must specify the `group`, `version`, and `kind` of the object (if applicable)\n * as this is how Pepr knows if the action should be triggered or not.\n *\n * Once you register a new Kind with Pepr, you can use the `When()` function with the new Kind. Ideally,\n * you should register custom Kinds at the top of your Capability file or Pepr Module so they are available\n * to all actions, but we are putting it here for demonstration purposes.\n *\n * You will need to wait for the CRD in `hello-pepr.samples.yaml` to be created, then you can apply\n *\n * ```yaml\n * apiVersion: pepr.dev/v1\n * kind: Unicorn\n * metadata:\n * name: example-2\n * namespace: pepr-demo\n * spec:\n * message: replace-me\n * counter: 0\n * ```*\n */\nclass UnicornKind extends a.GenericKind {\n spec: {\n /**\n * JSDoc comments can be added to explain more details about the field.\n *\n * @example\n * ```ts\n * request.Raw.spec.message = "Hello Pepr!";\n * ```\n * */\n message: string;\n counter: number;\n };\n}\n\nRegisterKind(UnicornKind, {\n group: "pepr.dev",\n version: "v1",\n kind: "Unicorn",\n});\n\nWhen(UnicornKind)\n .IsCreated()\n .WithName("example-2")\n .Mutate(request => {\n request.Merge({\n spec: {\n message: "Hello Pepr with type data!",\n counter: Math.random(),\n },\n });\n });\n\n/**\n * A callback function that is called once the Pepr Store is fully loaded.\n */\nStore.onReady(data => {\n Log.info(data, "Pepr Store Ready");\n});\n';
1966
+ var packageJSON = { name: "pepr", description: "Kubernetes application engine", author: "Defense Unicorns", homepage: "https://github.com/defenseunicorns/pepr", license: "Apache-2.0", bin: "dist/cli.js", repository: "defenseunicorns/pepr", engines: { node: ">=18.0.0" }, files: ["/dist", "/src"], version: "0.34.1", main: "dist/lib.js", types: "dist/lib.d.ts", scripts: { ci: "npm ci", "gen-data-json": "node hack/build-template-data.js", prebuild: "rm -fr dist/* && npm run gen-data-json", version: "node scripts/set-version.js", build: "tsc && node build.mjs", "build:image": "npm run build && docker buildx build --tag pepr:dev .", test: "npm run test:unit && npm run test:journey", "test:unit": "npm run gen-data-json && jest src --coverage --detectOpenHandles --coverageDirectory=./coverage", "test:journey": "npm run test:journey:k3d && npm run test:journey:build && npm run test:journey:image && npm run test:journey:run", "test:journey:prep": "if [ ! -d ./pepr-upgrade-test ]; then git clone https://github.com/defenseunicorns/pepr-upgrade-test.git ; fi", "test:journey-wasm": "npm run test:journey:k3d && npm run test:journey:build && npm run test:journey:image && npm run test:journey:run-wasm", "test:journey:k3d": "k3d cluster delete pepr-dev && k3d cluster create pepr-dev --k3s-arg '--debug@server:0' --wait && kubectl rollout status deployment -n kube-system", "test:journey:build": "npm run build && npm pack", "test:journey:image": "docker buildx build --tag pepr:dev . && k3d image import pepr:dev -c pepr-dev", "test:journey:run": "jest --detectOpenHandles journey/entrypoint.test.ts && npm run test:journey:prep && npm run test:journey:upgrade", "test:journey:run-wasm": "jest --detectOpenHandles journey/entrypoint-wasm.test.ts", "test:journey:upgrade": "npm run test:journey:k3d && npm run test:journey:image && jest --detectOpenHandles journey/pepr-upgrade.test.ts", "format:check": "eslint src && prettier src --check", "format:fix": "eslint src --fix && prettier src --write" }, dependencies: { "@types/ramda": "0.30.1", express: "4.19.2", "fast-json-patch": "3.1.1", "json-pointer": "^0.6.2", "kubernetes-fluent-client": "3.0.1", pino: "9.3.2", "pino-pretty": "11.2.2", "prom-client": "15.1.3", ramda: "0.30.1" }, devDependencies: { "@commitlint/cli": "19.4.0", "@commitlint/config-conventional": "19.2.2", "@fast-check/jest": "^2.0.1", "@jest/globals": "29.7.0", "@types/eslint": "9.6.0", "@types/express": "4.17.21", "@types/json-pointer": "^1.0.34", "@types/node": "22.x.x", "@types/node-forge": "1.3.11", "@types/prompts": "2.4.9", "@types/uuid": "10.0.0", "fast-check": "^3.19.0", jest: "29.7.0", "js-yaml": "^4.1.0", nock: "13.5.4", "ts-jest": "29.2.4" }, peerDependencies: { "@typescript-eslint/eslint-plugin": "7.18.0", "@typescript-eslint/parser": "7.18.0", commander: "12.1.0", esbuild: "0.23.0", eslint: "8.57.0", "node-forge": "1.3.1", prettier: "3.3.3", prompts: "2.4.2", typescript: "5.3.3", uuid: "10.0.0" } };
1967
1967
 
1968
1968
  // src/templates/pepr.code-snippets.json
1969
1969
  var pepr_code_snippets_default = {
@@ -51,7 +51,7 @@ if (process.env.LOG_LEVEL) {
51
51
  var logger_default = Log;
52
52
 
53
53
  // src/templates/data.json
54
- var packageJSON = { name: "pepr", description: "Kubernetes application engine", author: "Defense Unicorns", homepage: "https://github.com/defenseunicorns/pepr", license: "Apache-2.0", bin: "dist/cli.js", repository: "defenseunicorns/pepr", engines: { node: ">=18.0.0" }, files: ["/dist", "/src"], version: "0.34.0", main: "dist/lib.js", types: "dist/lib.d.ts", scripts: { ci: "npm ci", "gen-data-json": "node hack/build-template-data.js", prebuild: "rm -fr dist/* && npm run gen-data-json", version: "node scripts/set-version.js", build: "tsc && node build.mjs", "build:image": "npm run build && docker buildx build --tag pepr:dev .", test: "npm run test:unit && npm run test:journey", "test:unit": "npm run gen-data-json && jest src --coverage --detectOpenHandles --coverageDirectory=./coverage", "test:journey": "npm run test:journey:k3d && npm run test:journey:build && npm run test:journey:image && npm run test:journey:run", "test:journey:prep": "if [ ! -d ./pepr-upgrade-test ]; then git clone https://github.com/defenseunicorns/pepr-upgrade-test.git ; fi", "test:journey-wasm": "npm run test:journey:k3d && npm run test:journey:build && npm run test:journey:image && npm run test:journey:run-wasm", "test:journey:k3d": "k3d cluster delete pepr-dev && k3d cluster create pepr-dev --k3s-arg '--debug@server:0' --wait && kubectl rollout status deployment -n kube-system", "test:journey:build": "npm run build && npm pack", "test:journey:image": "docker buildx build --tag pepr:dev . && k3d image import pepr:dev -c pepr-dev", "test:journey:run": "jest --detectOpenHandles journey/entrypoint.test.ts && npm run test:journey:prep && npm run test:journey:upgrade", "test:journey:run-wasm": "jest --detectOpenHandles journey/entrypoint-wasm.test.ts", "test:journey:upgrade": "npm run test:journey:k3d && npm run test:journey:image && jest --detectOpenHandles journey/pepr-upgrade.test.ts", "format:check": "eslint src && prettier src --check", "format:fix": "eslint src --fix && prettier src --write" }, dependencies: { "@types/ramda": "0.30.1", express: "4.19.2", "fast-json-patch": "3.1.1", "json-pointer": "^0.6.2", "kubernetes-fluent-client": "3.0.1", pino: "9.3.2", "pino-pretty": "11.2.2", "prom-client": "15.1.3", ramda: "0.30.1" }, devDependencies: { "@commitlint/cli": "19.3.0", "@commitlint/config-conventional": "19.2.2", "@fast-check/jest": "^2.0.1", "@jest/globals": "29.7.0", "@types/eslint": "9.6.0", "@types/express": "4.17.21", "@types/json-pointer": "^1.0.34", "@types/node": "22.x.x", "@types/node-forge": "1.3.11", "@types/prompts": "2.4.9", "@types/uuid": "10.0.0", "fast-check": "^3.19.0", jest: "29.7.0", "js-yaml": "^4.1.0", nock: "13.5.4", "ts-jest": "29.2.4" }, peerDependencies: { "@typescript-eslint/eslint-plugin": "7.18.0", "@typescript-eslint/parser": "7.18.0", commander: "12.1.0", esbuild: "0.23.0", eslint: "8.57.0", "node-forge": "1.3.1", prettier: "3.3.3", prompts: "2.4.2", typescript: "5.3.3", uuid: "10.0.0" } };
54
+ var packageJSON = { name: "pepr", description: "Kubernetes application engine", author: "Defense Unicorns", homepage: "https://github.com/defenseunicorns/pepr", license: "Apache-2.0", bin: "dist/cli.js", repository: "defenseunicorns/pepr", engines: { node: ">=18.0.0" }, files: ["/dist", "/src"], version: "0.34.1", main: "dist/lib.js", types: "dist/lib.d.ts", scripts: { ci: "npm ci", "gen-data-json": "node hack/build-template-data.js", prebuild: "rm -fr dist/* && npm run gen-data-json", version: "node scripts/set-version.js", build: "tsc && node build.mjs", "build:image": "npm run build && docker buildx build --tag pepr:dev .", test: "npm run test:unit && npm run test:journey", "test:unit": "npm run gen-data-json && jest src --coverage --detectOpenHandles --coverageDirectory=./coverage", "test:journey": "npm run test:journey:k3d && npm run test:journey:build && npm run test:journey:image && npm run test:journey:run", "test:journey:prep": "if [ ! -d ./pepr-upgrade-test ]; then git clone https://github.com/defenseunicorns/pepr-upgrade-test.git ; fi", "test:journey-wasm": "npm run test:journey:k3d && npm run test:journey:build && npm run test:journey:image && npm run test:journey:run-wasm", "test:journey:k3d": "k3d cluster delete pepr-dev && k3d cluster create pepr-dev --k3s-arg '--debug@server:0' --wait && kubectl rollout status deployment -n kube-system", "test:journey:build": "npm run build && npm pack", "test:journey:image": "docker buildx build --tag pepr:dev . && k3d image import pepr:dev -c pepr-dev", "test:journey:run": "jest --detectOpenHandles journey/entrypoint.test.ts && npm run test:journey:prep && npm run test:journey:upgrade", "test:journey:run-wasm": "jest --detectOpenHandles journey/entrypoint-wasm.test.ts", "test:journey:upgrade": "npm run test:journey:k3d && npm run test:journey:image && jest --detectOpenHandles journey/pepr-upgrade.test.ts", "format:check": "eslint src && prettier src --check", "format:fix": "eslint src --fix && prettier src --write" }, dependencies: { "@types/ramda": "0.30.1", express: "4.19.2", "fast-json-patch": "3.1.1", "json-pointer": "^0.6.2", "kubernetes-fluent-client": "3.0.1", pino: "9.3.2", "pino-pretty": "11.2.2", "prom-client": "15.1.3", ramda: "0.30.1" }, devDependencies: { "@commitlint/cli": "19.4.0", "@commitlint/config-conventional": "19.2.2", "@fast-check/jest": "^2.0.1", "@jest/globals": "29.7.0", "@types/eslint": "9.6.0", "@types/express": "4.17.21", "@types/json-pointer": "^1.0.34", "@types/node": "22.x.x", "@types/node-forge": "1.3.11", "@types/prompts": "2.4.9", "@types/uuid": "10.0.0", "fast-check": "^3.19.0", jest: "29.7.0", "js-yaml": "^4.1.0", nock: "13.5.4", "ts-jest": "29.2.4" }, peerDependencies: { "@typescript-eslint/eslint-plugin": "7.18.0", "@typescript-eslint/parser": "7.18.0", commander: "12.1.0", esbuild: "0.23.0", eslint: "8.57.0", "node-forge": "1.3.1", prettier: "3.3.3", prompts: "2.4.2", typescript: "5.3.3", uuid: "10.0.0" } };
55
55
 
56
56
  // src/lib/k8s.ts
57
57
  var import_kubernetes_fluent_client = require("kubernetes-fluent-client");
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/lib/controller/store.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAM3C,eAAO,MAAM,eAAe,OAAO,CAAC;AAEpC,qBAAa,mBAAmB;;gBAMlB,YAAY,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,IAAI;CA2R3E"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/lib/controller/store.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAM3C,eAAO,MAAM,eAAe,OAAO,CAAC;AAEpC,qBAAa,mBAAmB;;gBAMlB,YAAY,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,IAAI;CAyR3E"}
@@ -4,6 +4,7 @@ export type DataSender = (op: DataOp, keys: string[], value?: string) => void;
4
4
  export type DataReceiver = (data: DataStore) => void;
5
5
  export type Unsubscribe = () => void;
6
6
  export declare function v2StoreKey(key: string): string;
7
+ export declare function v2UnescapedStoreKey(key: string): string;
7
8
  export declare function stripV2Prefix(key: string): string;
8
9
  export interface PeprStore {
9
10
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/lib/storage.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;AACtC,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAC/C,MAAM,MAAM,UAAU,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;AAC9E,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;AACrD,MAAM,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC;AAKrC,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,UAErC;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,UAExC;AACD,MAAM,WAAW,SAAS;IACxB;;OAEG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACpC;;OAEG;IACH,KAAK,IAAI,IAAI,CAAC;IACd;;OAEG;IACH,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B;;OAEG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAE1C;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,EAAE,YAAY,GAAG,WAAW,CAAC;IAE/C;;OAEG;IACH,OAAO,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;IAEtC;;;OAGG;IACH,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1D;;;OAGG;IACH,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAED;;;;GAIG;AAEH,qBAAa,OAAQ,YAAW,SAAS;;IAOvC,cAAc,SAAU,UAAU,UAEhC;IAEF,OAAO,SAAU,SAAS,UAWxB;IAEF,OAAO,QAAS,MAAM,mBAMpB;IAEF,KAAK,aAEH;IAEF,UAAU,QAAS,MAAM,UAEvB;IAEF,OAAO,QAAS,MAAM,SAAS,MAAM,UAEnC;IAEF;;;;;;;OAOG;IACH,cAAc,QAAS,MAAM,SAAS,MAAM,mBAgB1C;IAEF;;;;;;OAMG;IACH,iBAAiB,QAAS,MAAM,mBAgB9B;IAEF,SAAS,eAAgB,YAAY,gBAInC;IAEF,OAAO,aAAc,YAAY,UAE/B;IAEF;;;OAGG;IACH,WAAW,QAAS,MAAM,UAExB;CAqBH"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/lib/storage.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;AACtC,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAC/C,MAAM,MAAM,UAAU,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;AAC9E,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;AACrD,MAAM,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC;AAKrC,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,UAErC;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,UAE9C;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,UAExC;AACD,MAAM,WAAW,SAAS;IACxB;;OAEG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACpC;;OAEG;IACH,KAAK,IAAI,IAAI,CAAC;IACd;;OAEG;IACH,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B;;OAEG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAE1C;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,EAAE,YAAY,GAAG,WAAW,CAAC;IAE/C;;OAEG;IACH,OAAO,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;IAEtC;;;OAGG;IACH,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1D;;;OAGG;IACH,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAED;;;;GAIG;AAEH,qBAAa,OAAQ,YAAW,SAAS;;IAOvC,cAAc,SAAU,UAAU,UAEhC;IAEF,OAAO,SAAU,SAAS,UAWxB;IAEF,OAAO,QAAS,MAAM,mBAMpB;IAEF,KAAK,aAKH;IAEF,UAAU,QAAS,MAAM,UAEvB;IAEF,OAAO,QAAS,MAAM,SAAS,MAAM,UAEnC;IAEF;;;;;;;OAOG;IACH,cAAc,QAAS,MAAM,SAAS,MAAM,mBAiB1C;IAEF;;;;;;OAMG;IACH,iBAAiB,QAAS,MAAM,mBAgB9B;IAEF,SAAS,eAAgB,YAAY,gBAInC;IAEF,OAAO,aAAc,YAAY,UAE/B;IAEF;;;OAGG;IACH,WAAW,QAAS,MAAM,UAExB;CAqBH"}
package/dist/lib.js CHANGED
@@ -1436,6 +1436,9 @@ var STORE_VERSION_PREFIX = "v2";
1436
1436
  function v2StoreKey(key) {
1437
1437
  return `${STORE_VERSION_PREFIX}-${import_json_pointer.default.escape(key)}`;
1438
1438
  }
1439
+ function v2UnescapedStoreKey(key) {
1440
+ return `${STORE_VERSION_PREFIX}-${key}`;
1441
+ }
1439
1442
  var Storage = class {
1440
1443
  #store = {};
1441
1444
  #send;
@@ -1454,14 +1457,17 @@ var Storage = class {
1454
1457
  }
1455
1458
  };
1456
1459
  getItem = (key) => {
1457
- const result = this.#store[v2StoreKey(key)] || null;
1460
+ const result = this.#store[v2UnescapedStoreKey(key)] || null;
1458
1461
  if (result !== null && typeof result !== "function" && typeof result !== "object") {
1459
1462
  return result;
1460
1463
  }
1461
1464
  return null;
1462
1465
  };
1463
1466
  clear = () => {
1464
- this.#dispatchUpdate("remove", Object.keys(this.#store));
1467
+ this.#dispatchUpdate(
1468
+ "remove",
1469
+ Object.keys(this.#store).map((key) => import_json_pointer.default.escape(key))
1470
+ );
1465
1471
  };
1466
1472
  removeItem = (key) => {
1467
1473
  this.#dispatchUpdate("remove", [v2StoreKey(key)]);
@@ -1481,7 +1487,7 @@ var Storage = class {
1481
1487
  this.#dispatchUpdate("add", [v2StoreKey(key)], value);
1482
1488
  return new Promise((resolve, reject) => {
1483
1489
  const unsubscribe = this.subscribe((data) => {
1484
- if (data[`${v2StoreKey(key)}`] === value) {
1490
+ if (data[`${v2UnescapedStoreKey(key)}`] === value) {
1485
1491
  unsubscribe();
1486
1492
  resolve();
1487
1493
  }
@@ -1503,7 +1509,7 @@ var Storage = class {
1503
1509
  this.#dispatchUpdate("remove", [v2StoreKey(key)]);
1504
1510
  return new Promise((resolve, reject) => {
1505
1511
  const unsubscribe = this.subscribe((data) => {
1506
- if (!Object.hasOwn(data, `${v2StoreKey(key)}`)) {
1512
+ if (!Object.hasOwn(data, `${v2UnescapedStoreKey(key)}`)) {
1507
1513
  unsubscribe();
1508
1514
  resolve();
1509
1515
  }
package/dist/lib.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/lib.ts", "../src/lib/capability.ts", "../src/lib/logger.ts", "../src/lib/module.ts", "../src/lib/controller/index.ts", "../src/lib/metrics.ts", "../src/lib/mutate-processor.ts", "../src/lib/errors.ts", "../src/lib/k8s.ts", "../src/lib/filter.ts", "../src/lib/mutate-request.ts", "../src/lib/utils.ts", "../src/lib/validate-request.ts", "../src/lib/validate-processor.ts", "../src/lib/controller/store.ts", "../src/lib/watch-processor.ts", "../src/lib/helpers.ts", "../src/sdk/sdk.ts", "../src/lib/queue.ts", "../src/lib/storage.ts", "../src/lib/schedule.ts"],
4
- "sourcesContent": ["import { K8s, RegisterKind, kind as a, fetch, fetchStatus, kind } from \"kubernetes-fluent-client\";\nimport * as R from \"ramda\";\n\nimport { Capability } from \"./lib/capability\";\nimport Log from \"./lib/logger\";\nimport { PeprModule } from \"./lib/module\";\nimport { PeprMutateRequest } from \"./lib/mutate-request\";\nimport * as PeprUtils from \"./lib/utils\";\nimport { PeprValidateRequest } from \"./lib/validate-request\";\nimport * as sdk from \"./sdk/sdk\";\n\nexport {\n Capability,\n K8s,\n Log,\n PeprModule,\n PeprMutateRequest,\n PeprUtils,\n PeprValidateRequest,\n R,\n RegisterKind,\n a,\n fetch,\n fetchStatus,\n kind,\n sdk,\n};\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { GenericClass, GroupVersionKind, modelToGroupVersionKind } from \"kubernetes-fluent-client\";\nimport { WatchAction } from \"kubernetes-fluent-client/dist/fluent/types\";\nimport { pickBy } from \"ramda\";\n\nimport Log from \"./logger\";\nimport { isBuildMode, isDevMode, isWatchMode } from \"./module\";\nimport { PeprStore, Storage } from \"./storage\";\nimport { OnSchedule, Schedule } from \"./schedule\";\nimport {\n Binding,\n BindingFilter,\n BindingWithName,\n CapabilityCfg,\n CapabilityExport,\n Event,\n MutateAction,\n MutateActionChain,\n ValidateAction,\n ValidateActionChain,\n WhenSelector,\n} from \"./types\";\n\nconst registerAdmission = isBuildMode() || !isWatchMode();\nconst registerWatch = isBuildMode() || isWatchMode() || isDevMode();\n\n/**\n * A capability is a unit of functionality that can be registered with the Pepr runtime.\n */\nexport class Capability implements CapabilityExport {\n #name: string;\n #description: string;\n #namespaces?: string[] | undefined;\n #bindings: Binding[] = [];\n #store = new Storage();\n #scheduleStore = new Storage();\n #registered = false;\n #scheduleRegistered = false;\n hasSchedule: boolean;\n\n /**\n * Run code on a schedule with the capability.\n *\n * @param schedule The schedule to run the code on\n * @returns\n */\n OnSchedule: (schedule: Schedule) => void = (schedule: Schedule) => {\n const { name, every, unit, run, startTime, completions } = schedule;\n this.hasSchedule = true;\n\n if (process.env.PEPR_WATCH_MODE === \"true\" || process.env.PEPR_MODE === \"dev\") {\n // Only create/watch schedule store if necessary\n\n // Create a new schedule\n const newSchedule: Schedule = {\n name,\n every,\n unit,\n run,\n startTime,\n completions,\n };\n\n this.#scheduleStore.onReady(() => {\n new OnSchedule(newSchedule).setStore(this.#scheduleStore);\n });\n }\n };\n\n /**\n * Store is a key-value data store that can be used to persist data that should be shared\n * between requests. Each capability has its own store, and the data is persisted in Kubernetes\n * in the `pepr-system` namespace.\n *\n * Note: You should only access the store from within an action.\n */\n Store: PeprStore = {\n clear: this.#store.clear,\n getItem: this.#store.getItem,\n removeItem: this.#store.removeItem,\n removeItemAndWait: this.#store.removeItemAndWait,\n setItem: this.#store.setItem,\n subscribe: this.#store.subscribe,\n onReady: this.#store.onReady,\n setItemAndWait: this.#store.setItemAndWait,\n };\n\n /**\n * ScheduleStore is a key-value data store used to persist schedule data that should be shared\n * between intervals. Each Schedule shares store, and the data is persisted in Kubernetes\n * in the `pepr-system` namespace.\n *\n * Note: There is no direct access to schedule store\n */\n ScheduleStore: PeprStore = {\n clear: this.#scheduleStore.clear,\n getItem: this.#scheduleStore.getItem,\n removeItemAndWait: this.#scheduleStore.removeItemAndWait,\n removeItem: this.#scheduleStore.removeItem,\n setItemAndWait: this.#scheduleStore.setItemAndWait,\n setItem: this.#scheduleStore.setItem,\n subscribe: this.#scheduleStore.subscribe,\n onReady: this.#scheduleStore.onReady,\n };\n\n get bindings() {\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 constructor(cfg: CapabilityCfg) {\n this.#name = cfg.name;\n this.#description = cfg.description;\n this.#namespaces = cfg.namespaces;\n this.hasSchedule = false;\n\n Log.info(`Capability ${this.#name} registered`);\n Log.debug(cfg);\n }\n\n /**\n * Register the store with the capability. This is called automatically by the Pepr controller.\n *\n * @param store\n */\n registerScheduleStore = () => {\n Log.info(`Registering schedule store for ${this.#name}`);\n\n if (this.#scheduleRegistered) {\n throw new Error(`Schedule store already registered for ${this.#name}`);\n }\n\n this.#scheduleRegistered = true;\n\n // Pass back any ready callback to the controller\n return {\n scheduleStore: this.#scheduleStore,\n };\n };\n\n /**\n * Register the store with the capability. This is called automatically by the Pepr controller.\n *\n * @param store\n */\n registerStore = () => {\n Log.info(`Registering store for ${this.#name}`);\n\n if (this.#registered) {\n throw new Error(`Store already registered for ${this.#name}`);\n }\n\n this.#registered = true;\n\n // Pass back any ready callback to the controller\n return {\n store: this.#store,\n };\n };\n\n /**\n * The When method is used to register a 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 model,\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 };\n\n const bindings = this.#bindings;\n const prefix = `${this.#name}: ${model.name}`;\n const commonChain = { WithLabel, WithAnnotation, Mutate, Validate, Watch, Reconcile };\n const isNotEmpty = (value: object) => Object.keys(value).length > 0;\n const log = (message: string, cbString: string) => {\n const filteredObj = pickBy(isNotEmpty, binding.filters);\n\n Log.info(`${message} configured for ${binding.event}`, prefix);\n Log.info(filteredObj, prefix);\n Log.debug(cbString, prefix);\n };\n\n function Validate(validateCallback: ValidateAction<T>): ValidateActionChain<T> {\n if (registerAdmission) {\n log(\"Validate Action\", validateCallback.toString());\n\n // Push the binding to the list of bindings for this capability as a new BindingAction\n // with the callback function to preserve\n bindings.push({\n ...binding,\n isValidate: true,\n validateCallback,\n });\n }\n\n return { Watch, Reconcile };\n }\n\n function Mutate(mutateCallback: MutateAction<T>): MutateActionChain<T> {\n if (registerAdmission) {\n log(\"Mutate Action\", mutateCallback.toString());\n\n // Push the binding to the list of bindings for this capability as a new BindingAction\n // with the callback function to preserve\n bindings.push({\n ...binding,\n isMutate: true,\n mutateCallback,\n });\n }\n\n // Now only allow adding actions to the same binding\n return { Watch, Validate, Reconcile };\n }\n\n function Watch(watchCallback: WatchAction<T>) {\n if (registerWatch) {\n log(\"Watch Action\", watchCallback.toString());\n\n bindings.push({\n ...binding,\n isWatch: true,\n watchCallback,\n });\n }\n }\n\n function Reconcile(watchCallback: WatchAction<T>) {\n if (registerWatch) {\n log(\"Reconcile Action\", watchCallback.toString());\n\n bindings.push({\n ...binding,\n isWatch: true,\n isQueue: true,\n watchCallback,\n });\n }\n }\n\n function InNamespace(...namespaces: string[]): BindingWithName<T> {\n Log.debug(`Add namespaces filter ${namespaces}`, prefix);\n binding.filters.namespaces.push(...namespaces);\n return { ...commonChain, WithName };\n }\n\n function WithName(name: string): BindingFilter<T> {\n Log.debug(`Add name filter ${name}`, prefix);\n binding.filters.name = name;\n return commonChain;\n }\n\n function WithLabel(key: string, value = \"\"): BindingFilter<T> {\n Log.debug(`Add label filter ${key}=${value}`, prefix);\n binding.filters.labels[key] = value;\n return commonChain;\n }\n\n function WithAnnotation(key: string, value = \"\"): BindingFilter<T> {\n Log.debug(`Add annotation filter ${key}=${value}`, prefix);\n binding.filters.annotations[key] = value;\n return commonChain;\n }\n\n function bindEvent(event: Event) {\n binding.event = event;\n return {\n ...commonChain,\n InNamespace,\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 { pino, stdTimeFunctions } from \"pino\";\n\nconst isPrettyLog = process.env.PEPR_PRETTY_LOGS === \"true\";\n\nconst pretty = {\n target: \"pino-pretty\",\n options: {\n colorize: true,\n },\n};\n\nconst transport = isPrettyLog ? pretty : undefined;\n// epochTime is the pino default\nconst pinoTimeFunction =\n process.env.PINO_TIME_STAMP === \"iso\" ? () => stdTimeFunctions.isoTime() : () => stdTimeFunctions.epochTime();\nconst Log = pino({\n transport,\n timestamp: pinoTimeFunction,\n});\n\nif (process.env.LOG_LEVEL) {\n Log.level = process.env.LOG_LEVEL;\n}\nexport default Log;\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\nimport { clone } from \"ramda\";\nimport { Capability } from \"./capability\";\nimport { Controller } from \"./controller\";\nimport { ValidateError } from \"./errors\";\nimport { AdmissionRequest, MutateResponse, ValidateResponse, WebhookIgnore } from \"./k8s\";\nimport { CapabilityExport } from \"./types\";\nimport { setupWatch } from \"./watch-processor\";\nimport { Log } from \"../lib\";\n\n/** Custom Labels Type for package.json */\nexport interface CustomLabels {\n namespace?: Record<string, string>;\n}\n/** Global configuration for the Pepr runtime. */\nexport type ModuleConfig = {\n /** The Pepr version this module uses */\n peprVersion?: string;\n /** The user-defined version of the module */\n appVersion?: string;\n /** A unique identifier for this Pepr module. This is automatically generated by Pepr. */\n uuid: string;\n /** A description of the Pepr module and what it does. */\n description?: string;\n /** The webhookTimeout */\n webhookTimeout?: number;\n /** Reject K8s resource AdmissionRequests on error. */\n onError?: string;\n /** Configure global exclusions that will never be processed by Pepr. */\n alwaysIgnore: WebhookIgnore;\n /** Define the log level for the in-cluster controllers */\n logLevel?: string;\n /** Propagate env variables to in-cluster controllers */\n env?: Record<string, string>;\n /** Custom Labels for Kubernetes Objects */\n customLabels?: CustomLabels;\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: AdmissionRequest) => 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: MutateResponse | ValidateResponse) => void;\n};\n\n// Track if this is a watch mode controller\nexport const isWatchMode = () => process.env.PEPR_WATCH_MODE === \"true\";\n\n// Track if Pepr is running in build mode\nexport const isBuildMode = () => process.env.PEPR_MODE === \"build\";\n\nexport const isDevMode = () => process.env.PEPR_MODE === \"dev\";\n\nexport class PeprModule {\n #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 opts Options for the Pepr runtime\n */\n constructor({ description, pepr }: PackageJSON, capabilities: Capability[] = [], opts: PeprModuleOptions = {}) {\n const config: ModuleConfig = clone(pepr);\n config.description = description;\n\n // Need to validate at runtime since TS gets sad about parsing the package.json\n ValidateError(config.onError);\n\n // Handle build mode\n if (isBuildMode()) {\n // Fail if process.send is not defined\n if (!process.send) {\n throw new Error(\"process.send is not defined\");\n }\n\n const exportedCapabilities: CapabilityExport[] = [];\n\n // Send capability map to parent process\n for (const capability of capabilities) {\n // Convert the capability to a capability config\n exportedCapabilities.push({\n name: capability.name,\n description: capability.description,\n namespaces: capability.namespaces,\n bindings: capability.bindings,\n hasSchedule: capability.hasSchedule,\n });\n }\n\n // Send the capabilities back to the parent process\n process.send(exportedCapabilities);\n\n return;\n }\n\n this.#controller = new Controller(config, capabilities, opts.beforeHook, opts.afterHook, () => {\n // Wait for the controller to be ready before setting up watches\n if (isWatchMode() || isDevMode()) {\n try {\n setupWatch(capabilities);\n } catch (e) {\n Log.error(e, \"Error setting up watch\");\n process.exit(1);\n }\n }\n });\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, { NextFunction } from \"express\";\nimport fs from \"fs\";\nimport https from \"https\";\n\nimport { Capability } from \"../capability\";\nimport { MutateResponse, AdmissionRequest, ValidateResponse } from \"../k8s\";\nimport Log from \"../logger\";\nimport { metricsCollector, MetricsCollector } from \"../metrics\";\nimport { ModuleConfig, isWatchMode } from \"../module\";\nimport { mutateProcessor } from \"../mutate-processor\";\nimport { validateProcessor } from \"../validate-processor\";\nimport { PeprControllerStore } from \"./store\";\nimport { ResponseItem } from \"../types\";\n\nexport class Controller {\n // Track whether the server is running\n #running = false;\n\n // Metrics collector\n #metricsCollector = metricsCollector;\n\n // The token used to authenticate requests\n #token = \"\";\n\n // The express app instance\n readonly #app = express();\n\n // Initialized with the constructor\n readonly #config: ModuleConfig;\n readonly #capabilities: Capability[];\n readonly #beforeHook?: (req: AdmissionRequest) => void;\n readonly #afterHook?: (res: MutateResponse | ValidateResponse) => void;\n\n constructor(\n config: ModuleConfig,\n capabilities: Capability[],\n beforeHook?: (req: AdmissionRequest) => void,\n afterHook?: (res: MutateResponse | ValidateResponse) => void,\n onReady?: () => void,\n ) {\n this.#config = config;\n this.#capabilities = capabilities;\n\n // Initialize the Pepr store for each capability\n new PeprControllerStore(capabilities, `pepr-${config.uuid}-store`, () => {\n this.#bindEndpoints();\n onReady && onReady();\n Log.info(\"\u2705 Controller startup complete\");\n // Initialize the schedule store for each capability\n new PeprControllerStore(capabilities, `pepr-${config.uuid}-schedule`, () => {\n Log.info(\"\u2705 Scheduling processed\");\n });\n });\n\n // Middleware for logging requests\n this.#app.use(Controller.#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 if (beforeHook) {\n Log.info(`Using beforeHook: ${beforeHook}`);\n this.#beforeHook = beforeHook;\n }\n\n if (afterHook) {\n Log.info(`Using afterHook: ${afterHook}`);\n this.#afterHook = afterHook;\n }\n }\n\n /** Start the webhook server */\n 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 if not in watch mode\n if (!isWatchMode()) {\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 Log.info(`Using API token: ${this.#token}`);\n\n if (!this.#token) {\n throw new Error(\"API token not found\");\n }\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 Log.info(`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 Log.warn(\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 Log.info(\"Received SIGTERM, closing server\");\n server.close(() => {\n Log.info(\"Server closed\");\n process.exit(0);\n });\n });\n };\n\n #bindEndpoints = () => {\n // Health check endpoint\n this.#app.get(\"/healthz\", Controller.#healthz);\n\n // Metrics endpoint\n this.#app.get(\"/metrics\", this.#metrics);\n\n if (isWatchMode()) {\n return;\n }\n\n // Require auth for webhook endpoints\n this.#app.use([\"/mutate/:token\", \"/validate/:token\"], this.#validateToken);\n\n // Mutate endpoint\n this.#app.post(\"/mutate/:token\", this.#admissionReq(\"Mutate\"));\n\n // Validate endpoint\n this.#app.post(\"/validate/:token\", this.#admissionReq(\"Validate\"));\n };\n\n /**\n * Validate the token in the request path\n *\n * @param req The incoming request\n * @param res The outgoing response\n * @param next The next middleware function\n * @returns\n */\n #validateToken = (req: express.Request, res: express.Response, next: NextFunction) => {\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 Log.warn(err);\n res.status(401).send(err);\n this.#metricsCollector.alert();\n return;\n }\n\n // Token is valid, continue\n next();\n };\n\n /**\n * Metrics endpoint handler\n *\n * @param req the incoming request\n * @param res the outgoing response\n */\n #metrics = async (req: express.Request, res: express.Response) => {\n try {\n res.send(await this.#metricsCollector.getMetrics());\n } catch (err) {\n Log.error(err, `Error getting metrics`);\n res.status(500).send(\"Internal Server Error\");\n }\n };\n\n /**\n * Admission request handler for both mutate and validate requests\n *\n * @param admissionKind the type of admission request\n * @returns the request handler\n */\n #admissionReq = (admissionKind: \"Mutate\" | \"Validate\") => {\n // Create the admission request handler\n return async (req: express.Request, res: express.Response) => {\n // Start the metrics timer\n const startTime = MetricsCollector.observeStart();\n\n try {\n // Get the request from the body or create an empty request\n const request: AdmissionRequest = req.body?.request || ({} as AdmissionRequest);\n\n // Run the before hook if it exists\n this.#beforeHook && this.#beforeHook(request || {});\n\n // Setup identifiers for logging\n const name = request?.name ? `/${request.name}` : \"\";\n const namespace = request?.namespace || \"\";\n const gvk = request?.kind || { group: \"\", version: \"\", kind: \"\" };\n\n const reqMetadata = {\n uid: request.uid,\n namespace,\n name,\n };\n\n Log.info({ ...reqMetadata, gvk, operation: request.operation, admissionKind }, \"Incoming request\");\n Log.debug({ ...reqMetadata, request }, \"Incoming request body\");\n\n // Process the request\n let response: MutateResponse | ValidateResponse[];\n\n // Call mutate or validate based on the admission kind\n if (admissionKind === \"Mutate\") {\n response = await mutateProcessor(this.#config, this.#capabilities, request, reqMetadata);\n } else {\n response = await validateProcessor(this.#capabilities, request, reqMetadata);\n }\n\n // Run the after hook if it exists\n const responseList: ValidateResponse[] | MutateResponse[] = Array.isArray(response) ? response : [response];\n responseList.map(res => {\n this.#afterHook && this.#afterHook(res);\n // Log the response\n Log.info({ ...reqMetadata, res }, \"Check response\");\n });\n\n let kubeAdmissionResponse: ValidateResponse[] | MutateResponse | ResponseItem;\n\n if (admissionKind === \"Mutate\") {\n kubeAdmissionResponse = response;\n Log.debug({ ...reqMetadata, response }, \"Outgoing response\");\n res.send({\n apiVersion: \"admission.k8s.io/v1\",\n kind: \"AdmissionReview\",\n response: kubeAdmissionResponse,\n });\n } else {\n kubeAdmissionResponse =\n responseList.length === 0\n ? {\n uid: request.uid,\n allowed: true,\n status: { message: \"no in-scope validations -- allowed!\" },\n }\n : {\n uid: responseList[0].uid,\n allowed: responseList.filter(r => !r.allowed).length === 0,\n status: {\n message: (responseList as ValidateResponse[])\n .filter(rl => !rl.allowed)\n .map(curr => curr.status?.message)\n .join(\"; \"),\n },\n };\n res.send({\n apiVersion: \"admission.k8s.io/v1\",\n kind: \"AdmissionReview\",\n response: kubeAdmissionResponse,\n });\n }\n\n Log.debug({ ...reqMetadata, kubeAdmissionResponse }, \"Outgoing response\");\n\n this.#metricsCollector.observeEnd(startTime, admissionKind);\n } catch (err) {\n Log.error(err, `Error processing ${admissionKind} request`);\n res.status(500).send(\"Internal Server Error\");\n this.#metricsCollector.error();\n }\n };\n };\n\n /**\n * Middleware for logging requests\n *\n * @param req the incoming request\n * @param res the outgoing response\n * @param next the next middleware function\n */\n static #logger(req: express.Request, res: express.Response, next: express.NextFunction) {\n const startTime = Date.now();\n\n res.on(\"finish\", () => {\n const elapsedTime = Date.now() - startTime;\n const message = {\n uid: req.body?.request?.uid,\n method: req.method,\n url: req.originalUrl,\n status: res.statusCode,\n duration: `${elapsedTime} ms`,\n };\n\n res.statusCode >= 300 ? Log.warn(message) : Log.info(message);\n });\n\n next();\n }\n /**\n * Health check endpoint handler\n *\n * @param req the incoming request\n * @param res the outgoing response\n */\n static #healthz(req: express.Request, res: express.Response) {\n try {\n res.send(\"OK\");\n } catch (err) {\n Log.error(err, `Error processing health check`);\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\n/* eslint-disable class-methods-use-this */\n\nimport { performance } from \"perf_hooks\";\nimport promClient, { Counter, Registry, Gauge, Summary } from \"prom-client\";\nimport Log from \"./logger\";\n\nconst loggingPrefix = \"MetricsCollector\";\n\ninterface MetricNames {\n errors: string;\n alerts: string;\n mutate: string;\n validate: string;\n cacheMiss: string;\n resyncFailureCount: string;\n}\n\ninterface MetricArgs {\n name: string;\n help: string;\n registers: Registry[];\n labelNames?: string[];\n}\n\n/**\n * MetricsCollector class handles metrics collection using prom-client and performance hooks.\n */\nexport class MetricsCollector {\n #registry: Registry;\n #counters: Map<string, Counter<string>> = new Map();\n #gauges: Map<string, Gauge<string>> = new Map();\n #summaries: Map<string, Summary<string>> = new Map();\n #prefix: string;\n #cacheMissWindows: Map<string, number> = new Map();\n\n #metricNames: MetricNames = {\n errors: \"errors\",\n alerts: \"alerts\",\n mutate: \"mutate\",\n validate: \"validate\",\n cacheMiss: \"cache_miss\",\n resyncFailureCount: \"resync_failure_count\",\n };\n\n /**\n * Creates a MetricsCollector instance with prefixed metrics.\n * @param [prefix='pepr'] - The prefix for the metric names.\n */\n constructor(prefix = \"pepr\") {\n this.#registry = new Registry();\n this.#prefix = prefix;\n this.addCounter(this.#metricNames.errors, \"Mutation/Validate errors encountered\");\n this.addCounter(this.#metricNames.alerts, \"Mutation/Validate bad api token received\");\n this.addSummary(this.#metricNames.mutate, \"Mutation operation summary\");\n this.addSummary(this.#metricNames.validate, \"Validation operation summary\");\n this.addGauge(this.#metricNames.cacheMiss, \"Number of cache misses per window\", [\"window\"]);\n this.addGauge(this.#metricNames.resyncFailureCount, \"Number of failures per resync operation\", [\"count\"]);\n }\n\n #getMetricName = (name: string) => `${this.#prefix}_${name}`;\n\n #addMetric = <T extends Counter<string> | Gauge<string> | Summary<string>>(\n collection: Map<string, T>,\n MetricType: new (args: MetricArgs) => T,\n name: string,\n help: string,\n labelNames?: string[],\n ) => {\n if (collection.has(this.#getMetricName(name))) {\n Log.debug(`Metric for ${name} already exists`, loggingPrefix);\n return;\n }\n\n const metric = new MetricType({\n name: this.#getMetricName(name),\n help,\n registers: [this.#registry],\n labelNames,\n });\n\n collection.set(this.#getMetricName(name), metric);\n };\n\n addCounter = (name: string, help: string) => {\n this.#addMetric(this.#counters, promClient.Counter, name, help, []);\n };\n\n addSummary = (name: string, help: string) => {\n this.#addMetric(this.#summaries, promClient.Summary, name, help, []);\n };\n\n addGauge = (name: string, help: string, labelNames?: string[]) => {\n this.#addMetric(this.#gauges, promClient.Gauge, name, help, labelNames);\n };\n\n incCounter = (name: string) => {\n this.#counters.get(this.#getMetricName(name))?.inc();\n };\n\n incGauge = (name: string, labels?: Record<string, string>, value: number = 1) => {\n this.#gauges.get(this.#getMetricName(name))?.inc(labels || {}, value);\n };\n\n /**\n * Increments the error counter.\n */\n error = () => this.incCounter(this.#metricNames.errors);\n\n /**\n * Increments the alerts counter.\n */\n alert = () => this.incCounter(this.#metricNames.alerts);\n\n /**\n * Observes the duration since the provided start time and updates the summary.\n * @param startTime - The start time.\n * @param name - The metrics summary to increment.\n */\n observeEnd = (startTime: number, name: string = this.#metricNames.mutate) => {\n this.#summaries.get(this.#getMetricName(name))?.observe(performance.now() - startTime);\n };\n\n /**\n * Fetches the current metrics from the registry.\n * @returns The metrics.\n */\n getMetrics = () => this.#registry.metrics();\n\n /**\n * Returns the current timestamp from performance.now() method. Useful for start timing an operation.\n * @returns The timestamp.\n */\n static observeStart() {\n return performance.now();\n }\n\n /**\n * Increments the cache miss gauge for a given label.\n * @param label - The label for the cache miss.\n */\n incCacheMiss = (window: string) => {\n this.incGauge(this.#metricNames.cacheMiss, { window });\n };\n\n /**\n * Increments the retry count gauge.\n * @param count - The count to increment by.\n */\n incRetryCount = (count: string) => {\n this.incGauge(this.#metricNames.resyncFailureCount, { count });\n };\n\n /**\n * Initializes the cache miss gauge for a given label.\n * @param label - The label for the cache miss.\n */\n initCacheMissWindow = (window: string) => {\n this.#rollCacheMissWindows();\n this.#gauges.get(this.#getMetricName(this.#metricNames.cacheMiss))?.set({ window }, 0);\n this.#cacheMissWindows.set(window, 0);\n };\n\n /**\n * Manages the size of the cache miss gauge map.\n */\n #rollCacheMissWindows = () => {\n const maxCacheMissWindows = process.env.PEPR_MAX_CACHE_MISS_WINDOWS\n ? parseInt(process.env.PEPR_MAX_CACHE_MISS_WINDOWS, 10)\n : undefined;\n\n if (maxCacheMissWindows !== undefined && this.#cacheMissWindows.size >= maxCacheMissWindows) {\n const firstKey = this.#cacheMissWindows.keys().next().value;\n this.#cacheMissWindows.delete(firstKey);\n this.#gauges.get(this.#getMetricName(this.#metricNames.cacheMiss))?.remove({ window: firstKey });\n }\n };\n}\n\nexport const metricsCollector = new MetricsCollector(\"pepr\");\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport jsonPatch from \"fast-json-patch\";\nimport { kind } from \"kubernetes-fluent-client\";\n\nimport { Capability } from \"./capability\";\nimport { Errors } from \"./errors\";\nimport { shouldSkipRequest } from \"./filter\";\nimport { MutateResponse, AdmissionRequest } from \"./k8s\";\nimport Log from \"./logger\";\nimport { ModuleConfig } from \"./module\";\nimport { PeprMutateRequest } from \"./mutate-request\";\nimport { base64Encode, convertFromBase64Map, convertToBase64Map } from \"./utils\";\n\nexport async function mutateProcessor(\n config: ModuleConfig,\n capabilities: Capability[],\n req: AdmissionRequest,\n reqMetadata: Record<string, string>,\n): Promise<MutateResponse> {\n const wrapped = new PeprMutateRequest(req);\n const response: MutateResponse = {\n uid: req.uid,\n warnings: [],\n allowed: false,\n };\n\n // Track whether any capability matched the request\n let matchedAction = 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 kind.Secret);\n }\n\n Log.info(reqMetadata, `Processing request`);\n\n for (const { name, bindings, namespaces } of capabilities) {\n const actionMetadata = { ...reqMetadata, name };\n\n for (const action of bindings) {\n // Skip this action if it's not a mutate action\n if (!action.mutateCallback) {\n continue;\n }\n\n // Continue to the next action without doing anything if this one should be skipped\n if (shouldSkipRequest(action, req, namespaces)) {\n continue;\n }\n\n const label = action.mutateCallback.name;\n Log.info(actionMetadata, `Processing mutation action (${label})`);\n\n matchedAction = 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.mutateCallback(wrapped);\n\n Log.info(actionMetadata, `Mutation action succeeded (${label})`);\n\n // Add annotations to the request to indicate that the capability succeeded\n updateStatus(\"succeeded\");\n } catch (e) {\n updateStatus(\"warning\");\n response.warnings = response.warnings || [];\n\n let errorMessage = \"\";\n\n try {\n if (e.message && e.message !== \"[object Object]\") {\n errorMessage = e.message;\n } else {\n throw new Error(\"An error occurred in the mutate action.\");\n }\n } catch (e) {\n errorMessage = \"An error occurred with the mutate action.\";\n }\n\n Log.error(actionMetadata, `Action failed: ${errorMessage}`);\n response.warnings.push(`Action failed: ${errorMessage}`);\n\n switch (config.onError) {\n case Errors.reject:\n Log.error(actionMetadata, `Action failed: ${errorMessage}`);\n response.result = \"Pepr module configured to reject on error\";\n return response;\n\n case Errors.audit:\n response.auditAnnotations = response.auditAnnotations || {};\n response.auditAnnotations[Date.now()] = `Action failed: ${errorMessage}`;\n break;\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 (!matchedAction) {\n Log.info(reqMetadata, `No matching actions found`);\n return response;\n }\n\n // delete operations can't be mutate, just return before the transformation\n if (req.operation == \"DELETE\") {\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 kind.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 = base64Encode(JSON.stringify(patches));\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({ ...reqMetadata, patches }, `Patches generated`);\n\n return response;\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nexport const Errors = {\n audit: \"audit\",\n ignore: \"ignore\",\n reject: \"reject\",\n};\n\nexport const ErrorList = Object.values(Errors);\n\n/**\n * Validate the error or throw an error\n * @param error\n */\nexport function ValidateError(error = \"\") {\n if (!ErrorList.includes(error)) {\n throw new Error(`Invalid error: ${error}. Must be one of: ${ErrorList.join(\", \")}`);\n }\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { GenericKind, GroupVersionKind, KubernetesObject, RegisterKind } from \"kubernetes-fluent-client\";\n\nexport enum Operation {\n CREATE = \"CREATE\",\n UPDATE = \"UPDATE\",\n DELETE = \"DELETE\",\n CONNECT = \"CONNECT\",\n}\n\n/**\n * PeprStore for internal use by Pepr. This is used to store arbitrary data in the cluster.\n */\nexport class PeprStore extends GenericKind {\n declare data: {\n [key: string]: string;\n };\n}\n\nexport const peprStoreGVK = {\n kind: \"PeprStore\",\n version: \"v1\",\n group: \"pepr.dev\",\n};\n\nRegisterKind(PeprStore, peprStoreGVK);\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 AdmissionRequest<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 or DELETE 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 MutateResponse {\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 /**\n * AuditAnnotations is an unstructured key value map set by remote admission controller (e.g. error=image-blacklisted).\n *\n * See https://kubernetes.io/docs/reference/labels-annotations-taints/audit-annotations/ for more information\n */\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 interface ValidateResponse extends MutateResponse {\n /** Status contains extra details into why an admission request was denied. This field IS NOT consulted in any way if \"Allowed\" is \"true\". */\n status?: {\n /** A machine-readable description of why this operation is in the\n \"Failure\" status. If this value is empty there is no information available. */\n code: number;\n\n /** A human-readable description of the status of this operation. */\n message: string;\n };\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", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { AdmissionRequest, Operation } from \"./k8s\";\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 action binding\n * @param req the incoming request\n * @returns\n */\nexport function shouldSkipRequest(binding: Binding, req: AdmissionRequest, capabilityNamespaces: string[]) {\n const { group, kind, version } = binding.kind || {};\n const { namespaces, labels, annotations, name } = binding.filters || {};\n const operation = req.operation.toUpperCase();\n const uid = req.uid;\n // Use the old object if the request is a DELETE operation\n const srcObject = operation === Operation.DELETE ? req.oldObject : req.object;\n const { metadata } = srcObject || {};\n const combinedNamespaces = [...namespaces, ...capabilityNamespaces];\n\n // Test for matching operation\n if (!binding.event.includes(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 (\n (combinedNamespaces.length && !combinedNamespaces.includes(req.namespace || \"\")) ||\n (!namespaces.includes(req.namespace || \"\") && capabilityNamespaces.length !== 0 && namespaces.length !== 0)\n ) {\n let type = \"\";\n let label = \"\";\n\n if (binding.isMutate) {\n type = \"Mutate\";\n label = binding.mutateCallback!.name;\n } else if (binding.isValidate) {\n type = \"Validate\";\n label = binding.validateCallback!.name;\n } else if (binding.isWatch) {\n type = \"Watch\";\n label = binding.watchCallback!.name;\n }\n\n logger.debug({ uid }, `${type} binding (${label}) does not match request namespace \"${req.namespace}\"`);\n\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({ uid }, `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({ uid }, `${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({ uid }, `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({ uid }, `${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 { KubernetesObject } from \"kubernetes-fluent-client\";\nimport { clone, mergeDeepRight } from \"ramda\";\n\nimport { AdmissionRequest, Operation } from \"./k8s\";\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 PeprMutateRequest<T extends KubernetesObject> {\n Raw: T;\n\n #input: AdmissionRequest<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(input: AdmissionRequest<T>) {\n this.#input = input;\n\n // If this is a DELETE operation, use the oldObject instead\n if (input.operation.toUpperCase() === Operation.DELETE) {\n this.Raw = clone(input.oldObject as T);\n } else {\n // Otherwise, use the incoming object\n this.Raw = clone(input.object);\n }\n\n if (!this.Raw) {\n throw new Error(\"unable to load the request object into PeprRequest.RawP\");\n }\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\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\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 if (obj.data[key] == undefined) {\n obj.data[key] = \"\";\n } else {\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 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", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\n/* eslint-disable class-methods-use-this */\n\nimport { KubernetesObject } from \"kubernetes-fluent-client\";\n\nimport { clone } from \"ramda\";\nimport { Operation, AdmissionRequest } from \"./k8s\";\nimport { ValidateActionResponse } 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 PeprValidateRequest<T extends KubernetesObject> {\n Raw: T;\n\n #input: AdmissionRequest<T>;\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(input: AdmissionRequest<T>) {\n this.#input = input;\n\n // If this is a DELETE operation, use the oldObject instead\n if (input.operation.toUpperCase() === Operation.DELETE) {\n this.Raw = clone(input.oldObject as T);\n } else {\n // Otherwise, use the incoming object\n this.Raw = clone(input.object);\n }\n\n if (!this.Raw) {\n throw new Error(\"unable to load the request object into PeprRequest.Raw\");\n }\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 /**\n * Create a validation response that allows the request.\n *\n * @returns The validation response.\n */\n Approve = (): ValidateActionResponse => {\n return {\n allowed: true,\n };\n };\n\n /**\n * Create a validation response that denies the request.\n *\n * @param statusMessage Optional status message to return to the user.\n * @param statusCode Optional status code to return to the user.\n * @returns The validation response.\n */\n Deny = (statusMessage?: string, statusCode?: number): ValidateActionResponse => {\n return {\n allowed: false,\n statusCode,\n statusMessage,\n };\n };\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { kind } from \"kubernetes-fluent-client\";\n\nimport { Capability } from \"./capability\";\nimport { shouldSkipRequest } from \"./filter\";\nimport { AdmissionRequest, ValidateResponse } from \"./k8s\";\nimport Log from \"./logger\";\nimport { convertFromBase64Map } from \"./utils\";\nimport { PeprValidateRequest } from \"./validate-request\";\n\nexport async function validateProcessor(\n capabilities: Capability[],\n req: AdmissionRequest,\n reqMetadata: Record<string, string>,\n): Promise<ValidateResponse[]> {\n const wrapped = new PeprValidateRequest(req);\n const response: ValidateResponse[] = [];\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 convertFromBase64Map(wrapped.Raw as unknown as kind.Secret);\n }\n\n Log.info(reqMetadata, `Processing validation request`);\n\n for (const { name, bindings, namespaces } of capabilities) {\n const actionMetadata = { ...reqMetadata, name };\n\n for (const action of bindings) {\n // Skip this action if it's not a validation action\n if (!action.validateCallback) {\n continue;\n }\n\n const localResponse: ValidateResponse = {\n uid: req.uid,\n allowed: true, // Assume it's allowed until a validation check fails\n };\n\n // Continue to the next action without doing anything if this one should be skipped\n if (shouldSkipRequest(action, req, namespaces)) {\n continue;\n }\n\n const label = action.validateCallback.name;\n Log.info(actionMetadata, `Processing validation action (${label})`);\n\n try {\n // Run the validation callback, if it fails set allowed to false\n const resp = await action.validateCallback(wrapped);\n localResponse.allowed = resp.allowed;\n\n // If the validation callback returned a status code or message, set it in the Response\n if (resp.statusCode || resp.statusMessage) {\n localResponse.status = {\n code: resp.statusCode || 400,\n message: resp.statusMessage || `Validation failed for ${name}`,\n };\n }\n\n Log.info(actionMetadata, `Validation action complete (${label}): ${resp.allowed ? \"allowed\" : \"denied\"}`);\n } catch (e) {\n // If any validation throws an error, note the failure in the Response\n Log.error(actionMetadata, `Action failed: ${JSON.stringify(e)}`);\n localResponse.allowed = false;\n localResponse.status = {\n code: 500,\n message: `Action failed with error: ${JSON.stringify(e)}`,\n };\n return [localResponse];\n }\n response.push(localResponse);\n }\n }\n\n return response;\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { Operation } from \"fast-json-patch\";\nimport { K8s } from \"kubernetes-fluent-client\";\nimport { startsWith } from \"ramda\";\n\nimport { Capability } from \"../capability\";\nimport { PeprStore } from \"../k8s\";\nimport Log from \"../logger\";\nimport { DataOp, DataSender, DataStore, Storage } from \"../storage\";\n\nconst namespace = \"pepr-system\";\nexport const debounceBackoff = 5000;\n\nexport class PeprControllerStore {\n #name: string;\n #stores: Record<string, Storage> = {};\n #sendDebounce: NodeJS.Timeout | undefined;\n #onReady?: () => void;\n\n constructor(capabilities: Capability[], name: string, onReady?: () => void) {\n this.#onReady = onReady;\n\n // Setup Pepr State bindings\n this.#name = name;\n\n if (name.includes(\"schedule\")) {\n // Establish the store for each capability\n for (const { name, registerScheduleStore, hasSchedule } of capabilities) {\n // Guard Clause to exit early\n if (hasSchedule !== true) {\n continue;\n }\n // Register the scheduleStore with the capability\n const { scheduleStore } = registerScheduleStore();\n\n // Bind the store sender to the capability\n scheduleStore.registerSender(this.#send(name));\n\n // Store the storage instance\n this.#stores[name] = scheduleStore;\n }\n } else {\n // Establish the store for each capability\n for (const { name, registerStore } of capabilities) {\n // Register the store with the capability\n const { store } = registerStore();\n\n // Bind the store sender to the capability\n store.registerSender(this.#send(name));\n\n // Store the storage instance\n this.#stores[name] = store;\n }\n }\n\n // Add a jitter to the Store creation to avoid collisions\n setTimeout(\n () =>\n K8s(PeprStore)\n .InNamespace(namespace)\n .Get(this.#name)\n // If the get succeeds, migrate and setup the watch\n .then(async (store: PeprStore) => await this.#migrateAndSetupWatch(store))\n // Otherwise, create the resource\n .catch(this.#createStoreResource),\n Math.random() * 3000,\n );\n }\n\n #setupWatch = () => {\n const watcher = K8s(PeprStore, { name: this.#name, namespace }).Watch(this.#receive);\n watcher.start().catch(e => Log.error(e, \"Error starting Pepr store watch\"));\n };\n\n #migrateAndSetupWatch = async (store: PeprStore) => {\n Log.debug(store, \"Pepr Store migration\");\n const data: DataStore = store.data || {};\n const migrateCache: Record<string, Operation> = {};\n\n // Send the cached updates to the cluster\n const flushCache = async () => {\n const indexes = Object.keys(migrateCache);\n const payload = Object.values(migrateCache);\n\n // Loop over each key in the cache and delete it to avoid collisions with other sender calls\n for (const idx of indexes) {\n delete migrateCache[idx];\n }\n\n try {\n // Send the patch to the cluster\n await K8s(PeprStore, { namespace, name: this.#name }).Patch(payload);\n } catch (err) {\n Log.error(err, \"Pepr store update failure\");\n\n if (err.status === 422) {\n Object.keys(migrateCache).forEach(key => delete migrateCache[key]);\n } else {\n // On failure to update, re-add the operations to the cache to be retried\n for (const idx of indexes) {\n migrateCache[idx] = payload[Number(idx)];\n }\n }\n }\n };\n\n const fillCache = (name: string, op: DataOp, key: string[], val?: string) => {\n if (op === \"add\") {\n // adjust the path for the capability\n const path = `/data/${name}-v2-${key}`;\n const value = val || \"\";\n const cacheIdx = [op, path, value].join(\":\");\n\n // Add the operation to the cache\n migrateCache[cacheIdx] = { op, path, value };\n\n return;\n }\n\n if (op === \"remove\") {\n if (key.length < 1) {\n throw new Error(`Key is required for REMOVE operation`);\n }\n\n for (const k of key) {\n const path = `/data/${name}-${k}`;\n const cacheIdx = [op, path].join(\":\");\n\n // Add the operation to the cache\n migrateCache[cacheIdx] = { op, path };\n }\n\n return;\n }\n\n // If we get here, the operation is not supported\n throw new Error(`Unsupported operation: ${op}`);\n };\n\n for (const name of Object.keys(this.#stores)) {\n // Get the prefix offset for the keys\n const offset = `${name}-`.length;\n\n // Loop over each key in the store\n for (const key of Object.keys(data)) {\n // Match on the capability name as a prefix for non v2 keys\n if (startsWith(name, key) && !startsWith(`${name}-v2`, key)) {\n // populate migrate cache\n fillCache(name, \"remove\", [key.slice(offset)], data[key]);\n fillCache(name, \"add\", [key.slice(offset)], data[key]);\n }\n }\n\n // await K8s(PeprStore, { namespace, name: this.#name }).Patch(payload);\n }\n await flushCache();\n this.#setupWatch();\n };\n\n #receive = (store: PeprStore) => {\n Log.debug(store, \"Pepr Store update\");\n\n // Wrap the update in a debounced function\n const debounced = () => {\n // Base64 decode the data\n const data: DataStore = store.data || {};\n\n // Loop over each stored capability\n for (const name of Object.keys(this.#stores)) {\n // Get the prefix offset for the keys\n const offset = `${name}-`.length;\n\n // Get any keys that match the capability name prefix\n const filtered: DataStore = {};\n\n // Loop over each key in the secret\n for (const key of Object.keys(data)) {\n // Match on the capability name as a prefix\n if (startsWith(name, key)) {\n // Strip the prefix and store the value\n filtered[key.slice(offset)] = data[key];\n }\n }\n\n // Send the data to the receiver callback\n this.#stores[name].receive(filtered);\n }\n\n // Call the onReady callback if this is the first time the secret has been read\n if (this.#onReady) {\n this.#onReady();\n this.#onReady = undefined;\n }\n };\n\n // Debounce the update to 1 second to avoid multiple rapid calls\n clearTimeout(this.#sendDebounce);\n this.#sendDebounce = setTimeout(debounced, this.#onReady ? 0 : debounceBackoff);\n };\n\n #send = (capabilityName: string) => {\n const sendCache: Record<string, Operation> = {};\n\n // Load the sendCache with patch operations\n const fillCache = (op: DataOp, key: string[], val?: string) => {\n if (op === \"add\") {\n // adjust the path for the capability\n const path = `/data/${capabilityName}-${key}`;\n const value = val || \"\";\n const cacheIdx = [op, path, value].join(\":\");\n\n // Add the operation to the cache\n sendCache[cacheIdx] = { op, path, value };\n\n return;\n }\n\n if (op === \"remove\") {\n if (key.length < 1) {\n throw new Error(`Key is required for REMOVE operation`);\n }\n\n for (const k of key) {\n const path = `/data/${capabilityName}-${k}`;\n const cacheIdx = [op, path].join(\":\");\n\n // Add the operation to the cache\n sendCache[cacheIdx] = { op, path };\n }\n\n return;\n }\n\n // If we get here, the operation is not supported\n throw new Error(`Unsupported operation: ${op}`);\n };\n\n // Send the cached updates to the cluster\n const flushCache = async () => {\n const indexes = Object.keys(sendCache);\n const payload = Object.values(sendCache);\n\n // Loop over each key in the cache and delete it to avoid collisions with other sender calls\n for (const idx of indexes) {\n delete sendCache[idx];\n }\n\n try {\n // Send the patch to the cluster\n await K8s(PeprStore, { namespace, name: this.#name }).Patch(payload);\n } catch (err) {\n Log.error(err, \"Pepr store update failure\");\n\n if (err.status === 422) {\n Object.keys(sendCache).forEach(key => delete sendCache[key]);\n } else {\n // On failure to update, re-add the operations to the cache to be retried\n for (const idx of indexes) {\n sendCache[idx] = payload[Number(idx)];\n }\n }\n }\n };\n\n // Create a sender function for the capability to add/remove data from the store\n const sender: DataSender = async (op: DataOp, key: string[], val?: string) => {\n fillCache(op, key, val);\n };\n\n // Send any cached updates every debounceBackoff milliseconds\n setInterval(() => {\n if (Object.keys(sendCache).length > 0) {\n Log.debug(sendCache, \"Sending updates to Pepr store\");\n void flushCache();\n }\n }, debounceBackoff);\n\n return sender;\n };\n\n #createStoreResource = async (e: unknown) => {\n Log.info(`Pepr store not found, creating...`);\n Log.debug(e);\n\n try {\n await K8s(PeprStore).Apply({\n metadata: {\n name: this.#name,\n namespace,\n },\n data: {\n // JSON Patch will die if the data is empty, so we need to add a placeholder\n __pepr_do_not_delete__: \"k-thx-bye\",\n },\n });\n\n // Now that the resource exists, setup the watch\n this.#setupWatch();\n } catch (err) {\n Log.error(err, \"Failed to create Pepr store\");\n }\n };\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\nimport { K8s, KubernetesObject, WatchCfg, WatchEvent } from \"kubernetes-fluent-client\";\nimport { WatchPhase } from \"kubernetes-fluent-client/dist/fluent/types\";\nimport { Capability } from \"./capability\";\nimport { filterNoMatchReason } from \"./helpers\";\nimport Log from \"./logger\";\nimport { Queue } from \"./queue\";\nimport { Binding, Event } from \"./types\";\nimport { metricsCollector } from \"./metrics\";\n\n// Watch configuration\nconst watchCfg: WatchCfg = {\n resyncFailureMax: process.env.PEPR_RESYNC_FAILURE_MAX ? parseInt(process.env.PEPR_RESYNC_FAILURE_MAX, 10) : 5,\n resyncDelaySec: process.env.PEPR_RESYNC_DELAY_SECONDS ? parseInt(process.env.PEPR_RESYNC_DELAY_SECONDS, 10) : 5,\n lastSeenLimitSeconds: process.env.PEPR_LAST_SEEN_LIMIT_SECONDS\n ? parseInt(process.env.PEPR_LAST_SEEN_LIMIT_SECONDS, 10)\n : 300,\n relistIntervalSec: process.env.PEPR_RELIST_INTERVAL_SECONDS\n ? parseInt(process.env.PEPR_RELIST_INTERVAL_SECONDS, 10)\n : 1800,\n};\n\n// Map the event to the watch phase\nconst eventToPhaseMap = {\n [Event.Create]: [WatchPhase.Added],\n [Event.Update]: [WatchPhase.Modified],\n [Event.CreateOrUpdate]: [WatchPhase.Added, WatchPhase.Modified],\n [Event.Delete]: [WatchPhase.Deleted],\n [Event.Any]: [WatchPhase.Added, WatchPhase.Modified, WatchPhase.Deleted],\n};\n\n/**\n * Entrypoint for setting up watches for all capabilities\n *\n * @param capabilities The capabilities to load watches for\n */\nexport function setupWatch(capabilities: Capability[]) {\n capabilities.map(capability =>\n capability.bindings\n .filter(binding => binding.isWatch)\n .forEach(bindingElement => runBinding(bindingElement, capability.namespaces)),\n );\n}\n\n/**\n * Setup a watch for a binding\n *\n * @param binding the binding to watch\n * @param capabilityNamespaces list of namespaces to filter on\n */\nasync function runBinding(binding: Binding, capabilityNamespaces: string[]) {\n // Get the phases to match, fallback to any\n const phaseMatch: WatchPhase[] = eventToPhaseMap[binding.event] || eventToPhaseMap[Event.Any];\n\n // The watch callback is run when an object is received or dequeued\n\n Log.debug({ watchCfg }, \"Effective WatchConfig\");\n const watchCallback = async (obj: KubernetesObject, type: WatchPhase) => {\n // First, filter the object based on the phase\n if (phaseMatch.includes(type)) {\n try {\n // Then, check if the object matches the filter\n const filterMatch = filterNoMatchReason(binding, obj, capabilityNamespaces);\n if (filterMatch === \"\") {\n await binding.watchCallback?.(obj, type);\n } else {\n Log.debug(filterMatch);\n }\n } catch (e) {\n // Errors in the watch callback should not crash the controller\n Log.error(e, \"Error executing watch callback\");\n }\n }\n };\n\n const queue = new Queue();\n queue.setReconcile(watchCallback);\n\n // Setup the resource watch\n const watcher = K8s(binding.model, binding.filters).Watch(async (obj, type) => {\n Log.debug(obj, `Watch event ${type} received`);\n\n // If the binding is a queue, enqueue the object\n if (binding.isQueue) {\n await queue.enqueue(obj, type);\n } else {\n // Otherwise, run the watch callback directly\n await watchCallback(obj, type);\n }\n }, watchCfg);\n\n // If failure continues, log and exit\n watcher.events.on(WatchEvent.GIVE_UP, err => {\n Log.error(err, \"Watch failed after 5 attempts, giving up\");\n process.exit(1);\n });\n\n watcher.events.on(WatchEvent.CONNECT, url => logEvent(WatchEvent.CONNECT, url));\n\n watcher.events.on(WatchEvent.DATA_ERROR, err => logEvent(WatchEvent.DATA_ERROR, err.message));\n watcher.events.on(WatchEvent.RECONNECT, retryCount =>\n logEvent(WatchEvent.RECONNECT, `Reconnecting after ${retryCount} attempt${retryCount === 1 ? \"\" : \"s\"}`),\n );\n watcher.events.on(WatchEvent.RECONNECT_PENDING, () => logEvent(WatchEvent.RECONNECT_PENDING));\n watcher.events.on(WatchEvent.GIVE_UP, err => logEvent(WatchEvent.GIVE_UP, err.message));\n watcher.events.on(WatchEvent.ABORT, err => logEvent(WatchEvent.ABORT, err.message));\n watcher.events.on(WatchEvent.OLD_RESOURCE_VERSION, err => logEvent(WatchEvent.OLD_RESOURCE_VERSION, err));\n watcher.events.on(WatchEvent.NETWORK_ERROR, err => logEvent(WatchEvent.NETWORK_ERROR, err.message));\n watcher.events.on(WatchEvent.LIST_ERROR, err => logEvent(WatchEvent.LIST_ERROR, err.message));\n watcher.events.on(WatchEvent.LIST, list => logEvent(WatchEvent.LIST, JSON.stringify(list, undefined, 2)));\n watcher.events.on(WatchEvent.CACHE_MISS, windowName => {\n metricsCollector.incCacheMiss(windowName);\n });\n\n watcher.events.on(WatchEvent.INIT_CACHE_MISS, windowName => {\n metricsCollector.initCacheMissWindow(windowName);\n });\n\n watcher.events.on(WatchEvent.INC_RESYNC_FAILURE_COUNT, retryCount => {\n metricsCollector.incRetryCount(retryCount);\n });\n\n // Start the watch\n try {\n await watcher.start();\n } catch (err) {\n Log.error(err, \"Error starting watch\");\n process.exit(1);\n }\n}\n\nexport function logEvent(type: WatchEvent, message: string = \"\", obj?: KubernetesObject) {\n const logMessage = `Watch event ${type} received${message ? `. ${message}.` : \".\"}`;\n if (obj) {\n Log.debug(obj, logMessage);\n } else {\n Log.debug(logMessage);\n }\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { promises as fs } from \"fs\";\nimport { K8s, KubernetesObject, kind } from \"kubernetes-fluent-client\";\nimport Log from \"./logger\";\nimport { Binding, CapabilityExport } from \"./types\";\nimport { sanitizeResourceName } from \"../sdk/sdk\";\n\nexport class ValidationError extends Error {}\n\nexport function validateCapabilityNames(capabilities: CapabilityExport[] | undefined): void {\n if (capabilities && capabilities.length > 0) {\n for (let i = 0; i < capabilities.length; i++) {\n if (capabilities[i].name !== sanitizeResourceName(capabilities[i].name)) {\n throw new ValidationError(`Capability name is not a valid Kubernetes resource name: ${capabilities[i].name}`);\n }\n }\n }\n}\n\nexport function validateHash(expectedHash: string): void {\n // Require the hash to be a valid SHA-256 hash (64 characters, hexadecimal)\n const sha256Regex = /^[a-f0-9]{64}$/i;\n if (!expectedHash || !sha256Regex.test(expectedHash)) {\n Log.error(`Invalid hash. Expected a valid SHA-256 hash, got ${expectedHash}`);\n throw new ValidationError(\"Invalid hash\");\n }\n}\n\ntype RBACMap = {\n [key: string]: {\n verbs: string[];\n plural: string;\n };\n};\n\n// check for overlap with labels and annotations between bindings and kubernetes objects\nexport function checkOverlap(bindingFilters: Record<string, string>, objectFilters: Record<string, string>): boolean {\n // True if labels/annotations are empty\n if (Object.keys(bindingFilters).length === 0) {\n return true;\n }\n\n let matchCount = 0;\n\n for (const key in bindingFilters) {\n // object must have label/annotation\n if (Object.prototype.hasOwnProperty.call(objectFilters, key)) {\n const val1 = bindingFilters[key];\n const val2 = objectFilters[key];\n\n // If bindingFilter has empty value for this key, only need to ensure objectFilter has this key\n if (val1 === \"\" && key in objectFilters) {\n matchCount++;\n }\n // If bindingFilter has a value, it must match the value in objectFilter\n else if (val1 !== \"\" && val1 === val2) {\n matchCount++;\n }\n }\n }\n\n // For single-key objects in bindingFilter or matching all keys in multiple-keys scenario\n return matchCount === Object.keys(bindingFilters).length;\n}\n\n/**\n * Decide to run callback after the event comes back from API Server\n **/\nexport function filterNoMatchReason(\n binding: Partial<Binding>,\n obj: Partial<KubernetesObject>,\n capabilityNamespaces: string[],\n): string {\n // binding kind is namespace with a InNamespace filter\n if (binding.kind && binding.kind.kind === \"Namespace\" && binding.filters && binding.filters.namespaces.length !== 0) {\n return `Ignoring Watch Callback: Cannot use a namespace filter in a namespace object.`;\n }\n\n if (typeof obj === \"object\" && obj !== null && \"metadata\" in obj && obj.metadata !== undefined && binding.filters) {\n // binding labels and object labels dont match\n if (obj.metadata.labels && !checkOverlap(binding.filters.labels, obj.metadata.labels)) {\n return `Ignoring Watch Callback: No overlap between binding and object labels. Binding labels ${JSON.stringify(\n binding.filters.labels,\n )}, Object Labels ${JSON.stringify(obj.metadata.labels)}.`;\n }\n\n // binding annotations and object annotations dont match\n if (obj.metadata.annotations && !checkOverlap(binding.filters.annotations, obj.metadata.annotations)) {\n return `Ignoring Watch Callback: No overlap between binding and object annotations. Binding annotations ${JSON.stringify(\n binding.filters.annotations,\n )}, Object annotations ${JSON.stringify(obj.metadata.annotations)}.`;\n }\n }\n\n // Check object is in the capability namespace\n if (\n Array.isArray(capabilityNamespaces) &&\n capabilityNamespaces.length > 0 &&\n obj.metadata &&\n obj.metadata.namespace &&\n !capabilityNamespaces.includes(obj.metadata.namespace)\n ) {\n return `Ignoring Watch Callback: Object is not in the capability namespace. Capability namespaces: ${capabilityNamespaces.join(\n \", \",\n )}, Object namespace: ${obj.metadata.namespace}.`;\n }\n\n // chceck every filter namespace is a capability namespace\n if (\n Array.isArray(capabilityNamespaces) &&\n capabilityNamespaces.length > 0 &&\n binding.filters &&\n Array.isArray(binding.filters.namespaces) &&\n binding.filters.namespaces.length > 0 &&\n !binding.filters.namespaces.every(ns => capabilityNamespaces.includes(ns))\n ) {\n return `Ignoring Watch Callback: Binding namespace is not part of capability namespaces. Capability namespaces: ${capabilityNamespaces.join(\n \", \",\n )}, Binding namespaces: ${binding.filters.namespaces.join(\", \")}.`;\n }\n\n // filter namespace is not the same of object namespace\n if (\n binding.filters &&\n Array.isArray(binding.filters.namespaces) &&\n binding.filters.namespaces.length > 0 &&\n obj.metadata &&\n obj.metadata.namespace &&\n !binding.filters.namespaces.includes(obj.metadata.namespace)\n ) {\n return `Ignoring Watch Callback: Binding namespace and object namespace are not the same. Binding namespaces: ${binding.filters.namespaces.join(\n \", \",\n )}, Object namespace: ${obj.metadata.namespace}.`;\n }\n\n // no problems\n return \"\";\n}\n\nexport function addVerbIfNotExists(verbs: string[], verb: string) {\n if (!verbs.includes(verb)) {\n verbs.push(verb);\n }\n}\n\nexport function createRBACMap(capabilities: CapabilityExport[]): RBACMap {\n return capabilities.reduce((acc: RBACMap, capability: CapabilityExport) => {\n capability.bindings.forEach(binding => {\n const key = `${binding.kind.group}/${binding.kind.version}/${binding.kind.kind}`;\n\n acc[\"pepr.dev/v1/peprstore\"] = {\n verbs: [\"create\", \"get\", \"patch\", \"watch\"],\n plural: \"peprstores\",\n };\n\n acc[\"apiextensions.k8s.io/v1/customresourcedefinition\"] = {\n verbs: [\"patch\", \"create\"],\n plural: \"customresourcedefinitions\",\n };\n\n if (!acc[key] && binding.isWatch) {\n acc[key] = {\n verbs: [\"watch\"],\n plural: binding.kind.plural || `${binding.kind.kind.toLowerCase()}s`,\n };\n }\n });\n\n return acc;\n }, {});\n}\n\nexport async function createDirectoryIfNotExists(path: string) {\n try {\n await fs.access(path);\n } catch (error) {\n if (error.code === \"ENOENT\") {\n await fs.mkdir(path, { recursive: true });\n } else {\n throw error;\n }\n }\n}\n\nexport function hasEveryOverlap<T>(array1: T[], array2: T[]): boolean {\n if (!Array.isArray(array1) || !Array.isArray(array2)) {\n return false;\n }\n\n return array1.every(element => array2.includes(element));\n}\n\nexport function hasAnyOverlap<T>(array1: T[], array2: T[]): boolean {\n if (!Array.isArray(array1) || !Array.isArray(array2)) {\n return false;\n }\n\n return array1.some(element => array2.includes(element));\n}\n\nexport function ignoredNamespaceConflict(ignoreNamespaces: string[], bindingNamespaces: string[]) {\n return hasAnyOverlap(bindingNamespaces, ignoreNamespaces);\n}\n\nexport function bindingAndCapabilityNSConflict(bindingNamespaces: string[], capabilityNamespaces: string[]) {\n if (!capabilityNamespaces) {\n return false;\n }\n return capabilityNamespaces.length !== 0 && !hasEveryOverlap(bindingNamespaces, capabilityNamespaces);\n}\n\nexport function generateWatchNamespaceError(\n ignoredNamespaces: string[],\n bindingNamespaces: string[],\n capabilityNamespaces: string[],\n) {\n let err = \"\";\n\n // check if binding uses an ignored namespace\n if (ignoredNamespaceConflict(ignoredNamespaces, bindingNamespaces)) {\n err += `Binding uses a Pepr ignored namespace: ignoredNamespaces: [${ignoredNamespaces.join(\n \", \",\n )}] bindingNamespaces: [${bindingNamespaces.join(\", \")}].`;\n }\n\n // ensure filter namespaces are part of capability namespaces\n if (bindingAndCapabilityNSConflict(bindingNamespaces, capabilityNamespaces)) {\n err += `Binding uses namespace not governed by capability: bindingNamespaces: [${bindingNamespaces.join(\n \", \",\n )}] capabilityNamespaces: [${capabilityNamespaces.join(\", \")}].`;\n }\n\n // add a space if there is a period in the middle of the string\n return err.replace(/\\.([^ ])/g, \". $1\");\n}\n\n// namespaceComplianceValidator ensures that capability bindinds respect ignored and capability namespaces\nexport function namespaceComplianceValidator(capability: CapabilityExport, ignoredNamespaces?: string[]) {\n const { namespaces: capabilityNamespaces, bindings, name } = capability;\n const bindingNamespaces = bindings.flatMap(binding => binding.filters.namespaces);\n\n const namespaceError = generateWatchNamespaceError(\n ignoredNamespaces ? ignoredNamespaces : [],\n bindingNamespaces,\n capabilityNamespaces ? capabilityNamespaces : [],\n );\n if (namespaceError !== \"\") {\n throw new Error(\n `Error in ${name} capability. A binding violates namespace rules. Please check ignoredNamespaces and capability namespaces: ${namespaceError}`,\n );\n }\n}\n\n// check to see if all replicas are ready for all deployments in the pepr-system namespace\n// returns true if all deployments are ready, false otherwise\nexport async function checkDeploymentStatus(namespace: string) {\n const deployments = await K8s(kind.Deployment).InNamespace(namespace).Get();\n let status = false;\n let readyCount = 0;\n\n for (const deployment of deployments.items) {\n const readyReplicas = deployment.status?.readyReplicas ? deployment.status?.readyReplicas : 0;\n if (deployment.status?.readyReplicas !== deployment.spec?.replicas) {\n Log.info(\n `Waiting for deployment ${deployment.metadata?.name} rollout to finish: ${readyReplicas} of ${deployment.spec?.replicas} replicas are available`,\n );\n } else {\n Log.info(\n `Deployment ${deployment.metadata?.name} rolled out: ${readyReplicas} of ${deployment.spec?.replicas} replicas are available`,\n );\n readyCount++;\n }\n }\n if (readyCount === deployments.items.length) {\n status = true;\n }\n return status;\n}\n\n// wait for all deployments in the pepr-system namespace to be ready\nexport async function namespaceDeploymentsReady(namespace: string = \"pepr-system\") {\n Log.info(`Checking ${namespace} deployments status...`);\n let ready = false;\n while (!ready) {\n ready = await checkDeploymentStatus(namespace);\n if (ready) {\n return ready;\n }\n await new Promise(resolve => setTimeout(resolve, 1000));\n }\n Log.info(`All ${namespace} deployments are ready`);\n}\n\n// check if secret is over the size limit\nexport function secretOverLimit(str: string): boolean {\n const encoder = new TextEncoder();\n const encoded = encoder.encode(str);\n const sizeInBytes = encoded.length;\n const oneMiBInBytes = 1048576;\n return sizeInBytes > oneMiBInBytes;\n}\n\n/* eslint-disable @typescript-eslint/no-unused-vars */\nexport const parseTimeout = (value: string, previous: unknown): number => {\n const parsedValue = parseInt(value, 10);\n const floatValue = parseFloat(value);\n if (isNaN(parsedValue)) {\n throw new Error(\"Not a number.\");\n } else if (parsedValue !== floatValue) {\n throw new Error(\"Value must be an integer.\");\n } else if (parsedValue < 1 || parsedValue > 30) {\n throw new Error(\"Number must be between 1 and 30.\");\n }\n return parsedValue;\n};\n\n// Remove leading whitespace while keeping format of file\nexport function dedent(file: string) {\n // Check if the first line is empty and remove it\n const lines = file.split(\"\\n\");\n if (lines[0].trim() === \"\") {\n lines.shift(); // Remove the first line if it's empty\n file = lines.join(\"\\n\"); // Rejoin the remaining lines back into a single string\n }\n\n const match = file.match(/^[ \\t]*(?=\\S)/gm);\n const indent = match && Math.min(...match.map(el => el.length));\n if (indent && indent > 0) {\n const re = new RegExp(`^[ \\\\t]{${indent}}`, \"gm\");\n return file.replace(re, \"\");\n }\n return file;\n}\n\nexport function replaceString(str: string, stringA: string, stringB: string) {\n //eslint-disable-next-line\n const escapedStringA = stringA.replace(/[-\\/\\\\^$*+?.()|[\\]{}]/g, \"\\\\$&\");\n const regExp = new RegExp(escapedStringA, \"g\");\n return str.replace(regExp, stringB);\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { PeprValidateRequest } from \"../lib/validate-request\";\nimport { PeprMutateRequest } from \"../lib/mutate-request\";\nimport { a } from \"../lib\";\nimport { V1OwnerReference } from \"@kubernetes/client-node\";\nimport { GenericKind } from \"kubernetes-fluent-client\";\nimport { K8s, kind } from \"kubernetes-fluent-client\";\nimport Log from \"../lib/logger\";\n\n/**\n * Returns all containers in a pod\n * @param request the request/pod to get the containers from\n * @param containerType the type of container to get\n * @returns the list of containers in the pod\n */\nexport function containers(\n request: PeprValidateRequest<a.Pod> | PeprMutateRequest<a.Pod>,\n containerType?: \"containers\" | \"initContainers\" | \"ephemeralContainers\",\n) {\n const containers = request.Raw.spec?.containers || [];\n const initContainers = request.Raw.spec?.initContainers || [];\n const ephemeralContainers = request.Raw.spec?.ephemeralContainers || [];\n\n if (containerType === \"containers\") {\n return containers;\n }\n if (containerType === \"initContainers\") {\n return initContainers;\n }\n if (containerType === \"ephemeralContainers\") {\n return ephemeralContainers;\n }\n return [...containers, ...initContainers, ...ephemeralContainers];\n}\n\n/**\n * Write a K8s event for a CRD\n *\n * @param cr The custom resource to write the event for\n * @param event The event to write, should contain a human-readable message for the event\n * @param eventType The type of event to write, for example \"Warning\"\n * @param eventReason The reason for the event, for example \"ReconciliationFailed\"\n * @param reportingComponent The component that is reporting the event, for example \"uds.dev/operator\"\n * @param reportingInstance The instance of the component that is reporting the event, for example process.env.HOSTNAME\n */\nexport async function writeEvent(\n cr: GenericKind,\n event: Partial<kind.CoreEvent>,\n eventType: string,\n eventReason: string,\n reportingComponent: string,\n reportingInstance: string,\n) {\n Log.debug(cr.metadata, `Writing event: ${event.message}`);\n\n await K8s(kind.CoreEvent).Create({\n type: eventType,\n reason: eventReason,\n ...event,\n // Fixed values\n metadata: {\n namespace: cr.metadata!.namespace,\n generateName: cr.metadata!.name,\n },\n involvedObject: {\n apiVersion: cr.apiVersion,\n kind: cr.kind,\n name: cr.metadata!.name,\n namespace: cr.metadata!.namespace,\n uid: cr.metadata!.uid,\n },\n firstTimestamp: new Date(),\n reportingComponent: reportingComponent,\n reportingInstance: reportingInstance,\n });\n}\n\n/**\n * Get the owner reference for a custom resource\n * @param cr the custom resource to get the owner reference for\n * @returns the owner reference for the custom resource\n */\nexport function getOwnerRefFrom(cr: GenericKind): V1OwnerReference[] {\n const { name, uid } = cr.metadata!;\n\n return [\n {\n apiVersion: cr.apiVersion!,\n kind: cr.kind!,\n uid: uid!,\n name: name!,\n },\n ];\n}\n\n/**\n * Sanitize a resource name to make it a valid Kubernetes resource name.\n *\n * @param name the name of the resource to sanitize\n * @returns the sanitized resource name\n */\nexport function sanitizeResourceName(name: string) {\n return (\n name\n // The name must be lowercase\n .toLowerCase()\n // Replace sequences of non-alphanumeric characters with a single '-'\n .replace(/[^a-z0-9]+/g, \"-\")\n // Truncate the name to 250 characters\n .slice(0, 250)\n // Remove leading and trailing non-letter characters\n .replace(/^[^a-z]+|[^a-z]+$/g, \"\")\n );\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\nimport { KubernetesObject } from \"@kubernetes/client-node\";\nimport { WatchPhase } from \"kubernetes-fluent-client/dist/fluent/types\";\nimport Log from \"./logger\";\n\ntype QueueItem<K extends KubernetesObject> = {\n item: K;\n type: WatchPhase;\n resolve: (value: void | PromiseLike<void>) => void;\n reject: (reason?: string) => void;\n};\n\n/**\n * Queue is a FIFO queue for reconciling\n */\nexport class Queue<K extends KubernetesObject> {\n #queue: QueueItem<K>[] = [];\n #pendingPromise = false;\n #reconcile?: (obj: KubernetesObject, type: WatchPhase) => Promise<void>;\n\n constructor() {\n this.#reconcile = async () => await new Promise(resolve => resolve());\n }\n\n setReconcile(reconcile: (obj: KubernetesObject, type: WatchPhase) => Promise<void>) {\n this.#reconcile = reconcile;\n }\n\n /**\n * Enqueue adds an item to the queue and returns a promise that resolves when the item is\n * reconciled.\n *\n * @param item The object to reconcile\n * @returns A promise that resolves when the object is reconciled\n */\n enqueue(item: K, type: WatchPhase) {\n Log.debug(`Enqueueing ${item.metadata!.namespace}/${item.metadata!.name}`);\n return new Promise<void>((resolve, reject) => {\n this.#queue.push({ item, type, resolve, reject });\n return this.#dequeue();\n });\n }\n\n /**\n * Dequeue reconciles the next item in the queue\n *\n * @returns A promise that resolves when the webapp is reconciled\n */\n async #dequeue() {\n // If there is a pending promise, do nothing\n if (this.#pendingPromise) {\n Log.debug(\"Pending promise, not dequeuing\");\n return false;\n }\n\n // Take the next element from the queue\n const element = this.#queue.shift();\n\n // If there is no element, do nothing\n if (!element) {\n Log.debug(\"No element, not dequeuing\");\n return false;\n }\n\n try {\n // Set the pending promise flag to avoid concurrent reconciliations\n this.#pendingPromise = true;\n\n // Reconcile the element\n if (this.#reconcile) {\n Log.debug(`Reconciling ${element.item.metadata!.name}`);\n await this.#reconcile(element.item, element.type);\n }\n\n element.resolve();\n } catch (e) {\n Log.debug(`Error reconciling ${element.item.metadata!.name}`, { error: e });\n element.reject(e);\n } finally {\n // Reset the pending promise flag\n Log.debug(\"Resetting pending promise and dequeuing\");\n this.#pendingPromise = false;\n\n // After the element is reconciled, dequeue the next element\n await this.#dequeue();\n }\n }\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { clone } from \"ramda\";\nimport Log from \"./logger\";\nimport pointer from \"json-pointer\";\nexport type DataOp = \"add\" | \"remove\";\nexport type DataStore = Record<string, string>;\nexport type DataSender = (op: DataOp, keys: string[], value?: string) => void;\nexport type DataReceiver = (data: DataStore) => void;\nexport type Unsubscribe = () => void;\n\nconst MAX_WAIT_TIME = 15000;\nconst STORE_VERSION_PREFIX = \"v2\";\n\nexport function v2StoreKey(key: string) {\n return `${STORE_VERSION_PREFIX}-${pointer.escape(key)}`;\n}\n\nexport function stripV2Prefix(key: string) {\n return key.replace(/^v2-/, \"\");\n}\nexport interface PeprStore {\n /**\n * Returns the current value associated with the given key, or null if the given key does not exist.\n */\n getItem(key: string): string | null;\n /**\n * Removes all key/value pairs, if there are any.\n */\n clear(): void;\n /**\n * Removes the key/value pair with the given key, if a key/value pair with the given key exists.\n */\n removeItem(key: string): void;\n /**\n * Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously.\n */\n setItem(key: string, value: string): void;\n\n /**\n * Subscribe to changes in the store. This API behaves similarly to the [Svelte Store API](https://vercel.com/docs/beginner-sveltekit/svelte-stores#using-the-store).\n *\n * @param listener - The callback to be invoked when the store changes.\n * @returns A function to unsubscribe from the listener.\n */\n subscribe(listener: DataReceiver): Unsubscribe;\n\n /**\n * Register a function to be called when the store is ready.\n */\n onReady(callback: DataReceiver): void;\n\n /**\n * Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously.\n * Resolves when the key/value show up in the store.\n */\n setItemAndWait(key: string, value: string): Promise<void>;\n\n /**\n * Remove the value of the key.\n * Resolves when the key does not show up in the store.\n */\n removeItemAndWait(key: string): Promise<void>;\n}\n\n/**\n * A key-value data store that can be used to persist data that should be shared across Pepr controllers and capabilities.\n *\n * The API is similar to the [Storage API](https://developer.mozilla.org/docs/Web/API/Storage)\n */\n\nexport class Storage implements PeprStore {\n #store: DataStore = {};\n #send!: DataSender;\n #subscribers: Record<number, DataReceiver> = {};\n #subscriberId = 0;\n #readyHandlers: DataReceiver[] = [];\n\n registerSender = (send: DataSender) => {\n this.#send = send;\n };\n\n receive = (data: DataStore) => {\n Log.debug(data, `Pepr store data received`);\n this.#store = data || {};\n\n this.#onReady();\n\n // Notify all subscribers\n for (const idx in this.#subscribers) {\n // Send a unique clone of the store to each subscriber\n this.#subscribers[idx](clone(this.#store));\n }\n };\n\n getItem = (key: string) => {\n const result = this.#store[v2StoreKey(key)] || null;\n if (result !== null && typeof result !== \"function\" && typeof result !== \"object\") {\n return result;\n }\n return null;\n };\n\n clear = () => {\n this.#dispatchUpdate(\"remove\", Object.keys(this.#store));\n };\n\n removeItem = (key: string) => {\n this.#dispatchUpdate(\"remove\", [v2StoreKey(key)]);\n };\n\n setItem = (key: string, value: string) => {\n this.#dispatchUpdate(\"add\", [v2StoreKey(key)], value);\n };\n\n /**\n * Creates a promise and subscribes to the store, the promise resolves when\n * the key and value are seen in the store.\n *\n * @param key - The key to add into the store\n * @param value - The value of the key\n * @returns\n */\n setItemAndWait = (key: string, value: string) => {\n this.#dispatchUpdate(\"add\", [v2StoreKey(key)], value);\n return new Promise<void>((resolve, reject) => {\n const unsubscribe = this.subscribe(data => {\n if (data[`${v2StoreKey(key)}`] === value) {\n unsubscribe();\n resolve();\n }\n });\n\n // If promise has not resolved before MAX_WAIT_TIME reject\n setTimeout(() => {\n unsubscribe();\n return reject();\n }, MAX_WAIT_TIME);\n });\n };\n\n /**\n * Creates a promise and subscribes to the store, the promise resolves when\n * the key is removed from the store.\n *\n * @param key - The key to add into the store\n * @returns\n */\n removeItemAndWait = (key: string) => {\n this.#dispatchUpdate(\"remove\", [v2StoreKey(key)]);\n return new Promise<void>((resolve, reject) => {\n const unsubscribe = this.subscribe(data => {\n if (!Object.hasOwn(data, `${v2StoreKey(key)}`)) {\n unsubscribe();\n resolve();\n }\n });\n\n // If promise has not resolved before MAX_WAIT_TIME reject\n setTimeout(() => {\n unsubscribe();\n return reject();\n }, MAX_WAIT_TIME);\n });\n };\n\n subscribe = (subscriber: DataReceiver) => {\n const idx = this.#subscriberId++;\n this.#subscribers[idx] = subscriber;\n return () => this.unsubscribe(idx);\n };\n\n onReady = (callback: DataReceiver) => {\n this.#readyHandlers.push(callback);\n };\n\n /**\n * Remove a subscriber from the list of subscribers.\n * @param idx - The index of the subscriber to remove.\n */\n unsubscribe = (idx: number) => {\n delete this.#subscribers[idx];\n };\n\n #onReady = () => {\n // Notify all ready handlers with a clone of the store\n for (const handler of this.#readyHandlers) {\n handler(clone(this.#store));\n }\n\n // Make this a noop so that it can't be called again\n this.#onReady = () => {};\n };\n\n /**\n * Dispatch an update to the store and notify all subscribers.\n * @param op - The type of operation to perform.\n * @param keys - The keys to update.\n * @param [value] - The new value.\n */\n #dispatchUpdate = (op: DataOp, keys: string[], value?: string) => {\n this.#send(op, keys, value);\n };\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { PeprStore } from \"./storage\";\n\ntype Unit = \"seconds\" | \"second\" | \"minute\" | \"minutes\" | \"hours\" | \"hour\";\n\nexport interface Schedule {\n /**\n * * The name of the store\n */\n name: string;\n /**\n * The value associated with a unit of time\n */\n every: number;\n /**\n * The unit of time\n */\n unit: Unit;\n /**\n * The code to run\n */\n run: () => void;\n /**\n * The start time of the schedule\n */\n startTime?: Date | undefined;\n\n /**\n * The number of times the schedule has run\n */\n completions?: number | undefined;\n /**\n * Tje intervalID to clear the interval\n */\n intervalID?: NodeJS.Timeout;\n}\n\nexport class OnSchedule implements Schedule {\n intervalId: NodeJS.Timeout | null = null;\n store: PeprStore | undefined;\n name!: string;\n completions?: number | undefined;\n every: number;\n unit: Unit;\n run!: () => void;\n startTime?: Date | undefined;\n duration: number | undefined;\n lastTimestamp: Date | undefined;\n\n constructor(schedule: Schedule) {\n this.name = schedule.name;\n this.run = schedule.run;\n this.every = schedule.every;\n this.unit = schedule.unit;\n this.startTime = schedule?.startTime;\n this.completions = schedule?.completions;\n }\n setStore(store: PeprStore) {\n this.store = store;\n this.startInterval();\n }\n startInterval() {\n this.checkStore();\n this.getDuration();\n this.setupInterval();\n }\n /**\n * Checks the store for this schedule and sets the values if it exists\n * @returns\n */\n checkStore() {\n const result = this.store && this.store.getItem(this.name);\n if (result) {\n const storedSchedule = JSON.parse(result);\n this.completions = storedSchedule?.completions;\n this.startTime = storedSchedule?.startTime;\n this.lastTimestamp = storedSchedule?.lastTimestamp;\n }\n }\n\n /**\n * Saves the schedule to the store\n * @returns\n */\n saveToStore() {\n const schedule = {\n completions: this.completions,\n startTime: this.startTime,\n lastTimestamp: new Date(),\n name: this.name,\n };\n this.store && this.store.setItem(this.name, JSON.stringify(schedule));\n }\n\n /**\n * Gets the durations in milliseconds\n */\n getDuration() {\n switch (this.unit) {\n case \"seconds\":\n if (this.every < 10) throw new Error(\"10 Seconds in the smallest interval allowed\");\n this.duration = 1000 * this.every;\n break;\n case \"minutes\":\n case \"minute\":\n this.duration = 1000 * 60 * this.every;\n break;\n case \"hours\":\n case \"hour\":\n this.duration = 1000 * 60 * 60 * this.every;\n break;\n default:\n throw new Error(\"Invalid time unit\");\n }\n }\n\n /**\n * Sets up the interval\n */\n setupInterval() {\n const now = new Date();\n let delay: number | undefined;\n\n if (this.lastTimestamp && this.startTime) {\n this.startTime = undefined;\n }\n\n if (this.startTime) {\n delay = this.startTime.getTime() - now.getTime();\n } else if (this.lastTimestamp && this.duration) {\n const lastTimestamp = new Date(this.lastTimestamp);\n delay = this.duration - (now.getTime() - lastTimestamp.getTime());\n }\n\n if (delay === undefined || delay <= 0) {\n this.start();\n } else {\n setTimeout(() => {\n this.start();\n }, delay);\n }\n }\n\n /**\n * Starts the interval\n */\n start() {\n this.intervalId = setInterval(() => {\n if (this.completions === 0) {\n this.stop();\n return;\n } else {\n this.run();\n\n if (this.completions && this.completions !== 0) {\n this.completions -= 1;\n }\n this.saveToStore();\n }\n }, this.duration);\n }\n\n /**\n * Stops the interval\n */\n stop() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n this.intervalId = null;\n }\n this.store && this.store.removeItem(this.name);\n }\n}\n"],
5
- "mappings": "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",
6
- "names": ["a", "import_kubernetes_fluent_client", "import_kubernetes_fluent_client", "import_ramda", "import_ramda", "promClient", "kind", "e", "jsonPatch", "import_ramda", "import_kubernetes_fluent_client", "import_ramda", "name", "express", "fs", "https", "namespace", "res", "import_kubernetes_fluent_client", "import_types", "import_kubernetes_fluent_client", "import_kubernetes_fluent_client", "containers", "import_ramda", "pointer", "kind"]
4
+ "sourcesContent": ["import { K8s, RegisterKind, kind as a, fetch, fetchStatus, kind } from \"kubernetes-fluent-client\";\nimport * as R from \"ramda\";\n\nimport { Capability } from \"./lib/capability\";\nimport Log from \"./lib/logger\";\nimport { PeprModule } from \"./lib/module\";\nimport { PeprMutateRequest } from \"./lib/mutate-request\";\nimport * as PeprUtils from \"./lib/utils\";\nimport { PeprValidateRequest } from \"./lib/validate-request\";\nimport * as sdk from \"./sdk/sdk\";\n\nexport {\n Capability,\n K8s,\n Log,\n PeprModule,\n PeprMutateRequest,\n PeprUtils,\n PeprValidateRequest,\n R,\n RegisterKind,\n a,\n fetch,\n fetchStatus,\n kind,\n sdk,\n};\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { GenericClass, GroupVersionKind, modelToGroupVersionKind } from \"kubernetes-fluent-client\";\nimport { WatchAction } from \"kubernetes-fluent-client/dist/fluent/types\";\nimport { pickBy } from \"ramda\";\n\nimport Log from \"./logger\";\nimport { isBuildMode, isDevMode, isWatchMode } from \"./module\";\nimport { PeprStore, Storage } from \"./storage\";\nimport { OnSchedule, Schedule } from \"./schedule\";\nimport {\n Binding,\n BindingFilter,\n BindingWithName,\n CapabilityCfg,\n CapabilityExport,\n Event,\n MutateAction,\n MutateActionChain,\n ValidateAction,\n ValidateActionChain,\n WhenSelector,\n} from \"./types\";\n\nconst registerAdmission = isBuildMode() || !isWatchMode();\nconst registerWatch = isBuildMode() || isWatchMode() || isDevMode();\n\n/**\n * A capability is a unit of functionality that can be registered with the Pepr runtime.\n */\nexport class Capability implements CapabilityExport {\n #name: string;\n #description: string;\n #namespaces?: string[] | undefined;\n #bindings: Binding[] = [];\n #store = new Storage();\n #scheduleStore = new Storage();\n #registered = false;\n #scheduleRegistered = false;\n hasSchedule: boolean;\n\n /**\n * Run code on a schedule with the capability.\n *\n * @param schedule The schedule to run the code on\n * @returns\n */\n OnSchedule: (schedule: Schedule) => void = (schedule: Schedule) => {\n const { name, every, unit, run, startTime, completions } = schedule;\n this.hasSchedule = true;\n\n if (process.env.PEPR_WATCH_MODE === \"true\" || process.env.PEPR_MODE === \"dev\") {\n // Only create/watch schedule store if necessary\n\n // Create a new schedule\n const newSchedule: Schedule = {\n name,\n every,\n unit,\n run,\n startTime,\n completions,\n };\n\n this.#scheduleStore.onReady(() => {\n new OnSchedule(newSchedule).setStore(this.#scheduleStore);\n });\n }\n };\n\n /**\n * Store is a key-value data store that can be used to persist data that should be shared\n * between requests. Each capability has its own store, and the data is persisted in Kubernetes\n * in the `pepr-system` namespace.\n *\n * Note: You should only access the store from within an action.\n */\n Store: PeprStore = {\n clear: this.#store.clear,\n getItem: this.#store.getItem,\n removeItem: this.#store.removeItem,\n removeItemAndWait: this.#store.removeItemAndWait,\n setItem: this.#store.setItem,\n subscribe: this.#store.subscribe,\n onReady: this.#store.onReady,\n setItemAndWait: this.#store.setItemAndWait,\n };\n\n /**\n * ScheduleStore is a key-value data store used to persist schedule data that should be shared\n * between intervals. Each Schedule shares store, and the data is persisted in Kubernetes\n * in the `pepr-system` namespace.\n *\n * Note: There is no direct access to schedule store\n */\n ScheduleStore: PeprStore = {\n clear: this.#scheduleStore.clear,\n getItem: this.#scheduleStore.getItem,\n removeItemAndWait: this.#scheduleStore.removeItemAndWait,\n removeItem: this.#scheduleStore.removeItem,\n setItemAndWait: this.#scheduleStore.setItemAndWait,\n setItem: this.#scheduleStore.setItem,\n subscribe: this.#scheduleStore.subscribe,\n onReady: this.#scheduleStore.onReady,\n };\n\n get bindings() {\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 constructor(cfg: CapabilityCfg) {\n this.#name = cfg.name;\n this.#description = cfg.description;\n this.#namespaces = cfg.namespaces;\n this.hasSchedule = false;\n\n Log.info(`Capability ${this.#name} registered`);\n Log.debug(cfg);\n }\n\n /**\n * Register the store with the capability. This is called automatically by the Pepr controller.\n *\n * @param store\n */\n registerScheduleStore = () => {\n Log.info(`Registering schedule store for ${this.#name}`);\n\n if (this.#scheduleRegistered) {\n throw new Error(`Schedule store already registered for ${this.#name}`);\n }\n\n this.#scheduleRegistered = true;\n\n // Pass back any ready callback to the controller\n return {\n scheduleStore: this.#scheduleStore,\n };\n };\n\n /**\n * Register the store with the capability. This is called automatically by the Pepr controller.\n *\n * @param store\n */\n registerStore = () => {\n Log.info(`Registering store for ${this.#name}`);\n\n if (this.#registered) {\n throw new Error(`Store already registered for ${this.#name}`);\n }\n\n this.#registered = true;\n\n // Pass back any ready callback to the controller\n return {\n store: this.#store,\n };\n };\n\n /**\n * The When method is used to register a 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 model,\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 };\n\n const bindings = this.#bindings;\n const prefix = `${this.#name}: ${model.name}`;\n const commonChain = { WithLabel, WithAnnotation, Mutate, Validate, Watch, Reconcile };\n const isNotEmpty = (value: object) => Object.keys(value).length > 0;\n const log = (message: string, cbString: string) => {\n const filteredObj = pickBy(isNotEmpty, binding.filters);\n\n Log.info(`${message} configured for ${binding.event}`, prefix);\n Log.info(filteredObj, prefix);\n Log.debug(cbString, prefix);\n };\n\n function Validate(validateCallback: ValidateAction<T>): ValidateActionChain<T> {\n if (registerAdmission) {\n log(\"Validate Action\", validateCallback.toString());\n\n // Push the binding to the list of bindings for this capability as a new BindingAction\n // with the callback function to preserve\n bindings.push({\n ...binding,\n isValidate: true,\n validateCallback,\n });\n }\n\n return { Watch, Reconcile };\n }\n\n function Mutate(mutateCallback: MutateAction<T>): MutateActionChain<T> {\n if (registerAdmission) {\n log(\"Mutate Action\", mutateCallback.toString());\n\n // Push the binding to the list of bindings for this capability as a new BindingAction\n // with the callback function to preserve\n bindings.push({\n ...binding,\n isMutate: true,\n mutateCallback,\n });\n }\n\n // Now only allow adding actions to the same binding\n return { Watch, Validate, Reconcile };\n }\n\n function Watch(watchCallback: WatchAction<T>) {\n if (registerWatch) {\n log(\"Watch Action\", watchCallback.toString());\n\n bindings.push({\n ...binding,\n isWatch: true,\n watchCallback,\n });\n }\n }\n\n function Reconcile(watchCallback: WatchAction<T>) {\n if (registerWatch) {\n log(\"Reconcile Action\", watchCallback.toString());\n\n bindings.push({\n ...binding,\n isWatch: true,\n isQueue: true,\n watchCallback,\n });\n }\n }\n\n function InNamespace(...namespaces: string[]): BindingWithName<T> {\n Log.debug(`Add namespaces filter ${namespaces}`, prefix);\n binding.filters.namespaces.push(...namespaces);\n return { ...commonChain, WithName };\n }\n\n function WithName(name: string): BindingFilter<T> {\n Log.debug(`Add name filter ${name}`, prefix);\n binding.filters.name = name;\n return commonChain;\n }\n\n function WithLabel(key: string, value = \"\"): BindingFilter<T> {\n Log.debug(`Add label filter ${key}=${value}`, prefix);\n binding.filters.labels[key] = value;\n return commonChain;\n }\n\n function WithAnnotation(key: string, value = \"\"): BindingFilter<T> {\n Log.debug(`Add annotation filter ${key}=${value}`, prefix);\n binding.filters.annotations[key] = value;\n return commonChain;\n }\n\n function bindEvent(event: Event) {\n binding.event = event;\n return {\n ...commonChain,\n InNamespace,\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 { pino, stdTimeFunctions } from \"pino\";\n\nconst isPrettyLog = process.env.PEPR_PRETTY_LOGS === \"true\";\n\nconst pretty = {\n target: \"pino-pretty\",\n options: {\n colorize: true,\n },\n};\n\nconst transport = isPrettyLog ? pretty : undefined;\n// epochTime is the pino default\nconst pinoTimeFunction =\n process.env.PINO_TIME_STAMP === \"iso\" ? () => stdTimeFunctions.isoTime() : () => stdTimeFunctions.epochTime();\nconst Log = pino({\n transport,\n timestamp: pinoTimeFunction,\n});\n\nif (process.env.LOG_LEVEL) {\n Log.level = process.env.LOG_LEVEL;\n}\nexport default Log;\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\nimport { clone } from \"ramda\";\nimport { Capability } from \"./capability\";\nimport { Controller } from \"./controller\";\nimport { ValidateError } from \"./errors\";\nimport { AdmissionRequest, MutateResponse, ValidateResponse, WebhookIgnore } from \"./k8s\";\nimport { CapabilityExport } from \"./types\";\nimport { setupWatch } from \"./watch-processor\";\nimport { Log } from \"../lib\";\n\n/** Custom Labels Type for package.json */\nexport interface CustomLabels {\n namespace?: Record<string, string>;\n}\n/** Global configuration for the Pepr runtime. */\nexport type ModuleConfig = {\n /** The Pepr version this module uses */\n peprVersion?: string;\n /** The user-defined version of the module */\n appVersion?: string;\n /** A unique identifier for this Pepr module. This is automatically generated by Pepr. */\n uuid: string;\n /** A description of the Pepr module and what it does. */\n description?: string;\n /** The webhookTimeout */\n webhookTimeout?: number;\n /** Reject K8s resource AdmissionRequests on error. */\n onError?: string;\n /** Configure global exclusions that will never be processed by Pepr. */\n alwaysIgnore: WebhookIgnore;\n /** Define the log level for the in-cluster controllers */\n logLevel?: string;\n /** Propagate env variables to in-cluster controllers */\n env?: Record<string, string>;\n /** Custom Labels for Kubernetes Objects */\n customLabels?: CustomLabels;\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: AdmissionRequest) => 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: MutateResponse | ValidateResponse) => void;\n};\n\n// Track if this is a watch mode controller\nexport const isWatchMode = () => process.env.PEPR_WATCH_MODE === \"true\";\n\n// Track if Pepr is running in build mode\nexport const isBuildMode = () => process.env.PEPR_MODE === \"build\";\n\nexport const isDevMode = () => process.env.PEPR_MODE === \"dev\";\n\nexport class PeprModule {\n #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 opts Options for the Pepr runtime\n */\n constructor({ description, pepr }: PackageJSON, capabilities: Capability[] = [], opts: PeprModuleOptions = {}) {\n const config: ModuleConfig = clone(pepr);\n config.description = description;\n\n // Need to validate at runtime since TS gets sad about parsing the package.json\n ValidateError(config.onError);\n\n // Handle build mode\n if (isBuildMode()) {\n // Fail if process.send is not defined\n if (!process.send) {\n throw new Error(\"process.send is not defined\");\n }\n\n const exportedCapabilities: CapabilityExport[] = [];\n\n // Send capability map to parent process\n for (const capability of capabilities) {\n // Convert the capability to a capability config\n exportedCapabilities.push({\n name: capability.name,\n description: capability.description,\n namespaces: capability.namespaces,\n bindings: capability.bindings,\n hasSchedule: capability.hasSchedule,\n });\n }\n\n // Send the capabilities back to the parent process\n process.send(exportedCapabilities);\n\n return;\n }\n\n this.#controller = new Controller(config, capabilities, opts.beforeHook, opts.afterHook, () => {\n // Wait for the controller to be ready before setting up watches\n if (isWatchMode() || isDevMode()) {\n try {\n setupWatch(capabilities);\n } catch (e) {\n Log.error(e, \"Error setting up watch\");\n process.exit(1);\n }\n }\n });\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, { NextFunction } from \"express\";\nimport fs from \"fs\";\nimport https from \"https\";\n\nimport { Capability } from \"../capability\";\nimport { MutateResponse, AdmissionRequest, ValidateResponse } from \"../k8s\";\nimport Log from \"../logger\";\nimport { metricsCollector, MetricsCollector } from \"../metrics\";\nimport { ModuleConfig, isWatchMode } from \"../module\";\nimport { mutateProcessor } from \"../mutate-processor\";\nimport { validateProcessor } from \"../validate-processor\";\nimport { PeprControllerStore } from \"./store\";\nimport { ResponseItem } from \"../types\";\n\nexport class Controller {\n // Track whether the server is running\n #running = false;\n\n // Metrics collector\n #metricsCollector = metricsCollector;\n\n // The token used to authenticate requests\n #token = \"\";\n\n // The express app instance\n readonly #app = express();\n\n // Initialized with the constructor\n readonly #config: ModuleConfig;\n readonly #capabilities: Capability[];\n readonly #beforeHook?: (req: AdmissionRequest) => void;\n readonly #afterHook?: (res: MutateResponse | ValidateResponse) => void;\n\n constructor(\n config: ModuleConfig,\n capabilities: Capability[],\n beforeHook?: (req: AdmissionRequest) => void,\n afterHook?: (res: MutateResponse | ValidateResponse) => void,\n onReady?: () => void,\n ) {\n this.#config = config;\n this.#capabilities = capabilities;\n\n // Initialize the Pepr store for each capability\n new PeprControllerStore(capabilities, `pepr-${config.uuid}-store`, () => {\n this.#bindEndpoints();\n onReady && onReady();\n Log.info(\"\u2705 Controller startup complete\");\n // Initialize the schedule store for each capability\n new PeprControllerStore(capabilities, `pepr-${config.uuid}-schedule`, () => {\n Log.info(\"\u2705 Scheduling processed\");\n });\n });\n\n // Middleware for logging requests\n this.#app.use(Controller.#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 if (beforeHook) {\n Log.info(`Using beforeHook: ${beforeHook}`);\n this.#beforeHook = beforeHook;\n }\n\n if (afterHook) {\n Log.info(`Using afterHook: ${afterHook}`);\n this.#afterHook = afterHook;\n }\n }\n\n /** Start the webhook server */\n 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 if not in watch mode\n if (!isWatchMode()) {\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 Log.info(`Using API token: ${this.#token}`);\n\n if (!this.#token) {\n throw new Error(\"API token not found\");\n }\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 Log.info(`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 Log.warn(\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 Log.info(\"Received SIGTERM, closing server\");\n server.close(() => {\n Log.info(\"Server closed\");\n process.exit(0);\n });\n });\n };\n\n #bindEndpoints = () => {\n // Health check endpoint\n this.#app.get(\"/healthz\", Controller.#healthz);\n\n // Metrics endpoint\n this.#app.get(\"/metrics\", this.#metrics);\n\n if (isWatchMode()) {\n return;\n }\n\n // Require auth for webhook endpoints\n this.#app.use([\"/mutate/:token\", \"/validate/:token\"], this.#validateToken);\n\n // Mutate endpoint\n this.#app.post(\"/mutate/:token\", this.#admissionReq(\"Mutate\"));\n\n // Validate endpoint\n this.#app.post(\"/validate/:token\", this.#admissionReq(\"Validate\"));\n };\n\n /**\n * Validate the token in the request path\n *\n * @param req The incoming request\n * @param res The outgoing response\n * @param next The next middleware function\n * @returns\n */\n #validateToken = (req: express.Request, res: express.Response, next: NextFunction) => {\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 Log.warn(err);\n res.status(401).send(err);\n this.#metricsCollector.alert();\n return;\n }\n\n // Token is valid, continue\n next();\n };\n\n /**\n * Metrics endpoint handler\n *\n * @param req the incoming request\n * @param res the outgoing response\n */\n #metrics = async (req: express.Request, res: express.Response) => {\n try {\n res.send(await this.#metricsCollector.getMetrics());\n } catch (err) {\n Log.error(err, `Error getting metrics`);\n res.status(500).send(\"Internal Server Error\");\n }\n };\n\n /**\n * Admission request handler for both mutate and validate requests\n *\n * @param admissionKind the type of admission request\n * @returns the request handler\n */\n #admissionReq = (admissionKind: \"Mutate\" | \"Validate\") => {\n // Create the admission request handler\n return async (req: express.Request, res: express.Response) => {\n // Start the metrics timer\n const startTime = MetricsCollector.observeStart();\n\n try {\n // Get the request from the body or create an empty request\n const request: AdmissionRequest = req.body?.request || ({} as AdmissionRequest);\n\n // Run the before hook if it exists\n this.#beforeHook && this.#beforeHook(request || {});\n\n // Setup identifiers for logging\n const name = request?.name ? `/${request.name}` : \"\";\n const namespace = request?.namespace || \"\";\n const gvk = request?.kind || { group: \"\", version: \"\", kind: \"\" };\n\n const reqMetadata = {\n uid: request.uid,\n namespace,\n name,\n };\n\n Log.info({ ...reqMetadata, gvk, operation: request.operation, admissionKind }, \"Incoming request\");\n Log.debug({ ...reqMetadata, request }, \"Incoming request body\");\n\n // Process the request\n let response: MutateResponse | ValidateResponse[];\n\n // Call mutate or validate based on the admission kind\n if (admissionKind === \"Mutate\") {\n response = await mutateProcessor(this.#config, this.#capabilities, request, reqMetadata);\n } else {\n response = await validateProcessor(this.#capabilities, request, reqMetadata);\n }\n\n // Run the after hook if it exists\n const responseList: ValidateResponse[] | MutateResponse[] = Array.isArray(response) ? response : [response];\n responseList.map(res => {\n this.#afterHook && this.#afterHook(res);\n // Log the response\n Log.info({ ...reqMetadata, res }, \"Check response\");\n });\n\n let kubeAdmissionResponse: ValidateResponse[] | MutateResponse | ResponseItem;\n\n if (admissionKind === \"Mutate\") {\n kubeAdmissionResponse = response;\n Log.debug({ ...reqMetadata, response }, \"Outgoing response\");\n res.send({\n apiVersion: \"admission.k8s.io/v1\",\n kind: \"AdmissionReview\",\n response: kubeAdmissionResponse,\n });\n } else {\n kubeAdmissionResponse =\n responseList.length === 0\n ? {\n uid: request.uid,\n allowed: true,\n status: { message: \"no in-scope validations -- allowed!\" },\n }\n : {\n uid: responseList[0].uid,\n allowed: responseList.filter(r => !r.allowed).length === 0,\n status: {\n message: (responseList as ValidateResponse[])\n .filter(rl => !rl.allowed)\n .map(curr => curr.status?.message)\n .join(\"; \"),\n },\n };\n res.send({\n apiVersion: \"admission.k8s.io/v1\",\n kind: \"AdmissionReview\",\n response: kubeAdmissionResponse,\n });\n }\n\n Log.debug({ ...reqMetadata, kubeAdmissionResponse }, \"Outgoing response\");\n\n this.#metricsCollector.observeEnd(startTime, admissionKind);\n } catch (err) {\n Log.error(err, `Error processing ${admissionKind} request`);\n res.status(500).send(\"Internal Server Error\");\n this.#metricsCollector.error();\n }\n };\n };\n\n /**\n * Middleware for logging requests\n *\n * @param req the incoming request\n * @param res the outgoing response\n * @param next the next middleware function\n */\n static #logger(req: express.Request, res: express.Response, next: express.NextFunction) {\n const startTime = Date.now();\n\n res.on(\"finish\", () => {\n const elapsedTime = Date.now() - startTime;\n const message = {\n uid: req.body?.request?.uid,\n method: req.method,\n url: req.originalUrl,\n status: res.statusCode,\n duration: `${elapsedTime} ms`,\n };\n\n res.statusCode >= 300 ? Log.warn(message) : Log.info(message);\n });\n\n next();\n }\n /**\n * Health check endpoint handler\n *\n * @param req the incoming request\n * @param res the outgoing response\n */\n static #healthz(req: express.Request, res: express.Response) {\n try {\n res.send(\"OK\");\n } catch (err) {\n Log.error(err, `Error processing health check`);\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\n/* eslint-disable class-methods-use-this */\n\nimport { performance } from \"perf_hooks\";\nimport promClient, { Counter, Registry, Gauge, Summary } from \"prom-client\";\nimport Log from \"./logger\";\n\nconst loggingPrefix = \"MetricsCollector\";\n\ninterface MetricNames {\n errors: string;\n alerts: string;\n mutate: string;\n validate: string;\n cacheMiss: string;\n resyncFailureCount: string;\n}\n\ninterface MetricArgs {\n name: string;\n help: string;\n registers: Registry[];\n labelNames?: string[];\n}\n\n/**\n * MetricsCollector class handles metrics collection using prom-client and performance hooks.\n */\nexport class MetricsCollector {\n #registry: Registry;\n #counters: Map<string, Counter<string>> = new Map();\n #gauges: Map<string, Gauge<string>> = new Map();\n #summaries: Map<string, Summary<string>> = new Map();\n #prefix: string;\n #cacheMissWindows: Map<string, number> = new Map();\n\n #metricNames: MetricNames = {\n errors: \"errors\",\n alerts: \"alerts\",\n mutate: \"mutate\",\n validate: \"validate\",\n cacheMiss: \"cache_miss\",\n resyncFailureCount: \"resync_failure_count\",\n };\n\n /**\n * Creates a MetricsCollector instance with prefixed metrics.\n * @param [prefix='pepr'] - The prefix for the metric names.\n */\n constructor(prefix = \"pepr\") {\n this.#registry = new Registry();\n this.#prefix = prefix;\n this.addCounter(this.#metricNames.errors, \"Mutation/Validate errors encountered\");\n this.addCounter(this.#metricNames.alerts, \"Mutation/Validate bad api token received\");\n this.addSummary(this.#metricNames.mutate, \"Mutation operation summary\");\n this.addSummary(this.#metricNames.validate, \"Validation operation summary\");\n this.addGauge(this.#metricNames.cacheMiss, \"Number of cache misses per window\", [\"window\"]);\n this.addGauge(this.#metricNames.resyncFailureCount, \"Number of failures per resync operation\", [\"count\"]);\n }\n\n #getMetricName = (name: string) => `${this.#prefix}_${name}`;\n\n #addMetric = <T extends Counter<string> | Gauge<string> | Summary<string>>(\n collection: Map<string, T>,\n MetricType: new (args: MetricArgs) => T,\n name: string,\n help: string,\n labelNames?: string[],\n ) => {\n if (collection.has(this.#getMetricName(name))) {\n Log.debug(`Metric for ${name} already exists`, loggingPrefix);\n return;\n }\n\n const metric = new MetricType({\n name: this.#getMetricName(name),\n help,\n registers: [this.#registry],\n labelNames,\n });\n\n collection.set(this.#getMetricName(name), metric);\n };\n\n addCounter = (name: string, help: string) => {\n this.#addMetric(this.#counters, promClient.Counter, name, help, []);\n };\n\n addSummary = (name: string, help: string) => {\n this.#addMetric(this.#summaries, promClient.Summary, name, help, []);\n };\n\n addGauge = (name: string, help: string, labelNames?: string[]) => {\n this.#addMetric(this.#gauges, promClient.Gauge, name, help, labelNames);\n };\n\n incCounter = (name: string) => {\n this.#counters.get(this.#getMetricName(name))?.inc();\n };\n\n incGauge = (name: string, labels?: Record<string, string>, value: number = 1) => {\n this.#gauges.get(this.#getMetricName(name))?.inc(labels || {}, value);\n };\n\n /**\n * Increments the error counter.\n */\n error = () => this.incCounter(this.#metricNames.errors);\n\n /**\n * Increments the alerts counter.\n */\n alert = () => this.incCounter(this.#metricNames.alerts);\n\n /**\n * Observes the duration since the provided start time and updates the summary.\n * @param startTime - The start time.\n * @param name - The metrics summary to increment.\n */\n observeEnd = (startTime: number, name: string = this.#metricNames.mutate) => {\n this.#summaries.get(this.#getMetricName(name))?.observe(performance.now() - startTime);\n };\n\n /**\n * Fetches the current metrics from the registry.\n * @returns The metrics.\n */\n getMetrics = () => this.#registry.metrics();\n\n /**\n * Returns the current timestamp from performance.now() method. Useful for start timing an operation.\n * @returns The timestamp.\n */\n static observeStart() {\n return performance.now();\n }\n\n /**\n * Increments the cache miss gauge for a given label.\n * @param label - The label for the cache miss.\n */\n incCacheMiss = (window: string) => {\n this.incGauge(this.#metricNames.cacheMiss, { window });\n };\n\n /**\n * Increments the retry count gauge.\n * @param count - The count to increment by.\n */\n incRetryCount = (count: string) => {\n this.incGauge(this.#metricNames.resyncFailureCount, { count });\n };\n\n /**\n * Initializes the cache miss gauge for a given label.\n * @param label - The label for the cache miss.\n */\n initCacheMissWindow = (window: string) => {\n this.#rollCacheMissWindows();\n this.#gauges.get(this.#getMetricName(this.#metricNames.cacheMiss))?.set({ window }, 0);\n this.#cacheMissWindows.set(window, 0);\n };\n\n /**\n * Manages the size of the cache miss gauge map.\n */\n #rollCacheMissWindows = () => {\n const maxCacheMissWindows = process.env.PEPR_MAX_CACHE_MISS_WINDOWS\n ? parseInt(process.env.PEPR_MAX_CACHE_MISS_WINDOWS, 10)\n : undefined;\n\n if (maxCacheMissWindows !== undefined && this.#cacheMissWindows.size >= maxCacheMissWindows) {\n const firstKey = this.#cacheMissWindows.keys().next().value;\n this.#cacheMissWindows.delete(firstKey);\n this.#gauges.get(this.#getMetricName(this.#metricNames.cacheMiss))?.remove({ window: firstKey });\n }\n };\n}\n\nexport const metricsCollector = new MetricsCollector(\"pepr\");\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport jsonPatch from \"fast-json-patch\";\nimport { kind } from \"kubernetes-fluent-client\";\n\nimport { Capability } from \"./capability\";\nimport { Errors } from \"./errors\";\nimport { shouldSkipRequest } from \"./filter\";\nimport { MutateResponse, AdmissionRequest } from \"./k8s\";\nimport Log from \"./logger\";\nimport { ModuleConfig } from \"./module\";\nimport { PeprMutateRequest } from \"./mutate-request\";\nimport { base64Encode, convertFromBase64Map, convertToBase64Map } from \"./utils\";\n\nexport async function mutateProcessor(\n config: ModuleConfig,\n capabilities: Capability[],\n req: AdmissionRequest,\n reqMetadata: Record<string, string>,\n): Promise<MutateResponse> {\n const wrapped = new PeprMutateRequest(req);\n const response: MutateResponse = {\n uid: req.uid,\n warnings: [],\n allowed: false,\n };\n\n // Track whether any capability matched the request\n let matchedAction = 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 kind.Secret);\n }\n\n Log.info(reqMetadata, `Processing request`);\n\n for (const { name, bindings, namespaces } of capabilities) {\n const actionMetadata = { ...reqMetadata, name };\n\n for (const action of bindings) {\n // Skip this action if it's not a mutate action\n if (!action.mutateCallback) {\n continue;\n }\n\n // Continue to the next action without doing anything if this one should be skipped\n if (shouldSkipRequest(action, req, namespaces)) {\n continue;\n }\n\n const label = action.mutateCallback.name;\n Log.info(actionMetadata, `Processing mutation action (${label})`);\n\n matchedAction = 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.mutateCallback(wrapped);\n\n Log.info(actionMetadata, `Mutation action succeeded (${label})`);\n\n // Add annotations to the request to indicate that the capability succeeded\n updateStatus(\"succeeded\");\n } catch (e) {\n updateStatus(\"warning\");\n response.warnings = response.warnings || [];\n\n let errorMessage = \"\";\n\n try {\n if (e.message && e.message !== \"[object Object]\") {\n errorMessage = e.message;\n } else {\n throw new Error(\"An error occurred in the mutate action.\");\n }\n } catch (e) {\n errorMessage = \"An error occurred with the mutate action.\";\n }\n\n Log.error(actionMetadata, `Action failed: ${errorMessage}`);\n response.warnings.push(`Action failed: ${errorMessage}`);\n\n switch (config.onError) {\n case Errors.reject:\n Log.error(actionMetadata, `Action failed: ${errorMessage}`);\n response.result = \"Pepr module configured to reject on error\";\n return response;\n\n case Errors.audit:\n response.auditAnnotations = response.auditAnnotations || {};\n response.auditAnnotations[Date.now()] = `Action failed: ${errorMessage}`;\n break;\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 (!matchedAction) {\n Log.info(reqMetadata, `No matching actions found`);\n return response;\n }\n\n // delete operations can't be mutate, just return before the transformation\n if (req.operation == \"DELETE\") {\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 kind.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 = base64Encode(JSON.stringify(patches));\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({ ...reqMetadata, patches }, `Patches generated`);\n\n return response;\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nexport const Errors = {\n audit: \"audit\",\n ignore: \"ignore\",\n reject: \"reject\",\n};\n\nexport const ErrorList = Object.values(Errors);\n\n/**\n * Validate the error or throw an error\n * @param error\n */\nexport function ValidateError(error = \"\") {\n if (!ErrorList.includes(error)) {\n throw new Error(`Invalid error: ${error}. Must be one of: ${ErrorList.join(\", \")}`);\n }\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { GenericKind, GroupVersionKind, KubernetesObject, RegisterKind } from \"kubernetes-fluent-client\";\n\nexport enum Operation {\n CREATE = \"CREATE\",\n UPDATE = \"UPDATE\",\n DELETE = \"DELETE\",\n CONNECT = \"CONNECT\",\n}\n\n/**\n * PeprStore for internal use by Pepr. This is used to store arbitrary data in the cluster.\n */\nexport class PeprStore extends GenericKind {\n declare data: {\n [key: string]: string;\n };\n}\n\nexport const peprStoreGVK = {\n kind: \"PeprStore\",\n version: \"v1\",\n group: \"pepr.dev\",\n};\n\nRegisterKind(PeprStore, peprStoreGVK);\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 AdmissionRequest<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 or DELETE 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 MutateResponse {\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 /**\n * AuditAnnotations is an unstructured key value map set by remote admission controller (e.g. error=image-blacklisted).\n *\n * See https://kubernetes.io/docs/reference/labels-annotations-taints/audit-annotations/ for more information\n */\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 interface ValidateResponse extends MutateResponse {\n /** Status contains extra details into why an admission request was denied. This field IS NOT consulted in any way if \"Allowed\" is \"true\". */\n status?: {\n /** A machine-readable description of why this operation is in the\n \"Failure\" status. If this value is empty there is no information available. */\n code: number;\n\n /** A human-readable description of the status of this operation. */\n message: string;\n };\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", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { AdmissionRequest, Operation } from \"./k8s\";\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 action binding\n * @param req the incoming request\n * @returns\n */\nexport function shouldSkipRequest(binding: Binding, req: AdmissionRequest, capabilityNamespaces: string[]) {\n const { group, kind, version } = binding.kind || {};\n const { namespaces, labels, annotations, name } = binding.filters || {};\n const operation = req.operation.toUpperCase();\n const uid = req.uid;\n // Use the old object if the request is a DELETE operation\n const srcObject = operation === Operation.DELETE ? req.oldObject : req.object;\n const { metadata } = srcObject || {};\n const combinedNamespaces = [...namespaces, ...capabilityNamespaces];\n\n // Test for matching operation\n if (!binding.event.includes(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 (\n (combinedNamespaces.length && !combinedNamespaces.includes(req.namespace || \"\")) ||\n (!namespaces.includes(req.namespace || \"\") && capabilityNamespaces.length !== 0 && namespaces.length !== 0)\n ) {\n let type = \"\";\n let label = \"\";\n\n if (binding.isMutate) {\n type = \"Mutate\";\n label = binding.mutateCallback!.name;\n } else if (binding.isValidate) {\n type = \"Validate\";\n label = binding.validateCallback!.name;\n } else if (binding.isWatch) {\n type = \"Watch\";\n label = binding.watchCallback!.name;\n }\n\n logger.debug({ uid }, `${type} binding (${label}) does not match request namespace \"${req.namespace}\"`);\n\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({ uid }, `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({ uid }, `${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({ uid }, `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({ uid }, `${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 { KubernetesObject } from \"kubernetes-fluent-client\";\nimport { clone, mergeDeepRight } from \"ramda\";\n\nimport { AdmissionRequest, Operation } from \"./k8s\";\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 PeprMutateRequest<T extends KubernetesObject> {\n Raw: T;\n\n #input: AdmissionRequest<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(input: AdmissionRequest<T>) {\n this.#input = input;\n\n // If this is a DELETE operation, use the oldObject instead\n if (input.operation.toUpperCase() === Operation.DELETE) {\n this.Raw = clone(input.oldObject as T);\n } else {\n // Otherwise, use the incoming object\n this.Raw = clone(input.object);\n }\n\n if (!this.Raw) {\n throw new Error(\"unable to load the request object into PeprRequest.RawP\");\n }\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\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\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 if (obj.data[key] == undefined) {\n obj.data[key] = \"\";\n } else {\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 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", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\n/* eslint-disable class-methods-use-this */\n\nimport { KubernetesObject } from \"kubernetes-fluent-client\";\n\nimport { clone } from \"ramda\";\nimport { Operation, AdmissionRequest } from \"./k8s\";\nimport { ValidateActionResponse } 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 PeprValidateRequest<T extends KubernetesObject> {\n Raw: T;\n\n #input: AdmissionRequest<T>;\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(input: AdmissionRequest<T>) {\n this.#input = input;\n\n // If this is a DELETE operation, use the oldObject instead\n if (input.operation.toUpperCase() === Operation.DELETE) {\n this.Raw = clone(input.oldObject as T);\n } else {\n // Otherwise, use the incoming object\n this.Raw = clone(input.object);\n }\n\n if (!this.Raw) {\n throw new Error(\"unable to load the request object into PeprRequest.Raw\");\n }\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 /**\n * Create a validation response that allows the request.\n *\n * @returns The validation response.\n */\n Approve = (): ValidateActionResponse => {\n return {\n allowed: true,\n };\n };\n\n /**\n * Create a validation response that denies the request.\n *\n * @param statusMessage Optional status message to return to the user.\n * @param statusCode Optional status code to return to the user.\n * @returns The validation response.\n */\n Deny = (statusMessage?: string, statusCode?: number): ValidateActionResponse => {\n return {\n allowed: false,\n statusCode,\n statusMessage,\n };\n };\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { kind } from \"kubernetes-fluent-client\";\n\nimport { Capability } from \"./capability\";\nimport { shouldSkipRequest } from \"./filter\";\nimport { AdmissionRequest, ValidateResponse } from \"./k8s\";\nimport Log from \"./logger\";\nimport { convertFromBase64Map } from \"./utils\";\nimport { PeprValidateRequest } from \"./validate-request\";\n\nexport async function validateProcessor(\n capabilities: Capability[],\n req: AdmissionRequest,\n reqMetadata: Record<string, string>,\n): Promise<ValidateResponse[]> {\n const wrapped = new PeprValidateRequest(req);\n const response: ValidateResponse[] = [];\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 convertFromBase64Map(wrapped.Raw as unknown as kind.Secret);\n }\n\n Log.info(reqMetadata, `Processing validation request`);\n\n for (const { name, bindings, namespaces } of capabilities) {\n const actionMetadata = { ...reqMetadata, name };\n\n for (const action of bindings) {\n // Skip this action if it's not a validation action\n if (!action.validateCallback) {\n continue;\n }\n\n const localResponse: ValidateResponse = {\n uid: req.uid,\n allowed: true, // Assume it's allowed until a validation check fails\n };\n\n // Continue to the next action without doing anything if this one should be skipped\n if (shouldSkipRequest(action, req, namespaces)) {\n continue;\n }\n\n const label = action.validateCallback.name;\n Log.info(actionMetadata, `Processing validation action (${label})`);\n\n try {\n // Run the validation callback, if it fails set allowed to false\n const resp = await action.validateCallback(wrapped);\n localResponse.allowed = resp.allowed;\n\n // If the validation callback returned a status code or message, set it in the Response\n if (resp.statusCode || resp.statusMessage) {\n localResponse.status = {\n code: resp.statusCode || 400,\n message: resp.statusMessage || `Validation failed for ${name}`,\n };\n }\n\n Log.info(actionMetadata, `Validation action complete (${label}): ${resp.allowed ? \"allowed\" : \"denied\"}`);\n } catch (e) {\n // If any validation throws an error, note the failure in the Response\n Log.error(actionMetadata, `Action failed: ${JSON.stringify(e)}`);\n localResponse.allowed = false;\n localResponse.status = {\n code: 500,\n message: `Action failed with error: ${JSON.stringify(e)}`,\n };\n return [localResponse];\n }\n response.push(localResponse);\n }\n }\n\n return response;\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { Operation } from \"fast-json-patch\";\nimport { K8s } from \"kubernetes-fluent-client\";\nimport { startsWith } from \"ramda\";\n\nimport { Capability } from \"../capability\";\nimport { PeprStore } from \"../k8s\";\nimport Log from \"../logger\";\nimport { DataOp, DataSender, DataStore, Storage } from \"../storage\";\n\nconst namespace = \"pepr-system\";\nexport const debounceBackoff = 5000;\n\nexport class PeprControllerStore {\n #name: string;\n #stores: Record<string, Storage> = {};\n #sendDebounce: NodeJS.Timeout | undefined;\n #onReady?: () => void;\n\n constructor(capabilities: Capability[], name: string, onReady?: () => void) {\n this.#onReady = onReady;\n\n // Setup Pepr State bindings\n this.#name = name;\n\n if (name.includes(\"schedule\")) {\n // Establish the store for each capability\n for (const { name, registerScheduleStore, hasSchedule } of capabilities) {\n // Guard Clause to exit early\n if (hasSchedule !== true) {\n continue;\n }\n // Register the scheduleStore with the capability\n const { scheduleStore } = registerScheduleStore();\n\n // Bind the store sender to the capability\n scheduleStore.registerSender(this.#send(name));\n\n // Store the storage instance\n this.#stores[name] = scheduleStore;\n }\n } else {\n // Establish the store for each capability\n for (const { name, registerStore } of capabilities) {\n // Register the store with the capability\n const { store } = registerStore();\n\n // Bind the store sender to the capability\n store.registerSender(this.#send(name));\n\n // Store the storage instance\n this.#stores[name] = store;\n }\n }\n\n // Add a jitter to the Store creation to avoid collisions\n setTimeout(\n () =>\n K8s(PeprStore)\n .InNamespace(namespace)\n .Get(this.#name)\n // If the get succeeds, migrate and setup the watch\n .then(async (store: PeprStore) => await this.#migrateAndSetupWatch(store))\n // Otherwise, create the resource\n .catch(this.#createStoreResource),\n Math.random() * 3000,\n );\n }\n\n #setupWatch = () => {\n const watcher = K8s(PeprStore, { name: this.#name, namespace }).Watch(this.#receive);\n watcher.start().catch(e => Log.error(e, \"Error starting Pepr store watch\"));\n };\n\n #migrateAndSetupWatch = async (store: PeprStore) => {\n Log.debug(store, \"Pepr Store migration\");\n const data: DataStore = store.data || {};\n const migrateCache: Record<string, Operation> = {};\n\n // Send the cached updates to the cluster\n const flushCache = async () => {\n const indexes = Object.keys(migrateCache);\n const payload = Object.values(migrateCache);\n\n // Loop over each key in the cache and delete it to avoid collisions with other sender calls\n for (const idx of indexes) {\n delete migrateCache[idx];\n }\n\n try {\n // Send the patch to the cluster\n await K8s(PeprStore, { namespace, name: this.#name }).Patch(payload);\n } catch (err) {\n Log.error(err, \"Pepr store update failure\");\n\n if (err.status === 422) {\n Object.keys(migrateCache).forEach(key => delete migrateCache[key]);\n } else {\n // On failure to update, re-add the operations to the cache to be retried\n for (const idx of indexes) {\n migrateCache[idx] = payload[Number(idx)];\n }\n }\n }\n };\n\n const fillCache = (name: string, op: DataOp, key: string[], val?: string) => {\n if (op === \"add\") {\n // adjust the path for the capability\n const path = `/data/${name}-v2-${key}`;\n const value = val || \"\";\n const cacheIdx = [op, path, value].join(\":\");\n\n // Add the operation to the cache\n migrateCache[cacheIdx] = { op, path, value };\n\n return;\n }\n\n if (op === \"remove\") {\n if (key.length < 1) {\n throw new Error(`Key is required for REMOVE operation`);\n }\n\n for (const k of key) {\n const path = `/data/${name}-${k}`;\n const cacheIdx = [op, path].join(\":\");\n\n // Add the operation to the cache\n migrateCache[cacheIdx] = { op, path };\n }\n\n return;\n }\n\n // If we get here, the operation is not supported\n throw new Error(`Unsupported operation: ${op}`);\n };\n\n for (const name of Object.keys(this.#stores)) {\n // Get the prefix offset for the keys\n const offset = `${name}-`.length;\n\n // Loop over each key in the store\n for (const key of Object.keys(data)) {\n // Match on the capability name as a prefix for non v2 keys\n if (startsWith(name, key) && !startsWith(`${name}-v2`, key)) {\n // populate migrate cache\n fillCache(name, \"remove\", [key.slice(offset)], data[key]);\n fillCache(name, \"add\", [key.slice(offset)], data[key]);\n }\n }\n }\n await flushCache();\n this.#setupWatch();\n };\n\n #receive = (store: PeprStore) => {\n Log.debug(store, \"Pepr Store update\");\n\n // Wrap the update in a debounced function\n const debounced = () => {\n // Base64 decode the data\n const data: DataStore = store.data || {};\n\n // Loop over each stored capability\n for (const name of Object.keys(this.#stores)) {\n // Get the prefix offset for the keys\n const offset = `${name}-`.length;\n\n // Get any keys that match the capability name prefix\n const filtered: DataStore = {};\n\n // Loop over each key in the secret\n for (const key of Object.keys(data)) {\n // Match on the capability name as a prefix\n if (startsWith(name, key)) {\n // Strip the prefix and store the value\n filtered[key.slice(offset)] = data[key];\n }\n }\n\n // Send the data to the receiver callback\n this.#stores[name].receive(filtered);\n }\n\n // Call the onReady callback if this is the first time the secret has been read\n if (this.#onReady) {\n this.#onReady();\n this.#onReady = undefined;\n }\n };\n\n // Debounce the update to 1 second to avoid multiple rapid calls\n clearTimeout(this.#sendDebounce);\n this.#sendDebounce = setTimeout(debounced, this.#onReady ? 0 : debounceBackoff);\n };\n\n #send = (capabilityName: string) => {\n const sendCache: Record<string, Operation> = {};\n\n // Load the sendCache with patch operations\n const fillCache = (op: DataOp, key: string[], val?: string) => {\n if (op === \"add\") {\n // adjust the path for the capability\n const path = `/data/${capabilityName}-${key}`;\n const value = val || \"\";\n const cacheIdx = [op, path, value].join(\":\");\n\n // Add the operation to the cache\n sendCache[cacheIdx] = { op, path, value };\n\n return;\n }\n\n if (op === \"remove\") {\n if (key.length < 1) {\n throw new Error(`Key is required for REMOVE operation`);\n }\n\n for (const k of key) {\n const path = `/data/${capabilityName}-${k}`;\n const cacheIdx = [op, path].join(\":\");\n\n // Add the operation to the cache\n sendCache[cacheIdx] = { op, path };\n }\n\n return;\n }\n\n // If we get here, the operation is not supported\n throw new Error(`Unsupported operation: ${op}`);\n };\n\n // Send the cached updates to the cluster\n const flushCache = async () => {\n const indexes = Object.keys(sendCache);\n const payload = Object.values(sendCache);\n\n // Loop over each key in the cache and delete it to avoid collisions with other sender calls\n for (const idx of indexes) {\n delete sendCache[idx];\n }\n\n try {\n // Send the patch to the cluster\n await K8s(PeprStore, { namespace, name: this.#name }).Patch(payload);\n } catch (err) {\n Log.error(err, \"Pepr store update failure\");\n\n if (err.status === 422) {\n Object.keys(sendCache).forEach(key => delete sendCache[key]);\n } else {\n // On failure to update, re-add the operations to the cache to be retried\n for (const idx of indexes) {\n sendCache[idx] = payload[Number(idx)];\n }\n }\n }\n };\n\n // Create a sender function for the capability to add/remove data from the store\n const sender: DataSender = async (op: DataOp, key: string[], val?: string) => {\n fillCache(op, key, val);\n };\n\n // Send any cached updates every debounceBackoff milliseconds\n setInterval(() => {\n if (Object.keys(sendCache).length > 0) {\n Log.debug(sendCache, \"Sending updates to Pepr store\");\n void flushCache();\n }\n }, debounceBackoff);\n\n return sender;\n };\n\n #createStoreResource = async (e: unknown) => {\n Log.info(`Pepr store not found, creating...`);\n Log.debug(e);\n\n try {\n await K8s(PeprStore).Apply({\n metadata: {\n name: this.#name,\n namespace,\n },\n data: {\n // JSON Patch will die if the data is empty, so we need to add a placeholder\n __pepr_do_not_delete__: \"k-thx-bye\",\n },\n });\n\n // Now that the resource exists, setup the watch\n this.#setupWatch();\n } catch (err) {\n Log.error(err, \"Failed to create Pepr store\");\n }\n };\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\nimport { K8s, KubernetesObject, WatchCfg, WatchEvent } from \"kubernetes-fluent-client\";\nimport { WatchPhase } from \"kubernetes-fluent-client/dist/fluent/types\";\nimport { Capability } from \"./capability\";\nimport { filterNoMatchReason } from \"./helpers\";\nimport Log from \"./logger\";\nimport { Queue } from \"./queue\";\nimport { Binding, Event } from \"./types\";\nimport { metricsCollector } from \"./metrics\";\n\n// Watch configuration\nconst watchCfg: WatchCfg = {\n resyncFailureMax: process.env.PEPR_RESYNC_FAILURE_MAX ? parseInt(process.env.PEPR_RESYNC_FAILURE_MAX, 10) : 5,\n resyncDelaySec: process.env.PEPR_RESYNC_DELAY_SECONDS ? parseInt(process.env.PEPR_RESYNC_DELAY_SECONDS, 10) : 5,\n lastSeenLimitSeconds: process.env.PEPR_LAST_SEEN_LIMIT_SECONDS\n ? parseInt(process.env.PEPR_LAST_SEEN_LIMIT_SECONDS, 10)\n : 300,\n relistIntervalSec: process.env.PEPR_RELIST_INTERVAL_SECONDS\n ? parseInt(process.env.PEPR_RELIST_INTERVAL_SECONDS, 10)\n : 1800,\n};\n\n// Map the event to the watch phase\nconst eventToPhaseMap = {\n [Event.Create]: [WatchPhase.Added],\n [Event.Update]: [WatchPhase.Modified],\n [Event.CreateOrUpdate]: [WatchPhase.Added, WatchPhase.Modified],\n [Event.Delete]: [WatchPhase.Deleted],\n [Event.Any]: [WatchPhase.Added, WatchPhase.Modified, WatchPhase.Deleted],\n};\n\n/**\n * Entrypoint for setting up watches for all capabilities\n *\n * @param capabilities The capabilities to load watches for\n */\nexport function setupWatch(capabilities: Capability[]) {\n capabilities.map(capability =>\n capability.bindings\n .filter(binding => binding.isWatch)\n .forEach(bindingElement => runBinding(bindingElement, capability.namespaces)),\n );\n}\n\n/**\n * Setup a watch for a binding\n *\n * @param binding the binding to watch\n * @param capabilityNamespaces list of namespaces to filter on\n */\nasync function runBinding(binding: Binding, capabilityNamespaces: string[]) {\n // Get the phases to match, fallback to any\n const phaseMatch: WatchPhase[] = eventToPhaseMap[binding.event] || eventToPhaseMap[Event.Any];\n\n // The watch callback is run when an object is received or dequeued\n\n Log.debug({ watchCfg }, \"Effective WatchConfig\");\n const watchCallback = async (obj: KubernetesObject, type: WatchPhase) => {\n // First, filter the object based on the phase\n if (phaseMatch.includes(type)) {\n try {\n // Then, check if the object matches the filter\n const filterMatch = filterNoMatchReason(binding, obj, capabilityNamespaces);\n if (filterMatch === \"\") {\n await binding.watchCallback?.(obj, type);\n } else {\n Log.debug(filterMatch);\n }\n } catch (e) {\n // Errors in the watch callback should not crash the controller\n Log.error(e, \"Error executing watch callback\");\n }\n }\n };\n\n const queue = new Queue();\n queue.setReconcile(watchCallback);\n\n // Setup the resource watch\n const watcher = K8s(binding.model, binding.filters).Watch(async (obj, type) => {\n Log.debug(obj, `Watch event ${type} received`);\n\n // If the binding is a queue, enqueue the object\n if (binding.isQueue) {\n await queue.enqueue(obj, type);\n } else {\n // Otherwise, run the watch callback directly\n await watchCallback(obj, type);\n }\n }, watchCfg);\n\n // If failure continues, log and exit\n watcher.events.on(WatchEvent.GIVE_UP, err => {\n Log.error(err, \"Watch failed after 5 attempts, giving up\");\n process.exit(1);\n });\n\n watcher.events.on(WatchEvent.CONNECT, url => logEvent(WatchEvent.CONNECT, url));\n\n watcher.events.on(WatchEvent.DATA_ERROR, err => logEvent(WatchEvent.DATA_ERROR, err.message));\n watcher.events.on(WatchEvent.RECONNECT, retryCount =>\n logEvent(WatchEvent.RECONNECT, `Reconnecting after ${retryCount} attempt${retryCount === 1 ? \"\" : \"s\"}`),\n );\n watcher.events.on(WatchEvent.RECONNECT_PENDING, () => logEvent(WatchEvent.RECONNECT_PENDING));\n watcher.events.on(WatchEvent.GIVE_UP, err => logEvent(WatchEvent.GIVE_UP, err.message));\n watcher.events.on(WatchEvent.ABORT, err => logEvent(WatchEvent.ABORT, err.message));\n watcher.events.on(WatchEvent.OLD_RESOURCE_VERSION, err => logEvent(WatchEvent.OLD_RESOURCE_VERSION, err));\n watcher.events.on(WatchEvent.NETWORK_ERROR, err => logEvent(WatchEvent.NETWORK_ERROR, err.message));\n watcher.events.on(WatchEvent.LIST_ERROR, err => logEvent(WatchEvent.LIST_ERROR, err.message));\n watcher.events.on(WatchEvent.LIST, list => logEvent(WatchEvent.LIST, JSON.stringify(list, undefined, 2)));\n watcher.events.on(WatchEvent.CACHE_MISS, windowName => {\n metricsCollector.incCacheMiss(windowName);\n });\n\n watcher.events.on(WatchEvent.INIT_CACHE_MISS, windowName => {\n metricsCollector.initCacheMissWindow(windowName);\n });\n\n watcher.events.on(WatchEvent.INC_RESYNC_FAILURE_COUNT, retryCount => {\n metricsCollector.incRetryCount(retryCount);\n });\n\n // Start the watch\n try {\n await watcher.start();\n } catch (err) {\n Log.error(err, \"Error starting watch\");\n process.exit(1);\n }\n}\n\nexport function logEvent(type: WatchEvent, message: string = \"\", obj?: KubernetesObject) {\n const logMessage = `Watch event ${type} received${message ? `. ${message}.` : \".\"}`;\n if (obj) {\n Log.debug(obj, logMessage);\n } else {\n Log.debug(logMessage);\n }\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { promises as fs } from \"fs\";\nimport { K8s, KubernetesObject, kind } from \"kubernetes-fluent-client\";\nimport Log from \"./logger\";\nimport { Binding, CapabilityExport } from \"./types\";\nimport { sanitizeResourceName } from \"../sdk/sdk\";\n\nexport class ValidationError extends Error {}\n\nexport function validateCapabilityNames(capabilities: CapabilityExport[] | undefined): void {\n if (capabilities && capabilities.length > 0) {\n for (let i = 0; i < capabilities.length; i++) {\n if (capabilities[i].name !== sanitizeResourceName(capabilities[i].name)) {\n throw new ValidationError(`Capability name is not a valid Kubernetes resource name: ${capabilities[i].name}`);\n }\n }\n }\n}\n\nexport function validateHash(expectedHash: string): void {\n // Require the hash to be a valid SHA-256 hash (64 characters, hexadecimal)\n const sha256Regex = /^[a-f0-9]{64}$/i;\n if (!expectedHash || !sha256Regex.test(expectedHash)) {\n Log.error(`Invalid hash. Expected a valid SHA-256 hash, got ${expectedHash}`);\n throw new ValidationError(\"Invalid hash\");\n }\n}\n\ntype RBACMap = {\n [key: string]: {\n verbs: string[];\n plural: string;\n };\n};\n\n// check for overlap with labels and annotations between bindings and kubernetes objects\nexport function checkOverlap(bindingFilters: Record<string, string>, objectFilters: Record<string, string>): boolean {\n // True if labels/annotations are empty\n if (Object.keys(bindingFilters).length === 0) {\n return true;\n }\n\n let matchCount = 0;\n\n for (const key in bindingFilters) {\n // object must have label/annotation\n if (Object.prototype.hasOwnProperty.call(objectFilters, key)) {\n const val1 = bindingFilters[key];\n const val2 = objectFilters[key];\n\n // If bindingFilter has empty value for this key, only need to ensure objectFilter has this key\n if (val1 === \"\" && key in objectFilters) {\n matchCount++;\n }\n // If bindingFilter has a value, it must match the value in objectFilter\n else if (val1 !== \"\" && val1 === val2) {\n matchCount++;\n }\n }\n }\n\n // For single-key objects in bindingFilter or matching all keys in multiple-keys scenario\n return matchCount === Object.keys(bindingFilters).length;\n}\n\n/**\n * Decide to run callback after the event comes back from API Server\n **/\nexport function filterNoMatchReason(\n binding: Partial<Binding>,\n obj: Partial<KubernetesObject>,\n capabilityNamespaces: string[],\n): string {\n // binding kind is namespace with a InNamespace filter\n if (binding.kind && binding.kind.kind === \"Namespace\" && binding.filters && binding.filters.namespaces.length !== 0) {\n return `Ignoring Watch Callback: Cannot use a namespace filter in a namespace object.`;\n }\n\n if (typeof obj === \"object\" && obj !== null && \"metadata\" in obj && obj.metadata !== undefined && binding.filters) {\n // binding labels and object labels dont match\n if (obj.metadata.labels && !checkOverlap(binding.filters.labels, obj.metadata.labels)) {\n return `Ignoring Watch Callback: No overlap between binding and object labels. Binding labels ${JSON.stringify(\n binding.filters.labels,\n )}, Object Labels ${JSON.stringify(obj.metadata.labels)}.`;\n }\n\n // binding annotations and object annotations dont match\n if (obj.metadata.annotations && !checkOverlap(binding.filters.annotations, obj.metadata.annotations)) {\n return `Ignoring Watch Callback: No overlap between binding and object annotations. Binding annotations ${JSON.stringify(\n binding.filters.annotations,\n )}, Object annotations ${JSON.stringify(obj.metadata.annotations)}.`;\n }\n }\n\n // Check object is in the capability namespace\n if (\n Array.isArray(capabilityNamespaces) &&\n capabilityNamespaces.length > 0 &&\n obj.metadata &&\n obj.metadata.namespace &&\n !capabilityNamespaces.includes(obj.metadata.namespace)\n ) {\n return `Ignoring Watch Callback: Object is not in the capability namespace. Capability namespaces: ${capabilityNamespaces.join(\n \", \",\n )}, Object namespace: ${obj.metadata.namespace}.`;\n }\n\n // chceck every filter namespace is a capability namespace\n if (\n Array.isArray(capabilityNamespaces) &&\n capabilityNamespaces.length > 0 &&\n binding.filters &&\n Array.isArray(binding.filters.namespaces) &&\n binding.filters.namespaces.length > 0 &&\n !binding.filters.namespaces.every(ns => capabilityNamespaces.includes(ns))\n ) {\n return `Ignoring Watch Callback: Binding namespace is not part of capability namespaces. Capability namespaces: ${capabilityNamespaces.join(\n \", \",\n )}, Binding namespaces: ${binding.filters.namespaces.join(\", \")}.`;\n }\n\n // filter namespace is not the same of object namespace\n if (\n binding.filters &&\n Array.isArray(binding.filters.namespaces) &&\n binding.filters.namespaces.length > 0 &&\n obj.metadata &&\n obj.metadata.namespace &&\n !binding.filters.namespaces.includes(obj.metadata.namespace)\n ) {\n return `Ignoring Watch Callback: Binding namespace and object namespace are not the same. Binding namespaces: ${binding.filters.namespaces.join(\n \", \",\n )}, Object namespace: ${obj.metadata.namespace}.`;\n }\n\n // no problems\n return \"\";\n}\n\nexport function addVerbIfNotExists(verbs: string[], verb: string) {\n if (!verbs.includes(verb)) {\n verbs.push(verb);\n }\n}\n\nexport function createRBACMap(capabilities: CapabilityExport[]): RBACMap {\n return capabilities.reduce((acc: RBACMap, capability: CapabilityExport) => {\n capability.bindings.forEach(binding => {\n const key = `${binding.kind.group}/${binding.kind.version}/${binding.kind.kind}`;\n\n acc[\"pepr.dev/v1/peprstore\"] = {\n verbs: [\"create\", \"get\", \"patch\", \"watch\"],\n plural: \"peprstores\",\n };\n\n acc[\"apiextensions.k8s.io/v1/customresourcedefinition\"] = {\n verbs: [\"patch\", \"create\"],\n plural: \"customresourcedefinitions\",\n };\n\n if (!acc[key] && binding.isWatch) {\n acc[key] = {\n verbs: [\"watch\"],\n plural: binding.kind.plural || `${binding.kind.kind.toLowerCase()}s`,\n };\n }\n });\n\n return acc;\n }, {});\n}\n\nexport async function createDirectoryIfNotExists(path: string) {\n try {\n await fs.access(path);\n } catch (error) {\n if (error.code === \"ENOENT\") {\n await fs.mkdir(path, { recursive: true });\n } else {\n throw error;\n }\n }\n}\n\nexport function hasEveryOverlap<T>(array1: T[], array2: T[]): boolean {\n if (!Array.isArray(array1) || !Array.isArray(array2)) {\n return false;\n }\n\n return array1.every(element => array2.includes(element));\n}\n\nexport function hasAnyOverlap<T>(array1: T[], array2: T[]): boolean {\n if (!Array.isArray(array1) || !Array.isArray(array2)) {\n return false;\n }\n\n return array1.some(element => array2.includes(element));\n}\n\nexport function ignoredNamespaceConflict(ignoreNamespaces: string[], bindingNamespaces: string[]) {\n return hasAnyOverlap(bindingNamespaces, ignoreNamespaces);\n}\n\nexport function bindingAndCapabilityNSConflict(bindingNamespaces: string[], capabilityNamespaces: string[]) {\n if (!capabilityNamespaces) {\n return false;\n }\n return capabilityNamespaces.length !== 0 && !hasEveryOverlap(bindingNamespaces, capabilityNamespaces);\n}\n\nexport function generateWatchNamespaceError(\n ignoredNamespaces: string[],\n bindingNamespaces: string[],\n capabilityNamespaces: string[],\n) {\n let err = \"\";\n\n // check if binding uses an ignored namespace\n if (ignoredNamespaceConflict(ignoredNamespaces, bindingNamespaces)) {\n err += `Binding uses a Pepr ignored namespace: ignoredNamespaces: [${ignoredNamespaces.join(\n \", \",\n )}] bindingNamespaces: [${bindingNamespaces.join(\", \")}].`;\n }\n\n // ensure filter namespaces are part of capability namespaces\n if (bindingAndCapabilityNSConflict(bindingNamespaces, capabilityNamespaces)) {\n err += `Binding uses namespace not governed by capability: bindingNamespaces: [${bindingNamespaces.join(\n \", \",\n )}] capabilityNamespaces: [${capabilityNamespaces.join(\", \")}].`;\n }\n\n // add a space if there is a period in the middle of the string\n return err.replace(/\\.([^ ])/g, \". $1\");\n}\n\n// namespaceComplianceValidator ensures that capability bindinds respect ignored and capability namespaces\nexport function namespaceComplianceValidator(capability: CapabilityExport, ignoredNamespaces?: string[]) {\n const { namespaces: capabilityNamespaces, bindings, name } = capability;\n const bindingNamespaces = bindings.flatMap(binding => binding.filters.namespaces);\n\n const namespaceError = generateWatchNamespaceError(\n ignoredNamespaces ? ignoredNamespaces : [],\n bindingNamespaces,\n capabilityNamespaces ? capabilityNamespaces : [],\n );\n if (namespaceError !== \"\") {\n throw new Error(\n `Error in ${name} capability. A binding violates namespace rules. Please check ignoredNamespaces and capability namespaces: ${namespaceError}`,\n );\n }\n}\n\n// check to see if all replicas are ready for all deployments in the pepr-system namespace\n// returns true if all deployments are ready, false otherwise\nexport async function checkDeploymentStatus(namespace: string) {\n const deployments = await K8s(kind.Deployment).InNamespace(namespace).Get();\n let status = false;\n let readyCount = 0;\n\n for (const deployment of deployments.items) {\n const readyReplicas = deployment.status?.readyReplicas ? deployment.status?.readyReplicas : 0;\n if (deployment.status?.readyReplicas !== deployment.spec?.replicas) {\n Log.info(\n `Waiting for deployment ${deployment.metadata?.name} rollout to finish: ${readyReplicas} of ${deployment.spec?.replicas} replicas are available`,\n );\n } else {\n Log.info(\n `Deployment ${deployment.metadata?.name} rolled out: ${readyReplicas} of ${deployment.spec?.replicas} replicas are available`,\n );\n readyCount++;\n }\n }\n if (readyCount === deployments.items.length) {\n status = true;\n }\n return status;\n}\n\n// wait for all deployments in the pepr-system namespace to be ready\nexport async function namespaceDeploymentsReady(namespace: string = \"pepr-system\") {\n Log.info(`Checking ${namespace} deployments status...`);\n let ready = false;\n while (!ready) {\n ready = await checkDeploymentStatus(namespace);\n if (ready) {\n return ready;\n }\n await new Promise(resolve => setTimeout(resolve, 1000));\n }\n Log.info(`All ${namespace} deployments are ready`);\n}\n\n// check if secret is over the size limit\nexport function secretOverLimit(str: string): boolean {\n const encoder = new TextEncoder();\n const encoded = encoder.encode(str);\n const sizeInBytes = encoded.length;\n const oneMiBInBytes = 1048576;\n return sizeInBytes > oneMiBInBytes;\n}\n\n/* eslint-disable @typescript-eslint/no-unused-vars */\nexport const parseTimeout = (value: string, previous: unknown): number => {\n const parsedValue = parseInt(value, 10);\n const floatValue = parseFloat(value);\n if (isNaN(parsedValue)) {\n throw new Error(\"Not a number.\");\n } else if (parsedValue !== floatValue) {\n throw new Error(\"Value must be an integer.\");\n } else if (parsedValue < 1 || parsedValue > 30) {\n throw new Error(\"Number must be between 1 and 30.\");\n }\n return parsedValue;\n};\n\n// Remove leading whitespace while keeping format of file\nexport function dedent(file: string) {\n // Check if the first line is empty and remove it\n const lines = file.split(\"\\n\");\n if (lines[0].trim() === \"\") {\n lines.shift(); // Remove the first line if it's empty\n file = lines.join(\"\\n\"); // Rejoin the remaining lines back into a single string\n }\n\n const match = file.match(/^[ \\t]*(?=\\S)/gm);\n const indent = match && Math.min(...match.map(el => el.length));\n if (indent && indent > 0) {\n const re = new RegExp(`^[ \\\\t]{${indent}}`, \"gm\");\n return file.replace(re, \"\");\n }\n return file;\n}\n\nexport function replaceString(str: string, stringA: string, stringB: string) {\n //eslint-disable-next-line\n const escapedStringA = stringA.replace(/[-\\/\\\\^$*+?.()|[\\]{}]/g, \"\\\\$&\");\n const regExp = new RegExp(escapedStringA, \"g\");\n return str.replace(regExp, stringB);\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { PeprValidateRequest } from \"../lib/validate-request\";\nimport { PeprMutateRequest } from \"../lib/mutate-request\";\nimport { a } from \"../lib\";\nimport { V1OwnerReference } from \"@kubernetes/client-node\";\nimport { GenericKind } from \"kubernetes-fluent-client\";\nimport { K8s, kind } from \"kubernetes-fluent-client\";\nimport Log from \"../lib/logger\";\n\n/**\n * Returns all containers in a pod\n * @param request the request/pod to get the containers from\n * @param containerType the type of container to get\n * @returns the list of containers in the pod\n */\nexport function containers(\n request: PeprValidateRequest<a.Pod> | PeprMutateRequest<a.Pod>,\n containerType?: \"containers\" | \"initContainers\" | \"ephemeralContainers\",\n) {\n const containers = request.Raw.spec?.containers || [];\n const initContainers = request.Raw.spec?.initContainers || [];\n const ephemeralContainers = request.Raw.spec?.ephemeralContainers || [];\n\n if (containerType === \"containers\") {\n return containers;\n }\n if (containerType === \"initContainers\") {\n return initContainers;\n }\n if (containerType === \"ephemeralContainers\") {\n return ephemeralContainers;\n }\n return [...containers, ...initContainers, ...ephemeralContainers];\n}\n\n/**\n * Write a K8s event for a CRD\n *\n * @param cr The custom resource to write the event for\n * @param event The event to write, should contain a human-readable message for the event\n * @param eventType The type of event to write, for example \"Warning\"\n * @param eventReason The reason for the event, for example \"ReconciliationFailed\"\n * @param reportingComponent The component that is reporting the event, for example \"uds.dev/operator\"\n * @param reportingInstance The instance of the component that is reporting the event, for example process.env.HOSTNAME\n */\nexport async function writeEvent(\n cr: GenericKind,\n event: Partial<kind.CoreEvent>,\n eventType: string,\n eventReason: string,\n reportingComponent: string,\n reportingInstance: string,\n) {\n Log.debug(cr.metadata, `Writing event: ${event.message}`);\n\n await K8s(kind.CoreEvent).Create({\n type: eventType,\n reason: eventReason,\n ...event,\n // Fixed values\n metadata: {\n namespace: cr.metadata!.namespace,\n generateName: cr.metadata!.name,\n },\n involvedObject: {\n apiVersion: cr.apiVersion,\n kind: cr.kind,\n name: cr.metadata!.name,\n namespace: cr.metadata!.namespace,\n uid: cr.metadata!.uid,\n },\n firstTimestamp: new Date(),\n reportingComponent: reportingComponent,\n reportingInstance: reportingInstance,\n });\n}\n\n/**\n * Get the owner reference for a custom resource\n * @param cr the custom resource to get the owner reference for\n * @returns the owner reference for the custom resource\n */\nexport function getOwnerRefFrom(cr: GenericKind): V1OwnerReference[] {\n const { name, uid } = cr.metadata!;\n\n return [\n {\n apiVersion: cr.apiVersion!,\n kind: cr.kind!,\n uid: uid!,\n name: name!,\n },\n ];\n}\n\n/**\n * Sanitize a resource name to make it a valid Kubernetes resource name.\n *\n * @param name the name of the resource to sanitize\n * @returns the sanitized resource name\n */\nexport function sanitizeResourceName(name: string) {\n return (\n name\n // The name must be lowercase\n .toLowerCase()\n // Replace sequences of non-alphanumeric characters with a single '-'\n .replace(/[^a-z0-9]+/g, \"-\")\n // Truncate the name to 250 characters\n .slice(0, 250)\n // Remove leading and trailing non-letter characters\n .replace(/^[^a-z]+|[^a-z]+$/g, \"\")\n );\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\nimport { KubernetesObject } from \"@kubernetes/client-node\";\nimport { WatchPhase } from \"kubernetes-fluent-client/dist/fluent/types\";\nimport Log from \"./logger\";\n\ntype QueueItem<K extends KubernetesObject> = {\n item: K;\n type: WatchPhase;\n resolve: (value: void | PromiseLike<void>) => void;\n reject: (reason?: string) => void;\n};\n\n/**\n * Queue is a FIFO queue for reconciling\n */\nexport class Queue<K extends KubernetesObject> {\n #queue: QueueItem<K>[] = [];\n #pendingPromise = false;\n #reconcile?: (obj: KubernetesObject, type: WatchPhase) => Promise<void>;\n\n constructor() {\n this.#reconcile = async () => await new Promise(resolve => resolve());\n }\n\n setReconcile(reconcile: (obj: KubernetesObject, type: WatchPhase) => Promise<void>) {\n this.#reconcile = reconcile;\n }\n\n /**\n * Enqueue adds an item to the queue and returns a promise that resolves when the item is\n * reconciled.\n *\n * @param item The object to reconcile\n * @returns A promise that resolves when the object is reconciled\n */\n enqueue(item: K, type: WatchPhase) {\n Log.debug(`Enqueueing ${item.metadata!.namespace}/${item.metadata!.name}`);\n return new Promise<void>((resolve, reject) => {\n this.#queue.push({ item, type, resolve, reject });\n return this.#dequeue();\n });\n }\n\n /**\n * Dequeue reconciles the next item in the queue\n *\n * @returns A promise that resolves when the webapp is reconciled\n */\n async #dequeue() {\n // If there is a pending promise, do nothing\n if (this.#pendingPromise) {\n Log.debug(\"Pending promise, not dequeuing\");\n return false;\n }\n\n // Take the next element from the queue\n const element = this.#queue.shift();\n\n // If there is no element, do nothing\n if (!element) {\n Log.debug(\"No element, not dequeuing\");\n return false;\n }\n\n try {\n // Set the pending promise flag to avoid concurrent reconciliations\n this.#pendingPromise = true;\n\n // Reconcile the element\n if (this.#reconcile) {\n Log.debug(`Reconciling ${element.item.metadata!.name}`);\n await this.#reconcile(element.item, element.type);\n }\n\n element.resolve();\n } catch (e) {\n Log.debug(`Error reconciling ${element.item.metadata!.name}`, { error: e });\n element.reject(e);\n } finally {\n // Reset the pending promise flag\n Log.debug(\"Resetting pending promise and dequeuing\");\n this.#pendingPromise = false;\n\n // After the element is reconciled, dequeue the next element\n await this.#dequeue();\n }\n }\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { clone } from \"ramda\";\nimport Log from \"./logger\";\nimport pointer from \"json-pointer\";\nexport type DataOp = \"add\" | \"remove\";\nexport type DataStore = Record<string, string>;\nexport type DataSender = (op: DataOp, keys: string[], value?: string) => void;\nexport type DataReceiver = (data: DataStore) => void;\nexport type Unsubscribe = () => void;\n\nconst MAX_WAIT_TIME = 15000;\nconst STORE_VERSION_PREFIX = \"v2\";\n\nexport function v2StoreKey(key: string) {\n return `${STORE_VERSION_PREFIX}-${pointer.escape(key)}`;\n}\n\nexport function v2UnescapedStoreKey(key: string) {\n return `${STORE_VERSION_PREFIX}-${key}`;\n}\n\nexport function stripV2Prefix(key: string) {\n return key.replace(/^v2-/, \"\");\n}\nexport interface PeprStore {\n /**\n * Returns the current value associated with the given key, or null if the given key does not exist.\n */\n getItem(key: string): string | null;\n /**\n * Removes all key/value pairs, if there are any.\n */\n clear(): void;\n /**\n * Removes the key/value pair with the given key, if a key/value pair with the given key exists.\n */\n removeItem(key: string): void;\n /**\n * Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously.\n */\n setItem(key: string, value: string): void;\n\n /**\n * Subscribe to changes in the store. This API behaves similarly to the [Svelte Store API](https://vercel.com/docs/beginner-sveltekit/svelte-stores#using-the-store).\n *\n * @param listener - The callback to be invoked when the store changes.\n * @returns A function to unsubscribe from the listener.\n */\n subscribe(listener: DataReceiver): Unsubscribe;\n\n /**\n * Register a function to be called when the store is ready.\n */\n onReady(callback: DataReceiver): void;\n\n /**\n * Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously.\n * Resolves when the key/value show up in the store.\n */\n setItemAndWait(key: string, value: string): Promise<void>;\n\n /**\n * Remove the value of the key.\n * Resolves when the key does not show up in the store.\n */\n removeItemAndWait(key: string): Promise<void>;\n}\n\n/**\n * A key-value data store that can be used to persist data that should be shared across Pepr controllers and capabilities.\n *\n * The API is similar to the [Storage API](https://developer.mozilla.org/docs/Web/API/Storage)\n */\n\nexport class Storage implements PeprStore {\n #store: DataStore = {};\n #send!: DataSender;\n #subscribers: Record<number, DataReceiver> = {};\n #subscriberId = 0;\n #readyHandlers: DataReceiver[] = [];\n\n registerSender = (send: DataSender) => {\n this.#send = send;\n };\n\n receive = (data: DataStore) => {\n Log.debug(data, `Pepr store data received`);\n this.#store = data || {};\n\n this.#onReady();\n\n // Notify all subscribers\n for (const idx in this.#subscribers) {\n // Send a unique clone of the store to each subscriber\n this.#subscribers[idx](clone(this.#store));\n }\n };\n\n getItem = (key: string) => {\n const result = this.#store[v2UnescapedStoreKey(key)] || null;\n if (result !== null && typeof result !== \"function\" && typeof result !== \"object\") {\n return result;\n }\n return null;\n };\n\n clear = () => {\n this.#dispatchUpdate(\n \"remove\",\n Object.keys(this.#store).map(key => pointer.escape(key)),\n );\n };\n\n removeItem = (key: string) => {\n this.#dispatchUpdate(\"remove\", [v2StoreKey(key)]);\n };\n\n setItem = (key: string, value: string) => {\n this.#dispatchUpdate(\"add\", [v2StoreKey(key)], value);\n };\n\n /**\n * Creates a promise and subscribes to the store, the promise resolves when\n * the key and value are seen in the store.\n *\n * @param key - The key to add into the store\n * @param value - The value of the key\n * @returns\n */\n setItemAndWait = (key: string, value: string) => {\n this.#dispatchUpdate(\"add\", [v2StoreKey(key)], value);\n\n return new Promise<void>((resolve, reject) => {\n const unsubscribe = this.subscribe(data => {\n if (data[`${v2UnescapedStoreKey(key)}`] === value) {\n unsubscribe();\n resolve();\n }\n });\n\n // If promise has not resolved before MAX_WAIT_TIME reject\n setTimeout(() => {\n unsubscribe();\n return reject();\n }, MAX_WAIT_TIME);\n });\n };\n\n /**\n * Creates a promise and subscribes to the store, the promise resolves when\n * the key is removed from the store.\n *\n * @param key - The key to add into the store\n * @returns\n */\n removeItemAndWait = (key: string) => {\n this.#dispatchUpdate(\"remove\", [v2StoreKey(key)]);\n return new Promise<void>((resolve, reject) => {\n const unsubscribe = this.subscribe(data => {\n if (!Object.hasOwn(data, `${v2UnescapedStoreKey(key)}`)) {\n unsubscribe();\n resolve();\n }\n });\n\n // If promise has not resolved before MAX_WAIT_TIME reject\n setTimeout(() => {\n unsubscribe();\n return reject();\n }, MAX_WAIT_TIME);\n });\n };\n\n subscribe = (subscriber: DataReceiver) => {\n const idx = this.#subscriberId++;\n this.#subscribers[idx] = subscriber;\n return () => this.unsubscribe(idx);\n };\n\n onReady = (callback: DataReceiver) => {\n this.#readyHandlers.push(callback);\n };\n\n /**\n * Remove a subscriber from the list of subscribers.\n * @param idx - The index of the subscriber to remove.\n */\n unsubscribe = (idx: number) => {\n delete this.#subscribers[idx];\n };\n\n #onReady = () => {\n // Notify all ready handlers with a clone of the store\n for (const handler of this.#readyHandlers) {\n handler(clone(this.#store));\n }\n\n // Make this a noop so that it can't be called again\n this.#onReady = () => {};\n };\n\n /**\n * Dispatch an update to the store and notify all subscribers.\n * @param op - The type of operation to perform.\n * @param keys - The keys to update.\n * @param [value] - The new value.\n */\n #dispatchUpdate = (op: DataOp, keys: string[], value?: string) => {\n this.#send(op, keys, value);\n };\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// SPDX-FileCopyrightText: 2023-Present The Pepr Authors\n\nimport { PeprStore } from \"./storage\";\n\ntype Unit = \"seconds\" | \"second\" | \"minute\" | \"minutes\" | \"hours\" | \"hour\";\n\nexport interface Schedule {\n /**\n * * The name of the store\n */\n name: string;\n /**\n * The value associated with a unit of time\n */\n every: number;\n /**\n * The unit of time\n */\n unit: Unit;\n /**\n * The code to run\n */\n run: () => void;\n /**\n * The start time of the schedule\n */\n startTime?: Date | undefined;\n\n /**\n * The number of times the schedule has run\n */\n completions?: number | undefined;\n /**\n * Tje intervalID to clear the interval\n */\n intervalID?: NodeJS.Timeout;\n}\n\nexport class OnSchedule implements Schedule {\n intervalId: NodeJS.Timeout | null = null;\n store: PeprStore | undefined;\n name!: string;\n completions?: number | undefined;\n every: number;\n unit: Unit;\n run!: () => void;\n startTime?: Date | undefined;\n duration: number | undefined;\n lastTimestamp: Date | undefined;\n\n constructor(schedule: Schedule) {\n this.name = schedule.name;\n this.run = schedule.run;\n this.every = schedule.every;\n this.unit = schedule.unit;\n this.startTime = schedule?.startTime;\n this.completions = schedule?.completions;\n }\n setStore(store: PeprStore) {\n this.store = store;\n this.startInterval();\n }\n startInterval() {\n this.checkStore();\n this.getDuration();\n this.setupInterval();\n }\n /**\n * Checks the store for this schedule and sets the values if it exists\n * @returns\n */\n checkStore() {\n const result = this.store && this.store.getItem(this.name);\n if (result) {\n const storedSchedule = JSON.parse(result);\n this.completions = storedSchedule?.completions;\n this.startTime = storedSchedule?.startTime;\n this.lastTimestamp = storedSchedule?.lastTimestamp;\n }\n }\n\n /**\n * Saves the schedule to the store\n * @returns\n */\n saveToStore() {\n const schedule = {\n completions: this.completions,\n startTime: this.startTime,\n lastTimestamp: new Date(),\n name: this.name,\n };\n this.store && this.store.setItem(this.name, JSON.stringify(schedule));\n }\n\n /**\n * Gets the durations in milliseconds\n */\n getDuration() {\n switch (this.unit) {\n case \"seconds\":\n if (this.every < 10) throw new Error(\"10 Seconds in the smallest interval allowed\");\n this.duration = 1000 * this.every;\n break;\n case \"minutes\":\n case \"minute\":\n this.duration = 1000 * 60 * this.every;\n break;\n case \"hours\":\n case \"hour\":\n this.duration = 1000 * 60 * 60 * this.every;\n break;\n default:\n throw new Error(\"Invalid time unit\");\n }\n }\n\n /**\n * Sets up the interval\n */\n setupInterval() {\n const now = new Date();\n let delay: number | undefined;\n\n if (this.lastTimestamp && this.startTime) {\n this.startTime = undefined;\n }\n\n if (this.startTime) {\n delay = this.startTime.getTime() - now.getTime();\n } else if (this.lastTimestamp && this.duration) {\n const lastTimestamp = new Date(this.lastTimestamp);\n delay = this.duration - (now.getTime() - lastTimestamp.getTime());\n }\n\n if (delay === undefined || delay <= 0) {\n this.start();\n } else {\n setTimeout(() => {\n this.start();\n }, delay);\n }\n }\n\n /**\n * Starts the interval\n */\n start() {\n this.intervalId = setInterval(() => {\n if (this.completions === 0) {\n this.stop();\n return;\n } else {\n this.run();\n\n if (this.completions && this.completions !== 0) {\n this.completions -= 1;\n }\n this.saveToStore();\n }\n }, this.duration);\n }\n\n /**\n * Stops the interval\n */\n stop() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n this.intervalId = null;\n }\n this.store && this.store.removeItem(this.name);\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["a", "import_kubernetes_fluent_client", "import_kubernetes_fluent_client", "import_ramda", "import_ramda", "promClient", "kind", "e", "jsonPatch", "import_ramda", "import_kubernetes_fluent_client", "import_ramda", "name", "express", "fs", "https", "namespace", "res", "import_kubernetes_fluent_client", "import_types", "import_kubernetes_fluent_client", "import_kubernetes_fluent_client", "containers", "import_ramda", "pointer", "pointer", "kind"]
7
7
  }
package/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "/dist",
14
14
  "/src"
15
15
  ],
16
- "version": "0.34.0",
16
+ "version": "0.34.1",
17
17
  "main": "dist/lib.js",
18
18
  "types": "dist/lib.d.ts",
19
19
  "scripts": {
@@ -49,7 +49,7 @@
49
49
  "ramda": "0.30.1"
50
50
  },
51
51
  "devDependencies": {
52
- "@commitlint/cli": "19.3.0",
52
+ "@commitlint/cli": "19.4.0",
53
53
  "@commitlint/config-conventional": "19.2.2",
54
54
  "@fast-check/jest": "^2.0.1",
55
55
  "@jest/globals": "29.7.0",
@@ -152,8 +152,6 @@ export class PeprControllerStore {
152
152
  fillCache(name, "add", [key.slice(offset)], data[key]);
153
153
  }
154
154
  }
155
-
156
- // await K8s(PeprStore, { namespace, name: this.#name }).Patch(payload);
157
155
  }
158
156
  await flushCache();
159
157
  this.#setupWatch();
@@ -2,7 +2,7 @@
2
2
  // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
3
 
4
4
  import { beforeEach, describe, expect, it, jest } from "@jest/globals";
5
- import { DataStore, Storage, v2StoreKey, stripV2Prefix } from "./storage";
5
+ import { DataStore, Storage, v2StoreKey, v2UnescapedStoreKey, stripV2Prefix } from "./storage";
6
6
  import fc from "fast-check";
7
7
 
8
8
  describe("stripV2Prefix", () => {
@@ -27,6 +27,18 @@ describe("v2StoreKey", () => {
27
27
  }
28
28
  });
29
29
  });
30
+
31
+ describe("v2UnescapedStoreKey", () => {
32
+ it("should prefix the key with v2", () => {
33
+ const keys = ["https://google.com", "sso-client-http://bin", "key3", "key4", "key5"];
34
+ const results = ["v2-https://google.com", "v2-sso-client-http://bin", "v2-key3", "v2-key4", "v2-key5"];
35
+
36
+ for (let i = 0; i < keys.length; i++) {
37
+ const result = v2UnescapedStoreKey(keys[i]);
38
+ expect(result).toEqual(results[i]);
39
+ }
40
+ });
41
+ });
30
42
  describe("Storage with fuzzing and property-based tests", () => {
31
43
  let storage: Storage;
32
44
 
@@ -39,7 +51,7 @@ describe("Storage with fuzzing and property-based tests", () => {
39
51
  fc.assert(
40
52
  fc.property(fc.string(), fc.string(), (key, value) => {
41
53
  storage.setItem(key, value);
42
- const mockData: DataStore = { [v2StoreKey(key)]: value };
54
+ const mockData: DataStore = { [v2UnescapedStoreKey(key)]: value };
43
55
  storage.receive(mockData);
44
56
  if (value === "") {
45
57
  expect(storage.getItem(key)).toBeNull();
@@ -75,7 +87,7 @@ describe("Storage with fuzzing and property-based tests", () => {
75
87
  fc.assert(
76
88
  fc.property(fc.string(), fc.string(), (key, value) => {
77
89
  storage.setItem(key, value);
78
- const mockData: DataStore = { [v2StoreKey(key)]: value };
90
+ const mockData: DataStore = { [v2UnescapedStoreKey(key)]: value };
79
91
  storage.receive(mockData);
80
92
  if (value === "") {
81
93
  expect(storage.getItem(key)).toBeNull();
@@ -100,19 +112,20 @@ describe("Storage", () => {
100
112
  const key = "key1";
101
113
  storage.setItem(key, "value1");
102
114
 
103
- expect(mockSender).toHaveBeenCalledWith("add", [v2StoreKey(key)], "value1");
115
+ expect(mockSender).toHaveBeenCalledWith("add", [v2UnescapedStoreKey(key)], "value1");
104
116
  });
105
117
 
106
118
  it("should set an item and wait", () => {
107
119
  const mockSender = jest.fn();
108
120
  storage.registerSender(mockSender);
109
121
  jest.useFakeTimers();
110
- const key = "key1";
122
+ const keys = ["key1", "https://google.com", "sso-client-http://bin"];
111
123
 
112
- // asserting on sender invocation rather than Promise so no need to wait
113
- void storage.setItemAndWait(key, "value1");
124
+ keys.map(key => {
125
+ void storage.setItemAndWait(key, "value1");
126
+ expect(mockSender).toHaveBeenCalledWith("add", [v2StoreKey(key)], "value1");
127
+ });
114
128
 
115
- expect(mockSender).toHaveBeenCalledWith("add", [v2StoreKey(key)], "value1");
116
129
  jest.useRealTimers();
117
130
  });
118
131
 
@@ -142,10 +155,10 @@ describe("Storage", () => {
142
155
  const mockSender = jest.fn();
143
156
  storage.registerSender(mockSender);
144
157
 
145
- storage.receive({ key1: "value1", key2: "value2" });
158
+ storage.receive({ key1: "value1", key2: "value2", "sso-client-http://bin": "value3" });
146
159
  storage.clear();
147
160
 
148
- expect(mockSender).toHaveBeenCalledWith("remove", ["key1", "key2"], undefined);
161
+ expect(mockSender).toHaveBeenCalledWith("remove", ["key1", "key2", "sso-client-http:~1~1bin"], undefined);
149
162
  });
150
163
 
151
164
  it("should get an item", () => {
@@ -153,7 +166,7 @@ describe("Storage", () => {
153
166
  const results = ["value1", null, "!", "was-here", "3f7dd007-568f-4f4a-bbac-2e6bfff93860", "your-machine", " "];
154
167
 
155
168
  keys.map((key, i) => {
156
- const mockData: DataStore = { [v2StoreKey(key)]: results[i]! };
169
+ const mockData: DataStore = { [v2UnescapedStoreKey(key)]: results[i]! };
157
170
 
158
171
  storage.receive(mockData);
159
172
  const value = storage.getItem(keys[i]);
@@ -17,6 +17,10 @@ export function v2StoreKey(key: string) {
17
17
  return `${STORE_VERSION_PREFIX}-${pointer.escape(key)}`;
18
18
  }
19
19
 
20
+ export function v2UnescapedStoreKey(key: string) {
21
+ return `${STORE_VERSION_PREFIX}-${key}`;
22
+ }
23
+
20
24
  export function stripV2Prefix(key: string) {
21
25
  return key.replace(/^v2-/, "");
22
26
  }
@@ -95,7 +99,7 @@ export class Storage implements PeprStore {
95
99
  };
96
100
 
97
101
  getItem = (key: string) => {
98
- const result = this.#store[v2StoreKey(key)] || null;
102
+ const result = this.#store[v2UnescapedStoreKey(key)] || null;
99
103
  if (result !== null && typeof result !== "function" && typeof result !== "object") {
100
104
  return result;
101
105
  }
@@ -103,7 +107,10 @@ export class Storage implements PeprStore {
103
107
  };
104
108
 
105
109
  clear = () => {
106
- this.#dispatchUpdate("remove", Object.keys(this.#store));
110
+ this.#dispatchUpdate(
111
+ "remove",
112
+ Object.keys(this.#store).map(key => pointer.escape(key)),
113
+ );
107
114
  };
108
115
 
109
116
  removeItem = (key: string) => {
@@ -124,9 +131,10 @@ export class Storage implements PeprStore {
124
131
  */
125
132
  setItemAndWait = (key: string, value: string) => {
126
133
  this.#dispatchUpdate("add", [v2StoreKey(key)], value);
134
+
127
135
  return new Promise<void>((resolve, reject) => {
128
136
  const unsubscribe = this.subscribe(data => {
129
- if (data[`${v2StoreKey(key)}`] === value) {
137
+ if (data[`${v2UnescapedStoreKey(key)}`] === value) {
130
138
  unsubscribe();
131
139
  resolve();
132
140
  }
@@ -151,7 +159,7 @@ export class Storage implements PeprStore {
151
159
  this.#dispatchUpdate("remove", [v2StoreKey(key)]);
152
160
  return new Promise<void>((resolve, reject) => {
153
161
  const unsubscribe = this.subscribe(data => {
154
- if (!Object.hasOwn(data, `${v2StoreKey(key)}`)) {
162
+ if (!Object.hasOwn(data, `${v2UnescapedStoreKey(key)}`)) {
155
163
  unsubscribe();
156
164
  resolve();
157
165
  }
@@ -268,24 +268,39 @@ interface TheChuckNorrisJoke {
268
268
  }
269
269
 
270
270
  When(a.ConfigMap)
271
- .IsCreated()
271
+ .IsCreatedOrUpdated()
272
272
  .WithLabel("chuck-norris")
273
- .Mutate(async change => {
273
+ .Mutate(cm => cm.SetLabel("got-jokes", "true"))
274
+ .Watch(async cm => {
275
+ const jokeURL = "https://icanhazdadjoke.com/";
274
276
  // Try/catch is not needed as a response object will always be returned
275
- const response = await fetch<TheChuckNorrisJoke>(
276
- "https://icanhazdadjoke.com/",
277
- {
278
- headers: {
279
- Accept: "application/json",
280
- },
277
+ const response = await fetch<TheChuckNorrisJoke>(jokeURL, {
278
+ headers: {
279
+ Accept: "application/json",
281
280
  },
282
- );
281
+ });
283
282
 
284
283
  // Instead, check the `response.ok` field
285
284
  if (response.ok) {
285
+ const { joke } = response.data;
286
+ // Add Joke to the Store
287
+ await Store.setItemAndWait(jokeURL, joke);
286
288
  // Add the Chuck Norris joke to the configmap
287
- change.Raw.data["chuck-says"] = response.data.joke;
288
- return;
289
+ try {
290
+ await K8s(kind.ConfigMap).Apply({
291
+ metadata: {
292
+ name: cm.metadata.name,
293
+ namespace: cm.metadata.namespace,
294
+ },
295
+ data: {
296
+ "chuck-says": Store.getItem(jokeURL),
297
+ },
298
+ });
299
+ } catch (error) {
300
+ Log.error(error, "Failed to apply ConfigMap using server-side apply.", {
301
+ cm,
302
+ });
303
+ }
289
304
  }
290
305
 
291
306
  // You can also assert on different HTTP response codes