diff --git a/docs/tutorials/writing-policies/typescript.md b/docs/tutorials/writing-policies/typescript.md deleted file mode 100644 index f9c8d6f5e57..00000000000 --- a/docs/tutorials/writing-policies/typescript.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -sidebar_label: Typescript -sidebar_position: 60 -title: Typescript -description: Writing Kubewarden policies with Typescript -keywords: [kubewarden, kubernetes, typescript] -doc-type: [tutorial] -doc-topic: [root-branch] -doc-persona: [kubewarden-developer] ---- - - - - - -As stated on the [official website](https://www.typescriptlang.org/): - -> TypeScript extends JavaScript by adding types. -> -> By understanding JavaScript, TypeScript saves you time catching errors and -> providing fixes before you run code. - -TypeScript can't target WebAssembly, however -[AssemblyScript](https://www.assemblyscript.org/) -is a **subset** of TypeScript designed for WebAssembly. - -## Current state - -Currently, there's no Kubewarden SDK for AssemblyScript. -Resources permitting, we hope to have on in the near future. - -However, there are limitations affecting AssemblyScript: - -* There's no built-in way to serialize and deserialize Classes to - and from JSON. - See [this issue](https://github.com/AssemblyScript/assemblyscript/issues/292) -* It *seems* there's no JSON path library for AssemblyScript - -## Example - -[This GitHub repository branch ](https://github.com/kubewarden/pod-privileged-policy/tree/assemblyscript-implementation) -has a Kubewarden Policy written in AssemblyScript. - -This repository has a series of GitHub Actions that automate the following tasks: - -* Run unit tests and code linting on pull requests and after code is merged into the main branch -* Build the policy in `release` mode and push it to an OCI registry as an artifact diff --git a/docs/tutorials/writing-policies/typescript/01-intro-typescript.md b/docs/tutorials/writing-policies/typescript/01-intro-typescript.md new file mode 100644 index 00000000000..396a573ce25 --- /dev/null +++ b/docs/tutorials/writing-policies/typescript/01-intro-typescript.md @@ -0,0 +1,94 @@ +--- +sidebar_label: Writing policies in TypeScript/JavaScript +sidebar_position: 010 +title: Writing policies in TypeScript/JavaScript +description: A tutorial introduction to writing policies in TypeScript/JavaScript. +keywords: [kubewarden, kubernetes, writing policies in TypeScript, writing policies in JavaScript] +doc-type: [tutorial] +doc-topic: [kubewarden, writing-policies, typescript, javascript, introduction] +doc-persona: [kubewarden-policy-developer] +--- + + + + + +:::note +TypeScript/JavaScript support for WebAssembly is rapidly evolving. +This page was last revised in September 2025. +::: + +As stated on the [official website](https://www.typescriptlang.org/): + +> TypeScript extends JavaScript by adding types. +> +> By understanding JavaScript, TypeScript saves you time catching errors and +> providing fixes before you run code. + +Kubewarden uses [Javy](https://github.com/bytecodealliance/javy) (a Bytecode Alliance project) to build WebAssembly binaries from JavaScript and TypeScript. + +> Javy takes your JavaScript code and executes it in a WebAssembly context. +> +> It features an embedded QuickJS engine compiled to WebAssembly that can execute JavaScript. +> +> The project provides both a CLI and a set of APIs for embedding and customizing the behavior when running JavaScript in WebAssembly. + +The Kubewarden project currently uses Javy for these reasons: + +- Mature JavaScript engine (QuickJS) compiled to WebAssembly. +- Support for [WASI interface](../wasi/01-intro-wasi.md) through custom host functions. +- Smaller binary sizes compared to other JavaScript-to-WebAssembly solutions. +- Active development and maintenance by the Bytecode Alliance. + +## Javy limitations + +Javy runs JavaScript in a sandboxed WebAssembly environment with certain constraints: + +- **WASI environment only**: Access limited to stdin/stdout/stderr and explicitly provided host capabilities. +- **No Node.js APIs**: Standard Node.js modules like `fs`, `http`, or `crypto` aren't available. +- **Limited standard library**: Only core JavaScript features and explicitly enabled APIs are accessible. +- **Single-threaded execution**: No support for Web Workers or multi-threading. +- **STDOUT restrictions**: Writing to STDOUT breaks policies - use STDERR for logging instead. + +Despite these limitations, Javy provides sufficient capabilities for writing effective Kubewarden validation policies through the hosts capabilities system. + +## Tooling + +Writing Kubewarden policies requires: + +- **Node.js**: Version 18 or higher. +- **npm**: For dependency management. +- **TypeScript**: Recommended for type safety (optional). + +:::warning +Ensure you're using Node.js 18 or higher. Older versions may not be compatible with the compilation toolchain. +::: + +These TypeScript/JavaScript libraries are useful when writing a Kubewarden policy: + +- [Kubewarden JavaScript SDK](https://github.com/kubewarden/policy-sdk-js): Provides structures and functions reducing the amount of code necessary. It also provides test helpers and access to all host capabilities. +- [Kubernetes TypeScript types](https://github.com/silverlyra/kubernetes-types): Provides TypeScript definitions for all Kubernetes resources, enabling type-safe policy development. + +The Kubewarden project provides a [template JavaScript/TypeScript policy project](https://github.com/kubewarden/js-policy-template) you can use to create Kubewarden policies. + +## Getting the toolchain + +The easiest way to get the complete toolchain is by using the Kubewarden JavaScript SDK, which includes the Javy compilation plug-in: + +```bash +npm install kubewarden-policy-sdk +``` + +The Javy plug-in binary is automatically included and you can find it at: + +``` +node_modules/kubewarden-policy-sdk/plugin/javy-plugin-kubewarden.wasm +``` + +## Tutorial prerequisites + +During this tutorial you need these tools on your development machine: + +- **Node.js**: Version 18 or higher with npm for dependency management. +- [**`bats`**](https://github.com/bats-core/bats-core): Used to write the tests and automate their execution. +- [**`kwctl`**](https://github.com/kubewarden/kwctl/releases): CLI tool provided by Kubewarden to run its policies outside of Kubernetes, among other actions. It's covered in [the testing policies section](../../testing-policies/index.md) of the documentation. diff --git a/docs/tutorials/writing-policies/typescript/02-scaffold.md b/docs/tutorials/writing-policies/typescript/02-scaffold.md new file mode 100644 index 00000000000..83db3c35454 --- /dev/null +++ b/docs/tutorials/writing-policies/typescript/02-scaffold.md @@ -0,0 +1,123 @@ +--- +sidebar_label: New validation policy +sidebar_position: 020 +title: Creating a new validation policy +description: Creating a new validation policy for Kubewarden using TypeScript. +keywords: [kubewarden, kubernetes, writing policies in TypeScript, new validation policy] +doc-type: [tutorial] +doc-topic: [kubewarden, writing-policies, typescript, creating a new validation policy] +doc-persona: [kubewarden-policy-developer] +--- + + + + + +This tutorial covers creating a policy that validates the hostnames of Pod objects. + +The policy is to reject all Pods that use one or more hostnames on the deny list. +You provide policy configuration using runtime settings. + +To summarize, the policy settings should look like this: + +```yaml +denied_hostnames: + - bad-host + - forbidden-host +``` + +The policy rejects the creation of this Pod: + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: nginx +spec: + hostname: bad-host + containers: + - name: nginx + image: nginx:latest +``` + +However, it accepts the creation of this Pod: + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: nginx +spec: + hostname: allowed-host + containers: + - name: nginx + image: nginx:latest +``` + +## Scaffolding a new policy project + +You can create a new policy project using the [template repository](https://github.com/kubewarden/js-policy-template). Select the "Use this template" green button near the top of the page and follow GitHub's wizard. + +Clone the repository locally and update the `package.json` file to reflect your policy details: + +```json +{ + "name": "your-policy-name", + "version": "1.0.0", + "description": "Your policy description", + "repository": { + "type": "git", + "url": "https://github.com/your-username/your-policy-name" + } +} +``` + +Make sure to use a repository path that matches your actual GitHub repository. + +## Testing + +Provided the necessary tools are in place, the `make all` command builds the `annotated-policy.wasm` target. The command `make e2e` runs tests using `bats` with `kwctl`. + +
+Output from the `make` commands + +```console +make all +npx webpack --config webpack.config.cjs +asset bundled.js 5.52 KiB [compared for emit] [minimized] (name: main) +asset types.d.ts 430 bytes [compared for emit] +asset index.d.ts 11 bytes [compared for emit] +./src/index.ts 3.84 KiB [built] [code generated] +./node_modules/kubewarden-policy-sdk/dist/bundle.js 3.85 KiB [built] [code generated] +webpack 5.101.3 compiled successfully in 2280 ms +npm install + +up to date, audited 400 packages in 2s + +58 packages are looking for funding + run `npm fund` for details + +found 0 vulnerabilities +``` + +```console +make e2e +npx webpack --config webpack.config.cjs +asset bundled.js 5.52 KiB [compared for emit] [minimized] (name: main) +asset types.d.ts 430 bytes [compared for emit] +asset index.d.ts 11 bytes [compared for emit] +./src/index.ts 3.84 KiB [built] [code generated] +./node_modules/kubewarden-policy-sdk/dist/bundle.js 3.85 KiB [built] [code generated] +webpack 5.101.3 compiled successfully in 1909 ms +bats e2e.bats +e2e.bats + ✓ reject because hostname is on the deny list + ✓ accept because hostname is not on the deny list + ✓ accept because the deny list is empty + ✓ accept because pod has no hostname set + ✓ accept non-pod resources + +5 tests, 0 failures +``` + +
diff --git a/docs/tutorials/writing-policies/typescript/03-policy-settings.md b/docs/tutorials/writing-policies/typescript/03-policy-settings.md new file mode 100644 index 00000000000..2f1ccac3bfb --- /dev/null +++ b/docs/tutorials/writing-policies/typescript/03-policy-settings.md @@ -0,0 +1,109 @@ +--- +sidebar_label: Defining policy settings +sidebar_position: 030 +title: Defining policy settings +description: Defining policy settings for a Kubewarden policy written in TypeScript. +keywords: [kubewarden, kubernetes, defining policy settings, TypeScript] +doc-type: [tutorial] +doc-topic: [kubewarden, writing-policies, typescript, defining-policy-settings] +doc-persona: [kubewarden-policy-developer] +--- + + + + + +:::danger Critical: Don't write logging information to STDOUT. + +Writing to STDOUT breaks policies. Instead, use STDERR for logging or the logging facility provided by the Kubewarden SDK. The policy's output to STDOUT must only contain the validation response. + +::: + +First, define the structure that holds the policy settings in `src/types.ts`. + +```ts +import type { PodSpec } from 'kubernetes-types/core/v1'; +import type { ObjectMeta } from 'kubernetes-types/meta/v1'; + +/** + * Interface representing policy settings structure. + */ +export interface PolicySettings { + // List of hostnames that are denied by the policy. + denied_hostnames?: string[]; +} + +/** + * Generic Kubernetes resource interface + */ +export interface KubernetesResource { + apiVersion: string; + kind: string; + metadata: ObjectMeta; + spec?: PodSpec | any; +} +``` + +## Building Settings instances + +Kubewarden policies use two functions that handle settings: + +- `validate`: Called during object validation. +- `validateSettings`: Called at policy load time. + +In `src/index.ts`, the `validate` function looks like: + +```ts +function validate(): void { + try { + const validationRequest = Validation.Validation.readValidationRequest(); + const settings: PolicySettings = validationRequest.settings || {}; + + // Policy logic... + } catch (err) { + console.error('Validation error:', err); + writeOutput(Validation.Validation.rejectRequest(`Validation failed: ${err}`)); + } +} +``` + +## Implementing Settings validation + +```ts +function validateSettings(): void { + try { + const settingsInput = Validation.Validation.readValidationRequest(); + const settings: PolicySettings = settingsInput as PolicySettings; + + if (settings.denied_hostnames && !Array.isArray(settings.denied_hostnames)) { + const errorResponse = new Validation.Validation.SettingsValidationResponse( + false, + 'denied_hostnames must be an array of strings', + ); + writeOutput(errorResponse); + return; + } + + for (const hostname of settings.denied_hostnames || []) { + if (typeof hostname !== 'string') { + const errorResponse = new Validation.Validation.SettingsValidationResponse( + false, + 'All hostnames in denied_hostnames must be strings', + ); + writeOutput(errorResponse); + return; + } + } + + const response = new Validation.Validation.SettingsValidationResponse(true); + writeOutput(response); + } catch (err) { + console.error('Settings validation error:', err); + const errorResponse = new Validation.Validation.SettingsValidationResponse( + false, + `Settings validation failed: ${err}`, + ); + writeOutput(errorResponse); + } +} +``` \ No newline at end of file diff --git a/docs/tutorials/writing-policies/typescript/04-validation.md b/docs/tutorials/writing-policies/typescript/04-validation.md new file mode 100644 index 00000000000..a83c4e39b11 --- /dev/null +++ b/docs/tutorials/writing-policies/typescript/04-validation.md @@ -0,0 +1,174 @@ +--- +sidebar_label: Validation logic +sidebar_position: 040 +title: Writing the validation logic +description: A tutorial on writing validation logic for a Kubewarden policy using TypeScript. +keywords: [kubewarden, kubernetes, writing policies, typescript] +doc-type: [tutorial] +doc-topic: [kubewarden, writing-policies, typescript, validation-logic] +doc-persona: [kubewarden-policy-developer] +--- + + + + + +:::danger Critical: Don't write logging information to STDOUT. + +Writing to STDOUT breaks policies. Instead, use STDERR for logging or the logging facility provided by the Kubewarden SDK. The policy's output to STDOUT must only contain the validation response. + +::: + +The validation logic goes in the `src/index.ts` file. + +Your validation logic needs to: + +- Get the relevant information from the incoming validation request. +- Return a response based on the input and the policy settings. + +The incoming request contains a JSON object with the Kubernetes resource to validate. +You can access this JSON object data through the Kubewarden SDK's helper functions. + +## The `validate` function + +The policy provided by the template already has a `validate` function in `src/index.ts`. +You can use it there, adding your logic to reject Pods with denied hostnames. + +This is how the function should look once complete: + +```typescript +/** + * Validates the incoming request against policy settings. + * Accepts or rejects the request based on denied hostnames. + */ +function validate(): void { + try { + // NOTE 1 + // Read the validation request payload + const validationRequest = Validation.Validation.readValidationRequest(); + + // NOTE 2 + // Extract policy settings from the validation request + const settings: PolicySettings = validationRequest.settings || {}; + + // NOTE 3 + // Extract the Kubernetes object (Pod) from the validation request + const resource = getKubernetesResource(validationRequest); + if (!resource) { + writeOutput(Validation.Validation.rejectRequest('Failed to parse Kubernetes resource.')); + return; + } + + // Only process Pod resources + if (resource.kind !== 'Pod') { + writeOutput(Validation.Validation.acceptRequest()); + return; + } + + // NOTE 4 + // Extract hostname from the Pod spec + const hostname = getPodHostname(resource as Pod); + const deniedHostnames = settings.denied_hostnames || []; + + // NOTE 5 + // Validate the hostname against the deny list + if (!hostname) { + writeOutput(Validation.Validation.acceptRequest()); + return; + } + + if (deniedHostnames.includes(hostname)) { + writeOutput( + Validation.Validation.rejectRequest( + `Pod hostname '${hostname}' is not allowed. Denied hostnames: [${deniedHostnames.join(', ')}]` + ), + ); + } else { + writeOutput(Validation.Validation.acceptRequest()); + } + } catch (err) { + console.error('Validation error:', err); + writeOutput(Validation.Validation.rejectRequest(`Validation failed: ${err}`)); + } +} +``` + +### What each NOTE does: + +- **NOTE 1**: Read the incoming validation request using `readValidationRequest()`. +- **NOTE 2**: Extract user-defined settings from the validation request (for example, denied hostnames). +- **NOTE 3**: Parse the Kubernetes object (expected to be a Pod) from the request payload. +- **NOTE 4**: Extract the hostname field from the Pod's spec section. +- **NOTE 5**: Compare the hostname against the denied list and return an appropriate response. + +## Helper functions + +The policy uses several helper functions to process the validation request: + +### getKubernetesResource + +This function extracts the Kubernetes resource from the validation request: + +```typescript +/** + * Safely parses and extracts the Kubernetes resource from the validation request. + * + * @param {ValidationRequest} validationRequest - The validation request object. + * @returns {KubernetesResource | undefined} The parsed Kubernetes resource if available. + */ +function getKubernetesResource(validationRequest: ValidationRequest): KubernetesResource | undefined { + try { + let requestObject: string | KubernetesResource | undefined = validationRequest.request?.object; + if (typeof requestObject === 'string') { + requestObject = JSON.parse(requestObject) as unknown as KubernetesResource; + } else if (requestObject === undefined) { + return undefined; + } + return requestObject as KubernetesResource; + } catch (error) { + console.error('Error parsing Kubernetes resource:', error); + return undefined; + } +} +``` + +This function handles the case where the Kubernetes object is a JSON string or an already parsed object. + +### getPodHostname + +This function extracts the hostname from a Pod resource: + +```typescript +/** + * Extracts the hostname from a Pod resource. + * + * @param {Pod} pod - The Pod resource. + * @returns {string | undefined} The hostname if set, otherwise undefined. + */ +function getPodHostname(pod: Pod): string | undefined { + return pod.spec?.hostname; +} +``` + +This function extracts the hostname from the Pod's specification. + +## Policy entry point + +The policy uses a switch statement to handle different actions: + +```typescript +const action = policyAction(); +switch (action) { + case 'validate': + validate(); + break; + case 'validate-settings': + validateSettings(); + break; + default: + console.error('Unknown action:', action); + writeOutput(new Validation.Validation.ValidationResponse(false, 'Unknown policy action')); +} +``` + +The Kubewarden Javy plug-in provides the `policyAction()` function indicating whether the policy should validate a resource or validate its settings. diff --git a/docs/tutorials/writing-policies/typescript/05-e2e-tests.md b/docs/tutorials/writing-policies/typescript/05-e2e-tests.md new file mode 100644 index 00000000000..e85d7594664 --- /dev/null +++ b/docs/tutorials/writing-policies/typescript/05-e2e-tests.md @@ -0,0 +1,302 @@ +--- +sidebar_label: End-to-end testing +sidebar_position: 050 +title: End-to-end testing +description: Writing end-to-end tests for a Kubewarden policy using TypeScript. +keywords: [kubewarden, kubernetes, testing policies, typescript, e2e testing] +doc-type: [tutorial] +doc-topic: [kubewarden, writing-policies, typescript, end-to-end-testing] +doc-persona: [kubewarden-policy-developer] +--- + + + + + +This section shows how you can write end-to-end tests running against the actual WebAssembly binary produced by the Javy compilation toolchain. + +## Prerequisites + +Recall, you need these tools on your development machine: + +- **`bats`**: Used to write the tests and automate their execution. +- **`kwctl`**: CLI tool provided by Kubewarden to run its policies outside of Kubernetes, among other actions. It's covered in [the testing policies section](../../testing-policies/index.md) of the documentation. + +## Writing tests + +You'll be using `bats` to write and automate your tests. Each test has the following steps: + +1. Run the policy using `kwctl run` directly with the JSON resource file. +1. Perform assertions against the output produced by `kwctl`. + +All the end-to-end tests go in a file called `e2e.bats`. The project scaffolding project includes an example, `e2e.bats`. You need to extend its contents to provide comprehensive test coverage for your policy behavior. + +## Test data files + +The end-to-end tests use JSON files containing Kubernetes resources. You should have these test data files: + +**`test_data/pod.json`** - A Pod without a hostname: + +```json +{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "test-pod" + }, + "spec": { + "containers": [ + { + "name": "nginx", + "image": "nginx:latest" + } + ] + } +} +``` + +**`test_data/pod_with_hostname.json`** - A Pod with a hostname: + +```json +{ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "test-pod" + }, + "spec": { + "hostname": "test-hostname", + "containers": [ + { + "name": "nginx", + "image": "nginx:latest" + } + ] + } +} +``` + +**`test_data/deployment.json`** - A non-Pod resource: + +```json +{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "test-deployment" + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app": "test" + } + }, + "template": { + "metadata": { + "labels": { + "app": "test" + } + }, + "spec": { + "containers": [ + { + "name": "nginx", + "image": "nginx:latest" + } + ] + } + } + } +} +``` + +## Basic test cases + +The template already provides several basic tests. Here's how the complete `e2e.bats` file should look: + +```bash +#!/usr/bin/env bats +``` + +Here are the existing tests with explanations: + +### Test 1: Reject denied hostnames + +```bash +@test "reject because hostname is on deny list" { + run kwctl run annotated-policy.wasm -r test_data/pod_with_hostname.json --settings-json '{"denied_hostnames": ["forbidden-host", "test-hostname"]}' + + # this prints the output when one of the checks below fails + echo "output = ${output}" + + # request rejected + [ "$status" -eq 0 ] + [ $(expr "$output" : '.*allowed.*false') -ne 0 ] + [ $(expr "$output" : ".*Pod hostname 'test-hostname' is not allowed.*") -ne 0 ] +} +``` + +This test ensures the policy rejects Pods when their hostname is in the deny list. + +### Test 2: Accept allowed hostnames + +```bash +@test "accept because hostname is not on the deny list" { + run kwctl run annotated-policy.wasm -r test_data/pod_with_hostname.json --settings-json '{"denied_hostnames": ["forbidden-host"]}' + + # this prints the output when one of the checks below fails + echo "output = ${output}" + + # request accepted + [ "$status" -eq 0 ] + [ $(expr "$output" : '.*allowed.*true') -ne 0 ] +} +``` + +This test verifies the policy accepts Pods when their hostname isn't in the deny list. + +### Test 3: Accept when no settings provided + +```bash +@test "accept because the deny list is empty" { + run kwctl run annotated-policy.wasm -r test_data/pod_with_hostname.json + + # this prints the output when one of the checks below fails + echo "output = ${output}" + + # request accepted + [ "$status" -eq 0 ] + [ $(expr "$output" : '.*allowed.*true') -ne 0 ] +} +``` + +This test ensures the policy accepts requests when you provide no settings. + +### Test 4: Accept pods without hostnames + +```bash +@test "accept because pod has no hostname set" { + run kwctl run annotated-policy.wasm -r test_data/pod.json --settings-json '{"denied_hostnames": ["forbidden-host"]}' + + # this prints the output when one of the checks below fails + echo "output = ${output}" + + # request accepted (no hostname to check) + [ "$status" -eq 0 ] + [ $(expr "$output" : '.*allowed.*true') -ne 0 ] +} +``` + +This test verifies that the policy accepts Pods without hostnames regardless of the deny list. + +### Test 5: Accept non-Pod resources + +```bash +@test "accept non-pod resources" { + run kwctl run annotated-policy.wasm -r test_data/deployment.json --settings-json '{"denied_hostnames": ["forbidden-host"]}' + + # this prints the output when one of the checks below fails + echo "output = ${output}" + + # request accepted (not a pod) + [ "$status" -eq 0 ] + [ $(expr "$output" : '.*allowed.*true') -ne 0 ] +} +``` + +This test ensures the policy accepts resources that aren't Pods, since the policy only validates Pod hostnames. + +## Extended test coverage + +You can extend the test coverage by adding more test scenarios: + +### Test 6: Settings validation + +You can also add tests to verify settings validation works correctly: + +```bash +@test "accept valid settings" { + run kwctl run annotated-policy.wasm -r test_data/pod.json --settings-json '{"denied_hostnames": ["host1", "host2"]}' + + # this prints the output when one of the checks below fails + echo "output = ${output}" + + # settings are valid, request processed normally + [ "$status" -eq 0 ] + [ $(expr "$output" : '.*allowed.*true') -ne 0 ] +} +``` + +### Test 7: Edge cases + +```bash +@test "reject with multiple denied hostnames" { + run kwctl run annotated-policy.wasm -r test_data/pod_with_hostname.json --settings-json '{"denied_hostnames": ["bad-host", "test-hostname", "forbidden-host"]}' + + # this prints the output when one of the checks below fails + echo "output = ${output}" + + # request rejected + [ "$status" -eq 0 ] + [ $(expr "$output" : '.*allowed.*false') -ne 0 ] + [ $(expr "$output" : ".*Pod hostname 'test-hostname' is not allowed.*") -ne 0 ] +} +``` + +```bash +@test "accept with empty denied hostnames array" { + run kwctl run annotated-policy.wasm -r test_data/pod_with_hostname.json --settings-json '{"denied_hostnames": []}' + + # this prints the output when one of the checks below fails + echo "output = ${output}" + + # request accepted + [ "$status" -eq 0 ] + [ $(expr "$output" : '.*allowed.*true') -ne 0 ] +} +``` + +## Running the tests + +You can run all the end-to-end tests by using this command: + +```console +make e2e-tests +``` + +This produces output like: + +```console +bats e2e.bats +e2e.bats + ✓ reject because hostname is on the deny list + ✓ accept because hostname is not on the deny list + ✓ accept because the deny list is empty + ✓ accept because pod has no hostname set + ✓ accept non-pod resources + ✓ accept valid settings + ✓ reject with multiple denied hostnames + ✓ accept with empty denied hostnames array + +8 tests, 0 failures +``` + +## Understanding test output + +Each test uses `kwctl` to run the policy and checks: + +- **Exit status**: The command `kwctl` should exit with status 0 for successful policy execution. +- **Allowed field**: The JSON output contains an `allowed` field indicating if the request was accepted. +- **Message content**: For rejected requests, the output contains a descriptive error message. + +The `echo "output = ${output}"` statements in each test help with debugging by showing the actual policy output when a test fails. + +## Conclusion + +The end-to-end tests provide comprehensive coverage of the policy behavior by testing against the actual WebAssembly binary. This ensures that the policy works correctly when deployed in Kubewarden, not just in the TypeScript development environment. + +:::tip +For more comprehensive examples of end-to-end tests, you can explore the [policy-sdk-js source code](https://github.com/kubewarden/policy-sdk-js/blob/main/demo_policy/e2e.bats), which includes extensive e2e tests demonstrating each of Kubewarden's host capabilities. These tests can serve as inspiration for testing more advanced policy features like networking, cryptographic operations, and OCI registry interactions. +::: + diff --git a/docs/tutorials/writing-policies/typescript/_category_.json b/docs/tutorials/writing-policies/typescript/_category_.json new file mode 100644 index 00000000000..835062a6e26 --- /dev/null +++ b/docs/tutorials/writing-policies/typescript/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "TypeScript", + "position": 100, + "collapsed": true +}