Architecture

Oxid is a standalone infrastructure-as-code engine written in Rust. It parses HCL natively and communicates directly with Terraform providers via gRPC - no Terraform binary required.

Pipeline Overview

Every Oxid operation follows the same pipeline:

.tf files
  → Config Parser (HCL → WorkspaceConfig)
    → DAG Builder (WorkspaceConfig → ResourceGraph)
      → Provider Manager (gRPC connections)
        → Planner (diff state vs config)
          → DAG Walker (parallel execution)
            → State Backend (persist results)

Core Components

Config Parser

Reads .tf files using hcl-rs and produces a WorkspaceConfig - the unified intermediate representation for all infrastructure configuration. Handles variable resolution, string interpolation, and merging of multiple .tf files.

  • Loads .tfvars files and TF_VAR_* environment variables
  • Resolves variable references (var.xxx) and interpolation ($}var.xxx{-suffix)
  • Applies Terraform-compatible variable precedence

DAG Builder

Converts WorkspaceConfig into a directed acyclic graph (petgraph::DiGraph) where each node is a resource and each edge is a dependency. Handles:

  • count and for_each expansion into individual resource nodes
  • Explicit depends_on edges
  • Implicit edges from expression references (e.g., aws_vpc.main.id)
  • Cycle detection and validation

Provider Manager

Downloads provider binaries from registry.terraform.io, launches them as child processes, and manages gRPC connections using the tfplugin5 and tfplugin6 protocols.

  • Compiled from proto/tfplugin5.proto and proto/tfplugin6.proto via build.rs
  • Connection pooling with Arc<RwLock<HashMap>>
  • Supports all Terraform providers - AWS, GCP, Azure, Kubernetes, and every community provider
  • Provider binaries cached in .oxid/providers/

Planner

Compares the current state against the desired configuration to produce a set of ResourceAction values for each resource:

  • Create - Resource exists in config but not in state
  • Update - Resource exists in both but attributes differ
  • Delete - Resource exists in state but not in config
  • Replace - Resource must be destroyed and recreated (ForceNew attribute changed)
  • Read - Data source needs to be refreshed
  • NoOp - Resource is unchanged

DAG Walker

The execution engine that walks the dependency graph and executes operations in parallel. This is where Oxid's performance advantage comes from.

State Backend

Persists resource state, outputs, dependencies, runs, and history. Implements the StateBackend trait with SQLite and PostgreSQL backends.

Event-Driven Parallelism

Oxid uses event-driven per-resource parallelism instead of Terraform's wave-based approach.

Wave-based (Terraform)

Terraform processes resources in "waves" - all resources at depth 0 must complete before any resource at depth 1 starts. This means the slowest resource in each wave blocks everything else at the same depth.

Wave 0: [VPC] ─────────────────────────────── wait ──────
Wave 1: ─── wait ─── [Subnet A] [Subnet B] [IGW] ── wait
Wave 2: ─── wait ──────────── wait ─── [Instance A] [Instance B]

Event-driven (Oxid)

In Oxid, each resource starts executing the moment all its dependencies have completed. There are no waves - resources are dispatched individually as they become ready.

[VPC] ──→ [Subnet A] ──→ [Instance A]
      ├──→ [Subnet B] ──→ [Instance B]
      └──→ [IGW] (starts immediately when VPC completes)

This results in faster applies, especially for deep dependency graphs with uneven execution times.

Concurrency Model

Oxid uses several concurrency primitives from Tokio and the Rust ecosystem:

  • Arc<Semaphore> - Controls maximum parallelism in the DAG walker (default 10, configurable via --parallelism)
  • DashMap - Lock-free concurrent hash map for resource state during planning and apply. Multiple tasks can read and write resource states without blocking each other.
  • Arc<RwLock<HashMap>> - Provider connection pooling. Read lock for concurrent gRPC calls, write lock only during provider startup and configuration.
  • tokio::sync::mpsc - Channel-based communication between the DAG walker and task executors. Completed tasks send events back to the walker, which dispatches newly-ready dependents.
NoteAll state backend operations are async via #[async_trait], enabling non-blocking database I/O during parallel execution.

Expression Evaluation

The EvalContext in the executor handles runtime expression evaluation:

  • Variable references - var.instance_type resolved from defaults and tfvars
  • Cross-resource references - aws_vpc.main.id resolved from the shared DashMap populated during apply
  • Count index - count.index for indexed resources
  • Each key/value - each.key and each.value for for_each resources
  • String interpolation - $}var.prefix{-suffix with concatenation

Error Handling

Oxid uses a layered error handling strategy:

  • anyhow::Result with .context() chains for detailed error messages
  • thiserror for typed custom errors in the public API
  • Cascade-skip on failure - When a resource fails, only its transitive dependents are skipped. All other independent resources continue executing.