# 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: ```rust // 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: ```rust // 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