TinyNode CRD
TinyNode is the core Custom Resource that represents a component instance in a flow. Understanding TinyNode is essential for module development.
Full Specification
apiVersion: operator.tinysystems.io/v1alpha1
kind: TinyNode
metadata:
name: my-router-abc123
namespace: tinysystems
labels:
tiny.systems/flow-id: "flow-xyz"
tiny.systems/project-id: "project-123"
tiny.systems/workspace-id: "workspace-456"
spec:
# Which module provides this component
module: github.com/tiny-systems/common-module
# Component name within the module
component: router
# Module version
version: "1.0.0"
# Outgoing connections
edges:
- id: "edge-1"
port: "out_success" # Source port on this node
to: "next-node-name" # Target node name
toPort: "input" # Target port name
configuration: # Data mapping (expressions)
context: "{{$.result}}"
userId: "{{$.user.id}}"
status:
# Observed generation for idempotency
observedGeneration: 1
# Module information
moduleName: common-module
# Component information
component: router
# Port definitions with schemas
ports:
- name: input
label: "Input"
position: 3 # Left
source: false
schema: |
{"type": "object", "properties": {...}}
configuration: |
{"context": {}}
- name: out_success
label: "Success"
position: 1 # Right
source: true
schema: |
{"type": "object", "properties": {...}}
# Shared metadata across pods
metadata:
http-server-port: "8080"
custom-key: "custom-value"
# Error state
error: ""
# Last update timestamp
lastUpdateTime: "2024-01-15T10:30:00Z"Spec Fields
module
The Go module path that provides this component:
spec:
module: github.com/tiny-systems/common-moduleUsed to route messages and find the correct operator.
component
The component name within the module:
spec:
component: routerMust match the Name returned by GetInfo().
version
Semantic version of the module:
spec:
version: "1.0.0"edges
Array of outgoing connections:
spec:
edges:
- id: "edge-abc123" # Unique edge identifier
port: "output" # Source port on this node
to: "target-node-name" # Target TinyNode name
toPort: "input" # Target port name
configuration: # Expression-based data mapping
result: "{{$.data}}"
timestamp: "{{now()}}"Edge Configuration
The configuration object uses expressions to transform data:
configuration:
# Direct mapping
userId: "{{$.user.id}}"
# String interpolation
message: "Hello {{$.user.name}}!"
# Arithmetic
total: "{{$.price * $.quantity}}"
# Conditional (using comparison)
isValid: "{{$.status == 'active'}}"
# Literal values (no expressions)
staticValue: "constant"Status Fields
observedGeneration
Tracks which spec version was last processed:
if node.Status.ObservedGeneration >= node.Generation {
// Already processed this version
return ctrl.Result{}, nil
}
// Process and update
node.Status.ObservedGeneration = node.Generationports
Array of port definitions generated from the component:
status:
ports:
- name: input
label: "Input"
position: 3 # module.Left
source: false # Input port
schema: | # JSON Schema (stringified)
{
"type": "object",
"properties": {
"context": {"type": "object"}
}
}
configuration: | # Current configuration (stringified JSON)
{"context": {}}metadata
Key-value store for sharing state across pods:
status:
metadata:
http-server-port: "8080"
custom-state: "value"Used for CR-Based State Propagation.
error
Error message if the node is in an error state:
status:
error: "component initialization failed: missing required setting"Labels
Standard labels applied to TinyNodes:
| Label | Purpose | Example |
|---|---|---|
tiny.systems/flow-id | Flow identifier | flow-abc123 |
tiny.systems/project-id | Project identifier | project-xyz |
tiny.systems/workspace-id | Workspace identifier | workspace-456 |
tiny.systems/module | Module name | common-module |
Accessing TinyNode in Components
Via Reconcile Port
func (c *Component) Handle(ctx context.Context, output module.Handler, port string, msg any) any {
if port == v1alpha1.ReconcilePort {
node, ok := msg.(v1alpha1.TinyNode)
if !ok {
return nil
}
// Access node information
nodeName := node.Name
edges := node.Spec.Edges
metadata := node.Status.Metadata
// Read shared state
if port, ok := metadata["http-server-port"]; ok {
c.configuredPort = port
}
}
return nil
}Updating Node Status
Only the leader pod should update status:
func (c *Component) Handle(ctx context.Context, output module.Handler, port string, msg any) any {
// Update metadata (leader only)
if utils.IsLeader(ctx) {
output(ctx, v1alpha1.ReconcilePort, func(node *v1alpha1.TinyNode) {
if node.Status.Metadata == nil {
node.Status.Metadata = make(map[string]string)
}
node.Status.Metadata["my-key"] = "my-value"
})
}
return nil
}TinyNode Lifecycle
1. CREATION
┌────────────────────────────────────────────────────────────┐
│ Platform creates TinyNode CR when flow is deployed │
│ Labels link to flow, project, workspace │
└────────────────────────────────────────────────────────────┘
│
▼
2. RECONCILIATION
┌────────────────────────────────────────────────────────────┐
│ TinyNodeReconciler detects new node │
│ Creates Runner instance │
│ Sends to _settings port │
│ Updates status with port schemas │
└────────────────────────────────────────────────────────────┘
│
▼
3. EXECUTION
┌────────────────────────────────────────────────────────────┐
│ TinySignals trigger node execution │
│ Component.Handle() processes messages │
│ Output routed via edges to next nodes │
└────────────────────────────────────────────────────────────┘
│
▼
4. PERIODIC RECONCILIATION
┌────────────────────────────────────────────────────────────┐
│ Every 5 minutes: _reconcile port triggered │
│ Component can refresh state │
│ Leader updates status if needed │
└────────────────────────────────────────────────────────────┘
│
▼
5. DELETION
┌────────────────────────────────────────────────────────────┐
│ Platform deletes TinyNode CR when flow is undeployed │
│ Runner instance destroyed │
│ Resources cleaned up │
└────────────────────────────────────────────────────────────┘Example: Reading Edge Configuration
func (r *Runner) processEdges(ctx context.Context, port string, data any) error {
for _, edge := range r.node.Spec.Edges {
if edge.Port != port {
continue
}
// Evaluate expressions in edge configuration
transformedData := evaluator.Evaluate(edge.Configuration, data)
// Send to next node
r.scheduler.Handle(ctx, runner.Msg{
To: fmt.Sprintf("%s.%s", edge.To, edge.ToPort),
From: fmt.Sprintf("%s.%s", r.node.Name, port),
Data: transformedData,
})
}
return nil
}Next Steps
- TinyModule CRD - Module service discovery
- TinySignal CRD - Triggering execution
- Controller Reconciliation - Reconciliation patterns