Skip to content

Commit

Permalink
chore: add typing to templates.ts (#1602)
Browse files Browse the repository at this point in the history
## Description

Adds typing to `templates.ts`

End to End Test:  <!-- if applicable -->  
(See [Pepr Excellent
Examples](https://github.com/defenseunicorns/pepr-excellent-examples))

## Related Issue

Relates to #1364 

## Type of change

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [x] Other (security config, docs update, etc)

## Checklist before merging
- [x] Unit,
[Journey](https://github.com/defenseunicorns/pepr/tree/main/journey),
[E2E Tests](https://github.com/defenseunicorns/pepr-excellent-examples),
[docs](https://github.com/defenseunicorns/pepr/tree/main/docs),
[adr](https://github.com/defenseunicorns/pepr/tree/main/adr) added or
updated as needed
- [x] [Contributor Guide
Steps](https://docs.pepr.dev/main/contribute/#submitting-a-pull-request)
followed

---------

Co-authored-by: Case Wylie <[email protected]>
  • Loading branch information
samayer12 and cmwylie19 authored Jan 6, 2025
1 parent cd03899 commit 1955962
Show file tree
Hide file tree
Showing 11 changed files with 73 additions and 43 deletions.
2 changes: 1 addition & 1 deletion docs/030_user-guide/120_customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ Below are the available configurations through `package.json`.
| Field | Description | Example Values |
|------------------|----------------------------------------|---------------------------------|
| `uuid` | Unique identifier for the module | `hub-operator` |
| `onError` | Behavior of the webhook failure policy | `reject`, `ignore` |
| `onError` | Behavior of the webhook failure policy | `audit`, `ignore`, `reject` |
| `webhookTimeout` | Webhook timeout in seconds | `1` - `30` |
| `customLabels` | Custom labels for namespaces | `{namespace: {}}` |
| `alwaysIgnore` | Conditions to always ignore | `{namespaces: []}` |
Expand Down
9 changes: 9 additions & 0 deletions src/cli/init/enums.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export enum RbacMode {
SCOPED = "scoped",
ADMIN = "admin",
}
export enum OnError {
AUDIT = "audit",
IGNORE = "ignore",
REJECT = "reject",
}
5 changes: 3 additions & 2 deletions src/cli/init/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import {
} from "./templates";
import { createDir, sanitizeName, write } from "./utils";
import { confirm, PromptOptions, walkthrough } from "./walkthrough";
import { ErrorList, Errors } from "../../lib/errors";
import { ErrorList } from "../../lib/errors";
import { OnError } from "./enums";

export default function (program: RootCmd): void {
let response = {} as PromptOptions;
Expand All @@ -33,7 +34,7 @@ export default function (program: RootCmd): void {
.option("--description <string>", "Explain the purpose of the new module.")
.option("--name <string>", "Set the name of the new module.")
.option("--skip-post-init", "Skip npm install, git init, and VSCode launch.")
.option(`--errorBehavior <${ErrorList.join("|")}>`, "Set an errorBehavior.", Errors.reject)
.option(`--errorBehavior <${ErrorList.join("|")}>`, "Set an errorBehavior.", OnError.REJECT)
.hook("preAction", async thisCommand => {
// TODO: Overrides for testing. Don't be so gross with Node CLI testing
// TODO: See pepr/#1140
Expand Down
32 changes: 30 additions & 2 deletions src/cli/init/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,38 @@ import settingsJSON from "../../templates/settings.json";
import tsConfigJSON from "../../templates/tsconfig.module.json";
import { sanitizeName } from "./utils";
import { InitOptions } from "../types";
import { V1PolicyRule as PolicyRule } from "@kubernetes/client-node";
import { OnError, RbacMode } from "./enums";

export const { dependencies, devDependencies, peerDependencies, scripts, version } = packageJSON;

export function genPkgJSON(opts: InitOptions, pgkVerOverride?: string) {
type peprPackageJSON = {
data: {
name: string;
version: string;
description: string;
keywords: string[];
engines: { node: string };
pepr: {
uuid: string;
onError: OnError;
webhookTimeout: number;
customLabels: { namespace: Record<string, string> };
alwaysIgnore: { namespaces: string[] };
includedFiles: string[];
env: object;
rbac?: PolicyRule[];
rbacMode?: RbacMode;
};
scripts: { "k3d-setup": string };
dependencies: { pepr: string; undici: string };
devDependencies: { typescript: string };
};
path: string;
print: string;
};

export function genPkgJSON(opts: InitOptions, pgkVerOverride?: string): peprPackageJSON {
// Generate a random UUID for the module based on the module name
const uuid = uuidv5(opts.name, uuidv4());
// Generate a name for the module based on the module name
Expand Down Expand Up @@ -72,7 +100,7 @@ export function genPkgJSON(opts: InitOptions, pgkVerOverride?: string) {
};
}

export function genPeprTS() {
export function genPeprTS(): { path: string; data: string } {
return {
path: "pepr.ts",
data: peprTS,
Expand Down
5 changes: 3 additions & 2 deletions src/cli/init/walkthrough.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
setName,
setErrorBehavior,
} from "./walkthrough";
import { OnError } from "./enums";

let consoleLog: jest.Spied<typeof console.log>;
let consoleError: jest.Spied<typeof console.error>;
Expand Down Expand Up @@ -41,7 +42,7 @@ describe("when processing input", () => {
const expected: PromptOptions = {
name: "My Test Module",
description: "A test module for Pepr",
errorBehavior: "reject",
errorBehavior: OnError.REJECT,
};

// Set values for the flag(s) under test by making a subset of (expected)
Expand Down Expand Up @@ -78,7 +79,7 @@ describe("when processing input", () => {
it("should prompt for errorBehavior when given invalid input", async () => {
const expected = { errorBehavior: "audit" };
prompts.inject(["audit"]);
const result = await setErrorBehavior("not-valid" as "reject"); // Type-Coercion forces invalid input
const result = await setErrorBehavior("not-valid" as OnError); // Type-Coercion forces invalid input
expect(result).toStrictEqual(expected);
});
});
Expand Down
15 changes: 7 additions & 8 deletions src/cli/init/walkthrough.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
import { promises as fs } from "fs";
import prompt, { Answers, PromptObject } from "prompts";

import { ErrorList, Errors } from "../../lib/errors";
import { eslint, gitignore, prettier, readme, tsConfig } from "./templates";
import { sanitizeName } from "./utils";
import { OnError } from "./enums";
import { ErrorList } from "../../lib/errors";

export type PromptOptions = {
name: string;
description: string;
errorBehavior: "audit" | "ignore" | "reject";
errorBehavior: OnError;
};

export type PartialPromptOptions = Partial<PromptOptions>;
Expand Down Expand Up @@ -70,30 +71,28 @@ async function setDescription(description?: string): Promise<Answers<string>> {
return prompt([askDescription]);
}

export async function setErrorBehavior(
errorBehavior?: "audit" | "ignore" | "reject",
): Promise<Answers<string>> {
export async function setErrorBehavior(errorBehavior?: OnError): Promise<Answers<string>> {
const askErrorBehavior: PromptObject = {
type: "select",
name: "errorBehavior",
message: "How do you want Pepr to handle errors encountered during K8s operations?",
choices: [
{
title: "Reject the operation",
value: Errors.reject,
value: OnError.REJECT,
description:
"In the event that Pepr is down or other module errors occur, the operation will not be allowed to continue. (Recommended for production.)",
},
{
title: "Ignore",
value: Errors.ignore,
value: OnError.IGNORE,
description:
"In the event that Pepr is down or other module errors occur, an entry will be generated in the Pepr Controller Log and the operation will be allowed to continue. (Recommended for development, not for production.)",
selected: true,
},
{
title: "Log an audit event",
value: Errors.audit,
value: OnError.AUDIT,
description:
"Pepr will continue processing and generate an entry in the Pepr Controller log as well as an audit event in the cluster.",
},
Expand Down
8 changes: 4 additions & 4 deletions src/lib/core/module.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { beforeEach, expect, jest, test, describe } from "@jest/globals";
import { clone } from "ramda";
import { Capability } from "./capability";
import { Schedule } from "./schedule";
import { Errors } from "../errors";
import { PackageJSON, PeprModule } from "./module";
import { CapabilityExport } from "../types";
import { OnError } from "../../cli/init/enums";

// Mock Controller
const startServerMock = jest.fn();
Expand Down Expand Up @@ -62,13 +62,13 @@ test("should reject invalid pepr onError conditions", () => {

test("should allow valid pepr onError conditions", () => {
const cfg = clone(packageJSON);
cfg.pepr.onError = Errors.audit;
cfg.pepr.onError = OnError.AUDIT;
expect(() => new PeprModule(cfg)).not.toThrow();

cfg.pepr.onError = Errors.ignore;
cfg.pepr.onError = OnError.IGNORE;
expect(() => new PeprModule(cfg)).not.toThrow();

cfg.pepr.onError = Errors.reject;
cfg.pepr.onError = OnError.REJECT;
expect(() => new PeprModule(cfg)).not.toThrow();
});

Expand Down
15 changes: 6 additions & 9 deletions src/lib/errors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

import { expect, test, describe } from "@jest/globals";
import * as fc from "fast-check";
import { Errors, ErrorList, ValidateError } from "./errors";
import { ErrorList, ValidateError } from "./errors";
import { OnError } from "../cli/init/enums";

describe("ValidateError Fuzz Testing", () => {
test("should only accept predefined error values", () => {
Expand Down Expand Up @@ -59,17 +60,13 @@ describe("ValidateError Property-Based Testing", () => {
});

test("Errors object should have correct properties", () => {
expect(Errors).toEqual({
audit: "audit",
ignore: "ignore",
reject: "reject",
expect(OnError).toEqual({
AUDIT: "audit",
IGNORE: "ignore",
REJECT: "reject",
});
});

test("ErrorList should contain correct values", () => {
expect(ErrorList).toEqual(["audit", "ignore", "reject"]);
});

test("ValidateError should not throw an error for valid errors", () => {
expect(() => {
ValidateError("audit");
Expand Down
11 changes: 3 additions & 8 deletions src/lib/errors.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors

export const Errors = {
audit: "audit",
ignore: "ignore",
reject: "reject",
};

export const ErrorList = Object.values(Errors);
import { OnError } from "../cli/init/enums";

export const ErrorList = Object.values(OnError) as string[];
/**
* Validate the error or throw an error
* @param error
*/
export function ValidateError(error = ""): void {
export function ValidateError(error: string = ""): void {
if (!ErrorList.includes(error)) {
throw new Error(`Invalid error: ${error}. Must be one of: ${ErrorList.join(", ")}`);
}
Expand Down
6 changes: 3 additions & 3 deletions src/lib/processors/mutate-processor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Event, Operation } from "../enums";
import { convertFromBase64Map, convertToBase64Map } from "../utils";
import { GenericClass, KubernetesObject } from "kubernetes-fluent-client";
import { MutateResponse } from "../k8s";
import { Errors } from "../errors";
import { OnError } from "../../cli/init/enums";

jest.mock("../utils");
const mockConvertFromBase64Map = jest.mocked(convertFromBase64Map);
Expand Down Expand Up @@ -243,7 +243,7 @@ describe("processRequest", () => {
);
const testBinding = { ...clone(defaultBinding), mutateCallback };
const testBindable = { ...clone(defaultBindable), binding: testBinding };
testBindable.config.onError = Errors.reject;
testBindable.config.onError = OnError.REJECT;
const testPeprMutateRequest = defaultPeprMutateRequest();
const testMutateResponse = clone(defaultMutateResponse);
const annote = `${defaultModuleConfig.uuid}.pepr.dev/${defaultBindable.name}`;
Expand All @@ -268,7 +268,7 @@ describe("processRequest", () => {
);
const testBinding = { ...clone(defaultBinding), mutateCallback };
const testBindable = { ...clone(defaultBindable), binding: testBinding };
testBindable.config.onError = Errors.audit;
testBindable.config.onError = OnError.AUDIT;
const testPeprMutateRequest = defaultPeprMutateRequest();
const testMutateResponse = clone(defaultMutateResponse);
const annote = `${defaultModuleConfig.uuid}.pepr.dev/${defaultBindable.name}`;
Expand Down
8 changes: 4 additions & 4 deletions src/lib/processors/mutate-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import { kind, KubernetesObject } from "kubernetes-fluent-client";
import { clone } from "ramda";

import { Capability } from "../core/capability";
import { Errors } from "../errors";
import { shouldSkipRequest } from "../filter/filter";
import { MutateResponse } from "../k8s";
import { AdmissionRequest, Binding } from "../types";
import Log from "../telemetry/logger";
import { ModuleConfig } from "../core/module";
import { PeprMutateRequest } from "../mutate-request";
import { base64Encode, convertFromBase64Map, convertToBase64Map } from "../utils";
import { OnError } from "../../cli/init/enums";

export interface Bindable {
req: AdmissionRequest;
Expand Down Expand Up @@ -117,11 +117,11 @@ export async function processRequest(
response.warnings.push(`Action failed: ${errorMessage}`);

switch (config.onError) {
case Errors.reject:
case OnError.REJECT:
response.result = "Pepr module configured to reject on error";
break;

case Errors.audit:
case OnError.AUDIT:
response.auditAnnotations = response.auditAnnotations || {};
response.auditAnnotations[Date.now()] = `Action failed: ${errorMessage}`;
break;
Expand Down Expand Up @@ -181,7 +181,7 @@ export async function mutateProcessor(

for (const bindable of bindables) {
({ wrapped, response } = await processRequest(bindable, wrapped, response));
if (config.onError === Errors.reject && response?.warnings!.length > 0) {
if (config.onError === OnError.REJECT && response?.warnings!.length > 0) {
return response;
}
}
Expand Down

0 comments on commit 1955962

Please sign in to comment.