Infrastructure-as-code tools require a specific way of thinking about systems. Before diving into Misting’s APIs and features, understanding the underlying mental model will help you reason about how the system works and predict its behavior.
This page explains the core concepts that govern how Misting manages infrastructure.
Declarative vs Imperative Infrastructure
The most fundamental distinction in infrastructure management is between declarative and imperative approaches.
Imperative: Step-by-Step Instructions
Imperative infrastructure management describes how to reach a desired state through a sequence of commands:
# Imperative approach: a sequence of commands
az group create --name my-rg --location westeurope
az vm create --resource-group my-rg --name my-vm --image Ubuntu2204
az vm open-port --resource-group my-rg --name my-vm --port 80
This approach has several challenges:
- Order matters: Commands must execute in the correct sequence
- Idempotency problems: Running the same script twice may fail or create duplicates
- Partial failures: If step 2 fails, you’re left in an incomplete state
- No rollback: Undoing changes requires writing a separate teardown script
- Drift blindness: The script doesn’t know if resources already exist or were changed externally
Declarative: Describe the End State
Misting uses a declarative approach. You describe what you want, not how to get there:
// Declarative approach: describe desired state
let resource_group = ResourceGroup::new("my-rg")
.location("westeurope");
let vm = VirtualMachine::new("my-vm")
.resource_group(&resource_group)
.image("Ubuntu2204")
.open_ports(vec![80]);
Or in Mistwrite syntax:
resource "azure" "resource_group" "my-rg" {
location = "westeurope"
}
resource "azure" "virtual_machine" "my-vm" {
resource_group = resource_group.my-rg
image = "Ubuntu2204"
open_ports = [80]
}
The declarative approach means:
- Order is handled automatically: Misting determines execution order from dependencies
- Idempotent by design: Run the same configuration multiple times with the same result
- Transactional thinking: Changes are planned before execution
- Convergence: The system moves toward the declared state regardless of current state
- Drift detection: Misting can detect when reality diverges from configuration
The key insight: You describe the destination, not the journey. Misting figures out how to get there.
Desired State vs Current State
Misting operates by continuously comparing two views of your infrastructure:
Desired State
The desired state is what you want your infrastructure to look like. It comes from your configuration:
// This defines DESIRED state
let vm = VirtualMachine::new("web-server")
.cpu_count(4)
.memory_gb(16)
.disk_size_gb(100);
The desired state is:
- Defined in your code or configuration files
- Represents your intent
- May include computed values that are unknown until deployment
- Version controlled alongside your application
Current State (Actual State)
The current state is what actually exists in your cloud provider. Misting queries the upstream provider to discover:
- Which resources exist
- What their current property values are
- Their operational status
Current state:
- Lives in the cloud provider
- May differ from desired state due to manual changes or external processes
- Must be refreshed to get accurate information
- Can change at any time independent of Misting
The Reconciliation Loop
Misting’s core job is to make current state match desired state. This happens in three phases:
+---------------+
| Desired State |
| (Your config) |
+-------+-------+
|
v
+---------------+ +------+------+ +----------------+
| Current State |-->| Compare |-->| Deployment |
| (Cloud APIs) | | (Planning) | | Plan |
+---------------+ +-------------+ +--------+-------+
|
v
+--------+-------+
| Execute |
| (Apply changes)|
+----------------+
- Read: Query current state from the cloud provider
- Compare: Diff desired state against current state
- Plan: Generate a set of operations to reconcile differences
- Execute: Apply the planned operations to reach desired state
This reconciliation loop is the heart of declarative infrastructure management.
The Role of State Tracking
You might wonder: why does Misting need to store state if it can query the cloud provider?
Why Misting State Exists
Misting maintains its own persistent state for several important reasons:
1. Tracking resource identity
Cloud providers assign their own IDs to resources. Misting needs to remember which local resource definition maps to which upstream resource:
Your Config Misting State Cloud Provider
----------- ------------- --------------
web-server (name) <--------> matching_key <--------> /subscriptions/.../vms/abc123
(identity mapping) (provider ID)
Without this mapping, Misting wouldn’t know whether web-server in your config corresponds to an existing VM or needs to be created.
2. Detecting deletions
If you remove a resource from your configuration, how does Misting know to delete it? By comparing current configuration against stored state:
// Previous configuration (in state)
resources: [web-server, database, cache]
// Current configuration
resources: [web-server, database]
// Misting detects: cache was removed -> plan DELETE for cache
3. Tracking output values
Some values are only known after a resource is created. Misting stores these so they can be referenced by other resources:
// After creation, the VM has an IP address
let vm = VirtualMachine::new("web-server");
// vm.public_ip is unknown during planning
// vm.public_ip is stored in state after creation
// Other resources can reference vm.public_ip
4. Preventing concurrent modifications
State locking ensures that only one deployment process modifies infrastructure at a time:
// State locking in action
state_provider.lock().await?; // Acquire exclusive lock
// ... perform deployment ...
state_provider.unlock().await?; // Release lock
State Collections
Misting organizes state into collections:
| Collection | Key | Value | Purpose |
|---|---|---|---|
resources | matching_key | ObjectValue | Store resource property values |
ids_by_key | matching_key | ResourceId | Map local identity to upstream ID |
keys_by_id | resource_id | matching_key | Reverse lookup for ID resolution |
extensions:{ns} | key | Value | Plugin-specific extension data |
The matching_key is the canonical identifier for a resource within Misting. It combines the module path and either an explicit alias or the resource’s structural path:
// Without alias: identity based on position in code
let vm = VirtualMachine::new("web-server");
// matching_key: "root::web-server"
// With alias: stable identity that survives refactoring
let vm = VirtualMachine::new("web-server")
.alias("production-web");
// matching_key: "root::production-web"
Using aliases is recommended when you expect to reorganize your code, as the matching_key determines which stored state corresponds to which resource definition.
Change Detection and Reconciliation
When you run a deployment, Misting performs a sophisticated comparison to determine what needs to change.
The Three-Way Comparison
Misting compares three versions of each resource:
- Desired: What your configuration says the resource should be
- State: What Misting remembers from the last deployment
- Actual: What currently exists in the cloud provider (optional refresh)
+----------+ +----------+ +----------+
| Desired | | State | | Actual |
| (config) | | (stored) | | (cloud) |
+----+-----+ +----+-----+ +----+-----+
| | |
+-------+--------+-------+--------+
| |
v v
+------+------+ +------+------+
| Change | | Drift |
| Detection | | Detection |
+-------------+ +-------------+
Change detection (desired vs state): What you want to change Drift detection (state vs actual): What changed outside of Misting
Planned Actions
Based on the comparison, Misting determines one of these actions for each resource:
| Action | Condition | Description |
|---|---|---|
| Create | Resource in desired, not in state | New resource to provision |
| Update | Properties differ between desired and state | Modify existing resource |
| Delete | Resource in state, not in desired | Remove resource |
| Recreate | Immutable property changed | Delete then create (replacement) |
| NoChange | Desired equals state | Nothing to do |
Property-Level Analysis
Misting tracks changes at the property level, not just the resource level:
// Current state
vm: {
name: "web-server",
cpu_count: 2,
memory_gb: 8
}
// Desired state
vm: {
name: "web-server",
cpu_count: 4, // Changed
memory_gb: 16 // Changed
}
// Result: Update with properties [cpu_count, memory_gb]
This granularity enables:
- Precise change reporting
- Targeted updates (only send changed properties)
- Immutable property detection
Immutable Properties
Some properties cannot be changed after resource creation. Changing these requires destroying and recreating the resource:
// 'region' is immutable - cannot change after creation
let vm = VirtualMachine::new("web-server")
.region("westeurope") // Immutable
.cpu_count(4); // Mutable
// If you change region from "westeurope" to "eastus":
// - cpu_count change -> Update
// - region change -> Recreate (delete + create)
Misting’s schema system tracks which properties are immutable:
pub struct ElementSchema {
pub value_type: ValueType,
pub is_immutable: bool, // Cannot change after creation
pub is_input: bool, // User-provided vs computed
pub is_sensitive: bool, // Mask in output
// ...
}
Idempotency and Convergence
Two properties make declarative systems reliable: idempotency and convergence.
Idempotency
An operation is idempotent if running it multiple times produces the same result as running it once.
Non-idempotent operation:
# Running twice creates two VMs!
az vm create --name my-vm
az vm create --name my-vm # Error: already exists, or worse: duplicate
Idempotent operation (Misting):
// Running deployment twice results in same infrastructure
let plan = planner.create_plan(&resources).await?;
executor.execute(&plan).await?;
// Run again: Misting detects no changes needed
let plan = planner.create_plan(&resources).await?;
// plan.has_changes() == false
Misting achieves idempotency through:
- State tracking: Knows what already exists
- Comparison before action: Only executes necessary changes
- Upstream idempotency: Plugins implement idempotent operations
The UpstreamService trait documentation explicitly requires idempotency:
/// Plugin-agnostic upstream service that handles resource lifecycle operations.
///
/// ## Implementation Guidelines
///
/// 1. **Idempotency**: All operations should be idempotent. Creating a resource that
/// already exists or deleting a non-existent resource should not be treated as errors.
Convergence
Convergence means the system eventually reaches the desired state, regardless of the starting state.
Starting State A Starting State B Starting State C
(Nothing exists) (Partial resources) (Drifted state)
| | |
v v v
+----+----+ +----+----+ +----+----+
| Misting | | Misting | | Misting |
| Deploy | | Deploy | | Deploy |
+----+----+ +----+----+ +----+----+
| | |
+----------+------------+-----------+-----------+
| |
v v
Desired State Desired State
(Same result) (Same result)
No matter what state you start from:
- Missing resources are created
- Extra resources are deleted
- Incorrect properties are updated
This property means you can recover from any situation by simply running a deployment.
Handling Unknown Values
During planning, some values are unknown. These come from:
- Output-only properties: Values set by the cloud provider (IDs, IP addresses)
- Impure functions: Values that change each invocation (uuid(), now())
- Deployment inputs: Values provided at deploy time
Misting tracks unknowns explicitly:
pub enum UnknownSource {
/// Depends on output property not yet deployed
ResourceProperty { reference: ObjectReference, property: String },
/// Result of an impure function during planning
ImpureFunction { function_path: TypePath },
/// User input required at deployment time
DeploymentInput { input_name: String },
}
When a value is unknown, Misting still plans what it can:
// During planning phase
let ip = vm.public_ip; // Returns Unknown(ResourceProperty)
// The plan includes:
// 1. Create VM
// 2. After creation, public_ip will be known
// 3. Dependent resources wait for this value
For resources with unknown immutable properties, Misting defers planning to a second phase after the unknowns resolve.
Practical Implications
Understanding the mental model helps you:
Write Better Configurations
Since Misting manages desired state, focus on describing what you want:
// Good: Describe the end state
let vm = VirtualMachine::new("web-server")
.cpu_count(4)
.memory_gb(16);
// Avoid: Thinking about steps
// "First create resource group, then create VM, then..."
// Misting handles ordering automatically
Understand Plan Output
When you see a deployment plan, you’re seeing the diff between desired and current state:
+ resource "virtual_machine" "web-server" <- Create (not in current)
~ resource "database" "main-db" <- Update (properties changed)
cpu_count: 2 -> 4
- resource "cache" "old-cache" <- Delete (not in desired)
Debug Issues
When things don’t work as expected:
- Check state: What does Misting think exists?
- Check actual: What does the cloud provider say exists?
- Check desired: What does your configuration say?
Differences between these reveal the issue:
- State differs from actual: Drift occurred
- Desired differs from state: Pending changes
- State missing entry: Resource not tracked
Design for Reliability
The reconciliation model means:
- Retrying is safe: Idempotency means you can retry failed deployments
- State is critical: Corrupted state breaks the identity mapping
- Refreshing is important: Drift detection requires querying actual state
Summary
The Misting mental model centers on five key concepts:
-
Declarative over imperative: Describe what, not how
-
Desired vs current state: Misting reconciles the gap between intent and reality
-
State tracking: Persistent storage maintains identity mappings and enables deletion detection
-
Change detection: Property-level comparison determines minimal required operations
-
Idempotency and convergence: Multiple runs produce consistent results; any starting state reaches desired state
With this foundation, you’re ready to explore Misting’s specific features and APIs. The concepts covered here apply throughout the system.