From c2d3133473ac3b1207b246b554e19e7c6ccf2c66 Mon Sep 17 00:00:00 2001 From: Steven Borrelli Date: Mon, 26 Jan 2026 21:26:37 +0000 Subject: [PATCH 1/4] add build scripts Signed-off-by: Steven Borrelli --- env | 11 +++++++++++ scripts/configuration-xpkg-build.sh | 11 +++++++++++ scripts/configuration-xpkg-push.sh | 7 +++++++ scripts/function-docker-build.sh | 9 +++++++++ scripts/function-xpkg-build.sh | 12 ++++++++++++ scripts/function-xpkg-push.sh | 7 +++++++ 6 files changed, 57 insertions(+) create mode 100644 env create mode 100755 scripts/configuration-xpkg-build.sh create mode 100755 scripts/configuration-xpkg-push.sh create mode 100755 scripts/function-docker-build.sh create mode 100755 scripts/function-xpkg-build.sh create mode 100755 scripts/function-xpkg-push.sh diff --git a/env b/env new file mode 100644 index 0000000..c099df8 --- /dev/null +++ b/env @@ -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 diff --git a/scripts/configuration-xpkg-build.sh b/scripts/configuration-xpkg-build.sh new file mode 100755 index 0000000..0730899 --- /dev/null +++ b/scripts/configuration-xpkg-build.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +source ./env + +set -xeu + +mkdir -p ${XPKG_DIR} + crossplane xpkg build \ + --package-root="package" \ + --examples-root="examples" \ + -o ${XPKG_DIR}/${CONFIGURATION_NAME}-v${VERSION}.xpkg diff --git a/scripts/configuration-xpkg-push.sh b/scripts/configuration-xpkg-push.sh new file mode 100755 index 0000000..e4a7b08 --- /dev/null +++ b/scripts/configuration-xpkg-push.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +source ./env + +set -xe + +crossplane xpkg push -f ${XPKG_DIR}/${CONFIGURATION_NAME}-v${VERSION}.xpkg ${XPKG_REPO}/${CONFIGURATION_NAME}:v${VERSION} diff --git a/scripts/function-docker-build.sh b/scripts/function-docker-build.sh new file mode 100755 index 0000000..9cb3542 --- /dev/null +++ b/scripts/function-docker-build.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +set -e +source ./env + +mkdir -p ${DOCKER_IMAGE_DIR} +for platform in ${BUILD_PLATFORMS}; do + docker buildx build --platform linux/${platform} . --tag=${FN_NAME}:v${VERSION} --target=image --output type=docker,dest=${DOCKER_IMAGE_DIR}/${FN_NAME}-runtime-${platform}-v${VERSION}.tar +done diff --git a/scripts/function-xpkg-build.sh b/scripts/function-xpkg-build.sh new file mode 100755 index 0000000..2c982a7 --- /dev/null +++ b/scripts/function-xpkg-build.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +source ./env + +set -xe + +mkdir -p ${XPKG_DIR} +for platform in ${BUILD_PLATFORMS}; do + crossplane xpkg build --embed-runtime-image-tarball=${DOCKER_IMAGE_DIR}/${FN_NAME}-runtime-${platform}-v${VERSION}.tar \ + --package-root="package-function" \ + -o ${XPKG_DIR}/${FN_NAME}-${platform}-v${VERSION}.xpkg +done diff --git a/scripts/function-xpkg-push.sh b/scripts/function-xpkg-push.sh new file mode 100755 index 0000000..d968546 --- /dev/null +++ b/scripts/function-xpkg-push.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +source ./env + +set -xe + +crossplane xpkg push -f ${XPKG_DIR}/${FN_NAME}-amd64-v${VERSION}.xpkg,${XPKG_DIR}/${FN_NAME}-arm64-v${VERSION}.xpkg ${XPKG_REPO}/${FN_NAME}:v${VERSION} From 5f5324511ffba5d99fc817bd5fc50ac3bb8723ba Mon Sep 17 00:00:00 2001 From: Steven Borrelli Date: Mon, 26 Jan 2026 21:27:04 +0000 Subject: [PATCH 2/4] move .ts files to the src directory Signed-off-by: Steven Borrelli --- README.md | 233 ++++++++++++++++------- function.test.ts => src/function.test.ts | 0 function.ts => src/function.ts | 0 main.ts => src/main.ts | 0 test-helpers.ts => src/test-helpers.ts | 0 test-cases/README.md | 18 +- tsconfig.json | 2 +- 7 files changed, 164 insertions(+), 89 deletions(-) rename function.test.ts => src/function.test.ts (100%) rename function.ts => src/function.ts (100%) rename main.ts => src/main.ts (100%) rename test-helpers.ts => src/test-helpers.ts (100%) diff --git a/README.md b/README.md index 2bc4cb3..7a12ac2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 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/crossplane/function-sdk-typescript). ## Overview @@ -8,28 +8,40 @@ This template provides a starting point for developing Crossplane functions that ## Prerequisites -- Node.js 25 or later +- Node.js 24 or later - 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 @@ -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 ``` @@ -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 ``` @@ -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 . ``` @@ -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 { - // Your function logic here - } + async RunFunction(req: RunFunctionRequest, logger?: Logger): Promise { + // Your function logic here + } } ``` @@ -141,28 +194,30 @@ 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 @@ -170,26 +225,37 @@ dcds["my-config"] = resource; 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: @@ -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 diff --git a/function.test.ts b/src/function.test.ts similarity index 100% rename from function.test.ts rename to src/function.test.ts diff --git a/function.ts b/src/function.ts similarity index 100% rename from function.ts rename to src/function.ts diff --git a/main.ts b/src/main.ts similarity index 100% rename from main.ts rename to src/main.ts diff --git a/test-helpers.ts b/src/test-helpers.ts similarity index 100% rename from test-helpers.ts rename to src/test-helpers.ts diff --git a/test-cases/README.md b/test-cases/README.md index 00c9a77..696212d 100644 --- a/test-cases/README.md +++ b/test-cases/README.md @@ -174,20 +174,16 @@ expected: containers: [] ``` -This format: +The test format: -- **Mirrors the input structure** (consistent with `input.observed.resources`) +- Mirrors the input structure (consistent with `input.observed.resources`) - Makes it easy to reference specific resources by name - Validates with **partial match** - only the fields you specify are checked -Benefits: - -- Check specific fields without listing every field -- Validate nested properties -- Focus assertions on what's important for each test case - ### Composite Status +Test the status of the composition: + ```yaml expected: status: @@ -242,12 +238,6 @@ This simulates the scenario where: 2. The Kubernetes API has reconciled it and set the status fields 3. Your function receives this observed state and can use it to update the composite status -## Examples - -See the existing test case files in this directory: - -- [basic-app.yaml](./basic-app.yaml) - Basic application with Deployment and Pod resources - ## Writing Your Own Test Cases 1. Create a new `.yaml` or `.json` file in this directory diff --git a/tsconfig.json b/tsconfig.json index 697a8f2..c7d7b84 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "exclude": ["node_modules", "dist", "examples", "**/*.test.ts"], "compilerOptions": { // File Layout - // "rootDir": "./src", + "rootDir": "./src", "outDir": "./dist", // Environment Settings From cc8c014f0312507b482fa64f7820952ebdb4a321 Mon Sep 17 00:00:00 2001 From: Steven Borrelli Date: Mon, 26 Jan 2026 21:38:02 +0000 Subject: [PATCH 3/4] linter fixes Signed-off-by: Steven Borrelli --- scripts/configuration-xpkg-build.sh | 2 +- scripts/configuration-xpkg-push.sh | 4 ++-- scripts/function-docker-build.sh | 6 +++--- scripts/function-xpkg-build.sh | 13 ++++++------- scripts/function-xpkg-push.sh | 5 ++--- src/main.ts | 2 +- 6 files changed, 15 insertions(+), 17 deletions(-) diff --git a/scripts/configuration-xpkg-build.sh b/scripts/configuration-xpkg-build.sh index 0730899..5a36574 100755 --- a/scripts/configuration-xpkg-build.sh +++ b/scripts/configuration-xpkg-build.sh @@ -1,6 +1,6 @@ #!/bin/sh -source ./env +. ./env set -xeu diff --git a/scripts/configuration-xpkg-push.sh b/scripts/configuration-xpkg-push.sh index e4a7b08..c13d468 100755 --- a/scripts/configuration-xpkg-push.sh +++ b/scripts/configuration-xpkg-push.sh @@ -1,7 +1,7 @@ #!/bin/sh -source ./env +. ./env -set -xe +set -xeu crossplane xpkg push -f ${XPKG_DIR}/${CONFIGURATION_NAME}-v${VERSION}.xpkg ${XPKG_REPO}/${CONFIGURATION_NAME}:v${VERSION} diff --git a/scripts/function-docker-build.sh b/scripts/function-docker-build.sh index 9cb3542..e4d01a1 100755 --- a/scripts/function-docker-build.sh +++ b/scripts/function-docker-build.sh @@ -1,9 +1,9 @@ #!/bin/sh set -e -source ./env +. ./env -mkdir -p ${DOCKER_IMAGE_DIR} +mkdir -p "${DOCKER_IMAGE_DIR}" for platform in ${BUILD_PLATFORMS}; do - docker buildx build --platform linux/${platform} . --tag=${FN_NAME}:v${VERSION} --target=image --output type=docker,dest=${DOCKER_IMAGE_DIR}/${FN_NAME}-runtime-${platform}-v${VERSION}.tar + docker buildx build --platform "linux/${platform}" . --tag="${FN_NAME}:v${VERSION}" --target=image --output "type=docker,dest=${DOCKER_IMAGE_DIR}/${FN_NAME}-runtime-${platform}-v${VERSION}.tar" done diff --git a/scripts/function-xpkg-build.sh b/scripts/function-xpkg-build.sh index 2c982a7..247c599 100755 --- a/scripts/function-xpkg-build.sh +++ b/scripts/function-xpkg-build.sh @@ -1,12 +1,11 @@ -#!/bin/sh - -source ./env +#/bin/sh set -xe +. ./env -mkdir -p ${XPKG_DIR} +mkdir -p "${XPKG_DIR}" for platform in ${BUILD_PLATFORMS}; do - crossplane xpkg build --embed-runtime-image-tarball=${DOCKER_IMAGE_DIR}/${FN_NAME}-runtime-${platform}-v${VERSION}.tar \ + crossplane xpkg build --embed-runtime-image-tarball="${DOCKER_IMAGE_DIR}/${FN_NAME}-runtime-${platform}-v${VERSION}.tar" \ --package-root="package-function" \ - -o ${XPKG_DIR}/${FN_NAME}-${platform}-v${VERSION}.xpkg -done + -o "${XPKG_DIR}/${FN_NAME}-${platform}-v${VERSION}.xpkg" +done \ No newline at end of file diff --git a/scripts/function-xpkg-push.sh b/scripts/function-xpkg-push.sh index d968546..797911d 100755 --- a/scripts/function-xpkg-push.sh +++ b/scripts/function-xpkg-push.sh @@ -1,7 +1,6 @@ #!/bin/sh -source ./env - set -xe +. ./env -crossplane xpkg push -f ${XPKG_DIR}/${FN_NAME}-amd64-v${VERSION}.xpkg,${XPKG_DIR}/${FN_NAME}-arm64-v${VERSION}.xpkg ${XPKG_REPO}/${FN_NAME}:v${VERSION} +crossplane xpkg push -f "${XPKG_DIR}/${FN_NAME}-amd64-v${VERSION}.xpkg,${XPKG_DIR}/${FN_NAME}-arm64-v${VERSION}.xpkg" "${XPKG_REPO}/${FN_NAME}:v${VERSION}" diff --git a/src/main.ts b/src/main.ts index 9300c95..a3ce813 100644 --- a/src/main.ts +++ b/src/main.ts @@ -78,7 +78,7 @@ function main() { }); } catch (err) { logger.error(err); - process.exit(-1); + process.exit(1); } } From 25d3e5a965e6b5d2dfbd9a87f47525cec2f77c65 Mon Sep 17 00:00:00 2001 From: Steven Borrelli Date: Mon, 26 Jan 2026 21:43:51 +0000 Subject: [PATCH 4/4] fix git location Signed-off-by: Steven Borrelli --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7a12ac2..9726430 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Crossplane Function Template - TypeScript -A template for building Crossplane composition functions in TypeScript using the [@crossplane-org/function-sdk-typescript](https://github.com/crossplane/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 @@ -8,7 +8,7 @@ This template provides a starting point for developing Crossplane functions that ## Prerequisites -- Node.js 24 or later +- Node.js 24 or later recommended. - npm - Docker (for building container images) - TypeScript 5+ or TypeScript 7 (tsgo)