Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
3c570f0
fix: exemption store overwrites
TristanHoladay May 14, 2024
bb5ab32
Merge branch 'main' into fix-exemptions
TristanHoladay May 14, 2024
a0ff450
refactor to kfc watch
TristanHoladay May 15, 2024
b56875a
pepr format
TristanHoladay May 15, 2024
22b344b
update reconciler tests; added WatchPhase enum to exemptions.
TristanHoladay May 15, 2024
b0833c6
Merge branch 'main' into fix-exemptions
TristanHoladay May 15, 2024
dae141d
Update src/pepr/policies/index.ts
TristanHoladay May 15, 2024
6c49098
fix tests, clean up validator
TristanHoladay May 16, 2024
8fe77c7
refactor to exemption controller logic.
TristanHoladay May 16, 2024
2f867af
Merge branch 'main' into fix-exemptions
TristanHoladay May 16, 2024
3d9bcf2
format and lint
TristanHoladay May 16, 2024
8f8bd0a
adding logging to exemption controller; podinfo back to back exemptio…
TristanHoladay May 16, 2024
8a66cf2
add verify for podinfo in exempted-app test
TristanHoladay May 16, 2024
9e9055f
replacing console.log with Log
TristanHoladay May 17, 2024
c5a3da5
adding logging to isExempt() check
TristanHoladay May 17, 2024
5c64a9b
fix: tasks, schema [ci skip]
mjnagel May 17, 2024
033fdbf
wip: fix update issue
mjnagel May 17, 2024
3d01bf3
Merge branch 'main' into fix-exemptions
mjnagel May 17, 2024
c4d5cba
some more refactors
mjnagel May 17, 2024
f95f00e
use ramda for list equality/union
mjnagel May 17, 2024
53dad72
fix test
mjnagel May 18, 2024
065d171
Merge branch 'main' into fix-exemptions
TristanHoladay May 21, 2024
9aa1dba
Exemption store (#421)
rjferguson21 May 21, 2024
9e92245
Merge branch 'main' into fix-exemptions
mjnagel May 21, 2024
78a395c
rebase
mjnagel May 22, 2024
ae57973
Merge branch 'main' into fix-exemptions
mjnagel May 23, 2024
7dcc839
Merge branch 'main' into fix-exemptions
mjnagel May 23, 2024
890061b
Merge branch 'main' into fix-exemptions
mjnagel May 24, 2024
e25b971
Merge branch 'main' into fix-exemptions
mjnagel May 24, 2024
6e9085f
Merge branch 'main' into fix-exemptions
mjnagel May 24, 2024
b98ea0a
Merge branch 'main' into fix-exemptions
rjferguson21 May 28, 2024
b258e35
Merge branch 'main' into fix-exemptions
mjnagel May 28, 2024
d70fd03
Merge branch 'main' into fix-exemptions
mjnagel May 29, 2024
dda5de6
chore: move CRD apply to admission controller
mjnagel May 30, 2024
0c83b4d
Merge branch 'main' into fix-exemptions
mjnagel May 30, 2024
81e412e
chore: switch crd register to be more visible/timed well
mjnagel May 30, 2024
05176f0
chore: timing debug
mjnagel May 30, 2024
86a355e
pepr format
mjnagel May 30, 2024
7be93c8
Merge branch 'main' into fix-exemptions
mjnagel Jun 3, 2024
6d3adba
wip: iife CRD registration timing
mjnagel Jun 3, 2024
3f74d40
Merge branch 'main' into fix-exemptions
mjnagel Jun 4, 2024
b2c4e62
error handling
mjnagel Jun 4, 2024
c14e5ad
chore: peprstore cleanup
mjnagel Jun 5, 2024
5034cba
Merge branch 'main' into fix-exemptions
mjnagel Jun 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
293 changes: 133 additions & 160 deletions src/pepr/operator/controllers/exemptions/exemptions.spec.ts

Large diffs are not rendered by default.

64 changes: 25 additions & 39 deletions src/pepr/operator/controllers/exemptions/exemptions.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { Log } from "pepr";
import { policies } from "../../../policies/index";
import { ExemptionElement, Matcher, Policy, UDSExemption } from "../../crd";

type StoredMatcher = Matcher & { owner: string };
type PolicyMap = Map<Policy, StoredMatcher[]>;
import { PolicyMap, StoredMatcher } from "../../../policies";
import { ExemptionElement, Policy, UDSExemption } from "../../crd";

export enum WatchPhase {
Added = "ADDED",
Modified = "MODIFIED",
Deleted = "DELETED",
Bookmark = "BOOKMARK",
Error = "ERROR",
}

const isSame = (a: StoredMatcher, b: StoredMatcher) => {
return (
Expand Down Expand Up @@ -89,35 +94,18 @@ function deleteIfMatchersRemoved(
}
}

// Iterate through local Map and update Store
function updateStore(policyMap: PolicyMap) {
const { Store } = policies;
for (const [policy, matchers] of policyMap.entries()) {
Log.debug(`Updating uds policy ${policy} exemptions: ${JSON.stringify(matchers)}`);
Store.setItem(policy, JSON.stringify(matchers));
}
}

function setupPolicyMap() {
const { Store } = policies;
const policyMap: PolicyMap = new Map();
const policyList = Object.values(Policy);

for (const p of policyList) {
policyMap.set(p, JSON.parse(Store.getItem(p) || "[]"));
}

return { policyMap, policyList };
}

// Add Exemptions to Pepr store as "policy": "[{...matcher, owner: uid}]"
//(Performance Optimization) Use local map to do aggregation before updating store
export function processExemptions(exempt: UDSExemption) {
const { policyMap, policyList } = setupPolicyMap();
export function processExemptions(
exempt: UDSExemption,
phase: WatchPhase,
exemptionMap: Map<Policy, StoredMatcher[]>,
) {
const currExemptMatchers: StoredMatcher[] = [];
const ownerId = exempt.metadata?.uid || "";

// Iterate through all policies -- important for removing exemptions if CR is updated
const policyList = Object.values(Policy);
for (const p of policyList) {
for (const e of exempt.spec?.exemptions ?? []) {
currExemptMatchers.push({
Expand All @@ -126,35 +114,33 @@ export function processExemptions(exempt: UDSExemption) {
});

// Add if exemption has this policy in its list
addIfIncludesPolicy(p, policyMap, e, ownerId);
addIfIncludesPolicy(p, exemptionMap, e, ownerId);

// Check if matcher no longer has this policy from previous CR version
deleteIfPolicyRemoved(p, policyMap, e, ownerId);
deleteIfPolicyRemoved(p, exemptionMap, e, ownerId);
}

// Check if policy should no longer have this matcher from previous CR version
deleteIfMatchersRemoved(p, policyMap, currExemptMatchers, ownerId);
deleteIfMatchersRemoved(p, exemptionMap, currExemptMatchers, ownerId);
}

updateStore(policyMap);
if (phase === WatchPhase.Deleted) {
removeExemptions(exempt, exemptionMap);
}
}

//(Performance Optimization) Use local map to do aggregation before updating store
export function removeExemptions(exempt: UDSExemption) {
const { policyMap } = setupPolicyMap();

export function removeExemptions(exempt: UDSExemption, exemptionMap: Map<Policy, StoredMatcher[]>) {
Log.debug(`Removing policy exemptions for ${exempt.metadata?.name}`);

// Loop through exemptions and remove matchers from policies in the local map
for (const e of exempt.spec?.exemptions ?? []) {
for (const p of e.policies) {
const matchers = policyMap.get(p) || [];
const matchers = exemptionMap.get(p) || [];
const filteredList = matchers.filter(m => {
if (!isSame(m, { ...e.matcher, owner: exempt.metadata?.uid || "" })) return m;
});
policyMap.set(p, filteredList);
exemptionMap.set(p, filteredList);
}
}

updateStore(policyMap);
}
13 changes: 0 additions & 13 deletions src/pepr/operator/crd/generated/exemption-v1alpha1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { GenericKind, RegisterKind } from "kubernetes-fluent-client";

export class Exemption extends GenericKind {
spec?: Spec;
status?: Status;
}

export interface Spec {
Expand Down Expand Up @@ -64,18 +63,6 @@ export enum Policy {
RestrictVolumeTypes = "RestrictVolumeTypes",
}

export interface Status {
observedGeneration?: number;
phase?: Phase;
titles?: string[];
}

export enum Phase {
Failed = "Failed",
Pending = "Pending",
Ready = "Ready",
}

RegisterKind(Exemption, {
group: "uds.dev",
version: "v1alpha1",
Expand Down
2 changes: 0 additions & 2 deletions src/pepr/operator/crd/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ export {
} from "./generated/package-v1alpha1";

export {
Phase as ExemptPhase,
Status as ExemptStatus,
ExemptionElement,
Matcher,
Kind as MatcherKind,
Expand Down
41 changes: 0 additions & 41 deletions src/pepr/operator/crd/sources/exemption/v1alpha1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,51 +4,10 @@ export const v1alpha1: V1CustomResourceDefinitionVersion = {
name: "v1alpha1",
served: true,
storage: true,
additionalPrinterColumns: [
{
name: "Status",
type: "string",
description: "The status of the exemption",
jsonPath: ".status.phase",
},
{
name: "Exemptions",
type: "string",
description: "Titles of the exemptions",
jsonPath: ".status.titles",
},
{
name: "Age",
type: "date",
description: "The age of the exemption",
jsonPath: ".metadata.creationTimestamp",
},
],
subresources: {
status: {},
},
schema: {
openAPIV3Schema: {
type: "object",
properties: {
status: {
type: "object",
properties: {
observedGeneration: {
type: "integer",
},
phase: {
enum: ["Pending", "Ready", "Failed"],
type: "string",
},
titles: {
type: "array",
items: {
type: "string",
},
},
},
} as V1JSONSchemaProps,
spec: {
type: "object",
properties: {
Expand Down
13 changes: 6 additions & 7 deletions src/pepr/operator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { a } from "pepr";
import { When } from "./common";

// Controller imports
import { removeExemptions } from "./controllers/exemptions/exemptions";
import { cleanupNamespace } from "./controllers/istio/injection";
import { purgeSSOClients } from "./controllers/keycloak/client-sync";
import {
Expand All @@ -14,11 +13,11 @@ import {

// CRD imports
import { UDSExemption, UDSPackage } from "./crd";
import { exemptValidator } from "./crd/validators/exempt-validator";
import { validator } from "./crd/validators/package-validator";

// Reconciler imports
import { exemptReconciler } from "./reconcilers/exempt-reconciler";
import { startExemptionWatch } from "../policies";
import { exemptValidator } from "./crd/validators/exempt-validator";
import { packageReconciler } from "./reconcilers/package-reconciler";

// Export the operator capability for registration in the root pepr.ts
Expand Down Expand Up @@ -61,8 +60,8 @@ When(UDSPackage)
// Enqueue the package for processing
.Reconcile(packageReconciler);

//Watch for changes to the UDSExemption CRD and cleanup exemptions in policies Store
When(UDSExemption).IsDeleted().Watch(removeExemptions);
// Watch for Exemptions and validate
When(UDSExemption).IsCreatedOrUpdated().Validate(exemptValidator);

// Watch for changes to the UDSExemption CRD to enqueue an exemption for processing
When(UDSExemption).IsCreatedOrUpdated().Validate(exemptValidator).Reconcile(exemptReconciler);
// KFC watch for exemptions and update in-memory map
void startExemptionWatch();
34 changes: 0 additions & 34 deletions src/pepr/operator/reconcilers/exempt-reconciler.ts

This file was deleted.

17 changes: 3 additions & 14 deletions src/pepr/operator/reconcilers/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { K8s, Log, kind } from "pepr";

import { Mock } from "jest-mock";
import { handleFailure, shouldSkip, updateStatus, writeEvent } from ".";
import { ExemptStatus, Phase, PkgStatus, UDSExemption, UDSPackage } from "../crd";
import { Phase, PkgStatus, UDSPackage } from "../crd";

jest.mock("pepr", () => ({
K8s: jest.fn(),
Expand Down Expand Up @@ -77,17 +77,6 @@ describe("updateStatus", () => {
status,
});
});

it("should update the status of an exemption", async () => {
const cr = { kind: "Exemption", metadata: { name: "test", namespace: "default" } };
const status = { phase: Phase.Ready };
await updateStatus(cr as GenericKind, status as ExemptStatus);
expect(K8s).toHaveBeenCalledWith(UDSExemption);
expect(PatchStatus).toHaveBeenCalledWith({
metadata: { name: "test", namespace: "default" },
status,
});
});
});

describe("writeEvent", () => {
Expand Down Expand Up @@ -149,7 +138,7 @@ describe("handleFailure", () => {
it("should handle a 404 error", async () => {
const err = { status: 404, message: "Not found" };
const cr = { metadata: { namespace: "default", name: "test" } };
await handleFailure(err, cr as UDSPackage | UDSExemption);
await handleFailure(err, cr as UDSPackage);
expect(Log.warn).toHaveBeenCalledWith({ err }, "Package metadata seems to have been deleted");
expect(Create).not.toHaveBeenCalled();
});
Expand All @@ -161,7 +150,7 @@ describe("handleFailure", () => {
apiVersion: "v1",
metadata: { namespace: "default", name: "test", generation: 1, uid: "1" },
};
await handleFailure(err, cr as UDSPackage | UDSExemption);
await handleFailure(err, cr as UDSPackage);
expect(Log.error).toHaveBeenCalledWith({ err }, "Error configuring default/test");

expect(Create).toHaveBeenCalledWith({
Expand Down
16 changes: 6 additions & 10 deletions src/pepr/operator/reconcilers/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { GenericKind } from "kubernetes-fluent-client";
import { K8s, Log, kind } from "pepr";

import { ExemptStatus, Phase, PkgStatus, UDSExemption, UDSPackage } from "../crd";
import { Phase, PkgStatus, UDSPackage } from "../crd";
import { Status } from "../crd/generated/package-v1alpha1";

const uidSeen = new Set<string>();
Expand All @@ -12,7 +12,7 @@ const uidSeen = new Set<string>();
* @param cr The custom resource to check
* @returns true if the CRD is pending or the current generation has been processed
*/
export function shouldSkip(cr: UDSExemption | UDSPackage) {
export function shouldSkip(cr: GenericKind) {
const isPending = cr.status?.phase === Phase.Pending;
const isCurrentGeneration = cr.metadata?.generation === cr.status?.observedGeneration;

Expand Down Expand Up @@ -41,12 +41,11 @@ export function shouldSkip(cr: UDSExemption | UDSPackage) {
* @param cr The custom resource to update
* @param status The new status
*/
export async function updateStatus(cr: GenericKind, status: PkgStatus | ExemptStatus) {
const model = cr.kind === "Package" ? UDSPackage : UDSExemption;
export async function updateStatus(cr: UDSPackage, status: PkgStatus) {
Log.debug(cr.metadata, `Updating status to ${status.phase}`);

// Update the status of the CRD
await K8s(model).PatchStatus({
await K8s(UDSPackage).PatchStatus({
metadata: {
name: cr.metadata!.name,
namespace: cr.metadata!.namespace,
Expand All @@ -62,7 +61,7 @@ export async function updateStatus(cr: GenericKind, status: PkgStatus | ExemptSt
* @param message A human-readable message for the event
* @param type The type of event to write
*/
export async function writeEvent(cr: GenericKind, event: Partial<kind.CoreEvent>) {
export async function writeEvent(cr: UDSPackage, event: Partial<kind.CoreEvent>) {
Log.debug(cr.metadata, `Writing event: ${event.message}`);

await K8s(kind.CoreEvent).Create({
Expand Down Expand Up @@ -93,10 +92,7 @@ export async function writeEvent(cr: GenericKind, event: Partial<kind.CoreEvent>
* @param err The error-like object
* @param cr The custom resource that failed
*/
export async function handleFailure(
err: { status: number; message: string },
cr: UDSPackage | UDSExemption,
) {
export async function handleFailure(err: { status: number; message: string }, cr: UDSPackage) {
const metadata = cr.metadata!;
const identifier = `${metadata.namespace}/${metadata.name}`;

Expand Down
4 changes: 2 additions & 2 deletions src/pepr/policies/exemptions/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { KubernetesObject } from "kubernetes-fluent-client";
import { Log, PeprMutateRequest, PeprValidateRequest } from "pepr";
import { policyExemptionMap } from "..";
import { Policy } from "../../operator/crd";
import { Store } from "../common";

/**
* Check a resource against an exemption list for use by the validation action.
Expand All @@ -14,7 +14,7 @@ export function isExempt<T extends KubernetesObject>(
request: PeprValidateRequest<T> | PeprMutateRequest<T>,
policy: Policy,
) {
const exemptList = JSON.parse(Store.getItem(policy) || "[]");
const exemptList = policyExemptionMap.get(policy) || [];

// Loop through the exempt list
for (const exempt of exemptList) {
Expand Down
Loading