From 86c45e6e3598e296e5290579afe2c8e2cc7952c9 Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Fri, 21 Mar 2025 12:01:55 -0400 Subject: [PATCH 01/27] feat: only allow creation of one `UDSPackage` per namespace --- .../crd/validators/package-validator.spec.ts | 24 +++++++++++++++++++ .../crd/validators/package-validator.ts | 17 +++++++++++++ .../reconcilers/package-reconciler.ts | 2 +- 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/pepr/operator/crd/validators/package-validator.spec.ts b/src/pepr/operator/crd/validators/package-validator.spec.ts index 462b968f22..02ac686dcf 100644 --- a/src/pepr/operator/crd/validators/package-validator.spec.ts +++ b/src/pepr/operator/crd/validators/package-validator.spec.ts @@ -97,6 +97,30 @@ describe("Test validation of Exemption CRs", () => { expect(mockReq.Deny).toHaveBeenCalledTimes(1); }); + it("allows one package per namespace", async () => { + const mockReqValidPkg = makeMockReq({}, [], [{}], [{}], [{}]); + await validator(mockReqValidPkg); + const mockReqInvalidPkg = makeMockReq({ metadata: { name: "should-be-denied" } }, [], [], [], []); + await validator(mockReqInvalidPkg); + expect(mockReqInvalidPkg.Deny).toHaveBeenCalledTimes(1); + }); + + it("allows packages to be created in unique namespaces", async () => { + const mockReq = makeMockReq({}, [], [{}], [{}], [{}]); + await validator(mockReq); + const mockReqNewPkg = makeMockReq({ metadata: { namespace: "foo", name: "should-be-approved" } }, [], [], [], []); + await validator(mockReqNewPkg); + expect(mockReqNewPkg.Approve).toHaveBeenCalledTimes(1); + }); + + it("allows existing packages to be updated", async () => { + const mockReqValidPkg = makeMockReq({}, [{}], [{}], [{}], [{}]); + await validator(mockReqValidPkg); + const mockReqValidPkgUpdate = makeMockReq({ spec: { network: {} } }, [], [], [], []); + await validator(mockReqValidPkgUpdate); + expect(mockReqValidPkgUpdate.Approve).toHaveBeenCalledTimes(1); + }); + it("denies advancedHTTP when used with passthrough Gateways", async () => { const mockReq = makeMockReq( {}, diff --git a/src/pepr/operator/crd/validators/package-validator.ts b/src/pepr/operator/crd/validators/package-validator.ts index 33860ec0d1..048420799b 100644 --- a/src/pepr/operator/crd/validators/package-validator.ts +++ b/src/pepr/operator/crd/validators/package-validator.ts @@ -5,6 +5,7 @@ import { PeprValidateRequest } from "pepr"; +import { K8s } from "pepr"; import { Gateway, Protocol, UDSPackage } from ".."; import { generateVSName } from "../../controllers/istio/virtual-service"; import { generateMonitorName } from "../../controllers/monitoring/common"; @@ -24,6 +25,22 @@ export async function validator(req: PeprValidateRequest) { if (invalidNamespaces.includes(ns)) { return req.Deny("invalid namespace"); } + + // Check if a package already exists in the target namespace + const existingPackage = await K8s(UDSPackage) + .InNamespace(ns) + .Get() + .then(packages => + packages.items.find( + existingPkg => + existingPkg.metadata?.name !== pkg.metadata?.name, + ), + ); + if (existingPackage) { + return req.Deny( + `A package with the name "${existingPackage.metadata?.name}" already exists in the namespace "${ns}". Only one package can exist in a namespace.`, + ); + } const exposeList = pkg.spec?.network?.expose ?? []; diff --git a/src/pepr/operator/reconcilers/package-reconciler.ts b/src/pepr/operator/reconcilers/package-reconciler.ts index d8ce9e26d0..7f50a70900 100644 --- a/src/pepr/operator/reconcilers/package-reconciler.ts +++ b/src/pepr/operator/reconcilers/package-reconciler.ts @@ -26,7 +26,7 @@ const log = setupLogger(Component.OPERATOR_RECONCILERS); /** * The reconciler is called from the queue and is responsible for reconciling the state of the package - * with the cluster. This includes creating the namespace, network policies and virtual services. + * with the cluster. This includes creating the network policies, virtual services, sso, and monitoring config. * * @param pkg the package to reconcile */ From 8aab60b4d2b33e3cb410f8e4a75191cd2448b18d Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Fri, 21 Mar 2025 12:16:55 -0400 Subject: [PATCH 02/27] lint fix: pepr format --- .../crd/validators/package-validator.spec.ts | 18 +++++++++++++++--- .../crd/validators/package-validator.ts | 7 ++----- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/pepr/operator/crd/validators/package-validator.spec.ts b/src/pepr/operator/crd/validators/package-validator.spec.ts index 02ac686dcf..7b6c73e810 100644 --- a/src/pepr/operator/crd/validators/package-validator.spec.ts +++ b/src/pepr/operator/crd/validators/package-validator.spec.ts @@ -100,7 +100,13 @@ describe("Test validation of Exemption CRs", () => { it("allows one package per namespace", async () => { const mockReqValidPkg = makeMockReq({}, [], [{}], [{}], [{}]); await validator(mockReqValidPkg); - const mockReqInvalidPkg = makeMockReq({ metadata: { name: "should-be-denied" } }, [], [], [], []); + const mockReqInvalidPkg = makeMockReq( + { metadata: { name: "should-be-denied" } }, + [], + [], + [], + [], + ); await validator(mockReqInvalidPkg); expect(mockReqInvalidPkg.Deny).toHaveBeenCalledTimes(1); }); @@ -108,11 +114,17 @@ describe("Test validation of Exemption CRs", () => { it("allows packages to be created in unique namespaces", async () => { const mockReq = makeMockReq({}, [], [{}], [{}], [{}]); await validator(mockReq); - const mockReqNewPkg = makeMockReq({ metadata: { namespace: "foo", name: "should-be-approved" } }, [], [], [], []); + const mockReqNewPkg = makeMockReq( + { metadata: { namespace: "foo", name: "should-be-approved" } }, + [], + [], + [], + [], + ); await validator(mockReqNewPkg); expect(mockReqNewPkg.Approve).toHaveBeenCalledTimes(1); }); - + it("allows existing packages to be updated", async () => { const mockReqValidPkg = makeMockReq({}, [{}], [{}], [{}], [{}]); await validator(mockReqValidPkg); diff --git a/src/pepr/operator/crd/validators/package-validator.ts b/src/pepr/operator/crd/validators/package-validator.ts index 048420799b..fd566d568f 100644 --- a/src/pepr/operator/crd/validators/package-validator.ts +++ b/src/pepr/operator/crd/validators/package-validator.ts @@ -25,16 +25,13 @@ export async function validator(req: PeprValidateRequest) { if (invalidNamespaces.includes(ns)) { return req.Deny("invalid namespace"); } - + // Check if a package already exists in the target namespace const existingPackage = await K8s(UDSPackage) .InNamespace(ns) .Get() .then(packages => - packages.items.find( - existingPkg => - existingPkg.metadata?.name !== pkg.metadata?.name, - ), + packages.items.find(existingPkg => existingPkg.metadata?.name !== pkg.metadata?.name), ); if (existingPackage) { return req.Deny( From 72a762738e2fadd69704ef2a310a51b11b25592d Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Mon, 31 Mar 2025 14:15:08 -0400 Subject: [PATCH 03/27] wip: package watch --- src/pepr/logger.ts | 1 + .../controllers/exemptions/exemption-store.ts | 2 +- .../controllers/packages/package-store.ts | 61 +++++++++++++++++++ .../operator/controllers/packages/packages.ts | 33 ++++++++++ src/pepr/operator/reconcilers/index.ts | 17 ++++++ 5 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 src/pepr/operator/controllers/packages/package-store.ts create mode 100644 src/pepr/operator/controllers/packages/packages.ts diff --git a/src/pepr/logger.ts b/src/pepr/logger.ts index f881c0cdd7..ca51f0be04 100644 --- a/src/pepr/logger.ts +++ b/src/pepr/logger.ts @@ -17,6 +17,7 @@ export enum Component { OPERATOR_AUTHSERVICE = "operator.authservice", OPERATOR_MONITORING = "operator.monitoring", OPERATOR_NETWORK = "operator.network", + OPERATOR_PACKAGES = "operator.packages", OPERATOR_GENERATORS = "operator.generators", OPERATOR_CRD = "operator.crd", OPERATOR_RECONCILERS = "operator.reconcilers", diff --git a/src/pepr/operator/controllers/exemptions/exemption-store.ts b/src/pepr/operator/controllers/exemptions/exemption-store.ts index f32dcefaca..2265cb4382 100644 --- a/src/pepr/operator/controllers/exemptions/exemption-store.ts +++ b/src/pepr/operator/controllers/exemptions/exemption-store.ts @@ -46,7 +46,7 @@ function add(exemption: UDSExemption, logger: boolean = true) { // Remove any existing exemption for this owner, in case of WatchPhase.Modified remove(exemption); const owner = exemption.metadata?.uid || ""; - policyOwnerMap.set(owner, exemption); + policyOwnerMap.set( , exemption); for (const e of exemption.spec?.exemptions ?? []) { const policies = e.policies ?? []; diff --git a/src/pepr/operator/controllers/packages/package-store.ts b/src/pepr/operator/controllers/packages/package-store.ts new file mode 100644 index 0000000000..ba7339b141 --- /dev/null +++ b/src/pepr/operator/controllers/packages/package-store.ts @@ -0,0 +1,61 @@ +/** + * Copyright 2025 Defense Unicorns + * SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial + */ + +/** + * A collection of functions related to watching UDSPackages + * Manages an in-memory map of UDSPackage resources + * Used in Pepr Validating Webhook Pods when vetting UDS Package resources for admission +*/ +import { Component, setupLogger } from "../../../logger"; +import { UDSPackage } from "../../crd"; + +const log = setupLogger(Component.OPERATOR_PACKAGES); + +export type PackageNamespaceMap = Map; +let packageNamespaceMap: PackageNamespaceMap; + +/** + * Initializes an in-memory map of UDSPackages. + * + * @returns A new Map object to store UDSPackages. + */ +function init(): void { + packageNamespaceMap = new Map(); +} + +/** + * Adds a package to the package namespace map. + * + * @param {UDSPackage} pkg - The package to be added. It should contain metadata with a namespace. + * @param {boolean} [logger=true] - Optional flag to enable logging. Defaults to true. + * + * This function retrieves the namespace from the package metadata and adds the package + * to the packageNamespaceMap. If the namespace is not present, it defaults to an empty string. + */ +function add(pkg: UDSPackage, logger: boolean = true) { + const namespace = pkg.metadata?.namespace || "" + packageNamespaceMap.set(namespace, pkg) +} + +/** + * Removes a package from the package namespace map. + * + * @param {UDSPackage} pkg - The package to be removed. It should contain metadata with a namespace. + * @param {boolean} [logger=true] - Optional flag to enable logging. Defaults to true. + * + * This function retrieves the namespace from the package metadata and deletes it from the + * packageNamespaceMap. If the namespace is not present, it defaults to an empty string. + */ +function remove(pkg: UDSPackage, logger: boolean = true) { + const namespace = pkg.metadata?.namespace || "" + packageNamespaceMap.delete(namespace) +} + +export const PackageStore = { + init, + add, + remove + }; + \ No newline at end of file diff --git a/src/pepr/operator/controllers/packages/packages.ts b/src/pepr/operator/controllers/packages/packages.ts new file mode 100644 index 0000000000..4f8ace6a2e --- /dev/null +++ b/src/pepr/operator/controllers/packages/packages.ts @@ -0,0 +1,33 @@ +/** + * Copyright 2025 Defense Unicorns + * SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial + */ + +import { WatchPhase } from "kubernetes-fluent-client/dist/fluent/types"; +import { UDSPackage } from "../../crd"; +import { PackageStore } from "./package-store"; + +/** + * Processes exemptions based on the watch phase. + * This function determines how to handle a UDSPackage based on whether it has been added, modified, or deleted + * during a watch operation. It uses a switch statement to execute the appropriate logic + * based on the provided WatchPhase. + * + * @param {UDSPackage} pkg - The UDSPackage to process. UDSPackage is assumed to be a defined type/interface + * representing a package of exemptions. + * @param {WatchPhase} phase - The phase of the watch operation (Added, Modified, or Deleted). + * WatchPhase is assumed to be an enum or a set of constant values. + * @returns {void} This function does not return a value; it performs actions based on the input. + */ +export function processPackages(pkg: UDSPackage, phase: WatchPhase) { + switch (phase) { + case WatchPhase.Added: + case WatchPhase.Modified: + PackageStore.add(pkg); + break; + + case WatchPhase.Deleted: + PackageStore.remove(pkg); + break; + } +} diff --git a/src/pepr/operator/reconcilers/index.ts b/src/pepr/operator/reconcilers/index.ts index b7f7e132e4..c191d20679 100644 --- a/src/pepr/operator/reconcilers/index.ts +++ b/src/pepr/operator/reconcilers/index.ts @@ -6,6 +6,8 @@ import { K8s, kind } from "pepr"; import { Component, setupLogger } from "../../logger"; +import { PackageStore } from "../controllers/packages/package-store"; +import { processPackages } from "../controllers/packages/packages"; import { Phase, PkgStatus, UDSPackage } from "../crd"; import { StatusObject as Status, StatusEnum } from "../crd/generated/package-v1alpha1"; @@ -177,3 +179,18 @@ export function getReadinessConditions(ready: boolean = true) { }, ]; } + +export async function startPackageWatch() { + PackageStore.init(); + // only run in admission controller or dev mode + if (process.env.PEPR_WATCH_MODE === "false" || process.env.PEPR_MODE === "dev") { + const watcher = K8s(UDSPackage).Watch(async (pkg, phase) => { + log.debug(`Processing package ${pkg.metadata?.name}, watch phase: ${phase}`); + + processPackages(pkg, phase); + }); + // This will run until the process is terminated or the watch is aborted + log.debug("Starting package watch..."); + await watcher.start(); + } +} From d45ed74e91037e9c96382c1199c163e723048975 Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Tue, 1 Apr 2025 10:49:24 -0400 Subject: [PATCH 04/27] feat: add watch for UDSPackages --- pepr.ts | 2 + .../controllers/exemptions/exemption-store.ts | 2 +- .../controllers/packages/package-store.ts | 46 +++++++++---------- .../operator/controllers/packages/packages.ts | 5 +- .../crd/validators/package-validator.ts | 19 ++++---- src/pepr/operator/reconcilers/index.ts | 12 ++--- 6 files changed, 39 insertions(+), 47 deletions(-) diff --git a/pepr.ts b/pepr.ts index 64646428cf..a0f5f71360 100644 --- a/pepr.ts +++ b/pepr.ts @@ -11,6 +11,7 @@ import { Component, setupLogger } from "./src/pepr/logger"; import { operator } from "./src/pepr/operator"; import { setupAuthserviceSecret } from "./src/pepr/operator/controllers/keycloak/authservice/config"; import { registerCRDs } from "./src/pepr/operator/crd/register"; +import { startPackageWatch } from "./src/pepr/operator/reconcilers"; import { patches } from "./src/pepr/patches"; import { policies, startExemptionWatch } from "./src/pepr/policies"; import { prometheus } from "./src/pepr/prometheus"; @@ -22,6 +23,7 @@ const log = setupLogger(Component.STARTUP); await registerCRDs(); // KFC watch for exemptions and update in-memory map await startExemptionWatch(); + await startPackageWatch(); await setupAuthserviceSecret(); new PeprModule(cfg, [ // UDS Core Operator diff --git a/src/pepr/operator/controllers/exemptions/exemption-store.ts b/src/pepr/operator/controllers/exemptions/exemption-store.ts index 2265cb4382..f32dcefaca 100644 --- a/src/pepr/operator/controllers/exemptions/exemption-store.ts +++ b/src/pepr/operator/controllers/exemptions/exemption-store.ts @@ -46,7 +46,7 @@ function add(exemption: UDSExemption, logger: boolean = true) { // Remove any existing exemption for this owner, in case of WatchPhase.Modified remove(exemption); const owner = exemption.metadata?.uid || ""; - policyOwnerMap.set( , exemption); + policyOwnerMap.set(owner, exemption); for (const e of exemption.spec?.exemptions ?? []) { const policies = e.policies ?? []; diff --git a/src/pepr/operator/controllers/packages/package-store.ts b/src/pepr/operator/controllers/packages/package-store.ts index ba7339b141..bba543ced8 100644 --- a/src/pepr/operator/controllers/packages/package-store.ts +++ b/src/pepr/operator/controllers/packages/package-store.ts @@ -4,39 +4,33 @@ */ /** - * A collection of functions related to watching UDSPackages - * Manages an in-memory map of UDSPackage resources - * Used in Pepr Validating Webhook Pods when vetting UDS Package resources for admission -*/ + * A collection of functions related to watching UDSPackages + * Manages an in-memory map of UDSPackage resources + * Used in Pepr Validating Webhook Pods when vetting UDS Package resources for admission + */ import { Component, setupLogger } from "../../../logger"; import { UDSPackage } from "../../crd"; const log = setupLogger(Component.OPERATOR_PACKAGES); export type PackageNamespaceMap = Map; -let packageNamespaceMap: PackageNamespaceMap; - -/** - * Initializes an in-memory map of UDSPackages. - * - * @returns A new Map object to store UDSPackages. - */ -function init(): void { - packageNamespaceMap = new Map(); -} +const packageNamespaceMap: PackageNamespaceMap = new Map(); /** * Adds a package to the package namespace map. * * @param {UDSPackage} pkg - The package to be added. It should contain metadata with a namespace. * @param {boolean} [logger=true] - Optional flag to enable logging. Defaults to true. - * + * * This function retrieves the namespace from the package metadata and adds the package * to the packageNamespaceMap. If the namespace is not present, it defaults to an empty string. */ function add(pkg: UDSPackage, logger: boolean = true) { - const namespace = pkg.metadata?.namespace || "" - packageNamespaceMap.set(namespace, pkg) + const namespace = pkg.metadata?.namespace || ""; + packageNamespaceMap.set(namespace, pkg); + if (logger) { + log.debug(`Added package: ${namespace}/${pkg.metadata?.name} to package map`); + } } /** @@ -44,18 +38,20 @@ function add(pkg: UDSPackage, logger: boolean = true) { * * @param {UDSPackage} pkg - The package to be removed. It should contain metadata with a namespace. * @param {boolean} [logger=true] - Optional flag to enable logging. Defaults to true. - * + * * This function retrieves the namespace from the package metadata and deletes it from the * packageNamespaceMap. If the namespace is not present, it defaults to an empty string. */ function remove(pkg: UDSPackage, logger: boolean = true) { - const namespace = pkg.metadata?.namespace || "" - packageNamespaceMap.delete(namespace) + const namespace = pkg.metadata?.namespace || ""; + packageNamespaceMap.delete(namespace); + if (logger) { + log.debug(`Removed package: ${namespace}/${pkg.metadata?.name} from package map`); + } } export const PackageStore = { - init, - add, - remove - }; - \ No newline at end of file + add, + remove, + packageNamespaceMap, +}; diff --git a/src/pepr/operator/controllers/packages/packages.ts b/src/pepr/operator/controllers/packages/packages.ts index 4f8ace6a2e..f9c630194e 100644 --- a/src/pepr/operator/controllers/packages/packages.ts +++ b/src/pepr/operator/controllers/packages/packages.ts @@ -22,12 +22,11 @@ import { PackageStore } from "./package-store"; export function processPackages(pkg: UDSPackage, phase: WatchPhase) { switch (phase) { case WatchPhase.Added: - case WatchPhase.Modified: - PackageStore.add(pkg); + PackageStore.add(pkg); break; case WatchPhase.Deleted: - PackageStore.remove(pkg); + PackageStore.remove(pkg); break; } } diff --git a/src/pepr/operator/crd/validators/package-validator.ts b/src/pepr/operator/crd/validators/package-validator.ts index fd566d568f..f1a22c738b 100644 --- a/src/pepr/operator/crd/validators/package-validator.ts +++ b/src/pepr/operator/crd/validators/package-validator.ts @@ -5,11 +5,11 @@ import { PeprValidateRequest } from "pepr"; -import { K8s } from "pepr"; import { Gateway, Protocol, UDSPackage } from ".."; import { generateVSName } from "../../controllers/istio/virtual-service"; import { generateMonitorName } from "../../controllers/monitoring/common"; import { generateName } from "../../controllers/network/generate"; +import { PackageStore } from "../../controllers/packages/package-store"; import { sanitizeResourceName } from "../../controllers/utils"; import { Kind } from "../../crd/generated/package-v1alpha1"; import { migrate } from "../migrate"; @@ -27,16 +27,13 @@ export async function validator(req: PeprValidateRequest) { } // Check if a package already exists in the target namespace - const existingPackage = await K8s(UDSPackage) - .InNamespace(ns) - .Get() - .then(packages => - packages.items.find(existingPkg => existingPkg.metadata?.name !== pkg.metadata?.name), - ); - if (existingPackage) { - return req.Deny( - `A package with the name "${existingPackage.metadata?.name}" already exists in the namespace "${ns}". Only one package can exist in a namespace.`, - ); + for (const key of PackageStore.packageNamespaceMap.keys()) { + const existingPkgName = PackageStore.packageNamespaceMap.get(ns)?.metadata?.name ?? null + if (ns === key && existingPkgName !== pkgName ) { + return req.Deny( + `A package with the name "${existingPkgName}" already exists in the namespace "${ns}". Only one package can exist in a namespace.`, + ); + } } const exposeList = pkg.spec?.network?.expose ?? []; diff --git a/src/pepr/operator/reconcilers/index.ts b/src/pepr/operator/reconcilers/index.ts index c191d20679..63c3b4520d 100644 --- a/src/pepr/operator/reconcilers/index.ts +++ b/src/pepr/operator/reconcilers/index.ts @@ -6,7 +6,6 @@ import { K8s, kind } from "pepr"; import { Component, setupLogger } from "../../logger"; -import { PackageStore } from "../controllers/packages/package-store"; import { processPackages } from "../controllers/packages/packages"; import { Phase, PkgStatus, UDSPackage } from "../crd"; import { StatusObject as Status, StatusEnum } from "../crd/generated/package-v1alpha1"; @@ -181,14 +180,13 @@ export function getReadinessConditions(ready: boolean = true) { } export async function startPackageWatch() { - PackageStore.init(); // only run in admission controller or dev mode if (process.env.PEPR_WATCH_MODE === "false" || process.env.PEPR_MODE === "dev") { - const watcher = K8s(UDSPackage).Watch(async (pkg, phase) => { - log.debug(`Processing package ${pkg.metadata?.name}, watch phase: ${phase}`); - - processPackages(pkg, phase); - }); + const watcher = K8s(UDSPackage).Watch(async (pkg, phase) => { + log.debug(`Processing package ${pkg.metadata?.name}, watch phase: ${phase}`); + + processPackages(pkg, phase); + }); // This will run until the process is terminated or the watch is aborted log.debug("Starting package watch..."); await watcher.start(); From 81901264c7b8450b663ac2c6155803e04b61c27c Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Wed, 2 Apr 2025 08:57:00 -0400 Subject: [PATCH 05/27] adding `PackageStore.init`, adding `hasKey` and `getPkgName` functions, adding/updating tests --- .../packages/package-store.spec.ts | 59 +++++++++++++++++ .../controllers/packages/package-store.ts | 57 ++++++++++++++-- .../controllers/packages/packages.spec.ts | 65 +++++++++++++++++++ .../operator/controllers/packages/packages.ts | 4 +- .../crd/validators/package-validator.spec.ts | 3 + .../crd/validators/package-validator.ts | 7 +- src/pepr/operator/reconcilers/index.ts | 2 + 7 files changed, 187 insertions(+), 10 deletions(-) create mode 100644 src/pepr/operator/controllers/packages/package-store.spec.ts create mode 100644 src/pepr/operator/controllers/packages/packages.spec.ts diff --git a/src/pepr/operator/controllers/packages/package-store.spec.ts b/src/pepr/operator/controllers/packages/package-store.spec.ts new file mode 100644 index 0000000000..1939d62838 --- /dev/null +++ b/src/pepr/operator/controllers/packages/package-store.spec.ts @@ -0,0 +1,59 @@ +//should add packages +//should remove packages +//should add packages from different namespaces +import { describe, expect, it } from "@jest/globals"; +import { PeprValidateRequest } from "pepr"; +import { UDSPackage } from "../../crd"; +import { PackageStore } from "./package-store"; +PackageStore.init(); +//const packageNamespaceMap: PackageNamespaceMap = new Map(); + +const makeMockReq = (pkg: Partial) => { + const defaultPkg: UDSPackage = { + metadata: { + namespace: "application-system", + name: "application", + }, + spec: { + network: { + expose: [], + allow: [], + }, + sso: [], + monitor: [], + }, + }; + return { + Raw: { ...defaultPkg, ...pkg }, + } as unknown as PeprValidateRequest; +}; + +describe("Package Store", () => { + + it("Should add a package", async () => { + const mockReq = makeMockReq({}); + const ns = mockReq.Raw.metadata?.namespace || "" + PackageStore.add(mockReq.Raw); + expect(PackageStore.hasKey(ns)).toEqual(true); + }); + + it("Should add multiple unique packages", async () => { + const mockReq = makeMockReq({}); + const ns = mockReq.Raw.metadata?.namespace || ""; + const mockReqNewPkg = makeMockReq({ metadata: { namespace: "test", name: "other-package" } }); + const nsNewPkg = mockReqNewPkg.Raw.metadata?.namespace || ""; + PackageStore.add(mockReqNewPkg.Raw); + let pkgsExist = false + if (PackageStore.hasKey(ns) && PackageStore.hasKey(nsNewPkg)) { + pkgsExist = true + } + expect(pkgsExist).toEqual(true); + }); + + it("Should remove a package", async () => { + const mockReq = makeMockReq({}); + const ns = mockReq.Raw.metadata?.namespace || ""; + PackageStore.remove(mockReq.Raw); + expect(PackageStore.hasKey(ns)).toEqual(false); + }); +}); diff --git a/src/pepr/operator/controllers/packages/package-store.ts b/src/pepr/operator/controllers/packages/package-store.ts index bba543ced8..0a96cd3e29 100644 --- a/src/pepr/operator/controllers/packages/package-store.ts +++ b/src/pepr/operator/controllers/packages/package-store.ts @@ -10,11 +10,20 @@ */ import { Component, setupLogger } from "../../../logger"; import { UDSPackage } from "../../crd"; - const log = setupLogger(Component.OPERATOR_PACKAGES); export type PackageNamespaceMap = Map; -const packageNamespaceMap: PackageNamespaceMap = new Map(); +let packageNamespaceMap: PackageNamespaceMap; + +/** + * Initializes the package namespace map. + * + * This function creates a new `Map` object and assigns it to the `packageNamespaceMap` variable. + * The `packageNamespaceMap` is used to store packages, using their namespace as the key. + */ +function init(): void { + packageNamespaceMap = new Map(); +} /** * Adds a package to the package namespace map. @@ -25,7 +34,7 @@ const packageNamespaceMap: PackageNamespaceMap = new Map(); * This function retrieves the namespace from the package metadata and adds the package * to the packageNamespaceMap. If the namespace is not present, it defaults to an empty string. */ -function add(pkg: UDSPackage, logger: boolean = true) { +function add(pkg: UDSPackage, logger: boolean = true): void { const namespace = pkg.metadata?.namespace || ""; packageNamespaceMap.set(namespace, pkg); if (logger) { @@ -42,7 +51,7 @@ function add(pkg: UDSPackage, logger: boolean = true) { * This function retrieves the namespace from the package metadata and deletes it from the * packageNamespaceMap. If the namespace is not present, it defaults to an empty string. */ -function remove(pkg: UDSPackage, logger: boolean = true) { +function remove(pkg: UDSPackage, logger: boolean = true): void { const namespace = pkg.metadata?.namespace || ""; packageNamespaceMap.delete(namespace); if (logger) { @@ -50,8 +59,46 @@ function remove(pkg: UDSPackage, logger: boolean = true) { } } +/** + * Checks if a given namespace exists within the package namespace map. + * + * This function determines whether a namespace has been previously registered + * in the `packageNamespaceMap`. It provides a way to verify the existence + * of a package in a given namespace. + * + * @param namespace The namespace to check for existence. + * @returns `true` if the namespace exists in the map; otherwise, `false`. + */ +function hasKey(namespace: string): boolean { + return packageNamespaceMap.has(namespace); +} + +/** + * Retrieves the package name associated with a given namespace. + * + * This function looks up the namespace in the `packageNamespaceMap` and, if found, + * returns the `name` property from the `metadata` of the associated package. + * If the namespace is not found or the metadata or name is missing, it returns null. + * + * @param namespace The namespace to look up in the `packageNamespaceMap`. + * @returns The package name associated with the namespace, or null if not found. + * + * @example + * // Assuming packageNamespaceMap contains { 'my-namespace': { metadata: { name: 'my-package' } } } + * const packageName = getPkgName('my-namespace'); // Returns 'my-package' + * + * @example + * // Assuming packageNamespaceMap does not contain 'unknown-namespace' + * const packageName = getPkgName('unknown-namespace'); // Returns null + */ +function getPkgName(namespace: string): string | null { + return packageNamespaceMap.get(namespace)?.metadata?.name || null; +} + export const PackageStore = { + init, add, + hasKey, + getPkgName, remove, - packageNamespaceMap, }; diff --git a/src/pepr/operator/controllers/packages/packages.spec.ts b/src/pepr/operator/controllers/packages/packages.spec.ts new file mode 100644 index 0000000000..69c5c6548c --- /dev/null +++ b/src/pepr/operator/controllers/packages/packages.spec.ts @@ -0,0 +1,65 @@ +import { WatchPhase } from "kubernetes-fluent-client/dist/fluent/types"; +import { PeprValidateRequest } from "pepr"; +import { UDSPackage } from "../../crd"; +import { PackageStore } from "./package-store"; +import { processPackages } from "./packages"; + +PackageStore.init(); + +const makeMockReq = (pkg: Partial) => { + const defaultPkg: UDSPackage = { + metadata: { + namespace: "application-system", + name: "application", + }, + spec: { + network: { + expose: [], + allow: [], + }, + sso: [], + monitor: [], + }, + }; + return { + Raw: { ...defaultPkg, ...pkg }, + } as unknown as PeprValidateRequest; +}; + +describe("Package Watch", () => { + + it("Should add a package", async () => { + const mockReq = makeMockReq({}); + const ns = mockReq.Raw.metadata?.namespace || ""; + processPackages(mockReq.Raw, WatchPhase.Added); + const addedPackage = PackageStore.hasKey(ns); + expect(addedPackage).toBe(true); + }); + + it("Should add multiple unique packages", async () => { + const mockReq = makeMockReq({}); + const ns = mockReq.Raw.metadata?.namespace || ""; + const mockReqNewPkg = makeMockReq({ metadata: { namespace: "test", name: "other-package" } }); + const nsNewPkg = mockReqNewPkg.Raw.metadata?.namespace || ""; + processPackages(mockReqNewPkg.Raw, WatchPhase.Added); + let pkgsExist = false + if (PackageStore.hasKey(ns) && PackageStore.hasKey(nsNewPkg)) { + pkgsExist = true + } + expect(pkgsExist).toBe(true); + }); + + it("Should remove packages", async () => { + const mockReq = makeMockReq({}); + const mockReqNewPkg = makeMockReq({ metadata: { namespace: "test", name: "other-package" } }); + const ns = mockReq.Raw.metadata?.namespace || ""; + const nsNewPkg = mockReqNewPkg.Raw.metadata?.namespace || ""; + processPackages(mockReq.Raw, WatchPhase.Deleted); + processPackages(mockReqNewPkg.Raw, WatchPhase.Deleted); + let pkgsExist = false + if (PackageStore.hasKey(ns) && PackageStore.hasKey(nsNewPkg)) { + pkgsExist = true + } + expect(pkgsExist).toBe(false); + }); +}); diff --git a/src/pepr/operator/controllers/packages/packages.ts b/src/pepr/operator/controllers/packages/packages.ts index f9c630194e..6f1e927ef8 100644 --- a/src/pepr/operator/controllers/packages/packages.ts +++ b/src/pepr/operator/controllers/packages/packages.ts @@ -9,13 +9,13 @@ import { PackageStore } from "./package-store"; /** * Processes exemptions based on the watch phase. - * This function determines how to handle a UDSPackage based on whether it has been added, modified, or deleted + * This function determines how to handle a UDSPackage based on whether it has been added or deleted * during a watch operation. It uses a switch statement to execute the appropriate logic * based on the provided WatchPhase. * * @param {UDSPackage} pkg - The UDSPackage to process. UDSPackage is assumed to be a defined type/interface * representing a package of exemptions. - * @param {WatchPhase} phase - The phase of the watch operation (Added, Modified, or Deleted). + * @param {WatchPhase} phase - The phase of the watch operation (Added or Deleted). * WatchPhase is assumed to be an enum or a set of constant values. * @returns {void} This function does not return a value; it performs actions based on the input. */ diff --git a/src/pepr/operator/crd/validators/package-validator.spec.ts b/src/pepr/operator/crd/validators/package-validator.spec.ts index 7b6c73e810..d1a7c61aa8 100644 --- a/src/pepr/operator/crd/validators/package-validator.spec.ts +++ b/src/pepr/operator/crd/validators/package-validator.spec.ts @@ -16,8 +16,11 @@ import { Sso, UDSPackage, } from ".."; +import { PackageStore } from "../../controllers/packages/package-store"; import { validator } from "./package-validator"; +PackageStore.init(); + const makeMockReq = ( pkg: Partial, exposeList: Partial[], diff --git a/src/pepr/operator/crd/validators/package-validator.ts b/src/pepr/operator/crd/validators/package-validator.ts index f1a22c738b..e2611513a5 100644 --- a/src/pepr/operator/crd/validators/package-validator.ts +++ b/src/pepr/operator/crd/validators/package-validator.ts @@ -27,9 +27,10 @@ export async function validator(req: PeprValidateRequest) { } // Check if a package already exists in the target namespace - for (const key of PackageStore.packageNamespaceMap.keys()) { - const existingPkgName = PackageStore.packageNamespaceMap.get(ns)?.metadata?.name ?? null - if (ns === key && existingPkgName !== pkgName ) { + if (PackageStore.hasKey(ns)) { + const existingPkgName = PackageStore.getPkgName(ns); + //Since this function is called on admission, we need to allow updating existing packages + if (existingPkgName !== pkgName) { return req.Deny( `A package with the name "${existingPkgName}" already exists in the namespace "${ns}". Only one package can exist in a namespace.`, ); diff --git a/src/pepr/operator/reconcilers/index.ts b/src/pepr/operator/reconcilers/index.ts index 63c3b4520d..5b4d00436c 100644 --- a/src/pepr/operator/reconcilers/index.ts +++ b/src/pepr/operator/reconcilers/index.ts @@ -6,6 +6,7 @@ import { K8s, kind } from "pepr"; import { Component, setupLogger } from "../../logger"; +import { PackageStore } from "../controllers/packages/package-store"; import { processPackages } from "../controllers/packages/packages"; import { Phase, PkgStatus, UDSPackage } from "../crd"; import { StatusObject as Status, StatusEnum } from "../crd/generated/package-v1alpha1"; @@ -180,6 +181,7 @@ export function getReadinessConditions(ready: boolean = true) { } export async function startPackageWatch() { + PackageStore.init(); // only run in admission controller or dev mode if (process.env.PEPR_WATCH_MODE === "false" || process.env.PEPR_MODE === "dev") { const watcher = K8s(UDSPackage).Watch(async (pkg, phase) => { From 28f1de66f4ab95cf292c408711121699d32e6c78 Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Wed, 2 Apr 2025 09:01:13 -0400 Subject: [PATCH 06/27] `npx pepr format` --- .../operator/controllers/packages/package-store.spec.ts | 7 +++---- src/pepr/operator/controllers/packages/packages.spec.ts | 9 ++++----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/pepr/operator/controllers/packages/package-store.spec.ts b/src/pepr/operator/controllers/packages/package-store.spec.ts index 1939d62838..e4c790a84b 100644 --- a/src/pepr/operator/controllers/packages/package-store.spec.ts +++ b/src/pepr/operator/controllers/packages/package-store.spec.ts @@ -29,10 +29,9 @@ const makeMockReq = (pkg: Partial) => { }; describe("Package Store", () => { - it("Should add a package", async () => { const mockReq = makeMockReq({}); - const ns = mockReq.Raw.metadata?.namespace || "" + const ns = mockReq.Raw.metadata?.namespace || ""; PackageStore.add(mockReq.Raw); expect(PackageStore.hasKey(ns)).toEqual(true); }); @@ -43,9 +42,9 @@ describe("Package Store", () => { const mockReqNewPkg = makeMockReq({ metadata: { namespace: "test", name: "other-package" } }); const nsNewPkg = mockReqNewPkg.Raw.metadata?.namespace || ""; PackageStore.add(mockReqNewPkg.Raw); - let pkgsExist = false + let pkgsExist = false; if (PackageStore.hasKey(ns) && PackageStore.hasKey(nsNewPkg)) { - pkgsExist = true + pkgsExist = true; } expect(pkgsExist).toEqual(true); }); diff --git a/src/pepr/operator/controllers/packages/packages.spec.ts b/src/pepr/operator/controllers/packages/packages.spec.ts index 69c5c6548c..8c492e6d66 100644 --- a/src/pepr/operator/controllers/packages/packages.spec.ts +++ b/src/pepr/operator/controllers/packages/packages.spec.ts @@ -27,7 +27,6 @@ const makeMockReq = (pkg: Partial) => { }; describe("Package Watch", () => { - it("Should add a package", async () => { const mockReq = makeMockReq({}); const ns = mockReq.Raw.metadata?.namespace || ""; @@ -42,9 +41,9 @@ describe("Package Watch", () => { const mockReqNewPkg = makeMockReq({ metadata: { namespace: "test", name: "other-package" } }); const nsNewPkg = mockReqNewPkg.Raw.metadata?.namespace || ""; processPackages(mockReqNewPkg.Raw, WatchPhase.Added); - let pkgsExist = false + let pkgsExist = false; if (PackageStore.hasKey(ns) && PackageStore.hasKey(nsNewPkg)) { - pkgsExist = true + pkgsExist = true; } expect(pkgsExist).toBe(true); }); @@ -56,9 +55,9 @@ describe("Package Watch", () => { const nsNewPkg = mockReqNewPkg.Raw.metadata?.namespace || ""; processPackages(mockReq.Raw, WatchPhase.Deleted); processPackages(mockReqNewPkg.Raw, WatchPhase.Deleted); - let pkgsExist = false + let pkgsExist = false; if (PackageStore.hasKey(ns) && PackageStore.hasKey(nsNewPkg)) { - pkgsExist = true + pkgsExist = true; } expect(pkgsExist).toBe(false); }); From 73f467ea12078f16348f4dee442d1f2bcafd1ac1 Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Thu, 3 Apr 2025 12:44:02 -0400 Subject: [PATCH 07/27] always validate packages with `deletionTimestamp` --- src/pepr/operator/crd/validators/package-validator.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pepr/operator/crd/validators/package-validator.ts b/src/pepr/operator/crd/validators/package-validator.ts index e2611513a5..7e59c90452 100644 --- a/src/pepr/operator/crd/validators/package-validator.ts +++ b/src/pepr/operator/crd/validators/package-validator.ts @@ -21,7 +21,13 @@ export async function validator(req: PeprValidateRequest) { const pkgName = pkg.metadata?.name ?? "_unknown_"; const ns = pkg.metadata?.namespace ?? "_unknown_"; - + + // Always permit packages that are being removed + const deletionTimestamp = pkg.metadata?.deletionTimestamp ?? null + if (deletionTimestamp) { + return req.Approve() + } + if (invalidNamespaces.includes(ns)) { return req.Deny("invalid namespace"); } From 8e9c997cd6b65db48e459926257308da778820f4 Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Thu, 3 Apr 2025 14:18:24 -0400 Subject: [PATCH 08/27] multi dimensional package map --- .../controllers/packages/package-store.ts | 34 ++++++++++++++++--- .../crd/validators/package-validator.ts | 8 ++--- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/pepr/operator/controllers/packages/package-store.ts b/src/pepr/operator/controllers/packages/package-store.ts index 0a96cd3e29..163ad814a0 100644 --- a/src/pepr/operator/controllers/packages/package-store.ts +++ b/src/pepr/operator/controllers/packages/package-store.ts @@ -12,7 +12,7 @@ import { Component, setupLogger } from "../../../logger"; import { UDSPackage } from "../../crd"; const log = setupLogger(Component.OPERATOR_PACKAGES); -export type PackageNamespaceMap = Map; +export type PackageNamespaceMap = Map; let packageNamespaceMap: PackageNamespaceMap; /** @@ -36,7 +36,15 @@ function init(): void { */ function add(pkg: UDSPackage, logger: boolean = true): void { const namespace = pkg.metadata?.namespace || ""; - packageNamespaceMap.set(namespace, pkg); + // Get existing packages if any and merge into the one being added + const existingValue = packageNamespaceMap.get(namespace); + if (existingValue) { + existingValue.push(pkg); + packageNamespaceMap.set(namespace, existingValue); + } else { + packageNamespaceMap.set(namespace, [pkg]); + } + if (logger) { log.debug(`Added package: ${namespace}/${pkg.metadata?.name} to package map`); } @@ -53,7 +61,18 @@ function add(pkg: UDSPackage, logger: boolean = true): void { */ function remove(pkg: UDSPackage, logger: boolean = true): void { const namespace = pkg.metadata?.namespace || ""; - packageNamespaceMap.delete(namespace); + const pkgToRemove = pkg.metadata?.name; + const items = packageNamespaceMap.get(namespace) || []; + if (items.length > 1) { + items.forEach((value, index) => { + if (value.metadata?.name === pkgToRemove) { + items.splice(index, 1); + } + }); + packageNamespaceMap.set(namespace, items); + } else { + packageNamespaceMap.delete(namespace); + } if (logger) { log.debug(`Removed package: ${namespace}/${pkg.metadata?.name} from package map`); } @@ -92,7 +111,14 @@ function hasKey(namespace: string): boolean { * const packageName = getPkgName('unknown-namespace'); // Returns null */ function getPkgName(namespace: string): string | null { - return packageNamespaceMap.get(namespace)?.metadata?.name || null; + const items = packageNamespaceMap.get(namespace) || []; + if (items.length > 0) { + // Always return the first package found + const foundPkg = items[0]; + return foundPkg.metadata?.name || null; + } else { + return null; + } } export const PackageStore = { diff --git a/src/pepr/operator/crd/validators/package-validator.ts b/src/pepr/operator/crd/validators/package-validator.ts index 7e59c90452..c912987c86 100644 --- a/src/pepr/operator/crd/validators/package-validator.ts +++ b/src/pepr/operator/crd/validators/package-validator.ts @@ -21,13 +21,13 @@ export async function validator(req: PeprValidateRequest) { const pkgName = pkg.metadata?.name ?? "_unknown_"; const ns = pkg.metadata?.namespace ?? "_unknown_"; - + // Always permit packages that are being removed - const deletionTimestamp = pkg.metadata?.deletionTimestamp ?? null + const deletionTimestamp = pkg.metadata?.deletionTimestamp ?? null; if (deletionTimestamp) { - return req.Approve() + return req.Approve(); } - + if (invalidNamespaces.includes(ns)) { return req.Deny("invalid namespace"); } From ce704d3ccca6a247f79e51cdab9c781d3c766b30 Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Fri, 4 Apr 2025 08:39:14 -0400 Subject: [PATCH 09/27] add license --- src/pepr/operator/controllers/packages/package-store.spec.ts | 5 +++++ src/pepr/operator/controllers/packages/packages.spec.ts | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/pepr/operator/controllers/packages/package-store.spec.ts b/src/pepr/operator/controllers/packages/package-store.spec.ts index e4c790a84b..1d9afb357b 100644 --- a/src/pepr/operator/controllers/packages/package-store.spec.ts +++ b/src/pepr/operator/controllers/packages/package-store.spec.ts @@ -1,3 +1,8 @@ +/** + * Copyright 2025 Defense Unicorns + * SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial + */ + //should add packages //should remove packages //should add packages from different namespaces diff --git a/src/pepr/operator/controllers/packages/packages.spec.ts b/src/pepr/operator/controllers/packages/packages.spec.ts index 8c492e6d66..32a6bb893d 100644 --- a/src/pepr/operator/controllers/packages/packages.spec.ts +++ b/src/pepr/operator/controllers/packages/packages.spec.ts @@ -1,3 +1,8 @@ +/** + * Copyright 2025 Defense Unicorns + * SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial + */ + import { WatchPhase } from "kubernetes-fluent-client/dist/fluent/types"; import { PeprValidateRequest } from "pepr"; import { UDSPackage } from "../../crd"; From 02c2fda0cd1b62a424d1d627e0e07f2b6a8de90e Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Fri, 4 Apr 2025 09:07:28 -0400 Subject: [PATCH 10/27] update comments --- .../operator/controllers/packages/package-store.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/pepr/operator/controllers/packages/package-store.ts b/src/pepr/operator/controllers/packages/package-store.ts index 163ad814a0..90242cefde 100644 --- a/src/pepr/operator/controllers/packages/package-store.ts +++ b/src/pepr/operator/controllers/packages/package-store.ts @@ -33,6 +33,9 @@ function init(): void { * * This function retrieves the namespace from the package metadata and adds the package * to the packageNamespaceMap. If the namespace is not present, it defaults to an empty string. + * For backwards compatability, the function checks if the namespace has an existing package. If it does, + * the packge to be added is appended to the existing list of Packages. If not, a new key is created + * in packageNamespaceMap for that namespace, containing a list of one element. */ function add(pkg: UDSPackage, logger: boolean = true): void { const namespace = pkg.metadata?.namespace || ""; @@ -58,11 +61,16 @@ function add(pkg: UDSPackage, logger: boolean = true): void { * * This function retrieves the namespace from the package metadata and deletes it from the * packageNamespaceMap. If the namespace is not present, it defaults to an empty string. + * For backwards compatability, we check if the namespace has more than one package. + * If that is the case, we update the value for that namespace in packageNamespaceMap. + * Otherwise, if there is only one package in the namespace, we delete the key. + * */ function remove(pkg: UDSPackage, logger: boolean = true): void { const namespace = pkg.metadata?.namespace || ""; const pkgToRemove = pkg.metadata?.name; const items = packageNamespaceMap.get(namespace) || []; + if (items.length > 1) { items.forEach((value, index) => { if (value.metadata?.name === pkgToRemove) { @@ -75,7 +83,7 @@ function remove(pkg: UDSPackage, logger: boolean = true): void { } if (logger) { log.debug(`Removed package: ${namespace}/${pkg.metadata?.name} from package map`); - } + } } /** @@ -103,11 +111,11 @@ function hasKey(namespace: string): boolean { * @returns The package name associated with the namespace, or null if not found. * * @example - * // Assuming packageNamespaceMap contains { 'my-namespace': { metadata: { name: 'my-package' } } } + * Assuming packageNamespaceMap contains { 'my-namespace': [{ metadata: { name: 'my-package' } }], [{ metadata: { name: 'other-package' } }]} * const packageName = getPkgName('my-namespace'); // Returns 'my-package' * * @example - * // Assuming packageNamespaceMap does not contain 'unknown-namespace' + * Assuming packageNamespaceMap does not contain 'unknown-namespace' * const packageName = getPkgName('unknown-namespace'); // Returns null */ function getPkgName(namespace: string): string | null { From b7cf5b8188d53b1a869889b7878eb82ad0ce4204 Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Fri, 4 Apr 2025 09:23:36 -0400 Subject: [PATCH 11/27] update `app-curl` test to use single pkg per ns --- src/test/app-curl.yaml | 25 ++++++++++++++++--------- test/jest/network.spec.ts | 16 ++++++++-------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/test/app-curl.yaml b/src/test/app-curl.yaml index 30a03edec1..7caa180856 100644 --- a/src/test/app-curl.yaml +++ b/src/test/app-curl.yaml @@ -6,13 +6,13 @@ kind: Namespace metadata: labels: uds: curl-testing-namespace - name: curl-ns-deny-all + name: curl-ns-deny-all-1 --- apiVersion: uds.dev/v1alpha1 kind: Package metadata: name: curl-pkg-deny-all-1 - namespace: curl-ns-deny-all + namespace: curl-ns-deny-all-1 spec: network: allow: [] @@ -22,10 +22,10 @@ apiVersion: v1 kind: Service metadata: name: curl-pkg-deny-all-1 - namespace: curl-ns-deny-all + namespace: curl-ns-deny-all-1 labels: name: curl-pkg-deny-all-1 - namespace: curl-ns-deny-all + namespace: curl-ns-deny-all-1 spec: ports: - name: port8080 @@ -38,7 +38,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: curl-pkg-deny-all-1 - namespace: curl-ns-deny-all + namespace: curl-ns-deny-all-1 spec: replicas: 1 selector: @@ -67,11 +67,18 @@ spec: ports: - containerPort: 8080 --- +apiVersion: v1 +kind: Namespace +metadata: + labels: + uds: curl-testing-namespace + name: curl-ns-deny-all-2 +--- apiVersion: uds.dev/v1alpha1 kind: Package metadata: name: curl-pkg-deny-all-2 - namespace: curl-ns-deny-all + namespace: curl-ns-deny-all-2 spec: network: allow: [] @@ -81,10 +88,10 @@ apiVersion: v1 kind: Service metadata: name: curl-pkg-deny-all-2 - namespace: curl-ns-deny-all + namespace: curl-ns-deny-all-2 labels: name: curl-pkg-deny-all-2 - namespace: curl-ns-deny-all + namespace: curl-ns-deny-all-2 spec: ports: - name: port8080 @@ -97,7 +104,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: curl-pkg-deny-all-2 - namespace: curl-ns-deny-all + namespace: curl-ns-deny-all-2 spec: replicas: 1 selector: diff --git a/test/jest/network.spec.ts b/test/jest/network.spec.ts index 5d2cd7d06f..93a78bcfc2 100644 --- a/test/jest/network.spec.ts +++ b/test/jest/network.spec.ts @@ -118,7 +118,7 @@ beforeAll(async () => { curlPodName6, curlPodName8, ] = await Promise.all([ - getPodName("curl-ns-deny-all", "app=curl-pkg-deny-all-1"), + getPodName("curl-ns-deny-all-1", "app=curl-pkg-deny-all-1"), getPodName("test-admin-app", "app=httpbin"), getPodName("curl-ns-remote-ns-1", "app=curl-pkg-remote-ns-egress"), getPodName("curl-ns-kube-api", "app=curl-pkg-kube-api"), @@ -126,7 +126,7 @@ beforeAll(async () => { }); describe("Network Policy Validation", () => { - const INTERNAL_CURL_COMMAND_1 = getCurlCommand("curl-pkg-deny-all-2", "curl-ns-deny-all"); + const INTERNAL_CURL_COMMAND_1 = getCurlCommand("curl-pkg-deny-all-2", "curl-ns-deny-all-2"); const INTERNAL_CURL_COMMAND_2 = getCurlCommand("curl-pkg-allow-all", "curl-ns-allow-all"); const INTERNAL_CURL_COMMAND_5 = getCurlCommand("curl-pkg-remote-ns-ingress", "curl-ns-remote-ns-2"); const INTERNAL_CURL_COMMAND_7 = getCurlCommand("curl-pkg-remote-cidr", "curl-ns-remote-cidr"); @@ -143,20 +143,20 @@ describe("Network Policy Validation", () => { test.concurrent("Denied Requests by Default and Incorrect Ports and Labels", async () => { // Default Deny when no Ingress or Egress defined or Exposed Endpoints - const denied_external_response = await execInPod("curl-ns-deny-all", curlPodName1, "curl-pkg-deny-all-1", CURL_GATEWAY); + const denied_external_response = await execInPod("curl-ns-deny-all-1", curlPodName1, "curl-pkg-deny-all-1", CURL_GATEWAY); expect(denied_external_response.stdout).toBe("000"); // Default deny when no Ingress or Egress for internal curl command - const denied_internal_response = await execInPod("curl-ns-deny-all", curlPodName1, "curl-pkg-deny-all-1", INTERNAL_CURL_COMMAND_1); + const denied_internal_response = await execInPod("curl-ns-deny-all-1", curlPodName1, "curl-pkg-deny-all-1", INTERNAL_CURL_COMMAND_1); expect(denied_internal_response.stdout).toBe("503"); // Default Deny for Google Curl when no Egress defined - const denied_google_response = await execInPod("curl-ns-deny-all", curlPodName1, "curl-pkg-deny-all-1", GOOGLE_CURL); + const denied_google_response = await execInPod("curl-ns-deny-all-1", curlPodName1, "curl-pkg-deny-all-1", GOOGLE_CURL); expect(denied_google_response.stdout).toBe("000"); // Default Deny for Blocked Port - const blocked_port_curl = getCurlCommand("curl-pkg-deny-all-2", "curl-ns-deny-all", 9999); - const denied_port_response = await execInPod("curl-ns-deny-all", curlPodName1, "curl-pkg-deny-all-1", blocked_port_curl); + const blocked_port_curl = getCurlCommand("curl-pkg-deny-all-2", "curl-ns-deny-all-2", 9999); + const denied_port_response = await execInPod("curl-ns-deny-all-1", curlPodName1, "curl-pkg-deny-all-1", blocked_port_curl); expect(denied_port_response.stdout).toBe("503"); }); @@ -252,7 +252,7 @@ describe("Network Policy Validation", () => { expect(denied_google_response.stdout).toBe("000"); // Default Deny for Blocked Port - const blocked_port_curl = getCurlCommand("curl-pkg-deny-all-2", "curl-ns-deny-all", 9999); + const blocked_port_curl = getCurlCommand("curl-pkg-deny-all-2", "curl-ns-deny-all-2", 9999); const denied_port_response = await execInPod("curl-ns-kube-api", curlPodName8, "curl-pkg-kube-api", blocked_port_curl); expect(denied_port_response.stdout).toBe("503"); }); From f3190985035cd395083f51eb44f1f1e036b0708b Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Fri, 4 Apr 2025 09:27:35 -0400 Subject: [PATCH 12/27] pepr format --- src/pepr/operator/controllers/packages/package-store.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pepr/operator/controllers/packages/package-store.ts b/src/pepr/operator/controllers/packages/package-store.ts index 90242cefde..e7cde1d1bf 100644 --- a/src/pepr/operator/controllers/packages/package-store.ts +++ b/src/pepr/operator/controllers/packages/package-store.ts @@ -34,7 +34,7 @@ function init(): void { * This function retrieves the namespace from the package metadata and adds the package * to the packageNamespaceMap. If the namespace is not present, it defaults to an empty string. * For backwards compatability, the function checks if the namespace has an existing package. If it does, - * the packge to be added is appended to the existing list of Packages. If not, a new key is created + * the packge to be added is appended to the existing list of Packages. If not, a new key is created * in packageNamespaceMap for that namespace, containing a list of one element. */ function add(pkg: UDSPackage, logger: boolean = true): void { @@ -64,7 +64,7 @@ function add(pkg: UDSPackage, logger: boolean = true): void { * For backwards compatability, we check if the namespace has more than one package. * If that is the case, we update the value for that namespace in packageNamespaceMap. * Otherwise, if there is only one package in the namespace, we delete the key. - * + * */ function remove(pkg: UDSPackage, logger: boolean = true): void { const namespace = pkg.metadata?.namespace || ""; @@ -83,7 +83,7 @@ function remove(pkg: UDSPackage, logger: boolean = true): void { } if (logger) { log.debug(`Removed package: ${namespace}/${pkg.metadata?.name} from package map`); - } + } } /** From 40d13ccc635234d28e9de8f5b6602d97996a2802 Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Fri, 4 Apr 2025 13:01:16 -0400 Subject: [PATCH 13/27] fix spelling mistakes --- src/pepr/operator/controllers/packages/package-store.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pepr/operator/controllers/packages/package-store.ts b/src/pepr/operator/controllers/packages/package-store.ts index e7cde1d1bf..9702a95e0b 100644 --- a/src/pepr/operator/controllers/packages/package-store.ts +++ b/src/pepr/operator/controllers/packages/package-store.ts @@ -33,8 +33,8 @@ function init(): void { * * This function retrieves the namespace from the package metadata and adds the package * to the packageNamespaceMap. If the namespace is not present, it defaults to an empty string. - * For backwards compatability, the function checks if the namespace has an existing package. If it does, - * the packge to be added is appended to the existing list of Packages. If not, a new key is created + * For backwards compatibility, the function checks if the namespace has an existing package. If it does, + * the package to be added is appended to the existing list of Packages. If not, a new key is created * in packageNamespaceMap for that namespace, containing a list of one element. */ function add(pkg: UDSPackage, logger: boolean = true): void { @@ -61,7 +61,7 @@ function add(pkg: UDSPackage, logger: boolean = true): void { * * This function retrieves the namespace from the package metadata and deletes it from the * packageNamespaceMap. If the namespace is not present, it defaults to an empty string. - * For backwards compatability, we check if the namespace has more than one package. + * For backwards compatibility, we check if the namespace has more than one package. * If that is the case, we update the value for that namespace in packageNamespaceMap. * Otherwise, if there is only one package in the namespace, we delete the key. * From 1d14f70df7715ec3b0cd7d706af87e14120612aa Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Fri, 4 Apr 2025 16:01:06 -0400 Subject: [PATCH 14/27] update --- src/test/tasks.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/tasks.yaml b/src/test/tasks.yaml index 68ea95e161..4239914ba8 100644 --- a/src/test/tasks.yaml +++ b/src/test/tasks.yaml @@ -167,7 +167,7 @@ tasks: cluster: kind: Pod name: app=curl-pkg-deny-all-1 - namespace: curl-ns-deny-all + namespace: curl-ns-deny-all-1 condition: Ready - description: "Wait for Deny All Package 2 to be Ready" @@ -175,7 +175,7 @@ tasks: cluster: kind: Pod name: app=curl-pkg-deny-all-2 - namespace: curl-ns-deny-all + namespace: curl-ns-deny-all-2 condition: Ready - description: "Wait for Allow All Package to be Ready" From c3923549cc7c98e9d37b6e80beb7dd8d233b0226 Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Mon, 7 Apr 2025 12:12:49 -0400 Subject: [PATCH 15/27] update docs and diagram --- docs/.images/diagrams/uds-core-operator-uds-package.svg | 2 +- docs/reference/configuration/UDS operator/package.md | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/.images/diagrams/uds-core-operator-uds-package.svg b/docs/.images/diagrams/uds-core-operator-uds-package.svg index 248290c2c3..24195e366c 100644 --- a/docs/.images/diagrams/uds-core-operator-uds-package.svg +++ b/docs/.images/diagrams/uds-core-operator-uds-package.svg @@ -1,4 +1,4 @@ -

UDS Package

Validate, Reconcile, and Finalize When Created. Updated, or Deleted

packageReconciler(UDSPackage)

Log Initial Processing Info
Yes
No
is Package reconciling
Log Skip and Return
Yes
No
Is Retry Attempt
Wait for Backoff
Migrate Request Package to latest version
Update Package State to "Pending"
Configure Namespace and Network Policies
Enable Istio Injection
Yes
No
SSO 
Clients 
Present 
Configure SSO and AuthService Clients
Generate Istio Resources (VirtualServices, ServiceEntries)
Generate Service and Pod monitors
Update Package Status to Ready
Yes
Is Identity 
Deployed
Log error, throw exception
No
Log Information for Removal
success
Remove SSO Clients
success
Remove AuthService Configuration

packageFinalizer(UDSPackage)

is Package removing
Log Skip and Return
Yes

is Package Ready or
 Failed
No
Update Status to Removing
Log Waiting and Return
Create Failure Event
Update Status to RemovalFailed
No
Yes
success
failure
Remove Istio from Namespace
Log Success and Remove Finalizer

validator(PeprValidateRequest)

Migrate Request Package to latest version
Extract Package Name and Namespace
No
Approve Request
Yes
No
For expose list:
- Valid advancedHTTP usage
 - Valid directResponse usage
- Unique names
No
Yes
is 
Namespace a 
system 
Namespace
No
Yes
For monitor list:
- Unique names
Yes
For network.allow list:
- Valid remoteGenerated usage
- Unique names
For sso list:
- Valid usage of serviceAccountsEnabled
- Valid usage of publicClient
- Valid usage
of
 standardFlowEnabled
- Unique client ID

No
Yes
Deny Request
\ No newline at end of file +

UDS Package

Validate, Reconcile, and Finalize When Created. Updated, or Deleted

packageReconciler(UDSPackage)

Log Initial Processing Info
Yes
No
is Package reconciling
Log Skip and Return
Yes
No
Is Retry Attempt
Wait for Backoff
Migrate Request Package to latest version
Update Package State to "Pending"
Configure Namespace and Network Policies
Enable Istio Injection
Yes
No
SSO 
Clients 
Present 
Configure SSO and AuthService Clients
Generate Istio Resources (VirtualServices, ServiceEntries)
Generate Service and Pod monitors
Update Package Status to Ready
Yes
Is Identity 
Deployed
Log error, throw exception
No
Log Information for Removal
success
Remove SSO Clients
success
Remove AuthService Configuration

packageFinalizer(UDSPackage)

is Package removing
Log Skip and Return
Yes

is Package Ready or
 Failed
No
Update Status to Removing
Log Waiting and Return
Create Failure Event
Update Status to RemovalFailed
No
Yes
success
failure
Remove Istio from Namespace
Log Success and Remove Finalizer

validator(PeprValidateRequest)

Migrate Request Package to latest version
Yes
No
Is only Package resource in Namespace
No
Approve Request
Yes
For expose list:
- Valid advancedHTTP usage
 - Valid directResponse usage
- Unique names
Yes
is 
Namespace a 
system 
Namespace
No
Yes
For monitor list:
- Unique names
Yes
For network.allow list:
- Valid remoteGenerated usage
- Unique names
For sso list:
- Valid usage of serviceAccountsEnabled
- Valid usage of publicClient
- Valid usage
of
 standardFlowEnabled
- Unique client ID

No
Yes
Deny Request
Extract Package Name and Namespace
No
Yes
No
Has deletion timestamp
No
\ No newline at end of file diff --git a/docs/reference/configuration/UDS operator/package.md b/docs/reference/configuration/UDS operator/package.md index a9a85e38ff..2b8512c603 100644 --- a/docs/reference/configuration/UDS operator/package.md +++ b/docs/reference/configuration/UDS operator/package.md @@ -5,7 +5,12 @@ title: UDS Package ![UDS Operator Package Flowchart](https://github.com/defenseunicorns/uds-core/blob/main/docs/.images/diagrams/uds-core-operator-uds-package.svg?raw=true) ## Package +:::note +Only one UDS Package Custom Resource can exist in a namespace. This pattern was chosen by design to better enable workload isolation and to reduce complexity. +::: +Namespaces are common boundary for isolating workloads in multi-tenant clusters. When defining a UDS Package resource, consider the implications for all workloads that exist in the target namespace. If the UDS Package that you are defining has configurations that conflict each other or would be simplified by using a separate UDS Package definition, consider using a separate Kubernetes namespace. Read more about namespaces and mulitenancy [here](https://kubernetes.io/docs/concepts/security/multi-tenancy/). +The UDS Operator seamlessly enables the following enhancements and protections for your workloads: - **Enabling Istio Sidecar Injection:** - The operator facilitates the activation of Istio sidecar injection within namespaces where the CR is deployed. - **Support for Istio Ambient Mode:** From a652074dfda2b4dd533e3f4fae2257bc59e37ecf Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Mon, 7 Apr 2025 12:29:17 -0400 Subject: [PATCH 16/27] update diagram --- docs/.images/diagrams/uds-core-operator-uds-package.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/.images/diagrams/uds-core-operator-uds-package.svg b/docs/.images/diagrams/uds-core-operator-uds-package.svg index 24195e366c..2006e00f10 100644 --- a/docs/.images/diagrams/uds-core-operator-uds-package.svg +++ b/docs/.images/diagrams/uds-core-operator-uds-package.svg @@ -1,4 +1,4 @@ -

UDS Package

Validate, Reconcile, and Finalize When Created. Updated, or Deleted

packageReconciler(UDSPackage)

Log Initial Processing Info
Yes
No
is Package reconciling
Log Skip and Return
Yes
No
Is Retry Attempt
Wait for Backoff
Migrate Request Package to latest version
Update Package State to "Pending"
Configure Namespace and Network Policies
Enable Istio Injection
Yes
No
SSO 
Clients 
Present 
Configure SSO and AuthService Clients
Generate Istio Resources (VirtualServices, ServiceEntries)
Generate Service and Pod monitors
Update Package Status to Ready
Yes
Is Identity 
Deployed
Log error, throw exception
No
Log Information for Removal
success
Remove SSO Clients
success
Remove AuthService Configuration

packageFinalizer(UDSPackage)

is Package removing
Log Skip and Return
Yes

is Package Ready or
 Failed
No
Update Status to Removing
Log Waiting and Return
Create Failure Event
Update Status to RemovalFailed
No
Yes
success
failure
Remove Istio from Namespace
Log Success and Remove Finalizer

validator(PeprValidateRequest)

Migrate Request Package to latest version
Yes
No
Is only Package resource in Namespace
No
Approve Request
Yes
For expose list:
- Valid advancedHTTP usage
 - Valid directResponse usage
- Unique names
Yes
is 
Namespace a 
system 
Namespace
No
Yes
For monitor list:
- Unique names
Yes
For network.allow list:
- Valid remoteGenerated usage
- Unique names
For sso list:
- Valid usage of serviceAccountsEnabled
- Valid usage of publicClient
- Valid usage
of
 standardFlowEnabled
- Unique client ID

No
Yes
Deny Request
Extract Package Name and Namespace
No
Yes
No
Has deletion timestamp
No
\ No newline at end of file +

UDS Package

Validate, Reconcile, and Finalize When Created. Updated, or Deleted

packageReconciler(UDSPackage)

Log Initial Processing Info
Yes
No
is Package reconciling
Log Skip and Return
Yes
No
Is Retry Attempt
Wait for Backoff
Migrate Request Package to latest version
Update Package State to "Pending"
Configure Namespace and Network Policies
Enable Istio Injection
Yes
No
SSO 
Clients 
Present 
Configure SSO and AuthService Clients
Generate Istio Resources (VirtualServices, ServiceEntries)
Generate Service and Pod monitors
Update Package Status to Ready
Yes
Is Identity 
Deployed
Log error, throw exception
No
Log Information for Removal
success
Remove SSO Clients
success
Remove AuthService Configuration

packageFinalizer(UDSPackage)

is Package removing
Log Skip and Return
Yes

is Package Ready or
 Failed
No
Update Status to Removing
Log Waiting and Return
Create Failure Event
Update Status to RemovalFailed
No
Yes
success
failure
Remove Istio from Namespace
Log Success and Remove Finalizer

validator(PeprValidateRequest)

Migrate Request Package to latest version
Yes
No
Is only Package resource in Namespace
No
Approve Request
Yes
For expose list:
- Valid advancedHTTP usage
 - Valid directResponse usage
- Unique names
Yes
is 
Namespace a 
system 
Namespace
No
Yes
For monitor list:
- Unique names
Yes
For network.allow list:
- Valid remoteGenerated usage
- Unique names
For sso list:
- Valid usage of serviceAccountsEnabled
- Valid usage of publicClient
- Valid usage
of
 standardFlowEnabled
- Unique client ID

No
Yes
Deny Request
Extract Package Name and Namespace
No
Yes
No
Has deletion timestamp
No
\ No newline at end of file From 20c2c63f57460d108486f9ff3aea61ea9c9f781a Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Mon, 7 Apr 2025 13:32:58 -0400 Subject: [PATCH 17/27] address feedback --- docs/reference/configuration/UDS operator/package.md | 2 +- src/pepr/operator/controllers/packages/package-store.spec.ts | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/reference/configuration/UDS operator/package.md b/docs/reference/configuration/UDS operator/package.md index 2b8512c603..6be3b9d6d7 100644 --- a/docs/reference/configuration/UDS operator/package.md +++ b/docs/reference/configuration/UDS operator/package.md @@ -8,7 +8,7 @@ title: UDS Package :::note Only one UDS Package Custom Resource can exist in a namespace. This pattern was chosen by design to better enable workload isolation and to reduce complexity. ::: -Namespaces are common boundary for isolating workloads in multi-tenant clusters. When defining a UDS Package resource, consider the implications for all workloads that exist in the target namespace. If the UDS Package that you are defining has configurations that conflict each other or would be simplified by using a separate UDS Package definition, consider using a separate Kubernetes namespace. Read more about namespaces and mulitenancy [here](https://kubernetes.io/docs/concepts/security/multi-tenancy/). +Namespaces are a common boundary for isolating workloads in multi-tenant clusters. When defining a UDS Package resource, consider the implications for all workloads that exist in the target namespace. If the UDS Package that you are defining has configurations that conflict with each other or would be simplified by using a separate UDS Package definition, consider using a separate Kubernetes namespace. Read more about namespaces and mulitenancy [here](https://kubernetes.io/docs/concepts/security/multi-tenancy/). The UDS Operator seamlessly enables the following enhancements and protections for your workloads: - **Enabling Istio Sidecar Injection:** diff --git a/src/pepr/operator/controllers/packages/package-store.spec.ts b/src/pepr/operator/controllers/packages/package-store.spec.ts index 1d9afb357b..6bb4a0b569 100644 --- a/src/pepr/operator/controllers/packages/package-store.spec.ts +++ b/src/pepr/operator/controllers/packages/package-store.spec.ts @@ -3,15 +3,11 @@ * SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial */ -//should add packages -//should remove packages -//should add packages from different namespaces import { describe, expect, it } from "@jest/globals"; import { PeprValidateRequest } from "pepr"; import { UDSPackage } from "../../crd"; import { PackageStore } from "./package-store"; PackageStore.init(); -//const packageNamespaceMap: PackageNamespaceMap = new Map(); const makeMockReq = (pkg: Partial) => { const defaultPkg: UDSPackage = { From c1e2cc789dfb24d7570308dc58354c05fab73a49 Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Mon, 7 Apr 2025 15:26:03 -0400 Subject: [PATCH 18/27] addressing more feedback --- .../controllers/packages/package-store.spec.ts | 16 ++++++++++++++++ .../controllers/packages/package-store.ts | 18 ++++++++---------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/pepr/operator/controllers/packages/package-store.spec.ts b/src/pepr/operator/controllers/packages/package-store.spec.ts index 6bb4a0b569..9bd9874d3f 100644 --- a/src/pepr/operator/controllers/packages/package-store.spec.ts +++ b/src/pepr/operator/controllers/packages/package-store.spec.ts @@ -50,6 +50,22 @@ describe("Package Store", () => { expect(pkgsExist).toEqual(true); }); + it("Should return the first package in the namespace", async () => { + const mockReq = makeMockReq({}); + const ns = mockReq.Raw.metadata?.namespace || ""; + const pkgName = mockReq.Raw.metadata?.name || ""; + expect(PackageStore.getPkgName(ns)).toEqual(pkgName); + }); + + it("Should update an existing package", async () => { + const mockReq = makeMockReq({ metadata: { namespace: "test", name: "other-package", labels: { test: "value"}}}); + const ns = mockReq.Raw.metadata?.namespace || ""; + const pkgName = mockReq.Raw.metadata?.name || ""; + PackageStore.add(mockReq.Raw) + const updatedPackage = PackageStore.getPkgName(ns); + expect(updatedPackage).toEqual(pkgName) + }); + it("Should remove a package", async () => { const mockReq = makeMockReq({}); const ns = mockReq.Raw.metadata?.namespace || ""; diff --git a/src/pepr/operator/controllers/packages/package-store.ts b/src/pepr/operator/controllers/packages/package-store.ts index 9702a95e0b..cc84fe55f3 100644 --- a/src/pepr/operator/controllers/packages/package-store.ts +++ b/src/pepr/operator/controllers/packages/package-store.ts @@ -13,7 +13,7 @@ import { UDSPackage } from "../../crd"; const log = setupLogger(Component.OPERATOR_PACKAGES); export type PackageNamespaceMap = Map; -let packageNamespaceMap: PackageNamespaceMap; +let packageNamespaceMap: PackageNamespaceMap = new Map(); /** * Initializes the package namespace map. @@ -38,12 +38,14 @@ function init(): void { * in packageNamespaceMap for that namespace, containing a list of one element. */ function add(pkg: UDSPackage, logger: boolean = true): void { - const namespace = pkg.metadata?.namespace || ""; + if (!pkg.metadata?.namespace || !pkg.metadata.name) { + throw new Error(`Invalid Package definition, missing namespace or name`); + } + const namespace = pkg.metadata?.namespace; // Get existing packages if any and merge into the one being added const existingValue = packageNamespaceMap.get(namespace); if (existingValue) { existingValue.push(pkg); - packageNamespaceMap.set(namespace, existingValue); } else { packageNamespaceMap.set(namespace, [pkg]); } @@ -71,13 +73,9 @@ function remove(pkg: UDSPackage, logger: boolean = true): void { const pkgToRemove = pkg.metadata?.name; const items = packageNamespaceMap.get(namespace) || []; - if (items.length > 1) { - items.forEach((value, index) => { - if (value.metadata?.name === pkgToRemove) { - items.splice(index, 1); - } - }); - packageNamespaceMap.set(namespace, items); + const updatedItems = items.filter(pkg => pkg.metadata?.name !== pkgToRemove); + if (updatedItems.length > 0) { + packageNamespaceMap.set(namespace, updatedItems); } else { packageNamespaceMap.delete(namespace); } From e98d060208512ea56ba38b22507bf941a5cfb74c Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Mon, 7 Apr 2025 17:01:47 -0400 Subject: [PATCH 19/27] address more feedback --- .../uds-core-operator-uds-package.svg | 2 +- pepr.ts | 4 ++-- .../operator/controllers/packages/packages.ts | 21 ++++++++++++++++++- .../crd/validators/package-validator.ts | 9 ++------ src/pepr/operator/reconcilers/index.ts | 17 --------------- 5 files changed, 25 insertions(+), 28 deletions(-) diff --git a/docs/.images/diagrams/uds-core-operator-uds-package.svg b/docs/.images/diagrams/uds-core-operator-uds-package.svg index 2006e00f10..351550c142 100644 --- a/docs/.images/diagrams/uds-core-operator-uds-package.svg +++ b/docs/.images/diagrams/uds-core-operator-uds-package.svg @@ -1,4 +1,4 @@ -

UDS Package

Validate, Reconcile, and Finalize When Created. Updated, or Deleted

packageReconciler(UDSPackage)

Log Initial Processing Info
Yes
No
is Package reconciling
Log Skip and Return
Yes
No
Is Retry Attempt
Wait for Backoff
Migrate Request Package to latest version
Update Package State to "Pending"
Configure Namespace and Network Policies
Enable Istio Injection
Yes
No
SSO 
Clients 
Present 
Configure SSO and AuthService Clients
Generate Istio Resources (VirtualServices, ServiceEntries)
Generate Service and Pod monitors
Update Package Status to Ready
Yes
Is Identity 
Deployed
Log error, throw exception
No
Log Information for Removal
success
Remove SSO Clients
success
Remove AuthService Configuration

packageFinalizer(UDSPackage)

is Package removing
Log Skip and Return
Yes

is Package Ready or
 Failed
No
Update Status to Removing
Log Waiting and Return
Create Failure Event
Update Status to RemovalFailed
No
Yes
success
failure
Remove Istio from Namespace
Log Success and Remove Finalizer

validator(PeprValidateRequest)

Migrate Request Package to latest version
Yes
No
Is only Package resource in Namespace
No
Approve Request
Yes
For expose list:
- Valid advancedHTTP usage
 - Valid directResponse usage
- Unique names
Yes
is 
Namespace a 
system 
Namespace
No
Yes
For monitor list:
- Unique names
Yes
For network.allow list:
- Valid remoteGenerated usage
- Unique names
For sso list:
- Valid usage of serviceAccountsEnabled
- Valid usage of publicClient
- Valid usage
of
 standardFlowEnabled
- Unique client ID

No
Yes
Deny Request
Extract Package Name and Namespace
No
Yes
No
Has deletion timestamp
No
\ No newline at end of file +

UDS Package

Validate, Reconcile, and Finalize When Created. Updated, or Deleted

packageReconciler(UDSPackage)

Log Initial Processing Info
Yes
No
is Package reconciling
Log Skip and Return
Yes
No
Is Retry Attempt
Wait for Backoff
Migrate Request Package to latest version
Update Package State to "Pending"
Configure Namespace and Network Policies
Enable Istio Injection
Yes
No
SSO 
Clients 
Present 
Configure SSO and AuthService Clients
Generate Istio Resources (VirtualServices, ServiceEntries)
Generate Service and Pod monitors
Update Package Status to Ready
Yes
Is Identity 
Deployed
Log error, throw exception
No
Log Information for Removal
success
Remove SSO Clients
success
Remove AuthService Configuration

packageFinalizer(UDSPackage)

is Package removing
Log Skip and Return
Yes

is Package Ready or
 Failed
No
Update Status to Removing
Log Waiting and Return
Create Failure Event
Update Status to RemovalFailed
No
Yes
success
failure
Remove Istio from Namespace
Log Success and Remove Finalizer

validator(PeprValidateRequest)

Migrate Request Package to latest version
Yes
No
Is only Package resource in Namespace
No
Approve Request
Yes
For expose list:
- Valid advancedHTTP usage
 - Valid directResponse usage
- Unique names
Yes
is 
Namespace a 
system 
Namespace
No
Yes
For monitor list:
- Unique names
Yes
For network.allow list:
- Valid remoteGenerated usage
- Unique names
For sso list:
- Valid usage of serviceAccountsEnabled
- Valid usage of publicClient
- Valid usage
of
 standardFlowEnabled
- Unique client ID

No
Yes
Deny Request
Extract Package Name and Namespace
No
No
\ No newline at end of file diff --git a/pepr.ts b/pepr.ts index 84ecd733a9..4ce162bf51 100644 --- a/pepr.ts +++ b/pepr.ts @@ -10,12 +10,12 @@ import cfg from "./package.json"; import { Component, setupLogger } from "./src/pepr/logger"; import { operator } from "./src/pepr/operator"; import { setupAuthserviceSecret } from "./src/pepr/operator/controllers/keycloak/authservice/config"; +import { setupKeycloakClientSecret } from "./src/pepr/operator/controllers/keycloak/config"; +import { startPackageWatch } from "./src/pepr/operator/controllers/packages/packages"; import { registerCRDs } from "./src/pepr/operator/crd/register"; -import { startPackageWatch } from "./src/pepr/operator/reconcilers"; import { patches } from "./src/pepr/patches"; import { policies, startExemptionWatch } from "./src/pepr/policies"; import { prometheus } from "./src/pepr/prometheus"; -import { setupKeycloakClientSecret } from "./src/pepr/operator/controllers/keycloak/config"; const log = setupLogger(Component.STARTUP); diff --git a/src/pepr/operator/controllers/packages/packages.ts b/src/pepr/operator/controllers/packages/packages.ts index 6f1e927ef8..5849491916 100644 --- a/src/pepr/operator/controllers/packages/packages.ts +++ b/src/pepr/operator/controllers/packages/packages.ts @@ -4,9 +4,10 @@ */ import { WatchPhase } from "kubernetes-fluent-client/dist/fluent/types"; +import { K8s } from "pepr"; +import { Component, setupLogger } from "../../../logger"; import { UDSPackage } from "../../crd"; import { PackageStore } from "./package-store"; - /** * Processes exemptions based on the watch phase. * This function determines how to handle a UDSPackage based on whether it has been added or deleted @@ -19,6 +20,24 @@ import { PackageStore } from "./package-store"; * WatchPhase is assumed to be an enum or a set of constant values. * @returns {void} This function does not return a value; it performs actions based on the input. */ + +// configure subproject logger +const log = setupLogger(Component.OPERATOR_RECONCILERS); +export async function startPackageWatch() { + PackageStore.init(); + // only run in admission controller or dev mode + if (process.env.PEPR_WATCH_MODE === "false" || process.env.PEPR_MODE === "dev") { + const watcher = K8s(UDSPackage).Watch(async (pkg, phase) => { + log.debug(`Processing package ${pkg.metadata?.name}, watch phase: ${phase}`); + + processPackages(pkg, phase); + }); + // This will run until the process is terminated or the watch is aborted + log.debug("Starting package watch..."); + await watcher.start(); + } +} + export function processPackages(pkg: UDSPackage, phase: WatchPhase) { switch (phase) { case WatchPhase.Added: diff --git a/src/pepr/operator/crd/validators/package-validator.ts b/src/pepr/operator/crd/validators/package-validator.ts index 8402ceb984..94e5cd4c0d 100644 --- a/src/pepr/operator/crd/validators/package-validator.ts +++ b/src/pepr/operator/crd/validators/package-validator.ts @@ -21,21 +21,16 @@ export async function validator(req: PeprValidateRequest) { const pkgName = pkg.metadata?.name ?? "_unknown_"; const ns = pkg.metadata?.namespace ?? "_unknown_"; - - // Always permit packages that are being removed const deletionTimestamp = pkg.metadata?.deletionTimestamp ?? null; - if (deletionTimestamp) { - return req.Approve(); - } if (invalidNamespaces.includes(ns)) { return req.Deny("invalid namespace"); } // Check if a package already exists in the target namespace - if (PackageStore.hasKey(ns)) { + if (PackageStore.hasKey(ns) && !deletionTimestamp) { const existingPkgName = PackageStore.getPkgName(ns); - //Since this function is called on admission, we need to allow updating existing packages + // Since this function is called on admission, we need to allow updating existing packages if (existingPkgName !== pkgName) { return req.Deny( `A package with the name "${existingPkgName}" already exists in the namespace "${ns}". Only one package can exist in a namespace.`, diff --git a/src/pepr/operator/reconcilers/index.ts b/src/pepr/operator/reconcilers/index.ts index 43611e64c3..38d9485b87 100644 --- a/src/pepr/operator/reconcilers/index.ts +++ b/src/pepr/operator/reconcilers/index.ts @@ -6,8 +6,6 @@ import { K8s, kind } from "pepr"; import { Component, setupLogger } from "../../logger"; -import { PackageStore } from "../controllers/packages/package-store"; -import { processPackages } from "../controllers/packages/packages"; import { Phase, PkgStatus, UDSPackage } from "../crd"; import { StatusObject as Status, StatusEnum } from "../crd/generated/package-v1alpha1"; @@ -179,18 +177,3 @@ export function getReadinessConditions(ready: boolean = true) { }, ]; } - -export async function startPackageWatch() { - PackageStore.init(); - // only run in admission controller or dev mode - if (process.env.PEPR_WATCH_MODE === "false" || process.env.PEPR_MODE === "dev") { - const watcher = K8s(UDSPackage).Watch(async (pkg, phase) => { - log.debug(`Processing package ${pkg.metadata?.name}, watch phase: ${phase}`); - - processPackages(pkg, phase); - }); - // This will run until the process is terminated or the watch is aborted - log.debug("Starting package watch..."); - await watcher.start(); - } -} From 577c69cfc41842c4f27881b18ee30b246a51c11f Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Mon, 7 Apr 2025 17:05:32 -0400 Subject: [PATCH 20/27] `npx pepr format` --- .../operator/controllers/packages/package-store.spec.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/pepr/operator/controllers/packages/package-store.spec.ts b/src/pepr/operator/controllers/packages/package-store.spec.ts index 9bd9874d3f..4c377138e1 100644 --- a/src/pepr/operator/controllers/packages/package-store.spec.ts +++ b/src/pepr/operator/controllers/packages/package-store.spec.ts @@ -58,12 +58,14 @@ describe("Package Store", () => { }); it("Should update an existing package", async () => { - const mockReq = makeMockReq({ metadata: { namespace: "test", name: "other-package", labels: { test: "value"}}}); + const mockReq = makeMockReq({ + metadata: { namespace: "test", name: "other-package", labels: { test: "value" } }, + }); const ns = mockReq.Raw.metadata?.namespace || ""; const pkgName = mockReq.Raw.metadata?.name || ""; - PackageStore.add(mockReq.Raw) + PackageStore.add(mockReq.Raw); const updatedPackage = PackageStore.getPkgName(ns); - expect(updatedPackage).toEqual(pkgName) + expect(updatedPackage).toEqual(pkgName); }); it("Should remove a package", async () => { From 570d5f7f37a8243426078402e192f7cd8a69297a Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Tue, 8 Apr 2025 10:23:15 -0400 Subject: [PATCH 21/27] update diagram --- .../uds-core-operator-uds-package.svg | 2 +- .../uds-core-pepr-operator-flow.drawio | 346 ++++++------------ 2 files changed, 107 insertions(+), 241 deletions(-) diff --git a/docs/.images/diagrams/uds-core-operator-uds-package.svg b/docs/.images/diagrams/uds-core-operator-uds-package.svg index 351550c142..0795d040b8 100644 --- a/docs/.images/diagrams/uds-core-operator-uds-package.svg +++ b/docs/.images/diagrams/uds-core-operator-uds-package.svg @@ -1,4 +1,4 @@ -

UDS Package

Validate, Reconcile, and Finalize When Created. Updated, or Deleted

packageReconciler(UDSPackage)

Log Initial Processing Info
Yes
No
is Package reconciling
Log Skip and Return
Yes
No
Is Retry Attempt
Wait for Backoff
Migrate Request Package to latest version
Update Package State to "Pending"
Configure Namespace and Network Policies
Enable Istio Injection
Yes
No
SSO 
Clients 
Present 
Configure SSO and AuthService Clients
Generate Istio Resources (VirtualServices, ServiceEntries)
Generate Service and Pod monitors
Update Package Status to Ready
Yes
Is Identity 
Deployed
Log error, throw exception
No
Log Information for Removal
success
Remove SSO Clients
success
Remove AuthService Configuration

packageFinalizer(UDSPackage)

is Package removing
Log Skip and Return
Yes

is Package Ready or
 Failed
No
Update Status to Removing
Log Waiting and Return
Create Failure Event
Update Status to RemovalFailed
No
Yes
success
failure
Remove Istio from Namespace
Log Success and Remove Finalizer

validator(PeprValidateRequest)

Migrate Request Package to latest version
Yes
No
Is only Package resource in Namespace
No
Approve Request
Yes
For expose list:
- Valid advancedHTTP usage
 - Valid directResponse usage
- Unique names
Yes
is 
Namespace a 
system 
Namespace
No
Yes
For monitor list:
- Unique names
Yes
For network.allow list:
- Valid remoteGenerated usage
- Unique names
For sso list:
- Valid usage of serviceAccountsEnabled
- Valid usage of publicClient
- Valid usage
of
 standardFlowEnabled
- Unique client ID

No
Yes
Deny Request
Extract Package Name and Namespace
No
No
\ No newline at end of file +

UDS Package

Validate, Reconcile, and Finalize When Created. Updated, or Deleted

packageReconciler(UDSPackage)

Log Initial Processing Info
Yes
No
is Package reconciling
Log Skip and Return
Yes
No
Is Retry Attempt
Wait for Backoff
Migrate Request Package to latest version
Update Package State to "Pending"
Configure Namespace and Network Policies
Enable Istio Injection
Yes
No
SSO 
Clients 
Present 
Configure SSO and AuthService Clients
Generate Istio Resources (VirtualServices, ServiceEntries)
Generate Service and Pod monitors
Update Package Status to Ready
Yes
Is Identity 
Deployed
Log error, throw exception
No
Log Information for Removal
success
Remove SSO Clients
success
Remove AuthService Configuration

packageFinalizer(UDSPackage)

is Package removing
Log Skip and Return
Yes

is Package Ready or
 Failed
No
Update Status to Removing
Log Waiting and Return
Create Failure Event
Update Status to RemovalFailed
No
Yes
success
failure
Remove Istio from Namespace
Log Success and Remove Finalizer

validator(PeprValidateRequest)

Migrate Request Package to latest version
No
Is only Package resource in Namespace
No
Approve Request
Yes
For expose list:
- Valid advancedHTTP usage
 - Valid directResponse usage
- Unique names
Yes
is 
Namespace a 
system 
Namespace
No
Yes
For monitor list:
- Unique names
Yes
For network.allow list:
- Valid remoteGenerated usage
- Unique names
For sso list:
- Valid usage of 
serviceAccountsEnabled
- Valid usage of publicClient
- Valid usage
of standardFlowEnabled
- Unique client ID

No
Yes
Deny Request
Extract Package Name and Namespace
No
No
Yes
\ No newline at end of file diff --git a/docs/.images/diagrams/uds-core-pepr-operator-flow.drawio b/docs/.images/diagrams/uds-core-pepr-operator-flow.drawio index 5b20f778c2..29b20fe991 100644 --- a/docs/.images/diagrams/uds-core-pepr-operator-flow.drawio +++ b/docs/.images/diagrams/uds-core-pepr-operator-flow.drawio @@ -1,163 +1,8 @@ - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1220,19 +1065,19 @@ - + - + - + - + - + @@ -1324,7 +1169,7 @@ - + @@ -1337,7 +1182,7 @@ - + @@ -1369,7 +1214,7 @@ - + @@ -1383,7 +1228,7 @@ - + @@ -1403,7 +1248,7 @@ - + @@ -1417,7 +1262,7 @@ - + @@ -1425,6 +1270,9 @@ + + + @@ -1511,7 +1359,7 @@ - + @@ -1519,7 +1367,7 @@ - + @@ -1530,157 +1378,175 @@ - - - - - + + + + + - + - + - + - + - + - + - - + + + + + + + - - + + - - - + + + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - + - + - - - + + + - + - + - - - + + + - + - + - - + + - + - + - - + + - - + + - - - + + + - - + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 19519891572d527739265359e09bbb593bb757b0 Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Tue, 8 Apr 2025 10:28:14 -0400 Subject: [PATCH 22/27] nit: fix arrow color --- .../uds-core-operator-uds-package.svg | 2 +- .../uds-core-pepr-operator-flow.drawio | 32 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/.images/diagrams/uds-core-operator-uds-package.svg b/docs/.images/diagrams/uds-core-operator-uds-package.svg index 0795d040b8..11ff95ad7c 100644 --- a/docs/.images/diagrams/uds-core-operator-uds-package.svg +++ b/docs/.images/diagrams/uds-core-operator-uds-package.svg @@ -1,4 +1,4 @@ -

UDS Package

Validate, Reconcile, and Finalize When Created. Updated, or Deleted

packageReconciler(UDSPackage)

Log Initial Processing Info
Yes
No
is Package reconciling
Log Skip and Return
Yes
No
Is Retry Attempt
Wait for Backoff
Migrate Request Package to latest version
Update Package State to "Pending"
Configure Namespace and Network Policies
Enable Istio Injection
Yes
No
SSO 
Clients 
Present 
Configure SSO and AuthService Clients
Generate Istio Resources (VirtualServices, ServiceEntries)
Generate Service and Pod monitors
Update Package Status to Ready
Yes
Is Identity 
Deployed
Log error, throw exception
No
Log Information for Removal
success
Remove SSO Clients
success
Remove AuthService Configuration

packageFinalizer(UDSPackage)

is Package removing
Log Skip and Return
Yes

is Package Ready or
 Failed
No
Update Status to Removing
Log Waiting and Return
Create Failure Event
Update Status to RemovalFailed
No
Yes
success
failure
Remove Istio from Namespace
Log Success and Remove Finalizer

validator(PeprValidateRequest)

Migrate Request Package to latest version
No
Is only Package resource in Namespace
No
Approve Request
Yes
For expose list:
- Valid advancedHTTP usage
 - Valid directResponse usage
- Unique names
Yes
is 
Namespace a 
system 
Namespace
No
Yes
For monitor list:
- Unique names
Yes
For network.allow list:
- Valid remoteGenerated usage
- Unique names
For sso list:
- Valid usage of 
serviceAccountsEnabled
- Valid usage of publicClient
- Valid usage
of standardFlowEnabled
- Unique client ID

No
Yes
Deny Request
Extract Package Name and Namespace
No
No
Yes
\ No newline at end of file +

UDS Package

Validate, Reconcile, and Finalize When Created. Updated, or Deleted

packageReconciler(UDSPackage)

Log Initial Processing Info
Yes
No
is Package reconciling
Log Skip and Return
Yes
No
Is Retry Attempt
Wait for Backoff
Migrate Request Package to latest version
Update Package State to "Pending"
Configure Namespace and Network Policies
Enable Istio Injection
Yes
No
SSO 
Clients 
Present 
Configure SSO and AuthService Clients
Generate Istio Resources (VirtualServices, ServiceEntries)
Generate Service and Pod monitors
Update Package Status to Ready
Yes
Is Identity 
Deployed
Log error, throw exception
No
Log Information for Removal
success
Remove SSO Clients
success
Remove AuthService Configuration

packageFinalizer(UDSPackage)

is Package removing
Log Skip and Return
Yes

is Package Ready or
 Failed
No
Update Status to Removing
Log Waiting and Return
Create Failure Event
Update Status to RemovalFailed
No
Yes
success
failure
Remove Istio from Namespace
Log Success and Remove Finalizer

validator(PeprValidateRequest)

Migrate Request Package to latest version
No
Is only Package resource in Namespace
No
Approve Request
Yes
For expose list:
- Valid advancedHTTP usage
 - Valid directResponse usage
- Unique names
Yes
is 
Namespace a 
system 
Namespace
No
Yes
For monitor list:
- Unique names
Yes
For network.allow list:
- Valid remoteGenerated usage
- Unique names
For sso list:
- Valid usage of 
serviceAccountsEnabled
- Valid usage of publicClient
- Valid usage
of standardFlowEnabled
- Unique client ID

No
Yes
Deny Request
Extract Package Name and Namespace
No
No
Yes
\ No newline at end of file diff --git a/docs/.images/diagrams/uds-core-pepr-operator-flow.drawio b/docs/.images/diagrams/uds-core-pepr-operator-flow.drawio index 29b20fe991..7645c54e20 100644 --- a/docs/.images/diagrams/uds-core-pepr-operator-flow.drawio +++ b/docs/.images/diagrams/uds-core-pepr-operator-flow.drawio @@ -1,6 +1,6 @@ - + @@ -1262,17 +1262,17 @@ - - - - - - + + + + + + @@ -1393,7 +1393,7 @@ - + @@ -1416,7 +1416,7 @@
- + @@ -1442,7 +1442,7 @@
- + @@ -1455,7 +1455,7 @@
- + @@ -1494,7 +1494,7 @@
- + @@ -1524,7 +1524,7 @@ - + @@ -1537,15 +1537,15 @@
- + - + - + From 39f9c3b62414d1e80171fbb0868b37fdd68d0ca1 Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Tue, 8 Apr 2025 14:24:15 -0400 Subject: [PATCH 23/27] only init `packageNamespaceMap` on `Package.Store.init()`; add error checks to `PackageStore.remove()` --- src/pepr/operator/controllers/packages/package-store.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pepr/operator/controllers/packages/package-store.ts b/src/pepr/operator/controllers/packages/package-store.ts index cc84fe55f3..77a81037df 100644 --- a/src/pepr/operator/controllers/packages/package-store.ts +++ b/src/pepr/operator/controllers/packages/package-store.ts @@ -13,7 +13,7 @@ import { UDSPackage } from "../../crd"; const log = setupLogger(Component.OPERATOR_PACKAGES); export type PackageNamespaceMap = Map; -let packageNamespaceMap: PackageNamespaceMap = new Map(); +let packageNamespaceMap: PackageNamespaceMap /** * Initializes the package namespace map. @@ -69,7 +69,11 @@ function add(pkg: UDSPackage, logger: boolean = true): void { * */ function remove(pkg: UDSPackage, logger: boolean = true): void { - const namespace = pkg.metadata?.namespace || ""; + if (!pkg.metadata?.namespace || !pkg.metadata.name) { + throw new Error(`Invalid Package definition, missing namespace or name`); + } + + const namespace = pkg.metadata?.namespace; const pkgToRemove = pkg.metadata?.name; const items = packageNamespaceMap.get(namespace) || []; From 017f58d5e738ce325fd0cd1354f9dd2aaeff7845 Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Wed, 9 Apr 2025 09:55:03 -0400 Subject: [PATCH 24/27] add watch for modified packages, support updating existing package in map, add default watch config --- .../controllers/packages/package-store.ts | 24 +++++++++++++------ .../operator/controllers/packages/packages.ts | 21 +++++++++++++++- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/pepr/operator/controllers/packages/package-store.ts b/src/pepr/operator/controllers/packages/package-store.ts index 77a81037df..79176794ca 100644 --- a/src/pepr/operator/controllers/packages/package-store.ts +++ b/src/pepr/operator/controllers/packages/package-store.ts @@ -13,7 +13,7 @@ import { UDSPackage } from "../../crd"; const log = setupLogger(Component.OPERATOR_PACKAGES); export type PackageNamespaceMap = Map; -let packageNamespaceMap: PackageNamespaceMap +let packageNamespaceMap: PackageNamespaceMap; /** * Initializes the package namespace map. @@ -42,19 +42,29 @@ function add(pkg: UDSPackage, logger: boolean = true): void { throw new Error(`Invalid Package definition, missing namespace or name`); } const namespace = pkg.metadata?.namespace; + // Get existing packages if any and merge into the one being added const existingValue = packageNamespaceMap.get(namespace); if (existingValue) { + // Check if package already exists in map + const index = existingValue.findIndex(p => p.metadata?.name === pkg.metadata?.name); + if (index !== -1) { + // Update existing package + existingValue[index] = pkg; + if (logger) { + log.debug( + `Updating PackageStore for package ${pkg.metadata?.name} in namespace ${namespace}.`, + ); + } + } existingValue.push(pkg); } else { packageNamespaceMap.set(namespace, [pkg]); - } - - if (logger) { - log.debug(`Added package: ${namespace}/${pkg.metadata?.name} to package map`); + if (logger) { + log.debug(`Added package: ${namespace}/${pkg.metadata?.name} to package map`); + } } } - /** * Removes a package from the package namespace map. * @@ -72,7 +82,7 @@ function remove(pkg: UDSPackage, logger: boolean = true): void { if (!pkg.metadata?.namespace || !pkg.metadata.name) { throw new Error(`Invalid Package definition, missing namespace or name`); } - + const namespace = pkg.metadata?.namespace; const pkgToRemove = pkg.metadata?.name; const items = packageNamespaceMap.get(namespace) || []; diff --git a/src/pepr/operator/controllers/packages/packages.ts b/src/pepr/operator/controllers/packages/packages.ts index 5849491916..0c644d20da 100644 --- a/src/pepr/operator/controllers/packages/packages.ts +++ b/src/pepr/operator/controllers/packages/packages.ts @@ -3,6 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial */ +import { WatchCfg } from "kubernetes-fluent-client"; import { WatchPhase } from "kubernetes-fluent-client/dist/fluent/types"; import { K8s } from "pepr"; import { Component, setupLogger } from "../../../logger"; @@ -27,11 +28,25 @@ export async function startPackageWatch() { PackageStore.init(); // only run in admission controller or dev mode if (process.env.PEPR_WATCH_MODE === "false" || process.env.PEPR_MODE === "dev") { + const watchCfg: WatchCfg = { + resyncFailureMax: process.env.PEPR_RESYNC_FAILURE_MAX + ? parseInt(process.env.PEPR_RESYNC_FAILURE_MAX, 10) + : 5, + resyncDelaySec: process.env.PEPR_RESYNC_DELAY_SECONDS + ? parseInt(process.env.PEPR_RESYNC_DELAY_SECONDS, 10) + : 5, + lastSeenLimitSeconds: process.env.PEPR_LAST_SEEN_LIMIT_SECONDS + ? parseInt(process.env.PEPR_LAST_SEEN_LIMIT_SECONDS, 10) + : 300, + relistIntervalSec: process.env.PEPR_RELIST_INTERVAL_SECONDS + ? parseInt(process.env.PEPR_RELIST_INTERVAL_SECONDS, 10) + : 1800, + }; const watcher = K8s(UDSPackage).Watch(async (pkg, phase) => { log.debug(`Processing package ${pkg.metadata?.name}, watch phase: ${phase}`); processPackages(pkg, phase); - }); + }, watchCfg); // This will run until the process is terminated or the watch is aborted log.debug("Starting package watch..."); await watcher.start(); @@ -44,6 +59,10 @@ export function processPackages(pkg: UDSPackage, phase: WatchPhase) { PackageStore.add(pkg); break; + case WatchPhase.Modified: + PackageStore.add(pkg); + break; + case WatchPhase.Deleted: PackageStore.remove(pkg); break; From 4c93682ef71916685fe165667f593712f13bbfe3 Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Wed, 9 Apr 2025 12:05:59 -0400 Subject: [PATCH 25/27] fix logic in pkg update --- src/pepr/operator/controllers/packages/package-store.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pepr/operator/controllers/packages/package-store.ts b/src/pepr/operator/controllers/packages/package-store.ts index 79176794ca..50359c9baa 100644 --- a/src/pepr/operator/controllers/packages/package-store.ts +++ b/src/pepr/operator/controllers/packages/package-store.ts @@ -56,8 +56,9 @@ function add(pkg: UDSPackage, logger: boolean = true): void { `Updating PackageStore for package ${pkg.metadata?.name} in namespace ${namespace}.`, ); } + } else { + existingValue.push(pkg); } - existingValue.push(pkg); } else { packageNamespaceMap.set(namespace, [pkg]); if (logger) { From 614053228f5df4c98a8b031d1d83799ab1d96bd9 Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Wed, 9 Apr 2025 12:50:29 -0400 Subject: [PATCH 26/27] switch to map of map stucture for package store --- .../controllers/packages/package-store.ts | 110 ++++++++---------- 1 file changed, 51 insertions(+), 59 deletions(-) diff --git a/src/pepr/operator/controllers/packages/package-store.ts b/src/pepr/operator/controllers/packages/package-store.ts index 50359c9baa..97de4d2ed3 100644 --- a/src/pepr/operator/controllers/packages/package-store.ts +++ b/src/pepr/operator/controllers/packages/package-store.ts @@ -12,7 +12,8 @@ import { Component, setupLogger } from "../../../logger"; import { UDSPackage } from "../../crd"; const log = setupLogger(Component.OPERATOR_PACKAGES); -export type PackageNamespaceMap = Map; +// Map structure: namespace -> (package name -> package) +export type PackageNamespaceMap = Map>; let packageNamespaceMap: PackageNamespaceMap; /** @@ -28,74 +29,74 @@ function init(): void { /** * Adds a package to the package namespace map. * - * @param {UDSPackage} pkg - The package to be added. It should contain metadata with a namespace. + * @param {UDSPackage} pkg - The package to be added. It should contain metadata with a namespace and name. * @param {boolean} [logger=true] - Optional flag to enable logging. Defaults to true. * - * This function retrieves the namespace from the package metadata and adds the package - * to the packageNamespaceMap. If the namespace is not present, it defaults to an empty string. - * For backwards compatibility, the function checks if the namespace has an existing package. If it does, - * the package to be added is appended to the existing list of Packages. If not, a new key is created - * in packageNamespaceMap for that namespace, containing a list of one element. + * This function retrieves the namespace and name from the package metadata and adds the package + * to the packageNamespaceMap. If the namespace doesn't exist, it creates a new map for that namespace. + * The function then adds or updates the package in the namespace map using the package name as the key. */ function add(pkg: UDSPackage, logger: boolean = true): void { if (!pkg.metadata?.namespace || !pkg.metadata.name) { throw new Error(`Invalid Package definition, missing namespace or name`); } - const namespace = pkg.metadata?.namespace; - - // Get existing packages if any and merge into the one being added - const existingValue = packageNamespaceMap.get(namespace); - if (existingValue) { - // Check if package already exists in map - const index = existingValue.findIndex(p => p.metadata?.name === pkg.metadata?.name); - if (index !== -1) { - // Update existing package - existingValue[index] = pkg; - if (logger) { - log.debug( - `Updating PackageStore for package ${pkg.metadata?.name} in namespace ${namespace}.`, - ); - } + const namespace = pkg.metadata.namespace; + const name = pkg.metadata.name; + + // Get or create the namespace map + if (!packageNamespaceMap.has(namespace)) { + packageNamespaceMap.set(namespace, new Map()); + } + + const namespaceMap = packageNamespaceMap.get(namespace)!; + const isUpdate = namespaceMap.has(name); + + // Set the package + namespaceMap.set(name, pkg); + + if (logger) { + if (isUpdate) { + log.debug(`Updating PackageStore for package ${name} in namespace ${namespace}.`); } else { - existingValue.push(pkg); - } - } else { - packageNamespaceMap.set(namespace, [pkg]); - if (logger) { - log.debug(`Added package: ${namespace}/${pkg.metadata?.name} to package map`); + log.debug(`Added package: ${namespace}/${name} to package map`); } } } + /** * Removes a package from the package namespace map. * - * @param {UDSPackage} pkg - The package to be removed. It should contain metadata with a namespace. + * @param {UDSPackage} pkg - The package to be removed. It should contain metadata with a namespace and name. * @param {boolean} [logger=true] - Optional flag to enable logging. Defaults to true. * - * This function retrieves the namespace from the package metadata and deletes it from the - * packageNamespaceMap. If the namespace is not present, it defaults to an empty string. - * For backwards compatibility, we check if the namespace has more than one package. - * If that is the case, we update the value for that namespace in packageNamespaceMap. - * Otherwise, if there is only one package in the namespace, we delete the key. - * + * This function retrieves the namespace and name from the package metadata and removes the package + * from the packageNamespaceMap. If the namespace map becomes empty after removal, the namespace + * is also removed from the packageNamespaceMap. */ function remove(pkg: UDSPackage, logger: boolean = true): void { if (!pkg.metadata?.namespace || !pkg.metadata.name) { throw new Error(`Invalid Package definition, missing namespace or name`); } - const namespace = pkg.metadata?.namespace; - const pkgToRemove = pkg.metadata?.name; - const items = packageNamespaceMap.get(namespace) || []; + const namespace = pkg.metadata.namespace; + const name = pkg.metadata.name; + + const namespaceMap = packageNamespaceMap.get(namespace); + if (!namespaceMap) { + // Namespace doesn't exist, nothing to remove + return; + } + + // Remove the package + namespaceMap.delete(name); - const updatedItems = items.filter(pkg => pkg.metadata?.name !== pkgToRemove); - if (updatedItems.length > 0) { - packageNamespaceMap.set(namespace, updatedItems); - } else { + // If namespace map is empty, remove the namespace + if (namespaceMap.size === 0) { packageNamespaceMap.delete(namespace); } + if (logger) { - log.debug(`Removed package: ${namespace}/${pkg.metadata?.name} from package map`); + log.debug(`Removed package: ${namespace}/${name} from package map`); } } @@ -117,29 +118,20 @@ function hasKey(namespace: string): boolean { * Retrieves the package name associated with a given namespace. * * This function looks up the namespace in the `packageNamespaceMap` and, if found, - * returns the `name` property from the `metadata` of the associated package. - * If the namespace is not found or the metadata or name is missing, it returns null. + * returns the name of the first package in that namespace. + * If the namespace is not found or there are no packages, it returns null. * * @param namespace The namespace to look up in the `packageNamespaceMap`. * @returns The package name associated with the namespace, or null if not found. - * - * @example - * Assuming packageNamespaceMap contains { 'my-namespace': [{ metadata: { name: 'my-package' } }], [{ metadata: { name: 'other-package' } }]} - * const packageName = getPkgName('my-namespace'); // Returns 'my-package' - * - * @example - * Assuming packageNamespaceMap does not contain 'unknown-namespace' - * const packageName = getPkgName('unknown-namespace'); // Returns null */ function getPkgName(namespace: string): string | null { - const items = packageNamespaceMap.get(namespace) || []; - if (items.length > 0) { - // Always return the first package found - const foundPkg = items[0]; - return foundPkg.metadata?.name || null; - } else { + const namespaceMap = packageNamespaceMap.get(namespace); + if (!namespaceMap || namespaceMap.size === 0) { return null; } + + // Return the name of the first package in the namespace + return Array.from(namespaceMap.keys())[0]; } export const PackageStore = { From 40c41c96a5b23901a6a9954d89ce61daaf498969 Mon Sep 17 00:00:00 2001 From: Noah Birrer Date: Wed, 9 Apr 2025 12:54:15 -0400 Subject: [PATCH 27/27] consildate switch cases --- src/pepr/operator/controllers/packages/packages.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/pepr/operator/controllers/packages/packages.ts b/src/pepr/operator/controllers/packages/packages.ts index 0c644d20da..4303824f34 100644 --- a/src/pepr/operator/controllers/packages/packages.ts +++ b/src/pepr/operator/controllers/packages/packages.ts @@ -56,9 +56,6 @@ export async function startPackageWatch() { export function processPackages(pkg: UDSPackage, phase: WatchPhase) { switch (phase) { case WatchPhase.Added: - PackageStore.add(pkg); - break; - case WatchPhase.Modified: PackageStore.add(pkg); break;