ConfigRef Pattern
The ConfigRef pattern enables components to reference values from Kubernetes Secrets and ConfigMaps, keeping sensitive data secure and configurations centralized.
Overview
┌─────────────────────────────────────────────────────────────────────────────┐
│ CONFIGREF PATTERN │
└─────────────────────────────────────────────────────────────────────────────┘
Visual Editor Kubernetes Component
│ │ │
│ User selects: │ │
│ Secret: "api-credentials" │ │
│ Key: "api-key" │ │
│ │ │ │
│ ▼ │ │
│ TinyNode CR saved │ │
│ │ │ │
│ └───────────────────▶ Controller reads │
│ │ Secret, resolves value │
│ │ │ │
│ │ ▼ │
│ │ Delivers resolved │
│ │ value to component │
│ │ │ │
│ │ └──────────────────▶
│ │ Settings with
│ │ actual secret
▼ ▼ ▼ConfigRef Structure
go
type ConfigRef struct {
SecretName string `json:"secretName,omitempty" title:"Secret Name"`
ConfigMapName string `json:"configMapName,omitempty" title:"ConfigMap Name"`
Key string `json:"key" title:"Key" required:"true"`
}Usage in Settings
Secret Reference
go
type Settings struct {
// Direct value OR secret reference
APIKey string `json:"apiKey,omitempty" title:"API Key" format:"password"`
APIKeyRef ConfigRef `json:"apiKeyRef,omitempty" title:"API Key (from Secret)"`
// Other settings
Endpoint string `json:"endpoint" title:"API Endpoint" format:"uri"`
}
func (c *Component) Handle(ctx context.Context, output module.Handler, port string, msg any) error {
if port == v1alpha1.SettingsPort {
c.settings = msg.(Settings)
// SDK automatically resolves ConfigRef values
// c.settings.APIKey contains the actual value
// whether set directly or via APIKeyRef
}
return nil
}ConfigMap Reference
go
type Settings struct {
// Direct value OR configmap reference
Config string `json:"config,omitempty" title:"Configuration"`
ConfigRef ConfigRef `json:"configRef,omitempty" title:"Configuration (from ConfigMap)"`
}Creating Kubernetes Secrets
Using kubectl
bash
# Create secret from literal
kubectl create secret generic api-credentials \
--namespace tinysystems \
--from-literal=api-key=sk-12345
# Create secret from file
kubectl create secret generic tls-certs \
--namespace tinysystems \
--from-file=cert.pem=./cert.pem \
--from-file=key.pem=./key.pemUsing YAML
yaml
apiVersion: v1
kind: Secret
metadata:
name: api-credentials
namespace: tinysystems
type: Opaque
stringData:
api-key: sk-12345
api-secret: secret-value
---
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: tinysystems
data:
database-url: postgres://localhost:5432/mydb
log-level: infoComponent Implementation
Pattern 1: Single Field with Reference
go
type Settings struct {
// User can enter value directly OR select from secret
Password string `json:"password,omitempty" title:"Password" format:"password"`
PasswordRef ConfigRef `json:"passwordRef,omitempty" title:"Password (from Secret)"`
}
type Database struct {
settings Settings
}
func (d *Database) Handle(ctx context.Context, output module.Handler, port string, msg any) error {
if port == v1alpha1.SettingsPort {
d.settings = msg.(Settings)
// Use the password (resolved automatically)
d.connect(d.settings.Password)
}
return nil
}Pattern 2: Multiple Secret References
go
type Settings struct {
// API Configuration
APIKeyRef ConfigRef `json:"apiKeyRef" title:"API Key"`
APISecretRef ConfigRef `json:"apiSecretRef" title:"API Secret"`
// Database
DBPasswordRef ConfigRef `json:"dbPasswordRef" title:"Database Password"`
}
type Service struct {
settings Settings
}
func (s *Service) Handle(ctx context.Context, output module.Handler, port string, msg any) error {
if port == v1alpha1.SettingsPort {
settings := msg.(Settings)
// All references are resolved to actual values
s.initAPI(settings.APIKeyRef, settings.APISecretRef)
s.initDB(settings.DBPasswordRef)
}
return nil
}Pattern 3: Optional Reference
go
type Settings struct {
// Required endpoint
Endpoint string `json:"endpoint" title:"API Endpoint" format:"uri" required:"true"`
// Optional authentication
AuthToken string `json:"authToken,omitempty" title:"Auth Token" format:"password"`
AuthTokenRef ConfigRef `json:"authTokenRef,omitempty" title:"Auth Token (from Secret)"`
}
func (c *Client) isAuthenticated() bool {
return c.settings.AuthToken != "" || c.settings.AuthTokenRef.SecretName != ""
}UI Behavior
In the visual editor:
- User sees a dropdown to select Secret/ConfigMap
- Second dropdown shows keys within selected resource
- Selected reference stored in TinyNode CR
- Controller resolves value at runtime
┌────────────────────────────────────────────────────────┐
│ API Key (from Secret) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Secret: api-credentials ▼ │ │
│ └──────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Key: api-key ▼ │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────┘Complete Example
go
package oauth
import (
"context"
"github.com/tiny-systems/module/api/v1alpha1"
"github.com/tiny-systems/module/module"
"github.com/tiny-systems/module/pkg/schema"
)
type ConfigRef struct {
SecretName string `json:"secretName,omitempty" title:"Secret Name"`
ConfigMapName string `json:"configMapName,omitempty" title:"ConfigMap Name"`
Key string `json:"key,omitempty" title:"Key"`
}
type Settings struct {
// OAuth Configuration
ClientID string `json:"clientId,omitempty" title:"Client ID"`
ClientIDRef ConfigRef `json:"clientIdRef,omitempty" title:"Client ID (from Secret)"`
ClientSecret string `json:"clientSecret,omitempty" title:"Client Secret" format:"password"`
ClientSecretRef ConfigRef `json:"clientSecretRef,omitempty" title:"Client Secret (from Secret)"`
// Endpoints (from ConfigMap)
AuthURL string `json:"authUrl,omitempty" title:"Auth URL" format:"uri"`
AuthURLRef ConfigRef `json:"authUrlRef,omitempty" title:"Auth URL (from ConfigMap)"`
TokenURL string `json:"tokenUrl,omitempty" title:"Token URL" format:"uri"`
TokenURLRef ConfigRef `json:"tokenUrlRef,omitempty" title:"Token URL (from ConfigMap)"`
}
type OAuthClient struct {
settings Settings
}
func (o *OAuthClient) GetInfo() module.Info {
return module.Info{
Name: "oauth-client",
Description: "OAuth 2.0 authentication client",
Icon: "IconLock",
Tags: []string{"auth", "oauth"},
}
}
func (o *OAuthClient) Ports() []module.Port {
return []module.Port{
{
Name: v1alpha1.SettingsPort,
Label: "Settings",
Source: true,
Position: module.PositionTop,
Schema: schema.FromGo(Settings{}),
},
{
Name: "authenticate",
Label: "Authenticate",
Source: true,
Position: module.PositionLeft,
Schema: schema.FromGo(AuthRequest{}),
},
{
Name: "token",
Label: "Token",
Source: false,
Position: module.PositionRight,
Schema: schema.FromGo(TokenResponse{}),
},
{
Name: "error",
Label: "Error",
Source: false,
Position: module.PositionBottom,
Schema: schema.FromGo(ErrorOutput{}),
},
}
}
func (o *OAuthClient) Handle(ctx context.Context, output module.Handler, port string, msg any) error {
switch port {
case v1alpha1.SettingsPort:
o.settings = msg.(Settings)
// ConfigRef values are automatically resolved
// o.settings.ClientSecret contains the actual value
return nil
case "authenticate":
req := msg.(AuthRequest)
token, err := o.authenticate(ctx, req)
if err != nil {
return output(ctx, "error", ErrorOutput{Error: err.Error()})
}
return output(ctx, "token", token)
}
return nil
}
func (o *OAuthClient) authenticate(ctx context.Context, req AuthRequest) (*TokenResponse, error) {
// Use resolved settings
clientID := o.settings.ClientID
clientSecret := o.settings.ClientSecret
authURL := o.settings.AuthURL
tokenURL := o.settings.TokenURL
// Perform OAuth flow
// ...
return nil, nil
}
type AuthRequest struct {
Code string `json:"code" title:"Authorization Code"`
RedirectURI string `json:"redirectUri" title:"Redirect URI"`
}
type TokenResponse struct {
AccessToken string `json:"accessToken"`
TokenType string `json:"tokenType"`
ExpiresIn int `json:"expiresIn"`
RefreshToken string `json:"refreshToken,omitempty"`
}
type ErrorOutput struct {
Error string `json:"error"`
}
func (o *OAuthClient) Instance() module.Component {
return &OAuthClient{}
}Best Practices
1. Provide Both Options
go
type Settings struct {
// Allow direct value OR reference
APIKey string `json:"apiKey,omitempty"`
APIKeyRef ConfigRef `json:"apiKeyRef,omitempty"`
}2. Use Descriptive Titles
go
type Settings struct {
// Clear distinction
Password string `json:"password,omitempty" title:"Password (direct)"`
PasswordRef ConfigRef `json:"passwordRef,omitempty" title:"Password (from Kubernetes Secret)"`
}3. Group Related References
go
type Settings struct {
// Database connection
DBHost string `json:"dbHost" title:"Database Host"`
DBPort int `json:"dbPort" title:"Database Port" default:"5432"`
DBUser string `json:"dbUser" title:"Database User"`
DBPasswordRef ConfigRef `json:"dbPasswordRef" title:"Database Password"`
// API configuration
APIEndpoint string `json:"apiEndpoint" title:"API Endpoint"`
APIKeyRef ConfigRef `json:"apiKeyRef" title:"API Key"`
}4. Document Secret Requirements
Add descriptions explaining what secrets are needed:
go
type Settings struct {
CredentialsRef ConfigRef `json:"credentialsRef" title:"Credentials" description:"Secret must contain 'username' and 'password' keys"`
}Next Steps
- Dynamic Schemas - Runtime schema generation
- Schema from Go - Struct-based schemas
- Settings and Configuration - Settings patterns