Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
233 changes: 159 additions & 74 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,47 @@
# Crossplane Function Template - TypeScript

A template for building Crossplane composition functions in TypeScript using the [function-sdk-typescript](https://github.com/upbound/function-sdk-typescript).
A template for building Crossplane composition functions in TypeScript using the [@crossplane-org/function-sdk-typescript](https://github.com/upbound/function-sdk-typescript).

## Overview

This template provides a starting point for developing Crossplane functions that can transform, validate, and generate Kubernetes resources within Crossplane compositions. The example function creates sample Deployment and Pod resources.

## Prerequisites

- Node.js 25 or later
- Node.js 24 or later recommended.
- npm
- Docker (for building container images)
- TypeScript versions 5+ (tsgo can compile the project)
- TypeScript 5+ or TypeScript 7 (tsgo)

## Project Structure

```
```text
.
├── functionn.ts # Main function implementation
├── main.ts # Entry point and server setup
├── package.json # Dependencies and scripts
├── tsconfig.json # TypeScript configuration
├── Dockerfile # Container image definition
└── function-sdk-typescript/ # Local copy of the SDK (private repo)
├── src/ # Source files
│ ├── function.ts # Main function implementation
│ ├── function.test.ts # Function tests
│ ├── test-helpers.ts # Test utilities for loading YAML test cases
│ └── main.ts # Entry point and server setup
├── test-cases/ # YAML-based test cases
│ └── basic-app.yaml # Example test case
├── scripts/ # Build and deployment scripts
│ ├── function-docker-build.sh
│ ├── function-xpkg-build.sh
│ ├── function-xpkg-push.sh
│ ├── configuration-xpkg-build.sh
│ └── configuration-xpkg-push.sh
├── package.json # Dependencies and scripts
├── tsconfig.json # TypeScript configuration
├── jest.config.js # Jest test configuration
├── eslint.config.js # ESLint configuration
├── .prettierrc.json # Prettier configuration
└── Dockerfile # Container image definition
```

## Installation

1. Clone this repository
2. Ensure the `function-sdk-typescript` SDK is present in the project directory
3. Install dependencies:
2. Install dependencies:

```bash
npm install
Expand All @@ -39,15 +51,17 @@ npm install

### Build TypeScript

Compile TypeScript to JavaScript:
Compile TypeScript to JavaScript using TypeScript 5:

```bash
npm run tsc
```

Typescript 7 can also be used:
TypeScript 7 (tsgo) is the default engine for the build:

```bash
npm run build
# or
npm run tsgo
```

Expand All @@ -59,11 +73,41 @@ Check types without emitting files:
npm run check-types
```

### Testing

Run tests using Jest:

```bash
npm test
```

Tests are written in [src/function.test.ts](src/function.test.ts) and use YAML-based test cases from the [test-cases/](test-cases/) directory.

### Linting and Formatting

The project includes ESLint and Prettier for code quality:

```bash
# Lint code
npm run lint

# Auto-fix linting issues
npm run lint:fix

# Format code with Prettier
npm run format

# Check formatting without changing files
npm run format:check
```

### Running Locally

Run the function server in insecure mode for local testing:

```bash
npm run local
# or
node dist/main.js --insecure --debug
```

Expand All @@ -74,11 +118,15 @@ node dist/main.js --insecure --debug
- `--insecure` - Run without mTLS credentials (for local development)
- `--tls-server-certs-dir` - Directory containing mTLS certificates (default: `/tls/server`)

## Docker Build
## Building and Packaging

### Docker Build

Build the container image:

```bash
npm run function-docker-build
# or
docker build -t function-template-typescript .
```

Expand All @@ -87,43 +135,48 @@ The Dockerfile uses a multi-stage build:
1. **Build stage**: Uses `node:25` to install dependencies and compile TypeScript
2. **Runtime stage**: Uses `gcr.io/distroless/nodejs24-debian12` for a minimal, secure runtime

## Examples
Refer to the [`scripts`](./scripts/) directory for examples of multi-platform builds.

### Package as Crossplane Function

Build the function as a Crossplane package (xpkg):

```bash
# Build the function package
npm run function-xpkg-build

# Push to a registry
npm run function-xpkg-push

### Basic App Example
# Build and push in one step
npm run function-build-all
```

The [examples/basic-app](examples/basic-app) directory contains a complete example that demonstrates building a real-world Crossplane function. This example creates Kubernetes application resources (Deployments, Services, ServiceAccounts, and Ingress) based on a simplified API specification.
### Configuration Package

**Key Features:**
Build a Crossplane configuration package:

- Direct usage of `kubernetes-models` for type-safe resource creation
- Generates multiple related Kubernetes resources from a single composite resource
- Includes complete Crossplane configuration (XRD, Composition, and example claims)
- Demonstrates building and packaging functions as Crossplane packages (xpkg)
- Shows integration with `function-auto-ready` for resource readiness checks
```bash
# Build configuration package
npm run configuration-xpkg-build

**What You'll Learn:**
- Creating Kubernetes resources using `kubernetes-models` classes
- Working with composite resource specifications
- Conditional resource generation based on input parameters
- Building and deploying production-ready Crossplane functions
- Creating Crossplane packages for distribution
# Push configuration package
npm run configuration-xpkg-push
```

See the [basic-app README](examples/basic-app/README.md) for detailed instructions on building, testing, and deploying this example.
All build scripts are located in the [scripts/](scripts/) directory and can be customized for your needs.

## Implementation Guide

### Creating Your Function

Edit `fn.ts` to implement your function logic. The main interface is:
Edit [src/function.ts](src/function.ts) to implement your function logic. The main interface is:

```typescript
export class Function implements FunctionHandler {
async RunFunction(
req: RunFunctionRequest,
logger?: Logger,
): Promise<RunFunctionResponse> {
// Your function logic here
}
async RunFunction(req: RunFunctionRequest, logger?: Logger): Promise<RunFunctionResponse> {
// Your function logic here
}
}
```

Expand All @@ -141,55 +194,68 @@ The SDK provides helper functions for working with Crossplane resources:
- `fatal(rsp, message)` - Add a fatal condition to the response
- `to(req)` - Create a minimal response from a request

See [src/function.ts](src/function.ts) for examples of using these SDK functions.

### Example: Creating a Resource

```typescript
import { Resource } from "function-sdk-typescript";
import { Resource } from '@crossplane-org/function-sdk-typescript';

// Create from JSON
const resource = Resource.fromJSON({
resource: {
apiVersion: "v1",
kind: "ConfigMap",
metadata: {
name: "my-config",
namespace: "default",
},
data: {
key: "value",
},
resource: {
apiVersion: 'v1',
kind: 'ConfigMap',
metadata: {
name: 'my-config',
namespace: 'default',
},
data: {
key: 'value',
},
},
});

// Add to desired composed resources
dcds["my-config"] = resource;
desiredComposed['my-config'] = resource;
```

### Using Kubernetes Models

The template includes [kubernetes-models](https://github.com/tommy351/kubernetes-models-ts) for type-safe K8s resource creation:

```typescript
import { Pod } from "kubernetes-models/v1";
import { Pod } from 'kubernetes-models/v1';

const pod = new Pod({
metadata: {
name: "my-pod",
namespace: "default",
},
spec: {
containers: [{
name: "app",
image: "nginx:latest",
}],
},
metadata: {
name: 'my-pod',
namespace: 'default',
},
spec: {
containers: [
{
name: 'app',
image: 'nginx:latest',
},
],
},
});

pod.validate(); // Validate the resource

dcds["my-pod"] = Resource.fromJSON({ resource: pod.toJSON() });
desiredComposed['my-pod'] = Resource.fromJSON({ resource: pod.toJSON() });
```

### Testing Your Function

Create YAML test cases in the [test-cases/](test-cases/) directory. Each test case defines:

- Input: The observed composite resource and context
- Expected: Resource counts, types, and validation rules

See [test-cases/basic-app.yaml](test-cases/basic-app.yaml) for an example. Tests use [src/test-helpers.ts](src/test-helpers.ts) to load and validate YAML test cases.

## TypeScript Configuration

This template uses strict TypeScript settings:
Expand All @@ -204,39 +270,58 @@ The SDK directory is excluded from compilation to avoid conflicts with different
## Dependencies

### Production Dependencies
- `function-sdk-typescript` - Crossplane function SDK (local copy)

- `@crossplane-org/function-sdk-typescript` - Crossplane function SDK
- `commander` - CLI argument parsing
- `pino` - Structured logging
- `kubernetes-models` - Type-safe Kubernetes resource models
- `typescript` - TypeScript compiler
- `@types/node` - Node.js type definitions
- `yaml` - YAML parsing for test cases

### Dev Dependencies

- `@typescript/native-preview` - TypeScript native preview tooling
- `@typescript/native-preview` - TypeScript 7 (tsgo) native preview tooling
- `jest` - Testing framework
- `ts-jest` - TypeScript support for Jest
- `@types/jest` - Jest type definitions
- `eslint` - Linting
- `typescript-eslint` - TypeScript ESLint plugin
- `prettier` - Code formatting
- `glob` - File pattern matching for test discovery

## Notes

- The `function-sdk-typescript` is a private repository and must be copied locally into the project
- The SDK directory is excluded from TypeScript compilation to prevent config conflicts
- The Docker build includes the SDK copy in the build context
- The SDK is now available as `@crossplane-org/function-sdk-typescript` on npm
- mTLS is enabled by default when running in production (disable with `--insecure` for local dev)
- Tests are automatically discovered from YAML files in the [test-cases/](test-cases/) directory
- Build scripts in [scripts/](scripts/) handle Docker and xpkg packaging

## Troubleshooting

### TypeScript Compilation Errors

If you encounter TypeScript errors related to the SDK:
1. Ensure `function-sdk-typescript` is in the exclude list in `tsconfig.json`
2. Run `npm install` to ensure dependencies are properly linked
3. Check that the SDK's `dist` directory contains compiled JavaScript
If you encounter TypeScript errors:

1. Run `npm install` to ensure dependencies are properly installed
2. Try clearing the build directory: `npm run clean`
3. Check that TypeScript version is 5+ or use tsgo (TypeScript 7)

### Test Failures

If tests fail:

1. Verify test case YAML files are properly formatted in [test-cases/](test-cases/)
2. Check that expected resources match what your function generates
3. Run tests with verbose output: `NODE_OPTIONS=--experimental-vm-modules jest --verbose`

### Docker Build Failures

If the Docker build fails:
1. Ensure `function-sdk-typescript` directory exists in the project root
2. Verify `.dockerignore` doesn't exclude the SDK directory
3. Check that `package.json` references `file:./function-sdk-typescript`

1. Ensure all dependencies in [package.json](package.json) are available
2. Check that the build completes successfully locally first: `npm run build`
3. Verify Docker has access to the project directory

## License

Expand Down
11 changes: 11 additions & 0 deletions env
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
VERSION=$(npm pkg get version | tr -d '"')
CONFIGURATION_NAME=$(npm pkg get name | tr -d '"')
FN_NAME=${CONFIGURATION_NAME}-function

# Change this to your docker repo
XPKG_REPO=ghcr.io/crossplane

BUILD_PLATFORMS="amd64 arm64"
BUILD_DIR=_build
DOCKER_IMAGE_DIR=${BUILD_DIR}/docker_images
XPKG_DIR=${BUILD_DIR}/xpkg
11 changes: 11 additions & 0 deletions scripts/configuration-xpkg-build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/sh

. ./env

set -xeu

mkdir -p ${XPKG_DIR}
crossplane xpkg build \
--package-root="package" \
--examples-root="examples" \
-o ${XPKG_DIR}/${CONFIGURATION_NAME}-v${VERSION}.xpkg
Loading