From bb19ed4480931164330277475bea5d1bda744d6e Mon Sep 17 00:00:00 2001 From: Kim Schaefer Date: Wed, 23 Oct 2024 17:10:24 -0600 Subject: [PATCH 1/7] fix circular dep between types and mutate-request --- src/lib.ts | 2 +- src/lib/adjudicators.test.ts | 3 +- src/lib/adjudicators.ts | 3 +- src/lib/capability.test.ts | 4 +- src/lib/filter.ts | 5 +- src/lib/finalizer.test.ts | 4 +- src/lib/finalizer.ts | 6 +- src/lib/mutate-processor.ts | 2 +- src/lib/mutate-request.test.ts | 6 +- src/lib/mutate-request.ts | 149 +------------------------------ src/lib/mutate-types.ts | 137 ++++++++++++++++++++++++++++ src/lib/types.ts | 15 ++-- src/lib/validate-request.test.ts | 3 +- src/lib/validate-request.ts | 3 +- src/sdk/sdk.test.ts | 2 +- src/sdk/sdk.ts | 2 +- 16 files changed, 174 insertions(+), 172 deletions(-) create mode 100644 src/lib/mutate-types.ts diff --git a/src/lib.ts b/src/lib.ts index 5d3a530b4..5781154e9 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -4,7 +4,7 @@ import * as R from "ramda"; import { Capability } from "./lib/capability"; import Log from "./lib/logger"; import { PeprModule } from "./lib/module"; -import { PeprMutateRequest } from "./lib/mutate-request"; +import { PeprMutateRequest } from "./lib/mutate-types"; import * as PeprUtils from "./lib/utils"; import { PeprValidateRequest } from "./lib/validate-request"; import * as sdk from "./sdk/sdk"; diff --git a/src/lib/adjudicators.test.ts b/src/lib/adjudicators.test.ts index 5817d1b3a..aec379b0b 100644 --- a/src/lib/adjudicators.test.ts +++ b/src/lib/adjudicators.test.ts @@ -4,7 +4,8 @@ import { expect, describe, it } from "@jest/globals"; import * as sut from "./adjudicators"; import { KubernetesObject } from "kubernetes-fluent-client"; -import { AdmissionRequest, Binding, DeepPartial, Event, Operation } from "./types"; +import { AdmissionRequest, Binding, Event } from "./types"; +import { DeepPartial, Operation } from "./mutate-types"; describe("definesDeletionTimestamp", () => { //[ Binding, result ] diff --git a/src/lib/adjudicators.ts b/src/lib/adjudicators.ts index 36df1fcb6..708925188 100644 --- a/src/lib/adjudicators.ts +++ b/src/lib/adjudicators.ts @@ -1,7 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023-Present The Pepr Authors -import { Event, Operation } from "./types"; +import { Event } from "./types"; +import { Operation } from "./mutate-types"; import { __, allPass, diff --git a/src/lib/capability.test.ts b/src/lib/capability.test.ts index 9e7b30608..cf17d1d47 100644 --- a/src/lib/capability.test.ts +++ b/src/lib/capability.test.ts @@ -4,9 +4,9 @@ import { CapabilityCfg, FinalizeAction, MutateAction, ValidateAction, WatchLogAc import { a } from "../lib"; import { V1Pod } from "@kubernetes/client-node"; import { expect, describe, jest, beforeEach, it } from "@jest/globals"; -import { PeprMutateRequest } from "./mutate-request"; +import { PeprMutateRequest, Operation } from "./mutate-types"; import { PeprValidateRequest } from "./validate-request"; -import { Operation, AdmissionRequest } from "./types"; +import { AdmissionRequest } from "./types"; import { WatchPhase } from "kubernetes-fluent-client/dist/fluent/types"; import { Event } from "./types"; import { GenericClass } from "kubernetes-fluent-client"; diff --git a/src/lib/filter.ts b/src/lib/filter.ts index aa21bc1de..7967ca194 100644 --- a/src/lib/filter.ts +++ b/src/lib/filter.ts @@ -1,7 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023-Present The Pepr Authors -import { AdmissionRequest, Binding, Operation } from "./types"; +import { AdmissionRequest, Binding } from "./types"; +import { Operation } from "./mutate-types"; import { carriesIgnoredNamespace, carriedName, @@ -56,7 +57,7 @@ export function shouldSkipRequest( // prettier-ignore return ( - misboundDeleteWithDeletionTimestamp(binding) ? + misboundDeleteWithDeletionTimestamp(binding) ? `${prefix} Cannot use deletionTimestamp filter on a DELETE operation.` : mismatchedDeletionTimestamp(binding, obj) ? diff --git a/src/lib/finalizer.test.ts b/src/lib/finalizer.test.ts index 082d63a3d..2530b7348 100644 --- a/src/lib/finalizer.test.ts +++ b/src/lib/finalizer.test.ts @@ -5,8 +5,8 @@ import { beforeEach, describe, expect, it, jest } from "@jest/globals"; import { addFinalizer, removeFinalizer } from "./finalizer"; import { KubernetesObject, K8s, GenericClass, RegisterKind } from "kubernetes-fluent-client"; import { K8sInit } from "kubernetes-fluent-client/dist/fluent/types"; -import { AdmissionRequest, Operation } from "./types"; -import { PeprMutateRequest } from "./mutate-request"; +import { AdmissionRequest } from "./types"; +import { PeprMutateRequest, Operation } from "./mutate-types"; import { Binding } from "./types"; jest.mock("kubernetes-fluent-client"); diff --git a/src/lib/finalizer.ts b/src/lib/finalizer.ts index 85986b6cc..33eb6555f 100644 --- a/src/lib/finalizer.ts +++ b/src/lib/finalizer.ts @@ -3,9 +3,9 @@ import { K8s, KubernetesObject, RegisterKind } from "kubernetes-fluent-client"; import Log from "./logger"; -import { Binding, DeepPartial } from "./types"; -import { Operation } from "./types"; -import { PeprMutateRequest } from "./mutate-request"; +import { Binding } from "./types"; +import { Operation, DeepPartial } from "./mutate-types"; +import { PeprMutateRequest } from "./mutate-types"; export function addFinalizer(request: PeprMutateRequest) { // if a DELETE is being processed, don't add a finalizer diff --git a/src/lib/mutate-processor.ts b/src/lib/mutate-processor.ts index eef1f0ead..6cac8378c 100644 --- a/src/lib/mutate-processor.ts +++ b/src/lib/mutate-processor.ts @@ -11,7 +11,7 @@ import { MutateResponse } from "./k8s"; import { AdmissionRequest } from "./types"; import Log from "./logger"; import { ModuleConfig } from "./module"; -import { PeprMutateRequest } from "./mutate-request"; +import { PeprMutateRequest } from "./mutate-types"; import { base64Encode, convertFromBase64Map, convertToBase64Map } from "./utils"; export async function mutateProcessor( diff --git a/src/lib/mutate-request.test.ts b/src/lib/mutate-request.test.ts index 7a5c1d641..5ce9c7583 100644 --- a/src/lib/mutate-request.test.ts +++ b/src/lib/mutate-request.test.ts @@ -3,8 +3,8 @@ import { beforeEach, describe, expect, it } from "@jest/globals"; import { KubernetesObject } from "kubernetes-fluent-client"; -import { Operation, AdmissionRequest } from "./types"; -import { PeprMutateRequest } from "./mutate-request"; +import { AdmissionRequest } from "./types"; +import { PeprMutateRequest, Operation } from "./mutate-types"; describe("PeprMutateRequest", () => { let mockRequest: AdmissionRequest; @@ -92,7 +92,7 @@ describe("PeprMutateRequest", () => { object: undefined as unknown as KubernetesObject, }; - expect(() => new PeprMutateRequest(mockRequest)).toThrow("unable to load the request object into PeprRequest.RawP"); + expect(() => new PeprMutateRequest(mockRequest)).toThrow("Unable to load the request object into PeprRequest.Raw"); }); it("should merge the provided object with the current resource", () => { diff --git a/src/lib/mutate-request.ts b/src/lib/mutate-request.ts index 467ae0159..d26b69480 100644 --- a/src/lib/mutate-request.ts +++ b/src/lib/mutate-request.ts @@ -1,151 +1,10 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023-Present The Pepr Authors +import { PeprMutateRequest, Operation, AdmissionRequest, DeepPartial } from "./mutate-types"; import { KubernetesObject } from "kubernetes-fluent-client"; import { clone, mergeDeepRight } from "ramda"; -import { Operation, AdmissionRequest, DeepPartial } from "./types"; -/** - * The RequestWrapper class provides methods to modify Kubernetes objects in the context - * of a mutating webhook request. - */ -export class PeprMutateRequest { - Raw: T; - - #input: AdmissionRequest; - - get PermitSideEffects() { - return !this.#input.dryRun; - } - - /** - * Indicates whether the request is a dry run. - * @returns true if the request is a dry run, false otherwise. - */ - get IsDryRun() { - return this.#input.dryRun; - } - - /** - * Provides access to the old resource in the request if available. - * @returns The old Kubernetes resource object or null if not available. - */ - get OldResource() { - return this.#input.oldObject; - } - - /** - * Provides access to the request object. - * @returns The request object containing the Kubernetes resource. - */ - get Request() { - return this.#input; - } - - /** - * Creates a new instance of the action class. - * @param input - The request object containing the Kubernetes resource to modify. - */ - constructor(input: AdmissionRequest) { - this.#input = input; - - // If this is a DELETE operation, use the oldObject instead - if (input.operation.toUpperCase() === Operation.DELETE) { - this.Raw = clone(input.oldObject as T); - } else { - // Otherwise, use the incoming object - this.Raw = clone(input.object); - } - - if (!this.Raw) { - throw new Error("unable to load the request object into PeprRequest.RawP"); - } - } - - /** - * Deep merges the provided object with the current resource. - * - * @param obj - The object to merge with the current resource. - */ - Merge = (obj: DeepPartial) => { - this.Raw = mergeDeepRight(this.Raw, obj) as unknown as T; - }; - - /** - * Updates a label on the Kubernetes resource. - * @param key - The key of the label to update. - * @param value - The value of the label. - * @returns The current action instance for method chaining. - */ - SetLabel = (key: string, value: string) => { - const ref = this.Raw; - - ref.metadata = ref.metadata ?? {}; - ref.metadata.labels = ref.metadata.labels ?? {}; - ref.metadata.labels[key] = value; - - return this; - }; - - /** - * Updates an annotation on the Kubernetes resource. - * @param key - The key of the annotation to update. - * @param value - The value of the annotation. - * @returns The current action instance for method chaining. - */ - SetAnnotation = (key: string, value: string) => { - const ref = this.Raw; - - ref.metadata = ref.metadata ?? {}; - ref.metadata.annotations = ref.metadata.annotations ?? {}; - ref.metadata.annotations[key] = value; - - return this; - }; - - /** - * Removes a label from the Kubernetes resource. - * @param key - The key of the label to remove. - * @returns The current Action instance for method chaining. - */ - RemoveLabel = (key: string) => { - if (this.Raw.metadata?.labels?.[key]) { - delete this.Raw.metadata.labels[key]; - } - - return this; - }; - - /** - * Removes an annotation from the Kubernetes resource. - * @param key - The key of the annotation to remove. - * @returns The current Action instance for method chaining. - */ - RemoveAnnotation = (key: string) => { - if (this.Raw.metadata?.annotations?.[key]) { - delete this.Raw.metadata.annotations[key]; - } - - return this; - }; - - /** - * Check if a label exists on the Kubernetes resource. - * - * @param key the label key to check - * @returns - */ - HasLabel = (key: string) => { - return this.Raw.metadata?.labels?.[key] !== undefined; - }; - - /** - * Check if an annotation exists on the Kubernetes resource. - * - * @param key the annotation key to check - * @returns - */ - HasAnnotation = (key: string) => { - return this.Raw.metadata?.annotations?.[key] !== undefined; - }; -} +// The PeprMutateRequest class and other mutation logic are now in mutate-types.ts +// No additional changes needed in this file, just reusing the imported components. diff --git a/src/lib/mutate-types.ts b/src/lib/mutate-types.ts new file mode 100644 index 000000000..4ef827f57 --- /dev/null +++ b/src/lib/mutate-types.ts @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023-Present The Pepr Authors + +import { KubernetesObject } from "kubernetes-fluent-client"; +import { Logger } from "pino"; +import { GroupVersionKind, GenericClass } from "kubernetes-fluent-client"; +import { clone, mergeDeepRight } from "ramda"; + +// Basic operation type for mutation operations +export enum Operation { + CREATE = "CREATE", + UPDATE = "UPDATE", + DELETE = "DELETE", + CONNECT = "CONNECT", +} + +// AdmissionRequest interface for handling admission requests in the mutation context +export interface AdmissionRequest { + readonly uid: string; + readonly kind: GroupVersionKind; + readonly resource: GroupVersionResource; + readonly subResource?: string; + readonly requestKind?: GroupVersionKind; + readonly requestResource?: GroupVersionResource; + readonly requestSubResource?: string; + readonly name: string; + readonly namespace?: string; + readonly operation: Operation; + readonly userInfo: { + username?: string; + uid?: string; + groups?: string[]; + extra?: { [key: string]: string[] }; + }; + readonly object: T; + readonly oldObject?: T; + readonly dryRun?: boolean; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + readonly options?: any; +} + +// DeepPartial utility type for deep optional properties +export type DeepPartial = { + [P in keyof T]?: T[P] extends object ? DeepPartial : T[P]; +}; + +// MutateAction type for handling mutation callbacks +export type MutateAction> = ( + req: PeprMutateRequest, + logger?: Logger, +) => Promise | void | Promise> | PeprMutateRequest; + +// PeprMutateRequest class for mutation request handling +export class PeprMutateRequest { + Raw: T; + #input: AdmissionRequest; + + get PermitSideEffects() { + return !this.#input.dryRun; + } + + get IsDryRun() { + return this.#input.dryRun; + } + + get OldResource() { + return this.#input.oldObject; + } + + get Request() { + return this.#input; + } + + constructor(input: AdmissionRequest) { + this.#input = input; + // If this is a DELETE operation, use the oldObject instead + if (input.operation.toUpperCase() === Operation.DELETE) { + this.Raw = clone(input.oldObject as T); + } else { + // Otherwise, use the incoming object + this.Raw = clone(input.object); + } + + if (!this.Raw) { + throw new Error("Unable to load the request object into PeprRequest.Raw"); + } + } + + Merge = (obj: DeepPartial) => { + this.Raw = mergeDeepRight(this.Raw, obj) as unknown as T; + }; + + SetLabel = (key: string, value: string) => { + const ref = this.Raw; + ref.metadata = ref.metadata ?? {}; + ref.metadata.labels = ref.metadata.labels ?? {}; + ref.metadata.labels[key] = value; + return this; + }; + + SetAnnotation = (key: string, value: string) => { + const ref = this.Raw; + ref.metadata = ref.metadata ?? {}; + ref.metadata.annotations = ref.metadata.annotations ?? {}; + ref.metadata.annotations[key] = value; + return this; + }; + + RemoveLabel = (key: string) => { + if (this.Raw.metadata?.labels?.[key]) { + delete this.Raw.metadata.labels[key]; + } + return this; + }; + + RemoveAnnotation = (key: string) => { + if (this.Raw.metadata?.annotations?.[key]) { + delete this.Raw.metadata.annotations[key]; + } + return this; + }; + + HasLabel = (key: string) => { + return this.Raw.metadata?.labels?.[key] !== undefined; + }; + + HasAnnotation = (key: string) => { + return this.Raw.metadata?.annotations?.[key] !== undefined; + }; +} + +// GroupVersionResource interface for resource identification +export interface GroupVersionResource { + readonly group: string; + readonly version: string; + readonly resource: string; +} diff --git a/src/lib/types.ts b/src/lib/types.ts index ba5c66ff8..d9bc38b58 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -2,20 +2,21 @@ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors import { GenericClass, GroupVersionKind, KubernetesObject } from "kubernetes-fluent-client"; - +import { Operation } from "./mutate-types"; import { WatchPhase } from "kubernetes-fluent-client/dist/fluent/types"; +import { Logger } from "pino"; -import { PeprMutateRequest } from "./mutate-request"; +import { PeprMutateRequest } from "./mutate-types"; import { PeprValidateRequest } from "./validate-request"; -import { Logger } from "pino"; +//import { Logger } from "pino"; -export enum Operation { +/* export enum Operation { CREATE = "CREATE", UPDATE = "UPDATE", DELETE = "DELETE", CONNECT = "CONNECT", -} +} */ /** * Specifically for deploying images with a private registry */ @@ -43,9 +44,9 @@ export interface ResponseItem { /** * Recursively make all properties in T optional. */ -export type DeepPartial = { +/* export type DeepPartial = { [P in keyof T]?: T[P] extends object ? DeepPartial : T[P]; -}; +}; */ /** * The type of Kubernetes mutating webhook event that the action is registered for. diff --git a/src/lib/validate-request.test.ts b/src/lib/validate-request.test.ts index c84f9ad24..5ce5680ed 100644 --- a/src/lib/validate-request.test.ts +++ b/src/lib/validate-request.test.ts @@ -3,7 +3,8 @@ import { beforeEach, describe, expect, it } from "@jest/globals"; import { KubernetesObject } from "kubernetes-fluent-client"; -import { ValidateActionResponse, AdmissionRequest, Operation } from "./types"; +import { ValidateActionResponse, AdmissionRequest } from "./types"; +import { Operation } from "./mutate-types"; import { PeprValidateRequest } from "./validate-request"; describe("PeprValidateRequest", () => { let mockRequest: AdmissionRequest; diff --git a/src/lib/validate-request.ts b/src/lib/validate-request.ts index 2bc32fc9a..4e51e97e2 100644 --- a/src/lib/validate-request.ts +++ b/src/lib/validate-request.ts @@ -6,8 +6,9 @@ import { KubernetesObject } from "kubernetes-fluent-client"; import { clone } from "ramda"; -import { AdmissionRequest, Operation } from "./types"; +import { AdmissionRequest } from "./types"; import { ValidateActionResponse } from "./types"; +import { Operation } from "./mutate-types"; /** * The RequestWrapper class provides methods to modify Kubernetes objects in the context diff --git a/src/sdk/sdk.test.ts b/src/sdk/sdk.test.ts index 18e39ede8..401515408 100644 --- a/src/sdk/sdk.test.ts +++ b/src/sdk/sdk.test.ts @@ -3,7 +3,7 @@ import { expect, test } from "@jest/globals"; import { PeprValidateRequest } from "../lib/validate-request"; -import { PeprMutateRequest } from "../lib/mutate-request"; +import { PeprMutateRequest } from "../lib/mutate-types"; import { a } from "../lib"; import { containers, writeEvent, getOwnerRefFrom, sanitizeResourceName } from "./sdk"; import * as fc from "fast-check"; diff --git a/src/sdk/sdk.ts b/src/sdk/sdk.ts index 921eb7483..cc83d5386 100644 --- a/src/sdk/sdk.ts +++ b/src/sdk/sdk.ts @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors import { PeprValidateRequest } from "../lib/validate-request"; -import { PeprMutateRequest } from "../lib/mutate-request"; +import { PeprMutateRequest } from "../lib/mutate-types"; import { a } from "../lib"; import { V1OwnerReference } from "@kubernetes/client-node"; import { GenericKind } from "kubernetes-fluent-client"; From 1d82f2d163b39d8cea0e4bb6f19b8934564f3b57 Mon Sep 17 00:00:00 2001 From: Sam Mayer Date: Thu, 24 Oct 2024 08:52:15 -0500 Subject: [PATCH 2/7] Lower tolerance threshold for circular deps --- .github/workflows/dependency-review.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 19019eacb..66a9bd46a 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -35,11 +35,11 @@ jobs: run: | npx madge --circular --ts-config tsconfig.json --extensions ts,js src/ > tmp.log || true # Force exit 0 for post-processing tail -n +4 tmp.log > circular-deps.log - if [ $(wc -l < circular-deps.log) -gt 18 ]; then - echo "circular-deps.log has more than 18 circular dependencies." + if [ $(wc -l < circular-deps.log) -gt 17 ]; then + echo "circular-deps.log has more than 17 circular dependencies." wc -l circular-deps.log exit 1 else - echo "circular-deps.log has 18 or fewer circular dependencies." + echo "circular-deps.log has 17 or fewer circular dependencies." exit 0 fi \ No newline at end of file From 7b3747c2c4496a8b018d8f7f48d2513b0b6d4985 Mon Sep 17 00:00:00 2001 From: Kim Schaefer Date: Thu, 24 Oct 2024 09:41:36 -0600 Subject: [PATCH 3/7] updated circular dependency fix --- src/lib.ts | 2 +- src/lib/capability.test.ts | 3 +- src/lib/finalizer.test.ts | 3 +- src/lib/finalizer.ts | 2 +- src/lib/mutate-processor.ts | 2 +- src/lib/mutate-request.test.ts | 3 +- src/lib/mutate-request.ts | 91 ++++++++++++++++++++++++++++++++-- src/lib/mutate-types.ts | 89 +-------------------------------- src/lib/types.ts | 16 +----- src/sdk/sdk.test.ts | 2 +- src/sdk/sdk.ts | 2 +- 11 files changed, 100 insertions(+), 115 deletions(-) diff --git a/src/lib.ts b/src/lib.ts index 5781154e9..5d3a530b4 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -4,7 +4,7 @@ import * as R from "ramda"; import { Capability } from "./lib/capability"; import Log from "./lib/logger"; import { PeprModule } from "./lib/module"; -import { PeprMutateRequest } from "./lib/mutate-types"; +import { PeprMutateRequest } from "./lib/mutate-request"; import * as PeprUtils from "./lib/utils"; import { PeprValidateRequest } from "./lib/validate-request"; import * as sdk from "./sdk/sdk"; diff --git a/src/lib/capability.test.ts b/src/lib/capability.test.ts index cf17d1d47..352b65476 100644 --- a/src/lib/capability.test.ts +++ b/src/lib/capability.test.ts @@ -4,7 +4,8 @@ import { CapabilityCfg, FinalizeAction, MutateAction, ValidateAction, WatchLogAc import { a } from "../lib"; import { V1Pod } from "@kubernetes/client-node"; import { expect, describe, jest, beforeEach, it } from "@jest/globals"; -import { PeprMutateRequest, Operation } from "./mutate-types"; +import { Operation } from "./mutate-types"; +import { PeprMutateRequest } from "./mutate-request"; import { PeprValidateRequest } from "./validate-request"; import { AdmissionRequest } from "./types"; import { WatchPhase } from "kubernetes-fluent-client/dist/fluent/types"; diff --git a/src/lib/finalizer.test.ts b/src/lib/finalizer.test.ts index 2530b7348..e377f9225 100644 --- a/src/lib/finalizer.test.ts +++ b/src/lib/finalizer.test.ts @@ -6,7 +6,8 @@ import { addFinalizer, removeFinalizer } from "./finalizer"; import { KubernetesObject, K8s, GenericClass, RegisterKind } from "kubernetes-fluent-client"; import { K8sInit } from "kubernetes-fluent-client/dist/fluent/types"; import { AdmissionRequest } from "./types"; -import { PeprMutateRequest, Operation } from "./mutate-types"; +import { Operation } from "./mutate-types"; +import { PeprMutateRequest } from "./mutate-request"; import { Binding } from "./types"; jest.mock("kubernetes-fluent-client"); diff --git a/src/lib/finalizer.ts b/src/lib/finalizer.ts index 33eb6555f..e59aedf05 100644 --- a/src/lib/finalizer.ts +++ b/src/lib/finalizer.ts @@ -5,7 +5,7 @@ import { K8s, KubernetesObject, RegisterKind } from "kubernetes-fluent-client"; import Log from "./logger"; import { Binding } from "./types"; import { Operation, DeepPartial } from "./mutate-types"; -import { PeprMutateRequest } from "./mutate-types"; +import { PeprMutateRequest } from "./mutate-request"; export function addFinalizer(request: PeprMutateRequest) { // if a DELETE is being processed, don't add a finalizer diff --git a/src/lib/mutate-processor.ts b/src/lib/mutate-processor.ts index 6cac8378c..eef1f0ead 100644 --- a/src/lib/mutate-processor.ts +++ b/src/lib/mutate-processor.ts @@ -11,7 +11,7 @@ import { MutateResponse } from "./k8s"; import { AdmissionRequest } from "./types"; import Log from "./logger"; import { ModuleConfig } from "./module"; -import { PeprMutateRequest } from "./mutate-types"; +import { PeprMutateRequest } from "./mutate-request"; import { base64Encode, convertFromBase64Map, convertToBase64Map } from "./utils"; export async function mutateProcessor( diff --git a/src/lib/mutate-request.test.ts b/src/lib/mutate-request.test.ts index 5ce9c7583..4c380e140 100644 --- a/src/lib/mutate-request.test.ts +++ b/src/lib/mutate-request.test.ts @@ -4,7 +4,8 @@ import { beforeEach, describe, expect, it } from "@jest/globals"; import { KubernetesObject } from "kubernetes-fluent-client"; import { AdmissionRequest } from "./types"; -import { PeprMutateRequest, Operation } from "./mutate-types"; +import { Operation } from "./mutate-types"; +import { PeprMutateRequest } from "./mutate-request"; describe("PeprMutateRequest", () => { let mockRequest: AdmissionRequest; diff --git a/src/lib/mutate-request.ts b/src/lib/mutate-request.ts index d26b69480..7ce0d2d56 100644 --- a/src/lib/mutate-request.ts +++ b/src/lib/mutate-request.ts @@ -1,10 +1,93 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023-Present The Pepr Authors -import { PeprMutateRequest, Operation, AdmissionRequest, DeepPartial } from "./mutate-types"; +import { Operation, AdmissionRequest, DeepPartial } from "./mutate-types"; import { KubernetesObject } from "kubernetes-fluent-client"; import { clone, mergeDeepRight } from "ramda"; +import { Logger } from "pino"; +import { GenericClass } from "kubernetes-fluent-client"; -// The PeprMutateRequest class and other mutation logic are now in mutate-types.ts -// No additional changes needed in this file, just reusing the imported components. +// MutateAction type for handling mutation callbacks +export type MutateAction> = ( + req: PeprMutateRequest, + logger?: Logger, +) => Promise | void | Promise> | PeprMutateRequest; + +// PeprMutateRequest class for mutation request handling +export class PeprMutateRequest { + Raw: T; + #input: AdmissionRequest; + + get PermitSideEffects() { + return !this.#input.dryRun; + } + + get IsDryRun() { + return this.#input.dryRun; + } + + get OldResource() { + return this.#input.oldObject; + } + + get Request() { + return this.#input; + } + + constructor(input: AdmissionRequest) { + this.#input = input; + // If this is a DELETE operation, use the oldObject instead + if (input.operation.toUpperCase() === Operation.DELETE) { + this.Raw = clone(input.oldObject as T); + } else { + // Otherwise, use the incoming object + this.Raw = clone(input.object); + } + + if (!this.Raw) { + throw new Error("Unable to load the request object into PeprRequest.Raw"); + } + } + + Merge = (obj: DeepPartial) => { + this.Raw = mergeDeepRight(this.Raw, obj) as unknown as T; + }; + + SetLabel = (key: string, value: string) => { + const ref = this.Raw; + ref.metadata = ref.metadata ?? {}; + ref.metadata.labels = ref.metadata.labels ?? {}; + ref.metadata.labels[key] = value; + return this; + }; + + SetAnnotation = (key: string, value: string) => { + const ref = this.Raw; + ref.metadata = ref.metadata ?? {}; + ref.metadata.annotations = ref.metadata.annotations ?? {}; + ref.metadata.annotations[key] = value; + return this; + }; + + RemoveLabel = (key: string) => { + if (this.Raw.metadata?.labels?.[key]) { + delete this.Raw.metadata.labels[key]; + } + return this; + }; + + RemoveAnnotation = (key: string) => { + if (this.Raw.metadata?.annotations?.[key]) { + delete this.Raw.metadata.annotations[key]; + } + return this; + }; + + HasLabel = (key: string) => { + return this.Raw.metadata?.labels?.[key] !== undefined; + }; + + HasAnnotation = (key: string) => { + return this.Raw.metadata?.annotations?.[key] !== undefined; + }; +} diff --git a/src/lib/mutate-types.ts b/src/lib/mutate-types.ts index 4ef827f57..76ac01a63 100644 --- a/src/lib/mutate-types.ts +++ b/src/lib/mutate-types.ts @@ -2,9 +2,7 @@ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors import { KubernetesObject } from "kubernetes-fluent-client"; -import { Logger } from "pino"; -import { GroupVersionKind, GenericClass } from "kubernetes-fluent-client"; -import { clone, mergeDeepRight } from "ramda"; +import { GroupVersionKind } from "kubernetes-fluent-client"; // Basic operation type for mutation operations export enum Operation { @@ -44,91 +42,6 @@ export type DeepPartial = { [P in keyof T]?: T[P] extends object ? DeepPartial : T[P]; }; -// MutateAction type for handling mutation callbacks -export type MutateAction> = ( - req: PeprMutateRequest, - logger?: Logger, -) => Promise | void | Promise> | PeprMutateRequest; - -// PeprMutateRequest class for mutation request handling -export class PeprMutateRequest { - Raw: T; - #input: AdmissionRequest; - - get PermitSideEffects() { - return !this.#input.dryRun; - } - - get IsDryRun() { - return this.#input.dryRun; - } - - get OldResource() { - return this.#input.oldObject; - } - - get Request() { - return this.#input; - } - - constructor(input: AdmissionRequest) { - this.#input = input; - // If this is a DELETE operation, use the oldObject instead - if (input.operation.toUpperCase() === Operation.DELETE) { - this.Raw = clone(input.oldObject as T); - } else { - // Otherwise, use the incoming object - this.Raw = clone(input.object); - } - - if (!this.Raw) { - throw new Error("Unable to load the request object into PeprRequest.Raw"); - } - } - - Merge = (obj: DeepPartial) => { - this.Raw = mergeDeepRight(this.Raw, obj) as unknown as T; - }; - - SetLabel = (key: string, value: string) => { - const ref = this.Raw; - ref.metadata = ref.metadata ?? {}; - ref.metadata.labels = ref.metadata.labels ?? {}; - ref.metadata.labels[key] = value; - return this; - }; - - SetAnnotation = (key: string, value: string) => { - const ref = this.Raw; - ref.metadata = ref.metadata ?? {}; - ref.metadata.annotations = ref.metadata.annotations ?? {}; - ref.metadata.annotations[key] = value; - return this; - }; - - RemoveLabel = (key: string) => { - if (this.Raw.metadata?.labels?.[key]) { - delete this.Raw.metadata.labels[key]; - } - return this; - }; - - RemoveAnnotation = (key: string) => { - if (this.Raw.metadata?.annotations?.[key]) { - delete this.Raw.metadata.annotations[key]; - } - return this; - }; - - HasLabel = (key: string) => { - return this.Raw.metadata?.labels?.[key] !== undefined; - }; - - HasAnnotation = (key: string) => { - return this.Raw.metadata?.annotations?.[key] !== undefined; - }; -} - // GroupVersionResource interface for resource identification export interface GroupVersionResource { readonly group: string; diff --git a/src/lib/types.ts b/src/lib/types.ts index d9bc38b58..490a32dec 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -6,17 +6,9 @@ import { Operation } from "./mutate-types"; import { WatchPhase } from "kubernetes-fluent-client/dist/fluent/types"; import { Logger } from "pino"; -import { PeprMutateRequest } from "./mutate-types"; +import { PeprMutateRequest } from "./mutate-request"; import { PeprValidateRequest } from "./validate-request"; -//import { Logger } from "pino"; - -/* export enum Operation { - CREATE = "CREATE", - UPDATE = "UPDATE", - DELETE = "DELETE", - CONNECT = "CONNECT", -} */ /** * Specifically for deploying images with a private registry */ @@ -41,12 +33,6 @@ export interface ResponseItem { message: string; }; } -/** - * Recursively make all properties in T optional. - */ -/* export type DeepPartial = { - [P in keyof T]?: T[P] extends object ? DeepPartial : T[P]; -}; */ /** * The type of Kubernetes mutating webhook event that the action is registered for. diff --git a/src/sdk/sdk.test.ts b/src/sdk/sdk.test.ts index 401515408..18e39ede8 100644 --- a/src/sdk/sdk.test.ts +++ b/src/sdk/sdk.test.ts @@ -3,7 +3,7 @@ import { expect, test } from "@jest/globals"; import { PeprValidateRequest } from "../lib/validate-request"; -import { PeprMutateRequest } from "../lib/mutate-types"; +import { PeprMutateRequest } from "../lib/mutate-request"; import { a } from "../lib"; import { containers, writeEvent, getOwnerRefFrom, sanitizeResourceName } from "./sdk"; import * as fc from "fast-check"; diff --git a/src/sdk/sdk.ts b/src/sdk/sdk.ts index cc83d5386..921eb7483 100644 --- a/src/sdk/sdk.ts +++ b/src/sdk/sdk.ts @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors import { PeprValidateRequest } from "../lib/validate-request"; -import { PeprMutateRequest } from "../lib/mutate-types"; +import { PeprMutateRequest } from "../lib/mutate-request"; import { a } from "../lib"; import { V1OwnerReference } from "@kubernetes/client-node"; import { GenericKind } from "kubernetes-fluent-client"; From 5299d164439bd91164f08e000758162dbe3b4222 Mon Sep 17 00:00:00 2001 From: Kim Schaefer Date: Thu, 24 Oct 2024 12:14:28 -0600 Subject: [PATCH 4/7] CI fix --- src/lib/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/types.ts b/src/lib/types.ts index 490a32dec..61f0d2f8b 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -5,7 +5,6 @@ import { GenericClass, GroupVersionKind, KubernetesObject } from "kubernetes-flu import { Operation } from "./mutate-types"; import { WatchPhase } from "kubernetes-fluent-client/dist/fluent/types"; import { Logger } from "pino"; - import { PeprMutateRequest } from "./mutate-request"; import { PeprValidateRequest } from "./validate-request"; From d1e036c4e1ed7532dcb057b8ddb7b06d8ed89529 Mon Sep 17 00:00:00 2001 From: Kim Schaefer Date: Thu, 24 Oct 2024 13:22:49 -0600 Subject: [PATCH 5/7] updated comment for ci fix --- src/lib/mutate-types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/mutate-types.ts b/src/lib/mutate-types.ts index 76ac01a63..d4f15410a 100644 --- a/src/lib/mutate-types.ts +++ b/src/lib/mutate-types.ts @@ -4,7 +4,7 @@ import { KubernetesObject } from "kubernetes-fluent-client"; import { GroupVersionKind } from "kubernetes-fluent-client"; -// Basic operation type for mutation operations +// Operation type for mutation operations export enum Operation { CREATE = "CREATE", UPDATE = "UPDATE", From 85e27040c452a2455d86832ce8f4cb6aa572194e Mon Sep 17 00:00:00 2001 From: Kim Schaefer Date: Thu, 24 Oct 2024 13:26:03 -0600 Subject: [PATCH 6/7] updated comment for ci fix --- src/lib/mutate-types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/mutate-types.ts b/src/lib/mutate-types.ts index d4f15410a..219d1d405 100644 --- a/src/lib/mutate-types.ts +++ b/src/lib/mutate-types.ts @@ -12,7 +12,7 @@ export enum Operation { CONNECT = "CONNECT", } -// AdmissionRequest interface for handling admission requests in the mutation context +// AdmissionRequest interface for handling admission requests in mutation context export interface AdmissionRequest { readonly uid: string; readonly kind: GroupVersionKind; From 491cbf188f8b12af7b51ad98be4a337d5c948fa9 Mon Sep 17 00:00:00 2001 From: Kim Schaefer Date: Thu, 24 Oct 2024 13:44:27 -0600 Subject: [PATCH 7/7] update to PolicyRule --- src/lib/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/types.ts b/src/lib/types.ts index 371fcd96a..d2625901f 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -7,6 +7,7 @@ import { WatchPhase } from "kubernetes-fluent-client/dist/fluent/types"; import { Logger } from "pino"; import { PeprMutateRequest } from "./mutate-request"; import { PeprValidateRequest } from "./validate-request"; +import { V1PolicyRule as PolicyRule } from "@kubernetes/client-node"; /** * Specifically for deploying images with a private registry