@crossplane-org/function-sdk-typescript
v0.3.0
Published
A Crossplane Function SDK for Typescript
Maintainers
Readme
Crossplane Function SDK for TypeScript
A TypeScript SDK for building Crossplane Composition Functions. This SDK provides type-safe interfaces and utilities for creating functions that generate and manage Crossplane resources.
Overview
This SDK can be used as the base of a Crossplane Composition function.
For complete usage instructions on importing this SDK into your projects, see USAGE.md.
Quick Start (Using as an Importable SDK)
Installation
npm install @crossplane-org/function-sdk-typescriptCreating Your Function
Implement the FunctionHandler interface:
import type {
FunctionHandler,
RunFunctionRequest,
RunFunctionResponse,
Logger
} from "@crossplane-org/function-sdk-typescript";
import {
to,
normal,
fatal,
setDesiredComposedResources,
getDesiredComposedResources,
getObservedCompositeResource,
Resource
} from "@crossplane-org/function-sdk-typescript";
export class MyFunction implements FunctionHandler {
async RunFunction(req: RunFunctionRequest, logger?: Logger): Promise<RunFunctionResponse> {
let rsp = to(req);
try {
// Get observed composite resource and desired composed resources
const oxr = getObservedCompositeResource(req);
let dcds = getDesiredComposedResources(req);
logger?.info("Processing function request");
// Your function logic here - create a ConfigMap
dcds["my-resource"] = Resource.fromJSON({
resource: {
apiVersion: "v1",
kind: "ConfigMap",
metadata: { name: "my-config" },
data: { key: "value" }
}
});
rsp = setDesiredComposedResources(rsp, dcds);
normal(rsp, "Function completed successfully");
return rsp;
} catch (error) {
logger?.error({ error }, "Function failed");
fatal(rsp, error instanceof Error ? error.message : String(error));
return rsp;
}
}
}See USAGE.md for complete examples and API documentation.
Development Setup (For SDK Contributors)
Prerequisites
- Node.js 18+
- npm or yarn
- Docker (for building container images)
- protoc (Protocol Buffer compiler) -
brew install protobuf
Setup
npm installTesting
The SDK uses Vitest for testing. Tests are located alongside source files with the .test.ts extension.
# Run tests once
npm test
# Run tests in watch mode (for development)
npm run test:watch
# Run tests with coverage report
npm run test:coverageTest files are automatically excluded from the build output.
Running the Example Function
Start the function server in development mode:
npm run build
npm run local-runThe function will listen on 0.0.0.0:9443 by default.
Testing Your Function
After running npm run local-run in another terminal, use the Crossplane CLI to
call your local function using crossplane render:
cd example
./render.shBasic Function Structure
The main function logic goes in src/function/function.ts:
import { Resource, RunFunctionRequest, RunFunctionResponse } from "../proto/run_function.js";
import { to, setDesiredComposedResources, normal } from "../response/response.js";
import { getDesiredComposedResources } from "../request/request.js";
export class FunctionRunner {
async RunFunction(
req: RunFunctionRequest,
logger?: Logger,
): Promise<RunFunctionResponse> {
// Initialize response from request
let rsp = to(req);
// Get desired composed resources from request
let dcds = getDesiredComposedResources(req);
// Create a new resource using plain JSON
dcds["my-deployment"] = Resource.fromJSON({
resource: {
apiVersion: "apps/v1",
kind: "Deployment",
metadata: {
name: "my-deployment",
namespace: "default",
},
spec: {
replicas: 3,
// ... deployment spec
},
},
});
// Set desired resources in response
rsp = setDesiredComposedResources(rsp, dcds);
// Add a result message
normal(rsp, "Resources created successfully");
return rsp;
}
}Using Kubernetes Models
You can use type-safe Kubernetes models from the kubernetes-models package:
import { Deployment } from "kubernetes-models/apps/v1";
import { Pod } from "kubernetes-models/v1";
// Create a type-safe Pod
const pod = new Pod({
metadata: {
name: "my-pod",
namespace: "default",
},
spec: {
containers: [{
name: "app",
image: "nginx:latest",
}],
},
});
// Validate the pod
pod.validate();
// Convert to Resource
dcds["my-pod"] = Resource.fromJSON({
resource: pod.toJSON()
});Helper Functions
Request Helpers
The SDK provides comprehensive request helpers to extract data from RunFunctionRequest:
import {
getObservedCompositeResource,
getDesiredCompositeResource,
getDesiredComposedResources,
getObservedComposedResources,
getInput,
getContextKey,
getRequiredResources,
getCredentials,
} from "@crossplane-org/function-sdk-typescript";
// Get the observed composite resource (XR)
const oxr = getObservedCompositeResource(req);
// Get the desired composite resource
const dxr = getDesiredCompositeResource(req);
// Get desired composed resources
const dcds = getDesiredComposedResources(req);
// Get observed composed resources
const ocds = getObservedComposedResources(req);
// Get function input configuration
const input = getInput(req);
// Get context value from previous function
const [value, exists] = getContextKey(req, "my-key");
// Get required resources
const required = getRequiredResources(req);
// Get credentials
const creds = getCredentials(req, "aws-creds");Response Helpers
The SDK provides response helpers to build and manipulate RunFunctionResponse:
import {
to,
setDesiredComposedResources,
setDesiredCompositeResource,
setDesiredCompositeStatus,
setContextKey,
setOutput,
normal,
fatal,
warning,
update,
DEFAULT_TTL,
} from "@crossplane-org/function-sdk-typescript";
// Initialize response from request (with optional TTL)
let rsp = to(req, DEFAULT_TTL);
// Set desired composed resources (merges with existing)
rsp = setDesiredComposedResources(rsp, dcds);
// Set desired composite resource
rsp = setDesiredCompositeResource(rsp, dxr);
// Update composite resource status
rsp = setDesiredCompositeStatus({ rsp, status: { ready: true } });
// Set context for next function
rsp = setContextKey(rsp, "my-key", "my-value");
// Set output (returned to user)
rsp = setOutput(rsp, { result: "success" });
// Add result messages
normal(rsp, "Success message");
warning(rsp, "Warning message");
fatal(rsp, "Fatal error message");
// Update a resource by merging
const updated = update(sourceResource, targetResource);Resource Helpers
The SDK provides utilities for working with Kubernetes resources:
import {
Resource,
asObject,
asStruct,
fromObject,
toObject,
newDesiredComposed,
} from "@crossplane-org/function-sdk-typescript";
// Create a Resource from a plain object
const resource = fromObject({
apiVersion: "v1",
kind: "ConfigMap",
metadata: { name: "my-config" }
});
// Extract plain object from Resource
const obj = toObject(resource);
// Convert between struct and object formats
const struct = asStruct(obj);
const plainObj = asObject(struct);
// Create a new empty DesiredComposed resource
const desired = newDesiredComposed();Error Handling
try {
// Your function logic
} catch (error) {
fatal(rsp, error instanceof Error ? error.message : String(error));
return rsp;
}Publishing the SDK
This section is for SDK maintainers who want to publish updates to npm.
Prepare for Publishing
Update version in package.json
Build the TypeScript code:
npm run buildVerify the build output in
dist/:ls -la dist/
Test the Package Locally
Before publishing, test the package locally:
# Create a tarball
npm pack
# This creates function-sdk-typescript-<version>.tgz
# Install it in another project to test
npm install /path/to/function-sdk-typescript-<version>.tgzPublish to npm
# Dry run to see what will be published
npm publish --dry-run
# Publish to npm (requires authentication)
npm publishThe package.json files field ensures only necessary files are included:
dist/- Compiled JavaScript and type definitionsREADME.md- Documentation
Building Function Containers
If you're developing a function based on this SDK and need to containerize it:
- Create a
Dockerfilefor your function - Build the image with your function code
- Package as a Crossplane function package
See the Crossplane documentation for details on building and packaging functions.
Development
Generating Protobuf Code
This repo uses the Protobuf definitions from Crossplane. If the upstream definition is updated, copy it into this repo and regenerate the TypeScript code.
Protocol Buffers Prerequisites
You need the Protocol Buffer compiler (protoc) installed:
# macOS
brew install protobuf
# Verify installation
protoc --version # Should be 3.x or higherRegenerating Code
To regenerate TypeScript code from the Protobuf definitions:
./scripts/protoc-gen.shThis script uses ts-proto to generate:
- Type definitions from protobuf messages
- gRPC service stubs for the FunctionRunner service
- Conversion utilities for Protocol Buffer types
After regenerating, rebuild the project:
npm run buildLocal Development Workflow
For SDK contributors making changes to the SDK itself:
- Make changes to source files in
src/ - Build the SDK:
npm run build - Test locally by creating a tarball:
npm pack - Install the tarball in a test project to verify changes
For testing with an example function:
- Implement an example function using the SDK
- Build:
npm run build - Run the function server locally (if you have a main entry point)
- Test with
crossplane beta renderusing example compositions
Using with Crossplane
Create a Function resource in your cluster:
apiVersion: pkg.crossplane.io/v1beta1
kind: Function
metadata:
name: your-registry-function-typescript
spec:
package: your-registry/function-typescript:v0.1.0Reference it in your Composition:
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: my-composition
spec:
mode: Pipeline
pipeline:
- step: run-typescript-function
functionRef:
name: function-typescriptCreating Resources
Resources can be created from Typescript Objects using Resource.fromJSON():
// Plain JSON object automatically converted to Struct
Resource.fromJSON({
resource: {
apiVersion: "v1",
kind: "ConfigMap",
// ... any valid Kubernetes resource
}
});API Reference
Exported Types and Interfaces
The SDK exports all types and interfaces from a single entry point:
import {
// Core interfaces
FunctionHandler,
FunctionRunner,
Logger,
// Request/Response types
RunFunctionRequest,
RunFunctionResponse,
Resource,
// Resource types
Composite,
ObservedComposed,
DesiredComposed,
ConnectionDetails,
// Protocol buffer types
Severity,
Result,
State,
Ready,
Target,
Status,
Condition,
Resources,
Credentials,
CredentialData,
// Runtime types
ServerOptions,
} from "@crossplane-org/function-sdk-typescript";Core Functions
to(req, ttl?)- Initialize a response from a requestnormal(rsp, message)- Add a normal (info) resultwarning(rsp, message)- Add a warning resultfatal(rsp, message)- Add a fatal error resultgetObservedCompositeResource(req)- Get the observed XRgetDesiredCompositeResource(req)- Get the desired XRgetDesiredComposedResources(req)- Get desired composed resourcesgetObservedComposedResources(req)- Get observed composed resourcessetDesiredComposedResources(rsp, resources)- Set desired composed resourcessetDesiredCompositeStatus({rsp, status})- Update XR statussetContextKey(rsp, key, value)- Set context for next functiongetContextKey(req, key)- Get context from previous functiongetInput(req)- Get function input configurationgetRequiredResources(req)- Get required resourcesgetCredentials(req, name)- Get credentials by name (throws error if not found)
See USAGE.md for detailed API documentation and examples.
Dependencies
Runtime Dependencies
@grpc/grpc-js- gRPC implementation for Node.js@grpc/proto-loader- Protocol buffer loadergoogle-protobuf- Google Protocol Buffers runtimets-proto- TypeScript protobuf code generatorts-deepmerge- Deep merging utility for resourcespino- Fast, structured JSON loggerkubernetes-models- Type-safe Kubernetes resource models (optional)
Development Dependencies
typescript- TypeScript compiler (v5.7+)@types/node- Node.js type definitions@types/google-protobuf- Google Protobuf type definitionsts-node- TypeScript execution enginevitest- Fast unit test framework@vitest/coverage-v8- Code coverage reporting- Protocol Buffer compiler (
protoc) - Required for regenerating protobuf code
Troubleshooting
Installation Issues
Problem: Package not found or installation fails
# Verify npm registry access
npm ping
# Try installing with verbose output
npm install @crossplane-org/function-sdk-typescript --verboseType Errors
Problem: TypeScript can't find types from the SDK
Solution: Ensure your tsconfig.json includes:
{
"compilerOptions": {
"moduleResolution": "node",
"esModuleInterop": true
}
}Import Errors
Problem: Cannot find module '@crossplane-org/function-sdk-typescript'
Solution: Verify the package is installed:
npm list @crossplane-org/function-sdk-typescriptIf missing, reinstall:
npm install @crossplane-org/function-sdk-typescriptPort Already in Use (when running functions)
Problem: EADDRINUSE error when starting function server
Solution: Kill the process using the port:
# Find and kill process on port 9443
lsof -ti:9443 | xargs kill -9Protocol Buffer Compilation Errors (for SDK contributors)
Problem: Errors when running ./scripts/protoc-gen.sh
Solution: Ensure Protocol Buffer compiler is installed:
# Check version
protoc --version # Should be 3.x or higher
# macOS installation
brew install protobuf
# After installing, regenerate
./scripts/protoc-gen.sh
npm run buildContributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable (use
npm testto run tests) - Ensure all tests pass and the build succeeds (
npm run build) - Submit a pull request
License
Apache 2.0
Resources
Crossplane Documentation
SDK Documentation
- USAGE.md - Complete usage guide for this SDK
Related Tools
- ts-proto - TypeScript protobuf code generator
- kubernetes-models - Type-safe Kubernetes resource models for TypeScript
- Pino - Fast JSON logger
- gRPC-js - Pure JavaScript gRPC implementation
