Convert KCL (KCL Configuration Language) schemas to Crossplane Composite Resource Definitions (XRDs).
Inspired by crossplane-tools and kcl-openapi.
# Build from source
git clone https://github.com/ggkhrmv/kcl2xrd.git
cd kcl2xrd && make build
# Simple conversion with in-file metadata
./bin/kcl2xrd -i examples/kcl/dynatrace-with-metadata.k -o output.yaml
# Override with CLI flags
./bin/kcl2xrd -i examples/kcl/postgresql.k -g database.example.org -o output.yaml
# Generate with claims support
./bin/kcl2xrd -i examples/kcl/postgresql.k -g database.example.org --with-claims -o output.yaml- In-file XRD metadata with
__xrd_prefix variables - define everything in your KCL files - KCL runtime evaluation - automatically evaluates metadata variables including format expressions, property access, and variable references
- Import support - reuse central configuration files across multiple XRDs with KCL imports
@xrdannotation - mark parent schema, ignore unrelated code- KCL reserved fields - prefixed with
$the KCL reserved fields can be used for schema definition - Validation annotations - patterns, enums, ranges, string/numeric constraints, CEL expressions, oneOf/anyOf schema composition
- Kubernetes-specific annotations - immutability, preserveUnknownFields (with granular control for array items), mapType, listType, listMapKeys, additionalProperties
@statusannotation - separate status fields or define separate status schema for proper Crossplane resource state management@specannotation - define fields directly underspec(notspec.parameters) for Crossplane composition selectors and other spec-level fields@spec.pathannotation - define entire schemas as objects at custom paths underspec(e.g.,writeConnectionSecretToRef,publishConnectionDetailsTo)- Nested schema expansion - automatic reference resolution
anytype support - fields without type constraints for maximum flexibility (IAM policies, etc.){any:any}syntax - arbitrary property objects with@preserveUnknownFields- Claims support - automatic X-prefix handling for composite resources with unprefixed
__xrd_kind
Download from GitHub Releases:
# Linux (AMD64)
curl -LO https://github.com/ggkhrmv/kcl2xrd/releases/latest/download/kcl2xrd-linux-amd64
chmod +x kcl2xrd-linux-amd64 && sudo mv kcl2xrd-linux-amd64 /usr/local/bin/kcl2xrd
# macOS (Intel)
curl -LO https://github.com/ggkhrmv/kcl2xrd/releases/latest/download/kcl2xrd-darwin-amd64
chmod +x kcl2xrd-darwin-amd64 && sudo mv kcl2xrd-darwin-amd64 /usr/local/bin/kcl2xrd
# macOS (Apple Silicon)
curl -LO https://github.com/ggkhrmv/kcl2xrd/releases/latest/download/kcl2xrd-darwin-arm64
chmod +x kcl2xrd-darwin-arm64 && sudo mv kcl2xrd-darwin-arm64 /usr/local/bin/kcl2xrdgo install github.com/ggkhrmv/kcl2xrd/cmd/kcl2xrd@latest
# or
git clone https://github.com/ggkhrmv/kcl2xrd.git && cd kcl2xrd && make build# postgresql.k
schema PostgreSQLInstance:
# Storage in GB (required)
storageGB: int
# Instance size
instanceSize?: str = "small"kcl2xrd -i postgresql.k -g database.example.org -o postgresql.yaml# Full automation - no CLI flags needed
__xrd_kind = "DynatraceAlerting"
__xrd_group = "monitoring.crossplane.io"
__xrd_categories = ["monitoring", "alerting"]
# @xrd
schema DynatraceAlerting:
name: str
config: {str:str}kcl2xrd -i file.k -o output.yamlWhen using --with-claims, the tool automatically handles X-prefix naming. With the improvements in this version, you can now use unprefixed __xrd_kind for a more natural workflow:
Using Unprefixed __xrd_kind (Recommended):
__xrd_kind = "Bucket" # No X prefix needed
__xrd_group = "storage.example.org"
# @xrd
schema Bucket:
name: strkcl2xrd -i bucket.k --with-claims -o output.yaml
# Generates:
# - XRD Kind: XBucket (X-prefix automatically added)
# - XRD Plural: xbuckets
# - XRD Name: xbuckets.storage.example.org
# - Claim Kind: Bucket (unprefixed)
# - Claim Plural: bucketsBackward Compatible with Prefixed Names:
__xrd_kind = "XDatabase" # Already has X prefix
# @xrd
schema Database:
name: strkcl2xrd -i database.k --with-claims -o output.yaml
# Generates:
# - XRD Kind: XDatabase (keeps X-prefix)
# - Claim Kind: Database (X-prefix removed)| KCL Type | OpenAPI Type | CEL Type | Example |
|---|---|---|---|
str |
string |
string |
name: str |
int |
integer |
int |
count: int |
float |
number |
double |
price: float |
bool |
boolean |
bool |
enabled: bool |
any |
(no type) + x-kubernetes-preserve-unknown-fields |
dynamic |
principal?: any |
[T] |
array with items |
list |
tags: [str] |
{K:V} |
object with additionalProperties |
map |
labels: {str:str} |
{any:any} |
object with additionalProperties: {} |
map |
config: {any:any} |
Map Types: KCL map types like {str:str}, {str:int}, etc. are converted to OpenAPI object type with additionalProperties schema. The additionalProperties field specifies the type of the map values:
{str:str}→type: objectwithadditionalProperties: { type: string }{str:int}→type: objectwithadditionalProperties: { type: integer }{any:any}→type: objectwithadditionalProperties: {}(allows any value type)
This mapping ensures that CEL validations can properly recognize these fields as map types, enabling CEL expressions like self.labels.size() or self.config['key'].
Note: The any type is particularly useful for fields that can accept arbitrary JSON/YAML data (like AWS IAM policy principals, actions, etc.). When using any type with @preserveUnknownFields annotation, the field will not have a type constraint, allowing maximum flexibility.
Marks the schema to be converted to XRD. Only one schema in a file should be marked with @xrd.
# @xrd
schema MyResource:
name: strFields with names starting with $ are treated as KCL internal variables and are automatically omitted from the generated XRD. This is useful for KCL-specific fields like $filter or internal configuration variables that should not appear in the Crossplane XRD.
schema MyApp:
# Regular field - included in XRD
name: str
replicas?: int = 3
# KCL reserved keyword with $ prefix - rendered as "filter" in XRD
$filter: str
# KCL reserved keyword with $ prefix - rendered as "type" in XRD
$type: str
# Regular field - included in XRD
region?: str = "us-east-1"This generates an XRD with the $ prefix stripped from field names:
spec:
properties:
parameters:
type: object
properties:
name:
type: string
replicas:
type: integer
default: 3
filter:
type: string
type:
type: string
region:
type: string
default: us-east-1Key points:
- Use $ prefix for KCL reserved keywords (e.g., $filter, $type, $import)
- The $ is automatically stripped in the generated XRD
- Allows you to define fields with names that conflict with KCL syntax
- Works with all field types and validation annotations
Applies a regex pattern validation to string fields.
# @pattern("^[a-z0-9-]+$")
name: strSets minimum length for string fields.
# @minLength(3)
name: strSets maximum length for string fields.
# @maxLength(63)
name: strSets minimum value for integer fields.
# @minimum(0)
replicas: intSets maximum value for integer fields.
# @maximum(100)
replicas: intSets minimum number of items in arrays.
# @minItems(1)
tags: [str]Sets maximum number of items in arrays.
# @maxItems(10)
tags: [str]Specifies the format for string fields. Common formats include "date-time", "email", "uuid", "uri", "ipv4", "ipv6", etc.
# @format("date-time")
createdAt: str
# @format("email")
email: str
# @format("uuid")
id: strRestricts field to specific allowed values.
# @enum(["active", "inactive", "pending"])
status: strSpecifies that exactly one of the given field combinations must be present. This is useful for mutually exclusive options.
Applied to a field:
schema AccessControl:
groupName?: str
groupRef?: str
# Exactly one of groupName or groupRef must be provided
# @oneOf([["groupName"], ["groupRef"]])
config: {str:str}This generates:
config:
type: object
oneOf:
- required: ["groupName"]
- required: ["groupRef"]Apply directly to the schema to constrain the parameters object itself:
Applied to schema (parameters level):
# @xrd
# @oneOf([["emailAddress"], ["userId"]])
schema UserAccount:
emailAddress?: str
userId?: str
displayName: strThis generates:
spec:
parameters:
type: object
properties:
emailAddress:
type: string
userId:
type: string
displayName:
type: string
required:
- displayName
oneOf:
- required: ["emailAddress"]
- required: ["userId"]Specifies that at least one of the given field combinations must be present. Can be applied at both field and schema level.
Field level:
schema User:
userEmail?: str
userObjectId?: str
# At least one of userEmail or userObjectId must be provided
# @anyOf([["userEmail"], ["userObjectId"]])
userConfig: {str:str}Schema level:
# @xrd
# @anyOf([["password"], ["sshKey"]])
schema UserAccount:
password?: str
sshKey?: str
username: strThis generates:
anyOf:
- required: ["userEmail"]
- required: ["userObjectId"]You can use both annotations together at either level for complex validation requirements:
# @xrd
# @oneOf([["emailAddress"], ["userId"]])
# @anyOf([["password"], ["sshKey"]])
schema UserAccount:
emailAddress?: str
userId?: str
password?: str
sshKey?: str
displayName: strThis generates both oneOf and anyOf constraints at the parameters level.
Specifies the format for items in an array field. This is useful for validating array elements.
# @itemsFormat("email")
emails: [str]
# @itemsFormat("uuid")
identifiers: [str]
# @itemsFormat("uri")
websites?: [str]This generates:
emails:
type: array
items:
type: string
format: email
identifiers:
type: array
items:
type: string
format: uuidMarks a field as immutable (sets x-kubernetes-immutable: true).
# @immutable
resourceId: strAllows arbitrary properties (sets x-kubernetes-preserve-unknown-fields: true). Typically used with {any:any} type.
For object fields:
# @preserveUnknownFields
config: {any:any}For array fields with [{any:any}] pattern:
When used with [{any:any}], the annotation applies only to the array items, not the array itself. This matches the desired behavior for Kubernetes CRDs where you want items to be flexible but the array structure to be strict.
schema MyApp:
# @preserveUnknownFields
filter: [{any:any}]This generates:
filter:
type: array
items:
type: object
x-kubernetes-preserve-unknown-fields: trueNote: x-kubernetes-preserve-unknown-fields is on the items, NOT on the array itself.
Applies x-kubernetes-preserve-unknown-fields: true only to array items, not the array itself. This provides explicit control for any array type.
schema MyApp:
# @itemsPreserveUnknownFields
configs: [{str:str}]This generates:
configs:
type: array
items:
type: object
additionalProperties:
type: string
x-kubernetes-preserve-unknown-fields: trueUse cases:
- Use
@preserveUnknownFieldson[{any:any}]for backward compatibility (applies to items) - Use
@itemsPreserveUnknownFieldsfor explicit control on any array type - Use
@preserveUnknownFieldson non-array fields to apply to the field itself
Sets x-kubernetes-map-type. Valid values: "atomic", "granular".
# @mapType("atomic")
settings: {str:str}Sets x-kubernetes-list-type. Valid values: "atomic", "set", "map".
# @listType("set")
tags: [str]Sets x-kubernetes-list-map-keys for list-map type lists.
# @listType("map")
# @listMapKeys(["name"])
items: [Item]Marks a field as a status field, placing it in the status section of the XRD instead of spec.parameters. Status fields represent the observed state of the resource rather than the desired state.
Option 1: Status fields in main schema
schema Database:
# Spec fields (desired state)
name: str
size: str
# Status fields (observed state)
# @status
ready: bool
# @status
# @preserveUnknownFields
conditions?: {any:any}
# @status
endpoint?: strOption 2: Separate status schema (recommended)
You can define status as a separate schema marked with @status:
# @xrd
schema Application:
name: str
replicas: int
# @status
schema AppStatus:
ready: bool
phase?: str
endpoint?: strThis generates an XRD with separate spec and status sections:
- Spec fields go to
spec.parameters - Status fields go to
status(sibling tospec) - All validation and Kubernetes annotations work with status fields
Empty Status with Preserve Unknown Fields:
You can also define status without any fields using the __xrd_status_preserve_unknown_fields metadata variable:
__xrd_group = "example.org"
__xrd_kind = "KafkaCluster"
__xrd_status_preserve_unknown_fields = True
schema KafkaCluster:
tenant: str
replicas?: int = 3This generates a status section with just x-kubernetes-preserve-unknown-fields: true, allowing any status fields to be set dynamically.
Marks a field as a spec-level field, placing it directly under spec instead of spec.parameters. This is useful for Crossplane-specific fields like compositionSelector, compositionRef, or compositionRevisionRef that need to be at the spec level.
Basic usage:
schema MyComposite:
# Regular fields - go to spec.parameters
name: str
replicas?: int = 3
# Spec-level fields - go directly under spec
# @spec
compositionSelector?: {str:str}
# @spec
compositionRef?: str
# @spec
# @immutable
compositionRevisionRef?: strThis generates an XRD with:
spec:
properties:
parameters:
type: object
properties:
name:
type: string
replicas:
type: integer
default: 3
required:
- name
compositionSelector:
type: object
additionalProperties:
type: string
compositionRef:
type: string
compositionRevisionRef:
type: string
x-kubernetes-immutable: true
required:
- parametersKey points:
- Spec-level fields go directly under
spec, not underspec.parameters - All validation annotations work with spec-level fields (
@immutable,@pattern, etc.) - Required spec-level fields are added to
spec.required - Useful for Crossplane composition-related fields
Common use cases:
compositionSelector- Select composition based on labelscompositionRef- Reference a specific compositioncompositionRevisionRef- Pin to a specific composition revision- Custom spec-level fields for advanced Crossplane features
Marks an entire schema to be placed at spec.path in the generated XRD. Similar to @status for schemas, but allows specifying a custom path under spec. This is particularly useful for Crossplane features like writeConnectionSecretToRef and publishConnectionDetailsTo.
Usage with separate schemas:
# @xrd
schema Database:
# Regular fields go to spec.parameters
name: str
replicas?: int = 3
# @spec.writeConnectionSecretToRef
schema ConnectionSecretRef:
name: str
namespace?: str
# @spec.publishConnectionDetailsTo
schema PublishConnectionDetails:
name: str
# @additionalProperties
metadata?: {str:str}
# @status
schema DatabaseStatus:
ready: bool
endpoint?: strThis generates an XRD with:
spec:
properties:
parameters: # Regular fields
type: object
properties:
name:
type: string
replicas:
type: integer
required:
- name
writeConnectionSecretToRef: # From @spec.writeConnectionSecretToRef schema
type: object
properties:
name:
type: string
namespace:
type: string
required:
- name
publishConnectionDetailsTo: # From @spec.publishConnectionDetailsTo schema
type: object
properties:
name:
type: string
metadata:
type: object
additionalProperties: true
required:
- name
required:
- parameters
status: # From @status schema
properties:
ready:
type: boolean
endpoint:
type: stringKey points:
- Schema-level annotation: applies to entire schema, not individual fields
- Creates a nested object at the specified path under
spec - All fields from the annotated schema become properties of that object
- Works alongside
@statusschemas and regular fields - Validation annotations work on fields within the schema
Common Crossplane use cases:
@spec.writeConnectionSecretToRef- Connection secret configuration@spec.publishConnectionDetailsTo- Connection details publishing@spec.resourceRefs- Cross-resource references- Custom spec-level objects for platform-specific configurations
Allows a field to accept additional properties beyond those defined in its schema. Sets additionalProperties: true on the field.
schema Config:
# Accept any additional string properties
# @additionalProperties
settings: {str:str}
# @status
# @additionalProperties
metrics?: {str:int}This generates:
settings:
type: object
additionalProperties: trueAdds CEL (Common Expression Language) validation rules with optional error message.
# @validate("self > 0", "Must be positive")
value: int
# @validate("self.startsWith('prefix-')")
identifier: strschema ValidatedResource:
# String with pattern and length constraints
# @pattern("^[a-z0-9-]+$")
# @minLength(3)
# @maxLength(63)
name: str
# String with format validation
# @format("date-time")
createdAt: str
# Email with format and pattern
# @format("email")
email?: str
# Enum validation
# @enum(["active", "inactive", "pending"])
status?: str = "active"
# Immutable field
# @immutable
resourceId: str
# Numeric constraints
# @minimum(0)
# @maximum(100)
replicas?: int = 1
# Arbitrary properties with atomic map type
# @preserveUnknownFields
# @mapType("atomic")
settings?: {any:any}
# List with set semantics
# @listType("set")
tags?: [str]
# Array with minimum and maximum items
# @minItems(1)
# @maxItems(10)
requiredItems: [str]
# CEL validation
# @validate("self > 0", "Must be positive")
value: int
# oneOf/anyOf example for mutually exclusive options
groupName?: str
groupRef?: str
userEmail?: str
userObjectId?: str
# Configuration requiring exactly one group identifier and at least one user identifier
# @oneOf([["groupName"], ["groupRef"]])
# @anyOf([["userEmail"], ["userObjectId"]])
accessConfig?: {str:str}Define in your KCL file with __xrd_ prefix:
__xrd_kind- Schema kind name (can be variable reference like_myKind)__xrd_group- API group (supports literals, format expressions, property access, and variable references)__xrd_version- API version (default: v1alpha1)__xrd_categories- Categories list__xrd_served- Served flag (True/False)__xrd_referenceable- Referenceable flag (True/False)__xrd_status_preserve_unknown_fields- Enable empty status with preserve-unknown-fields (True/False)__xrd_printer_columns- Printer columns list
All metadata variables are evaluated using the KCL runtime, which provides maximum flexibility:
1. String Literals:
__xrd_kind = "Database"
__xrd_group = "platform.example.com"2. Variable References:
_myKind = "PostgreSQL"
_myGroup = "database.example.com"
__xrd_kind = _myKind
__xrd_group = _myGroup3. Format Expressions:
_subgroup = "storage"
_domain = "mycorp.io"
__xrd_group = "{}.{}".format(_subgroup, _domain)
# Resolves to: storage.mycorp.io4. Property Access:
settings = {
PLATFORM_API_GROUP: "platform.example.com"
}
__xrd_group = "{}.{}".format("database", settings.PLATFORM_API_GROUP)
# Resolves to: database.platform.example.com5. Imports from Central Configuration:
import base.settings
__xrd_group = "{}.{}".format("storage", settings.PLATFORM_API_GROUP)
__xrd_version = settings.DEFAULT_VERSION
# Values resolved from imported settings moduleYou can create central configuration files and import them across multiple XRDs:
Central Settings File (base/settings.k):
# Organization-wide settings
PLATFORM_API_GROUP = "platform.mycorp.io"
DEFAULT_VERSION = "v1alpha1"
DEFAULT_CATEGORIES = ["platform", "managed"]XRD File Using Imports:
import base.settings
_subgroup = "database"
__xrd_kind = "PostgreSQL"
__xrd_group = "{}.{}".format(_subgroup, settings.PLATFORM_API_GROUP)
__xrd_version = settings.DEFAULT_VERSION
# @xrd
schema PostgreSQL:
name: str
size?: intResult:
- Group automatically resolves to:
database.platform.mycorp.io - Version uses:
v1alpha1(from central settings) - No CLI flags needed!
How it works:
- First, the tool tries to evaluate the file with imports intact
- If imports can be resolved (local modules exist), they work correctly
- If imports fail (missing dependencies), it falls back to filtering imports and evaluating variable references
- This ensures maximum flexibility while maintaining robustness
Note on __xrd_group:
- Supports any valid KCL expression: string literals, format expressions, property access, variable references
- Automatically evaluated using the KCL runtime for maximum flexibility
- Works with imports from central configuration files
- Examples:
__xrd_group = "example.org"(literal)__xrd_group = _myGroup(variable reference)__xrd_group = "{}.{}".format(_sub, _domain)(format expression)__xrd_group = "{}.{}".format("db", settings.API_GROUP)(property access)__xrd_group = "{}.{}".format("db", settings.PLATFORM_API_GROUP)(imported module)
Note on __xrd_kind:
- Specifies the
spec.names.kindfor the XRD (e.g.,"Bucket","Database") - Supports string literals and variable references (e.g.,
__xrd_kind = _myKind) - When using
--with-claimsflag, accepts unprefixed names:- If
__xrd_kind = "Bucket", generates XRD kindXBucketand claim kindBucket - Automatically adds X prefix for XRD, uses unprefixed for claims
- Backward compatible: if you provide
"XBucket", it strips and re-adds X correctly
- If
- The XRD
metadata.nameuses the plural of this kind (e.g.,"buckets.group"or"xbuckets.group"with claims) - If not specified, defaults to the schema name
- Useful when you want the XRD kind to differ from the schema name
# Define variables
_xrSubgroup = "aws"
_platformGroup = "mycorp.io"
# Use format expression - automatically evaluated by KCL
__xrd_kind = "XBucket"
__xrd_group = "{}.{}".format(_xrSubgroup, _platformGroup)
# @xrd
schema Bucket:
name: str# No --group flag needed - expression is automatically evaluated
kcl2xrd -i bucket.k -o bucket.yaml
# Generates:
# - metadata.name: xbuckets.aws.mycorp.io (plural of XBucket)
# - spec.names.kind: XBucket (from __xrd_kind)
# - spec.group: aws.mycorp.io (from evaluated __xrd_group)# Using property access and nested structures
settings = {
PLATFORM_API_GROUP: "platform.example.com"
}
_xrSubgroup = "storage"
__xrd_kind = "XDatabase"
__xrd_group = "{}.{}".format(_xrSubgroup, settings.PLATFORM_API_GROUP)
# @xrd
schema Database:
name: str# All expressions are evaluated using KCL runtime
kcl2xrd -i database.k -o database.yaml
# Automatically resolves to: storage.platform.example.com-i, --input: Input KCL file (required)-g, --group: API group (optional if__xrd_groupin file)-o, --output: Output file (stdout if not specified)--with-claims: Generate claimable XRD with automatic X-prefix handling--schema: Select specific schema--version: API version (default: v1alpha1)--categories: Override categories--printer-columns: Override printer columns
When your KCL files contain both schema definitions and other code (like composition templates, module-level variables, etc.), follow these guidelines:
- Use the
@xrdannotation to explicitly mark which schema should be converted:
# Other code and imports
import base.schemas as schemas
# @xrd <-- Mark the schema for conversion
schema MyResource:
name: str
config?: any
# Other module-level variables (these won't be parsed as schema fields)
_composition: schemas.Composition{
xrKind: "MyResource"
}-
Module-level variables after schemas are ignored: The parser stops collecting schema fields when it encounters a non-indented line (module-level code). This ensures composition templates and other variables don't pollute your XRD schema.
-
Use
anytype for flexible fields: For fields that need to accept arbitrary JSON/YAML structures (like IAM policies, custom configurations), use theanytype with@preserveUnknownFields:
schema PolicyStatement:
# @preserveUnknownFields
# Can be a string, array, or object
principal?: any
# @preserveUnknownFields
# Can be a string, array, or object
action?: anyThis generates fields without a type constraint, only with x-kubernetes-preserve-unknown-fields: true, allowing maximum flexibility.
For Crossplane composite resources, separate desired state (spec) from observed state (status) using the @status annotation:
schema Database:
"""A database composite resource"""
# Spec fields: what the user wants
# @pattern("^[a-z0-9-]+$")
name: str
# @enum(["small", "medium", "large"])
size?: str = "small"
# @minimum(1)
replicas?: int = 1
# Status fields: what the system observes
# @status
ready: bool
# @status
phase?: str
# @status
# @preserveUnknownFields
conditions?: {any:any}
# @status
endpoint?: strThis generates an XRD with:
spec.parameterscontaining desired state fields (name, size, replicas)statussection containing observed state fields (ready, phase, conditions, endpoint)
Benefits:
- Clear separation of concerns
- Follows Kubernetes resource conventions
- Status fields can use all annotations (validation, preserveUnknownFields, etc.)
- Required fields are tracked separately for spec and status
For teams managing multiple XRDs, create central configuration files to maintain consistency:
1. Create a central settings file:
# base/settings.k
PLATFORM_API_GROUP = "platform.mycorp.io"
DEFAULT_VERSION = "v1alpha1"
COMMON_CATEGORIES = ["platform", "managed"]2. Import and use in your XRDs:
# s3bucket.k
import base.settings
__xrd_kind = "S3Bucket"
__xrd_group = "{}.{}".format("storage", settings.PLATFORM_API_GROUP)
__xrd_version = settings.DEFAULT_VERSION
# @xrd
schema S3Bucket:
name: str
versioned?: bool# database.k
import base.settings
__xrd_kind = "PostgreSQL"
__xrd_group = "{}.{}".format("database", settings.PLATFORM_API_GROUP)
__xrd_version = settings.DEFAULT_VERSION
# @xrd
schema PostgreSQL:
name: str
size: intBenefits:
- ✅ Consistent API group across all XRDs
- ✅ Easy updates (change once, applies everywhere)
- ✅ Reduced duplication
- ✅ Type-safe with KCL validation
3. Use variable references for dynamic configuration:
import base.settings
# Define per-resource configuration
_subgroup = "storage"
_resourceType = "S3Bucket"
# Use in metadata
__xrd_kind = _resourceType
__xrd_group = "{}.{}".format(_subgroup, settings.PLATFORM_API_GROUP)
# @xrd
schema S3Bucket:
name: strThis pattern makes it easy to generate multiple related XRDs with consistent naming conventions.
See examples/ directory:
- postgresql.k - Basic schema with optional fields
- validated.k - Validation annotations
- nested-schema.k - Nested schema references
- dynatrace-with-metadata.k - Full in-file metadata
- preserve-unknown-fields.k - Arbitrary properties with
{any:any} - s3-bucket-with-policy.k - Complex example with
anytype fields and IAM policies - oneof-anyof-example.k - Field-level oneOf and anyOf validations
- schema-level-oneof-anyof.k - Schema-level oneOf and anyOf (applied to parameters)
- items-format-example.k - Array items with format validation
# Build
make build
# Run tests
make test
# Generate examples
make examples
# Create release (requires tag)
git tag v1.0.0 && git push origin v1.0.0Apache License 2.0 - See LICENSE file for details.