Skip to content

[Enhancement]: Make activate column optional in valueMappings with default-active behavior #21

Description

@selenehyun

Feature Description

Make the activate column in valueMappings optional instead of required. When not specified, all rows should be treated as active by default.

Current Behavior

Currently, the activate field is required in valueMappings:

apiVersion: operator.lynq.sh/v1
kind: LynqHub
metadata:
  name: mysql-prod
spec:
  valueMappings:
    uid: node_id
    activate: is_active  # ❌ Required - must specify

Problems:

  1. Unnecessary complexity: For many use cases, all rows should always be active
  2. Database schema overhead: Forces users to add an activation column even when not needed
  3. Migration burden: Existing databases must be modified to add activation columns

Proposed Behavior

Make activate optional with smart defaults:

# Option 1: Explicit activate column (current behavior, still supported)
spec:
  valueMappings:
    uid: node_id
    activate: is_active  # ✅ Optional - explicit control

# Option 2: No activate column (new behavior)
spec:
  valueMappings:
    uid: node_id
    # activate not specified - all rows treated as active ✅

Logic

When activate is specified:

  • Query and evaluate the column value
  • Only include rows where activate is truthy (true, "true", 1, "1", etc.)
  • Existing behavior unchanged

When activate is NOT specified:

  • Treat all returned rows as active
  • No filtering based on activation status
  • New default-active behavior

Use Cases

Use Case 1: Simple Multi-Tenant Setup

# All tenants are always active - no need for activation column
apiVersion: operator.lynq.sh/v1
kind: LynqHub
metadata:
  name: tenants-hub
spec:
  source:
    type: mysql
    mysql:
      query: "SELECT tenant_id FROM tenants"  # No is_active column needed
  valueMappings:
    uid: tenant_id
    # No activate field - all tenants always active

Use Case 2: Dev/Test Environments

# Development environment - all test instances always on
apiVersion: operator.lynq.sh/v1
kind: LynqHub
metadata:
  name: dev-instances
spec:
  valueMappings:
    uid: instance_id
    # No activation control needed in dev

Use Case 3: Gradual Migration (Backward Compatible)

# Existing production setup - keep activation logic
apiVersion: operator.lynq.sh/v1
kind: LynqHub
metadata:
  name: prod-tenants
spec:
  valueMappings:
    uid: tenant_id
    activate: is_active  # Still works exactly as before

Implementation Details

Validation Changes

Location: api/v1/lynqhub_webhook.go

// Current validation (strict)
func (r *LynqHub) validateValueMappings() error {
    if r.Spec.ValueMappings.UID == "" {
        return fmt.Errorf("valueMappings.uid is required")
    }
    if r.Spec.ValueMappings.Activate == "" {  // ❌ Remove this check
        return fmt.Errorf("valueMappings.activate is required")
    }
    return nil
}

// Proposed validation (relaxed)
func (r *LynqHub) validateValueMappings() error {
    if r.Spec.ValueMappings.UID == "" {
        return fmt.Errorf("valueMappings.uid is required")
    }
    // ✅ activate is now optional - no validation error if missing
    return nil
}

Controller Changes

Location: internal/controller/lynqhub_controller.go

// Current behavior (requires activate column)
func (r *LynqHubReconciler) filterActiveRows(rows []Row, activateCol string) []Row {
    var active []Row
    for _, row := range rows {
        if isActiveTruthy(row[activateCol]) {  // ❌ Fails if activateCol is empty
            active = append(active, row)
        }
    }
    return active
}

// Proposed behavior (activate column optional)
func (r *LynqHubReconciler) filterActiveRows(rows []Row, activateCol string) []Row {
    // ✅ If no activate column specified, all rows are active
    if activateCol == "" {
        return rows  // All rows treated as active
    }
    
    // Existing logic when activate column is specified
    var active []Row
    for _, row := range rows {
        if isActiveTruthy(row[activateCol]) {
            active = append(active, row)
        }
    }
    return active
}

Template Variable Changes

Location: internal/template/engine.go

// Current behavior (activate always present)
func BuildVariables(uid, activate string, extraMappings map[string]string) Variables {
    vars := Variables{
        "uid":      uid,
        "activate": activate,  // ❌ May be empty string if not mapped
    }
    // ...
}

// Proposed behavior (activate defaults to "true" if not mapped)
func BuildVariables(uid, activate string, extraMappings map[string]string) Variables {
    vars := Variables{
        "uid": uid,
    }
    
    // ✅ Only add activate variable if it was explicitly mapped
    if activate != "" {
        vars["activate"] = activate
    } else {
        vars["activate"] = "true"  // Default to active if not specified
    }
    
    // ...
}

Backward Compatibility

This change is 100% backward compatible:

Scenario Current Behavior New Behavior Compatible?
activate specified, column exists Rows filtered by column value Same ✅ Yes
activate specified, column missing Query error Query error (same) ✅ Yes
activate not specified Validation error All rows treated as active ✅ Yes (new feature)

Migration path:

  1. Existing LynqHub CRs with activate mapping → No changes needed, works as-is
  2. New LynqHub CRs without activate mapping → Automatically treats all rows as active

Benefits

Simpler setup: No need for activation columns in simple use cases
Backward compatible: Existing configurations unchanged
Database flexibility: Works with existing tables without schema modifications
Reduced boilerplate: Less YAML to write for common scenarios
Clearer intent: Explicit activation column = "I need control", no column = "always on"

Edge Cases

  1. Empty activate column value: Treated as inactive (existing behavior preserved)
  2. Activate column with NULL values: Treated as inactive (existing behavior preserved)
  3. Mixed setup: Some hubs use activate, some don't - both work independently ✅
  4. Template variable .activate: Always available, defaults to "true" when not mapped

Implementation Checklist

  • Update CRD validation in api/v1/lynqhub_webhook.go:
    • Remove required validation for activate field
    • Update validation tests
  • Update controller logic in internal/controller/lynqhub_controller.go:
    • Modify filterActiveRows() to handle empty activateCol
    • Update active row counting logic
    • Add unit tests for both scenarios (with/without activate)
  • Update template engine in internal/template/engine.go:
    • Set default activate variable when not mapped
    • Update template tests
  • Update documentation:
    • docs/datasource.md: Document optional activate field
    • docs/configuration.md: Add examples of both patterns
    • docs/templates.md: Document .activate variable behavior
    • Update API reference
  • Add example manifests in config/samples/:
    • Example without activate (all-active pattern)
    • Example with activate (selective activation)
  • Update CHANGELOG.md
  • Add integration tests:
    • Test hub without activate mapping
    • Test hub with activate mapping (regression)
    • Test mixed scenario

Additional Context

Related to:

  • Original requirement tracking: All rows must have activation control
  • User feedback: Many use cases don't need activation logic

Common patterns this enables:

  • Static tenant lists (always active)
  • Development/staging environments (no activation needed)
  • Migration from external systems (gradual activation adoption)
  • Read-only datasources (can't add activation columns)

Priority: Medium (quality-of-life improvement, not critical)

Estimated Effort: Small-Medium (2-3 days for full implementation + tests + docs)

Compatible Versions: v1.2.0 and later (requires minor version bump for new behavior)

Breaking Changes: None - fully backward compatible


Are you willing to contribute this feature?

  • Yes, I'd like to implement this
  • Open for community contributions

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions