Skip to content

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.pem

Using 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: info

Component 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:

  1. User sees a dropdown to select Secret/ConfigMap
  2. Second dropdown shows keys within selected resource
  3. Selected reference stored in TinyNode CR
  4. 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)"`
}
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

Build flow-based applications on Kubernetes