From e9f213a0868c0dc68adddc8bf19600a956caa38c Mon Sep 17 00:00:00 2001 From: Case Wylie Date: Wed, 8 Jan 2025 12:03:09 -0500 Subject: [PATCH] chore: finish up Signed-off-by: Case Wylie --- src/lib/assets/index.test.ts | 61 ++++++++++++++++++++++++ src/lib/assets/webhooks.test.ts | 22 +++++++++ src/lib/assets/webhooks.ts | 18 ++++++- src/lib/core/module.ts | 3 +- src/lib/processors/mutate-processor.ts | 3 +- src/lib/processors/validate-processor.ts | 8 +++- 6 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 src/lib/assets/index.test.ts create mode 100644 src/lib/assets/webhooks.test.ts diff --git a/src/lib/assets/index.test.ts b/src/lib/assets/index.test.ts new file mode 100644 index 000000000..0d21faf53 --- /dev/null +++ b/src/lib/assets/index.test.ts @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023-Present The Pepr Authors +import { it, describe, expect } from "@jest/globals"; +import { createWebhookYaml } from "./index"; +import { kind } from "kubernetes-fluent-client"; + +describe("createWebhookYaml", () => { + const webhookConfiguration = new kind.MutatingWebhookConfiguration(); + webhookConfiguration.apiVersion = "admissionregistration.k8s.io/v1"; + webhookConfiguration.kind = "MutatingWebhookConfiguration"; + webhookConfiguration.metadata = { name: "pepr-static-test" }; + webhookConfiguration.webhooks = [ + { + name: "pepr-static-test.pepr.dev", + admissionReviewVersions: ["v1", "v1beta1"], + clientConfig: { + caBundle: "", + service: { + name: "pepr-static-test", + namespace: "pepr-system", + path: "", + }, + }, + failurePolicy: "Fail", + matchPolicy: "Equivalent", + timeoutSeconds: 15, + namespaceSelector: { + matchExpressions: [ + { + key: "kubernetes.io/metadata.name", + operator: "NotIn", + values: ["kube-system", "pepr-system", "something"], + }, + ], + }, + sideEffects: "None", + }, + ]; + + const moduleConfig = { + onError: "reject", + webhookTimeout: 15, + uuid: "some-uuid", + alwaysIgnore: { + namespaces: ["kube-system", "pepr-system"], + }, + }; + + it("replaces placeholders in the YAML correctly", () => { + const result = createWebhookYaml("pepr-static-test", moduleConfig, webhookConfiguration); + console.log(result); + expect(result).toContain("{{ .Values.uuid }}"); + expect(result).toContain("{{ .Values.admission.failurePolicy }}"); + expect(result).toContain("{{ .Values.admission.webhookTimeout }}"); + expect(result).toContain("- pepr-system"); + expect(result).toContain("- kube-system"); + expect(result).toContain("{{- range .Values.additionalIgnoredNamespaces }}"); + expect(result).toContain("{{ . }}"); + expect(result).toContain("{{- end }}"); + }); +}); diff --git a/src/lib/assets/webhooks.test.ts b/src/lib/assets/webhooks.test.ts new file mode 100644 index 000000000..83491493b --- /dev/null +++ b/src/lib/assets/webhooks.test.ts @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023-Present The Pepr Authors +import { it, describe, expect } from "@jest/globals"; +import { resolveIgnoreNamespaces } from "./webhooks"; + +describe("resolveIgnoreNamespaces", () => { + it("should default to empty array ig config is empty", () => { + const result = resolveIgnoreNamespaces(); + expect(result).toEqual([]); + }); + + it("should return the config ignore namespaces if not provided PEPR_ADDITIONAL_IGNORED_NAMESPACES is not provided", () => { + const result = resolveIgnoreNamespaces(["payments", "istio-system"]); + expect(result).toEqual(["payments", "istio-system"]); + }); + + it("should include additionalIgnoredNamespaces when PEPR_ADDITIONAL_IGNORED_NAMESPACES is provided", () => { + process.env.PEPR_ADDITIONAL_IGNORED_NAMESPACES = "uds, project-fox"; + const result = resolveIgnoreNamespaces(["zarf", "lula"]); + expect(result).toEqual(["uds", "project-fox", "zarf", "lula"]); + }); +}); diff --git a/src/lib/assets/webhooks.ts b/src/lib/assets/webhooks.ts index a96ede649..e750a6361 100644 --- a/src/lib/assets/webhooks.ts +++ b/src/lib/assets/webhooks.ts @@ -13,6 +13,7 @@ import { Assets } from "./assets"; import { Event } from "../enums"; import { Binding } from "../types"; +// Order matters for helm template - must be kube-system, then pepr-system const peprIgnoreNamespaces: string[] = ["kube-system", "pepr-system"]; const validateRule = (binding: Binding, isMutateWebhook: boolean): V1RuleWithOperations | undefined => { @@ -39,6 +40,21 @@ const validateRule = (binding: Binding, isMutateWebhook: boolean): V1RuleWithOpe return ruleObject; }; +export function resolveIgnoreNamespaces(ignoredNSConfig: string[] = []): string[] { + const ignoredNSEnv = process.env.PEPR_ADDITIONAL_IGNORED_NAMESPACES; + if (!ignoredNSEnv) { + return ignoredNSConfig; + } + + const namespaces = ignoredNSEnv.split(",").map(ns => ns.trim()); + + // add alwaysIgnore.namespaces to the list + if (ignoredNSConfig) { + namespaces.push(...ignoredNSConfig); + } + return namespaces.filter(ns => ns.length > 0); +} + export async function generateWebhookRules(assets: Assets, isMutateWebhook: boolean): Promise { const { config, capabilities } = assets; @@ -61,7 +77,7 @@ export async function webhookConfig( const ignore: V1LabelSelectorRequirement[] = []; const { name, tls, config, apiToken, host } = assets; - const ignoreNS = concat(peprIgnoreNamespaces, config?.alwaysIgnore?.namespaces || []); + const ignoreNS = concat(peprIgnoreNamespaces, resolveIgnoreNamespaces(config?.alwaysIgnore?.namespaces)); // Add any namespaces to ignore if (ignoreNS) { diff --git a/src/lib/core/module.ts b/src/lib/core/module.ts index 427261a55..dde53af81 100644 --- a/src/lib/core/module.ts +++ b/src/lib/core/module.ts @@ -9,6 +9,7 @@ import { CapabilityExport, AdmissionRequest } from "../types"; import { setupWatch } from "../processors/watch-processor"; import { Log } from "../../lib"; import { V1PolicyRule as PolicyRule } from "@kubernetes/client-node"; +import { resolveIgnoreNamespaces } from "../assets/webhooks"; /** Custom Labels Type for package.json */ export interface CustomLabels { @@ -113,7 +114,7 @@ export class PeprModule { // Wait for the controller to be ready before setting up watches if (isWatchMode() || isDevMode()) { try { - setupWatch(capabilities, pepr?.alwaysIgnore?.namespaces); + setupWatch(capabilities, resolveIgnoreNamespaces(pepr?.alwaysIgnore?.namespaces)); } catch (e) { Log.error(e, "Error setting up watch"); process.exit(1); diff --git a/src/lib/processors/mutate-processor.ts b/src/lib/processors/mutate-processor.ts index bf1ad044c..5cc9f52a0 100644 --- a/src/lib/processors/mutate-processor.ts +++ b/src/lib/processors/mutate-processor.ts @@ -14,6 +14,7 @@ import { ModuleConfig } from "../core/module"; import { PeprMutateRequest } from "../mutate-request"; import { base64Encode, convertFromBase64Map, convertToBase64Map } from "../utils"; import { OnError } from "../../cli/init/enums"; +import { resolveIgnoreNamespaces } from "../assets/webhooks"; export interface Bindable { req: AdmissionRequest; @@ -169,7 +170,7 @@ export async function mutateProcessor( bind.binding, bind.req, bind.namespaces, - bind.config?.alwaysIgnore?.namespaces, + resolveIgnoreNamespaces(bind.config?.alwaysIgnore?.namespaces), ); if (shouldSkip !== "") { Log.debug(shouldSkip); diff --git a/src/lib/processors/validate-processor.ts b/src/lib/processors/validate-processor.ts index ebb98efb9..1fbd7372f 100644 --- a/src/lib/processors/validate-processor.ts +++ b/src/lib/processors/validate-processor.ts @@ -10,6 +10,7 @@ import Log from "../telemetry/logger"; import { convertFromBase64Map } from "../utils"; import { PeprValidateRequest } from "../validate-request"; import { ModuleConfig } from "../core/module"; +import { resolveIgnoreNamespaces } from "../assets/webhooks"; export async function processRequest( binding: Binding, @@ -78,7 +79,12 @@ export async function validateProcessor( } // Continue to the next action without doing anything if this one should be skipped - const shouldSkip = shouldSkipRequest(binding, req, namespaces, config?.alwaysIgnore?.namespaces); + const shouldSkip = shouldSkipRequest( + binding, + req, + namespaces, + resolveIgnoreNamespaces(config?.alwaysIgnore?.namespaces), + ); if (shouldSkip !== "") { Log.debug(shouldSkip); continue;