Skip to content

Commit 374c58c

Browse files
authored
Merge pull request #699 from kubewarden/ts-tutorial
feat(typescript): writing policies with typescript/javascript tutorial.
2 parents 27f64fc + 1136667 commit 374c58c

File tree

9 files changed

+764
-49
lines changed

9 files changed

+764
-49
lines changed

docs/tutorials/writing-policies/dotnet.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
sidebar_label: C#
3-
sidebar_position: 40
3+
sidebar_position: 50
44
title: C#
55
description: Kubewarden policies using C# and .NET
66
keywords: [kubewarden, kubernetes, writing policies, c#, .net]

docs/tutorials/writing-policies/swift.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
sidebar_label: Swift
3-
sidebar_position: 50
3+
sidebar_position: 60
44
title: Swift
55
description: Kubewarden policies with Swift
66
keywords: [kubewarden, kubernetes, writing policies, swift]

docs/tutorials/writing-policies/typescript.md

Lines changed: 0 additions & 47 deletions
This file was deleted.
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
---
2+
sidebar_label: Writing policies in TypeScript/JavaScript
3+
sidebar_position: 010
4+
title: Writing policies in TypeScript/JavaScript
5+
description: A tutorial introduction to writing policies in TypeScript/JavaScript.
6+
keywords: [kubewarden, kubernetes, writing policies in TypeScript, writing policies in JavaScript]
7+
doc-type: [tutorial]
8+
doc-topic: [kubewarden, writing-policies, typescript, javascript, introduction]
9+
doc-persona: [kubewarden-policy-developer]
10+
---
11+
12+
<head>
13+
<link rel="canonical" href="https://docs.kubewarden.io/tutorials/writing-policies/intro-typescript"/>
14+
</head>
15+
16+
:::note
17+
TypeScript/JavaScript support for WebAssembly is rapidly evolving.
18+
This page was last revised in November 2025.
19+
:::
20+
21+
As stated on the [official website](https://www.typescriptlang.org/):
22+
23+
> TypeScript extends JavaScript by adding types.
24+
>
25+
> By understanding JavaScript, TypeScript saves you time catching errors and
26+
> providing fixes before you run code.
27+
28+
Kubewarden uses [Javy](https://github.com/bytecodealliance/javy) (a Bytecode Alliance project) to build WebAssembly binaries from JavaScript and TypeScript.
29+
30+
> Javy takes your JavaScript code and executes it in a WebAssembly context.
31+
>
32+
> It features an embedded QuickJS engine compiled to WebAssembly that can execute JavaScript.
33+
>
34+
> The project provides both a CLI and a set of APIs for embedding and customizing the behavior when running JavaScript in WebAssembly.
35+
36+
The Kubewarden project currently uses Javy for these reasons:
37+
38+
- Mature JavaScript engine (QuickJS) compiled to WebAssembly.
39+
- Support for [WASI interface](../wasi/01-intro-wasi.md) through custom host functions.
40+
- Smaller binary sizes compared to other JavaScript-to-WebAssembly solutions.
41+
- Active development and maintenance by the Bytecode Alliance.
42+
43+
## Javy limitations
44+
45+
Javy runs JavaScript in a sandboxed WebAssembly environment with certain constraints:
46+
47+
- **WASI environment only**: Access limited to stdin/stdout/stderr and explicitly provided host capabilities.
48+
- **No Node.js APIs**: Standard Node.js modules like `fs`, `http`, or `crypto` aren't available.
49+
- **Limited standard library**: Only core JavaScript features and explicitly enabled APIs are accessible.
50+
- **Single-threaded execution**: No support for Web Workers or multi-threading.
51+
52+
Despite these limitations, Javy provides sufficient capabilities for writing effective Kubewarden validation policies through the hosts capabilities system.
53+
54+
:::warning
55+
Writing to STDOUT breaks policies - use STDERR for logging instead.
56+
:::
57+
58+
## Tooling
59+
60+
Writing Kubewarden policies requires:
61+
62+
- **Node.js**: JavaScript runtime.
63+
- **npm**: For dependency management.
64+
- **TypeScript**: Recommended for type safety (optional).
65+
66+
:::warning
67+
Ensure you're using Node.js 18 or higher. Older versions may not be compatible with the compilation toolchain.
68+
:::
69+
70+
These TypeScript/JavaScript libraries are useful when writing a Kubewarden policy:
71+
72+
- [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.
73+
- [Kubernetes TypeScript types](https://github.com/silverlyra/kubernetes-types): Provides TypeScript definitions for all Kubernetes resources, enabling type-safe policy development.
74+
75+
The Kubewarden project provides a [template JavaScript/TypeScript policy project](https://github.com/kubewarden/js-policy-template) you can use to create Kubewarden policies.
76+
77+
## Getting the toolchain
78+
79+
The easiest way to get the toolchain is by using the Kubewarden JavaScript SDK, which includes the Javy compilation plug-in:
80+
81+
```bash
82+
npm install kubewarden-policy-sdk
83+
```
84+
85+
The Javy plug-in binary is automatically included and you can find it at:
86+
87+
```
88+
node_modules/kubewarden-policy-sdk/plugin/javy-plugin-kubewarden.wasm
89+
```
90+
91+
## Tutorial prerequisites
92+
93+
During this tutorial you need these tools on your development machine:
94+
95+
- **Node.js**: Version 18 or higher with npm for dependency management.
96+
- [**`bats`**](https://github.com/bats-core/bats-core): Used to write the tests and automate their execution.
97+
- [**`kwctl`**`v1.30`](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.
98+
- [**`javy`**`6.0.0`](https://github.com/bytecodealliance/javy): CLI tool for compiling JavaScript code to WebAssembly modules.
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
---
2+
sidebar_label: New validation policy
3+
sidebar_position: 020
4+
title: Creating a new validation policy
5+
description: Creating a new validation policy for Kubewarden using TypeScript.
6+
keywords: [kubewarden, kubernetes, writing policies in TypeScript, new validation policy]
7+
doc-type: [tutorial]
8+
doc-topic: [kubewarden, writing-policies, typescript, creating a new validation policy]
9+
doc-persona: [kubewarden-policy-developer]
10+
---
11+
12+
<head>
13+
<link rel="canonical" href="https://docs.kubewarden.io/tutorials/writing-policies/typescript/scaffold"/>
14+
</head>
15+
16+
This tutorial covers creating a policy that validates the hostnames of Pod objects.
17+
18+
The policy is to reject all Pods that use one or more hostnames on the deny list.
19+
You provide policy configuration using runtime settings.
20+
21+
To summarize, the policy settings should look like this:
22+
23+
```yaml
24+
denied_hostnames:
25+
- bad-host
26+
- forbidden-host
27+
```
28+
29+
The policy rejects the creation of this Pod:
30+
31+
```yaml
32+
apiVersion: v1
33+
kind: Pod
34+
metadata:
35+
name: nginx
36+
spec:
37+
hostname: bad-host
38+
containers:
39+
- name: nginx
40+
image: nginx:latest
41+
```
42+
43+
However, it accepts the creation of this Pod:
44+
45+
```yaml
46+
apiVersion: v1
47+
kind: Pod
48+
metadata:
49+
name: nginx
50+
spec:
51+
hostname: allowed-host
52+
containers:
53+
- name: nginx
54+
image: nginx:latest
55+
```
56+
57+
## Scaffolding a new policy project
58+
59+
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.
60+
61+
Clone the repository locally and update the `package.json` file to reflect your policy details:
62+
63+
```json
64+
{
65+
"name": "your-policy-name",
66+
"version": "1.0.0",
67+
"description": "Your policy description",
68+
"repository": {
69+
"type": "git",
70+
"url": "https://github.com/your-username/your-policy-name"
71+
}
72+
}
73+
```
74+
75+
Make sure to use a repository path that matches your actual GitHub repository.
76+
77+
## Testing
78+
79+
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`.
80+
81+
<details>
82+
<summary>Output from the `make` commands</summary>
83+
84+
```console
85+
make all
86+
npx webpack --config webpack.config.cjs
87+
asset bundled.js 5.52 KiB [compared for emit] [minimized] (name: main)
88+
asset types.d.ts 430 bytes [compared for emit]
89+
asset index.d.ts 11 bytes [compared for emit]
90+
./src/index.ts 3.84 KiB [built] [code generated]
91+
./node_modules/kubewarden-policy-sdk/dist/bundle.js 3.85 KiB [built] [code generated]
92+
webpack 5.101.3 compiled successfully in 2280 ms
93+
npm install
94+
95+
up to date, audited 400 packages in 2s
96+
97+
58 packages are looking for funding
98+
run `npm fund` for details
99+
100+
found 0 vulnerabilities
101+
```
102+
103+
```console
104+
make e2e
105+
npx webpack --config webpack.config.cjs
106+
asset bundled.js 5.52 KiB [compared for emit] [minimized] (name: main)
107+
asset types.d.ts 430 bytes [compared for emit]
108+
asset index.d.ts 11 bytes [compared for emit]
109+
./src/index.ts 3.84 KiB [built] [code generated]
110+
./node_modules/kubewarden-policy-sdk/dist/bundle.js 3.85 KiB [built] [code generated]
111+
webpack 5.101.3 compiled successfully in 1909 ms
112+
bats e2e.bats
113+
e2e.bats
114+
✓ reject because hostname is on the deny list
115+
✓ accept because hostname is not on the deny list
116+
✓ accept because the deny list is empty
117+
✓ accept because pod has no hostname set
118+
✓ accept non-pod resources
119+
120+
5 tests, 0 failures
121+
```
122+
123+
</details>
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
---
2+
sidebar_label: Defining policy settings
3+
sidebar_position: 030
4+
title: Defining policy settings
5+
description: Defining policy settings for a Kubewarden policy written in TypeScript.
6+
keywords: [kubewarden, kubernetes, defining policy settings, TypeScript]
7+
doc-type: [tutorial]
8+
doc-topic: [kubewarden, writing-policies, typescript, defining-policy-settings]
9+
doc-persona: [kubewarden-policy-developer]
10+
---
11+
12+
<head>
13+
<link rel="canonical" href="https://docs.kubewarden.io/tutorials/writing-policies/typescript/policy-settings"/>
14+
</head>
15+
16+
:::danger Critical: Don't write logging information to STDOUT.
17+
18+
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.
19+
20+
:::
21+
22+
First, define the structure that holds the policy settings in `src/types.ts`.
23+
24+
```ts
25+
import type { PodSpec } from 'kubernetes-types/core/v1';
26+
import type { ObjectMeta } from 'kubernetes-types/meta/v1';
27+
28+
/**
29+
* Interface representing policy settings structure.
30+
*/
31+
export interface PolicySettings {
32+
// List of hostnames that are denied by the policy.
33+
denied_hostnames?: string[];
34+
}
35+
36+
/**
37+
* Generic Kubernetes resource interface
38+
*/
39+
export interface KubernetesResource {
40+
apiVersion: string;
41+
kind: string;
42+
metadata: ObjectMeta;
43+
spec?: PodSpec | any;
44+
}
45+
```
46+
47+
## Building Settings instances
48+
49+
Kubewarden policies use two functions that handle settings:
50+
51+
- `validate`: Called during object validation.
52+
- `validateSettings`: Called at policy load time.
53+
54+
In `src/index.ts`, the `validate` function looks like:
55+
56+
```ts
57+
function validate(): void {
58+
try {
59+
const validationRequest = Validation.Validation.readValidationRequest();
60+
const settings: PolicySettings = validationRequest.settings || {};
61+
62+
// Policy logic...
63+
} catch (err) {
64+
console.error('Validation error:', err);
65+
writeOutput(Validation.Validation.rejectRequest(`Validation failed: ${err}`));
66+
}
67+
}
68+
```
69+
70+
## Implementing Settings validation
71+
72+
```ts
73+
function validateSettings(): void {
74+
try {
75+
const settingsInput = Validation.Validation.readValidationRequest();
76+
const settings: PolicySettings = settingsInput as PolicySettings;
77+
78+
if (settings.denied_hostnames && !Array.isArray(settings.denied_hostnames)) {
79+
const errorResponse = new Validation.Validation.SettingsValidationResponse(
80+
false,
81+
'denied_hostnames must be an array of strings',
82+
);
83+
writeOutput(errorResponse);
84+
return;
85+
}
86+
87+
for (const hostname of settings.denied_hostnames || []) {
88+
if (typeof hostname !== 'string') {
89+
const errorResponse = new Validation.Validation.SettingsValidationResponse(
90+
false,
91+
'All hostnames in denied_hostnames must be strings',
92+
);
93+
writeOutput(errorResponse);
94+
return;
95+
}
96+
}
97+
98+
const response = new Validation.Validation.SettingsValidationResponse(true);
99+
writeOutput(response);
100+
} catch (err) {
101+
console.error('Settings validation error:', err);
102+
const errorResponse = new Validation.Validation.SettingsValidationResponse(
103+
false,
104+
`Settings validation failed: ${err}`,
105+
);
106+
writeOutput(errorResponse);
107+
}
108+
}
109+
```

0 commit comments

Comments
 (0)