diff --git a/docs/030_user-guide/120_customization.md b/docs/030_user-guide/120_customization.md index 71d5eb3a8..27f0013ce 100644 --- a/docs/030_user-guide/120_customization.md +++ b/docs/030_user-guide/120_customization.md @@ -41,18 +41,18 @@ The Watch configuration is a part of the Pepr module that allows you to watch fo ## Configuring Reconcile -The [Reconcile Action](./030_actions/030_reconcile.md) allows you to maintain ordering of resource updates processed by a Pepr controller. The Reconcile configuration can be customized by specific enviroment variables of the Watcher Deployment and can be set in the `package.json` or in the helm `values.yaml` file. +The [Reconcile Action](./030_actions/030_reconcile.md) allows you to maintain ordering of resource updates processed by a Pepr controller. The Reconcile configuration can be customized via enviroment variable on the Watcher Deployment, which can be set in the `package.json` or in the helm `values.yaml` file. | Field | Description | Example Values | |-|-|-| -| `PEPR_RECONCILE_STRATEGY` | How Pepr should order resource updates being Reconcile()'d. | default: `"singular"` | +| `PEPR_RECONCILE_STRATEGY` | How Pepr should order resource updates being Reconcile()'d. | default: `"kind"` | | Available Options || |-|-| -| `singular` | Pepr will keep a single queue of events across _all_ Reconcile()'d resources of a kind. -| `sharded` | Pepr will keep multiple queues of events, one for each resource _instance_ of a kind. - - +| `kind` | separate queues of events for Reconcile()'d resources of a kind | +| `kindNs` | separate queues of events for Reconcile()'d resources of a kind, within a namespace | +| `kindNsName` | separate queues of events for Reconcile()'d resources of a kind, within a namespace, per name | +| `global` | a single queue of events for all Reconcile()'d resources | ## Customizing with Helm diff --git a/src/lib/watch-processor.test.ts b/src/lib/watch-processor.test.ts index 998a6ffba..607c76239 100644 --- a/src/lib/watch-processor.test.ts +++ b/src/lib/watch-processor.test.ts @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023-Present The Pepr Authors -import { afterAll, beforeEach, beforeAll, describe, expect, it, jest } from "@jest/globals"; +import { afterAll, beforeEach, describe, expect, it, jest } from "@jest/globals"; import { GenericClass, K8s, KubernetesObject, kind } from "kubernetes-fluent-client"; import { K8sInit, WatchPhase } from "kubernetes-fluent-client/dist/fluent/types"; import { WatchCfg, WatchEvent, Watcher } from "kubernetes-fluent-client/dist/fluent/watch"; @@ -330,133 +330,57 @@ describe("logEvent function", () => { }); describe("queueKey", () => { - describe("PEPR_RECONCILE_STRATEGY=sharded", () => { - const original = process.env.PEPR_RECONCILE_STRATEGY; - - beforeAll(() => { - process.env.PEPR_RECONCILE_STRATEGY = "sharded"; - }); - afterAll(() => { - process.env.PEPR_RECONCILE_STRATEGY = original; - }); - - it("should return correct key for an object with 'kind/name/namespace'", () => { - const obj: KubernetesObject = { - kind: "Pod", - metadata: { - name: "my-pod", - namespace: "my-namespace", - }, - }; - - expect(queueKey(obj)).toBe("Pod/my-pod/my-namespace"); - }); - - it("should handle objects with missing namespace", () => { - const obj: KubernetesObject = { - kind: "Pod", - metadata: { - name: "my-pod", - }, - }; - - expect(queueKey(obj)).toBe("Pod/my-pod/cluster-scoped"); - }); - - it("should handle objects with missing name", () => { - const obj: KubernetesObject = { - kind: "Pod", - metadata: { - namespace: "my-namespace", - }, - }; - - expect(queueKey(obj)).toBe("Pod/Unnamed/my-namespace"); - }); - - it("should handle objects with missing metadata", () => { - const obj: KubernetesObject = { - kind: "Pod", - }; - - expect(queueKey(obj)).toBe("Pod/Unnamed/cluster-scoped"); - }); - - it("should handle objects with missing kind", () => { - const obj: KubernetesObject = { - metadata: { - name: "my-pod", - namespace: "my-namespace", - }, - }; - - expect(queueKey(obj)).toBe("UnknownKind/my-pod/my-namespace"); - }); - - it("should handle completely empty objects", () => { - const obj: KubernetesObject = {}; - - expect(queueKey(obj)).toBe("UnknownKind/Unnamed/cluster-scoped"); - }); + const withKindNsName = { kind: "Pod", metadata: { namespace: "my-ns", name: "my-name" } } as KubernetesObject; + const withKindNs = { kind: "Pod", metadata: { namespace: "my-ns" } } as KubernetesObject; + const withKindName = { kind: "Pod", metadata: { name: "my-name" } } as KubernetesObject; + const withNsName = { metadata: { namespace: "my-ns", name: "my-name" } } as KubernetesObject; + const withKind = { kind: "Pod" } as KubernetesObject; + const withNs = { metadata: { namespace: "my-ns" } } as KubernetesObject; + const withName = { metadata: { name: "my-name" } } as KubernetesObject; + const withNone = {} as KubernetesObject; + + const original = process.env.PEPR_RECONCILE_STRATEGY; + + it.each([ + ["kind", withKindNsName, "Pod"], + ["kind", withKindNs, "Pod"], + ["kind", withKindName, "Pod"], + ["kind", withNsName, "UnknownKind"], + ["kind", withKind, "Pod"], + ["kind", withNs, "UnknownKind"], + ["kind", withName, "UnknownKind"], + ["kind", withNone, "UnknownKind"], + ["kindNs", withKindNsName, "Pod/my-ns"], + ["kindNs", withKindNs, "Pod/my-ns"], + ["kindNs", withKindName, "Pod/cluster-scoped"], + ["kindNs", withNsName, "UnknownKind/my-ns"], + ["kindNs", withKind, "Pod/cluster-scoped"], + ["kindNs", withNs, "UnknownKind/my-ns"], + ["kindNs", withName, "UnknownKind/cluster-scoped"], + ["kindNs", withNone, "UnknownKind/cluster-scoped"], + ["kindNsName", withKindNsName, "Pod/my-ns/my-name"], + ["kindNsName", withKindNs, "Pod/my-ns/Unnamed"], + ["kindNsName", withKindName, "Pod/cluster-scoped/my-name"], + ["kindNsName", withNsName, "UnknownKind/my-ns/my-name"], + ["kindNsName", withKind, "Pod/cluster-scoped/Unnamed"], + ["kindNsName", withNs, "UnknownKind/my-ns/Unnamed"], + ["kindNsName", withName, "UnknownKind/cluster-scoped/my-name"], + ["kindNsName", withNone, "UnknownKind/cluster-scoped/Unnamed"], + ["global", withKindNsName, "global"], + ["global", withKindNs, "global"], + ["global", withKindName, "global"], + ["global", withNsName, "global"], + ["global", withKind, "global"], + ["global", withNs, "global"], + ["global", withName, "global"], + ["global", withNone, "global"], + ])("PEPR_RECONCILE_STRATEGY='%s' over '%j' becomes '%s'", (strat, obj, key) => { + process.env.PEPR_RECONCILE_STRATEGY = strat; + expect(queueKey(obj)).toBe(key); }); - describe("PEPR_RECONCILE_STRATEGY=singular", () => { - const original = process.env.PEPR_RECONCILE_STRATEGY; - - beforeAll(() => { - process.env.PEPR_RECONCILE_STRATEGY = "singular"; - }); - afterAll(() => { - process.env.PEPR_RECONCILE_STRATEGY = original; - }); - - it("should return correct key for an object with 'kind/namespace'", () => { - const obj: KubernetesObject = { - kind: "Pod", - metadata: { - name: "my-pod", - namespace: "my-namespace", - }, - }; - - expect(queueKey(obj)).toBe("Pod/my-namespace"); - }); - - it("should handle objects with missing namespace", () => { - const obj: KubernetesObject = { - kind: "Pod", - metadata: { - name: "my-pod", - }, - }; - - expect(queueKey(obj)).toBe("Pod/cluster-scoped"); - }); - - it("should handle objects with missing metadata", () => { - const obj: KubernetesObject = { - kind: "Pod", - }; - - expect(queueKey(obj)).toBe("Pod/cluster-scoped"); - }); - - it("should handle objects with missing kind", () => { - const obj: KubernetesObject = { - metadata: { - name: "my-pod", - namespace: "my-namespace", - }, - }; - - expect(queueKey(obj)).toBe("UnknownKind/my-namespace"); - }); - - it("should handle completely empty objects", () => { - const obj: KubernetesObject = {}; - - expect(queueKey(obj)).toBe("UnknownKind/cluster-scoped"); - }); + afterAll(() => { + process.env.PEPR_RECONCILE_STRATEGY = original; }); }); diff --git a/src/lib/watch-processor.ts b/src/lib/watch-processor.ts index 1ed341383..dfec5ac29 100644 --- a/src/lib/watch-processor.ts +++ b/src/lib/watch-processor.ts @@ -19,8 +19,8 @@ const queues: Record> = {}; * @returns The key to a Queue in the list of queues */ export function queueKey(obj: KubernetesObject) { - const options = ["singular", "sharded"]; - const d3fault = "singular"; + const options = ["kind", "kindNs", "kindNsName", "global"]; + const d3fault = "kind"; let strat = process.env.PEPR_RECONCILE_STRATEGY || d3fault; strat = options.includes(strat) ? strat : d3fault; @@ -29,7 +29,13 @@ export function queueKey(obj: KubernetesObject) { const kind = obj.kind ?? "UnknownKind"; const name = obj.metadata?.name ?? "Unnamed"; - return strat === "singular" ? `${kind}/${ns}` : `${kind}/${name}/${ns}`; + const lookup: Record = { + kind: `${kind}`, + kindNs: `${kind}/${ns}`, + kindNsName: `${kind}/${ns}/${name}`, + global: "global", + }; + return lookup[strat]; } export function getOrCreateQueue(obj: KubernetesObject) {