Skip to content

v0.5.0

Latest

Choose a tag to compare

@stevendborrelli stevendborrelli released this 05 Mar 21:46
9b69c9f

v0.5.0 - Required Resources, Schemas, and Capability Support

This release adds major new features for working with required resources and schemas, bringing the TypeScript SDK to feature parity with the Python SDK. Functions can now request Crossplane fetch additional resources and OpenAPI schemas, and check Crossplane's capabilities before using advanced features.

Installation

npm install @crossplane-org/function-sdk-typescript@0.5.0

What's New

Required Resources Support

Dynamically Fetch Additional Resources: Functions can now request that Crossplane fetch specific Kubernetes resources and include them in subsequent requests. This enables functions to read ConfigMaps, Secrets, and other cluster resources to inform composition logic.

Response: Request Resources

Use requireResource() to tell Crossplane which resources to fetch:

import { requireResource } from '@crossplane-org/function-sdk-typescript';

// Match a specific ConfigMap by name
rsp = requireResource(rsp, "app-config", {
  apiVersion: "v1",
  kind: "ConfigMap",
  matchName: "my-app-config",
  namespace: "production"
});

// Match all Secrets with specific labels
rsp = requireResource(rsp, "db-secrets", {
  apiVersion: "v1",
  kind: "Secret",
  matchLabels: {
    labels: {
      app: "database",
      tier: "backend"
    }
  },
  namespace: "production"
});

Request: Retrieve Required Resources

In the next function invocation, retrieve the fetched resources:

import { getRequiredResource } from '@crossplane-org/function-sdk-typescript';

const [resources, resolved] = getRequiredResource(req, "app-config");
if (!resolved) {
  console.log("Resource requirement not yet resolved by Crossplane");
} else if (resources.length === 0) {
  console.log("Resource requirement resolved but no resources found");
} else {
  console.log("Found resources:", resources);
  resources.forEach(r => console.log(r.resource));
}

The selector supports:

  • Match by name: Fetch a specific resource with matchName
  • Match by labels: Fetch all resources matching label selectors with matchLabels
  • Namespace scoping: Specify namespace for namespaced resources, or omit for cluster-scoped resources
  • Cross-namespace: Omit namespace when matching by labels to search all namespaces

Required Schemas Support

Access OpenAPI Schemas: Functions can now request OpenAPI v3 schemas for resource kinds. This enables validation, default value application, and schema-aware composition logic.

Response: Request Schemas

Use requireSchema() to tell Crossplane which schemas to fetch:

import { requireSchema } from '@crossplane-org/function-sdk-typescript';

// Request the OpenAPI schema for an XR type
rsp = requireSchema(rsp, "xr-schema", "example.org/v1", "MyResource");

// Request schema for a composed resource type
rsp = requireSchema(rsp, "bucket-schema", "s3.aws.upbound.io/v1beta1", "Bucket");

Request: Retrieve Required Schemas

In the next function invocation, access the schemas:

import { getRequiredSchema, getRequiredSchemas } from '@crossplane-org/function-sdk-typescript';

// Get a specific schema
const [schema, resolved] = getRequiredSchema(req, "xr-schema");
if (!resolved) {
  console.log("Schema not yet resolved by Crossplane");
} else if (!schema) {
  console.log("Schema resolved but not found (kind may not exist)");
} else {
  console.log("Schema properties:", schema.properties);
  console.log("Required fields:", schema.required);
}

// Get all required schemas
const schemas = getRequiredSchemas(req);
for (const [name, schema] of Object.entries(schemas)) {
  if (schema) {
    console.log(`Schema ${name}:`, schema);
  } else {
    console.log(`Schema ${name} was requested but not found`);
  }
}

For CRDs, Crossplane returns the spec.versions[].schema.openAPIV3Schema field. If Crossplane cannot find a schema for the requested kind, the schema will be undefined (with resolved returning true).

Capability Detection

Check Crossplane Features: Functions can now detect which capabilities Crossplane supports, enabling graceful degradation and conditional feature usage.

import { advertiseCapabilities, hasCapability, Capability } from '@crossplane-org/function-sdk-typescript';

// Check if Crossplane advertises capabilities (v2.2+)
if (!advertiseCapabilities(req)) {
  console.log("Crossplane version predates capability advertisement");
} else {
  // Check for specific capabilities
  if (hasCapability(req, Capability.CAPABILITY_REQUIRED_SCHEMAS)) {
    requireSchema(rsp, "xr", xrApiVersion, xrKind);
  }

  if (hasCapability(req, Capability.CAPABILITY_REQUIRED_RESOURCES)) {
    requireResource(rsp, "config", configSelector);
  }
}

Available capabilities:

  • CAPABILITY_CAPABILITIES - Crossplane advertises its capabilities
  • CAPABILITY_REQUIRED_RESOURCES - Support for required resources
  • CAPABILITY_REQUIRED_SCHEMAS - Support for required schemas
  • CAPABILITY_WATCHED_RESOURCES - Support for watched resources (deprecated)

Crossplane v2.2 and later advertise their capabilities in the request metadata. For older versions, advertiseCapabilities() returns false and hasCapability() will always return false, even for features the older Crossplane may support.

Enhanced Resource Utilities

Merge and Update Resources: New helper functions for working with resource conditions and updates.

Update Resources with Merge

The update() function performs deep merges of resource data:

import { update } from '@crossplane-org/function-sdk-typescript';

const bucket = getDesiredComposedResources(req)["my-bucket"];
if (bucket) {
  // Update specific fields while preserving others
  // Arrays are replaced by default (matches Python SDK behavior)
  update(bucket, {
    resource: {
      spec: {
        forProvider: {
          region: "us-west-2",
          tags: ["env:prod", "team:platform"]  // Replaces existing tags
        }
      }
    }
  });
}

// Optionally concatenate arrays instead of replacing them
update(bucket, {
  resource: {
    spec: {
      forProvider: {
        tags: ["new-tag"]  // Will be appended to existing tags
      }
    }
  }
}, { mergeArrays: true });

By default, arrays are replaced (matching Python SDK semantics). Set mergeArrays: true to concatenate arrays instead.

Get Status Conditions

The getCondition() function extracts Kubernetes status conditions:

import { getCondition } from '@crossplane-org/function-sdk-typescript';

const oxr = getObservedCompositeResource(req);
if (oxr?.resource) {
  const readyCondition = getCondition(oxr.resource, "Ready");
  if (readyCondition.status === "True") {
    console.log("Resource is ready");
  } else if (readyCondition.status === "False") {
    console.log("Resource not ready:", readyCondition.message);
  } else {
    console.log("Ready status unknown");
  }
}

// Check if a composed resource is synced
const bucket = observedResources["my-bucket"];
if (bucket?.resource) {
  const synced = getCondition(bucket.resource, "Synced");
  console.log("Sync status:", synced.status, synced.reason);
}

Returns a condition with status "Unknown" if the requested condition type is not found.

New Types and Exports

This release adds several new TypeScript types and exports:

Request helpers:

  • getRequiredResource() - Get a required resource by name
  • getRequiredResources() - Get all required resources
  • getRequiredSchema() - Get a required schema by name
  • getRequiredSchemas() - Get all required schemas
  • advertiseCapabilities() - Check if Crossplane advertises capabilities
  • hasCapability() - Check for a specific capability

Response helpers:

  • requireResource() - Request Crossplane fetch resources
  • requireSchema() - Request Crossplane fetch schemas

Resource utilities:

  • update() - Deep merge resource data with configurable array handling
  • getCondition() - Extract status conditions from resources
  • Condition - TypeScript interface for status conditions
  • MergeOptions - Options for controlling merge behavior

Protocol buffer types:

  • Capability - Enum of Crossplane capabilities
  • ResourceSelector - Selector for matching required resources
  • SchemaSelector - Selector for matching required schemas
  • Requirements - Protocol buffer message for requirements

Protobuf Updates

Updated Function Runtime API: Regenerated protobuf TypeScript files from the latest Crossplane Function Runtime API, adding support for:

  • Required resources (requiredResources field)
  • Required schemas (requiredSchemas field)
  • Capability advertisement (meta.capabilities field)
  • Resource requirements (requirements.resources field)
  • Schema requirements (requirements.schemas field)

The protobuf updates add 572 lines of new types and message handling code for these features.

Feature Parity with Python SDK

This release brings the TypeScript SDK to feature parity with the Python SDK (v0.8+) for:

  • ✅ Required resources with selectors (name and label-based)
  • ✅ Required schemas with OpenAPI v3 support
  • ✅ Capability detection and advertisement
  • ✅ Resource update with configurable merge semantics
  • ✅ Status condition extraction
  • ✅ Comprehensive test coverage for all new features

Testing

All new features include comprehensive test coverage:

  • ✅ Required resource fetching and resolution
  • ✅ Required schema fetching and resolution
  • ✅ Capability detection and checking
  • ✅ Resource update and merge operations (with and without array merging)
  • ✅ Status condition extraction
  • ✅ Edge cases and error handling
  • ✅ Full test suite passing with 2,985 new lines of test coverage
  • ✅ CI/CD pipeline validates all changes

Documentation

📖 README.md - Complete documentation
📖 USAGE.md - Usage guide

Requirements

  • Node.js 18+
  • TypeScript 5.9+
  • Compatible with Crossplane Function Runtime API v1
  • Requires Crossplane v2.2+ for capability advertisement
  • Required resources and schemas supported in Crossplane v2.2+

Breaking Changes

None. This release is fully backward compatible with v0.4.0.

Full Changelog

Full Changelog: v0.4.0...v0.5.0

Pull Requests

  • #15: Match python sdk - Feature parity for required resources, schemas, and capabilities
  • #14: Add Required resources and schemas

Commits

  • Merge pull request #15 from stevendborrelli/match-python-sdk (e5816ba)
  • update based on copilot review (39dd3c2)
  • npm run format (04e9c85)
  • add capability support (32b0cfc)
  • update test and check for proto resource type (b3c7c70)
  • sync with python sdk (b0ae0ef)
  • Merge pull request #14 from stevendborrelli/required-resources (257b405)
  • formt and export capability (b9447a8)
  • fix copilot review issues (a69015d)
  • drop error (515f1f0)
  • simplify function return (0a79b3e)
  • add support for required resources (bf39a2f)

Migration Guide

No migration required. All existing code continues to work unchanged. To use the new features:

  1. Required Resources: Add requireResource() calls to your response to request resources, then use getRequiredResource() in subsequent requests to access them.

  2. Required Schemas: Add requireSchema() calls to your response to request schemas, then use getRequiredSchema() in subsequent requests to access them.

  3. Capability Detection: Use advertiseCapabilities() and hasCapability() to conditionally enable features based on Crossplane version.

  4. Resource Updates: Replace manual object spreading with the update() function for safer deep merges with configurable array handling.

  5. Status Conditions: Use getCondition() instead of manually traversing resource.status.conditions arrays.

Example function using new features:

import {
  FunctionRunner,
  getObservedCompositeResource,
  getRequiredResource,
  getRequiredSchema,
  requireResource,
  requireSchema,
  setDesiredComposedResources,
  advertiseCapabilities,
  hasCapability,
  Capability,
  update,
  getCondition,
} from '@crossplane-org/function-sdk-typescript';

const runner = new FunctionRunner();

runner.addFunction('compose', async (req, log) => {
  const rsp = await runner.createResponse(req);

  // Check capabilities before using advanced features
  if (advertiseCapabilities(req) && hasCapability(req, Capability.CAPABILITY_REQUIRED_RESOURCES)) {
    // Request a ConfigMap in the first invocation
    requireResource(rsp, "app-config", {
      apiVersion: "v1",
      kind: "ConfigMap",
      matchName: "my-config",
      namespace: "default"
    });

    // In subsequent invocations, use the config
    const [configs, resolved] = getRequiredResource(req, "app-config");
    if (resolved && configs.length > 0) {
      const config = configs[0];
      log.info({ config: config.resource }, "Using config from cluster");

      // Check if the config is ready
      const ready = getCondition(config.resource, "Ready");
      if (ready.status === "True") {
        // Use config data to compose resources
        const bucket = desiredResources["my-bucket"];
        if (bucket) {
          update(bucket, {
            resource: {
              spec: {
                forProvider: {
                  region: config.resource?.data?.region || "us-east-1"
                }
              }
            }
          });
        }
      }
    }
  }

  return rsp;
});