provisioning-platform/prov-ecosystem/crates/gitops/ARCHITECTURE.md

11 KiB

GitOps Module Architecture

Objective: Event-driven GitOps orchestration with adaptive execution based on available capabilities.


Core Design Principles

  1. Single Entry Point: All external access through GitOpsEngine
  2. Minimal Public API: Only 3 public types exposed to consumers
  3. Internal Coupling Acceptable: Internal modules can depend on each other; this doesn't affect external users
  4. Capability-Driven Execution: Choose HOW to execute based on WHAT'S available

Module Tiers

Core Tier (No Dependencies)

error.rs         → Error types and result type
config.rs        → Configuration structures

Purpose: Foundational types used by all other modules.

Event Tier

event/           → Event definitions and sources
├─ Depends on: error
└─ Used by: engine, flow

Responsibility:

  • Define event sources (Git, webhooks, alerts, schedules, health checks, n8n, manual)
  • Provide event matching interface
  • Broadcast events through async channels

Provider Tier

provider/        → Git provider abstractions
├─ Depends on: error
└─ Used by: integration, event handlers

Responsibility:

  • Unified interface for GitHub, GitLab, Gitea, Forgejo
  • Repository operations, webhook registration
  • Event parsing from git providers

Rule Tier

rule/            → Rule definition and registry
├─ Depends on: error, serialization
└─ Used by: engine, flow, executor, generator

Responsibility:

  • Define rule structure (When/Then clauses)
  • Rule parsing from YAML/TOML/KCL
  • Rule matching against events

Environment Tier

environment/     → Capability detection
├─ Depends on: error, observability
└─ Used by: flow, engine

Responsibility:

  • Auto-detect available deployment tools
    • Kubernetes (kubeconfig, API access)
    • Docker/Podman (daemon socket)
    • ArgoCD (API endpoint)
    • Flux (CRDs in cluster)
    • systemd (service support)
    • n8n (workflow API)
    • Prometheus (metrics endpoint)
  • Maintain capability cache
  • Provide refresh mechanism

Executor Tier (Internal)

executor/        → Action execution implementations
├─ Depends on: error, rule
└─ Used by: flow, engine (indirectly through flow)

Responsibility:

  • Implement execution for each tool (ArgoCD, Flux, Kubernetes, Docker, systemd, n8n, script)
  • Translate rule actions to tool-specific commands
  • Handle execution results

Generator Tier (Internal)

generator/       → Configuration generation
├─ Depends on: error, rule
└─ Used by: executor, flow (indirectly)

Responsibility:

  • Generate tool-specific configurations from rules
  • Support output formats: Kubernetes YAML, ArgoCD CRDs, Flux CRDs, Docker Compose, systemd units, CI/CD workflows
  • Validate generated configurations

Flow Tier (Internal)

flow/            → Execution flow resolution
├─ Depends on: error, rule, environment, executor
└─ Used by: engine

Responsibility:

  • Map actions to available executors
  • Define execution fallback chains
    • deploy: ArgoCD → Flux → Kubernetes → Docker → systemd
    • rollback: Kubernetes → Docker → systemd
    • notify: n8n → webhook
    • etc.
  • Select primary and fallback executors

Orchestration Tier

engine/          → Central coordinator
├─ Depends on: error, rule, environment, flow
└─ Public entry point for all external access

Responsibility:

  • Initialize the GitOps system
  • Load and validate rules
  • Detect environment capabilities
  • Coordinate rule execution
  • Main event loop (listens for events, matches rules, executes actions)

Integration Tier

integration/     → External integrations
├─ Depends on: config, error, (optional) other modules
└─ Used by: external code, optional features

Responsibility:

  • Syntaxis integration for configuration management
  • Custom integrations for specific tools
  • Optional extensions

Dependency Graph

┌─────────────────────────────────────────────────────────┐
│                   External Users                         │
└──────────────────────────┬──────────────────────────────┘
                           │
                    (public API only)
                           │
                           ▼
                    ┌──────────────┐
                    │   engine     │
                    └──────┬───────┘
                           │
        ┌──────────────────┼──────────────────┐
        │                  │                  │
        ▼                  ▼                  ▼
    ┌─────────┐    ┌─────────────┐    ┌──────────────┐
    │   rule  │    │ environment │    │     flow     │
    └────┬────┘    └─────────────┘    └──────┬───────┘
         │                                    │
         │                           ┌────────┼────────┐
         │                           │        │        │
         ▼                           ▼        ▼        ▼
    ┌─────────┐                ┌─────────┬────────┬────────┐
    │ event   │                │executor │ query  │provider│
    └─────────┘                └────────┬┘        └────────┘
                                        │
                                        ▼
                                   ┌──────────┐
                                   │generator │
                                   └──────────┘

┌─────────────────────────────────────────────────┐
│ Core (no internal dependencies)                 │
│  error.rs, config.rs, deps/mod.rs              │
└─────────────────────────────────────────────────┘

Internal Coupling Analysis

Current State: gitops::mod shows 25 internal dependencies

Why This Is Acceptable:

  1. External Users See Only 3 Public Types:

    • GitOpsEngine - main entry point
    • GitOpsError/GitOpsResult - error handling
    • Config - configuration
  2. Internal Modules Can Depend on Each Other:

    • flow depends on rule, environment, executor - this is necessary for its responsibility
    • event depends on rule for matching - expected
    • executor and generator both use rule - appropriate
  3. Coupling Doesn't Propagate to External API:

    • External code doesn't need to know about flow, executor, generator, environment
    • All access goes through GitOpsEngine
  4. Implementation Details Are Marked pub(crate):

    • executor, generator, flow, event (mostly), provider, rule - all internal
    • This prevents external code from depending on implementation details
    • Makes refactoring easier in the future

Public API Surface

The intentionally minimal public API:

// Stable external interface
pub use engine::GitOpsEngine;           // Main entry point
pub use error::{GitOpsError, GitOpsResult};  // Error handling
pub use config::Config;                 // Configuration

// Everything else is internal
pub(crate) mod event;                   // Implementation detail
pub(crate) mod rule;                    // Implementation detail
pub(crate) mod environment;             // Implementation detail
pub(crate) mod executor;                // Implementation detail
pub(crate) mod generator;               // Implementation detail
pub(crate) mod flow;                    // Implementation detail
pub mod provider;                       // Interface but limited use
pub mod integration;                    // Optional extensions

Usage Pattern

For external users:

// Step 1: Load rules
let rules = RuleRegistry::from_yaml("rules.yaml")?;

// Step 2: Create engine (auto-detects environment)
let engine = GitOpsEngine::new(rules).await?;

// Step 3: Run the engine
engine.run().await?;

No need to know about flow, executor, generator, environment, event internals.


Future Optimizations

If internal coupling becomes problematic, consider:

  1. Mediator Pattern: Central event bus for module communication

    • Modules send commands/queries to mediator
    • Reduces direct module-to-module dependencies
    • Trade-off: More complex, requires routing layer
  2. Command/Query Separation: Explicit command handlers

    • ExecuteCommand → flow resolves → executor runs
    • QueryCapabilities → environment responds
    • More structured, but more boilerplate
  3. Layer Enforcement: Strict dependency constraints

    • Tier N modules can only depend on Tier N-1
    • Prevents some current dependencies (e.g., flow → executor)
    • More rigid architecture

Testing Strategy

  1. Unit Tests: Test each module in isolation with mocks
  2. Integration Tests: Test complete flow from event to execution
  3. End-to-End Tests: Full engine lifecycle with real rules

Module mocking strategy:

  • External integration points (git providers, executors) are traits
  • Use mockall for creating test implementations
  • Internal modules use pub(crate) so tests can access them

Coupling Metrics

Metric Target Current Status
External API Size < 5 types 3 types Optimal
Public Module Count < 4 3 (engine, provider, integration) Good
Module Visibility All internal use pub(crate) Most do Good
Circular Dependencies 0 0 Good
External Coupling Minimal GitOpsEngine only Good

Notes

  • The 25 internal dependencies reported by cargo coupling are intentional and don't affect external users
  • Making all modules pub(crate) provides clear API surface without forcing a major internal refactoring
  • Future versions can implement mediator pattern if internal complexity grows
  • Current design is pragmatic balance between simplicity and decoupling