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:

  1. Order matters: Commands must execute in the correct sequence
  2. Idempotency problems: Running the same script twice may fail or create duplicates
  3. Partial failures: If step 2 fails, you’re left in an incomplete state
  4. No rollback: Undoing changes requires writing a separate teardown script
  5. 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:

  1. Order is handled automatically: Misting determines execution order from dependencies
  2. Idempotent by design: Run the same configuration multiple times with the same result
  3. Transactional thinking: Changes are planned before execution
  4. Convergence: The system moves toward the declared state regardless of current state
  5. 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)|
                                      +----------------+
  1. Read: Query current state from the cloud provider
  2. Compare: Diff desired state against current state
  3. Plan: Generate a set of operations to reconcile differences
  4. 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:

CollectionKeyValuePurpose
resourcesmatching_keyObjectValueStore resource property values
ids_by_keymatching_keyResourceIdMap local identity to upstream ID
keys_by_idresource_idmatching_keyReverse lookup for ID resolution
extensions:{ns}keyValuePlugin-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:

  1. Desired: What your configuration says the resource should be
  2. State: What Misting remembers from the last deployment
  3. 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:

ActionConditionDescription
CreateResource in desired, not in stateNew resource to provision
UpdateProperties differ between desired and stateModify existing resource
DeleteResource in state, not in desiredRemove resource
RecreateImmutable property changedDelete then create (replacement)
NoChangeDesired equals stateNothing 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:

  1. State tracking: Knows what already exists
  2. Comparison before action: Only executes necessary changes
  3. 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:

  1. Output-only properties: Values set by the cloud provider (IDs, IP addresses)
  2. Impure functions: Values that change each invocation (uuid(), now())
  3. 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:

  1. Check state: What does Misting think exists?
  2. Check actual: What does the cloud provider say exists?
  3. 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:

  1. Declarative over imperative: Describe what, not how

  2. Desired vs current state: Misting reconciles the gap between intent and reality

  3. State tracking: Persistent storage maintains identity mappings and enables deletion detection

  4. Change detection: Property-level comparison determines minimal required operations

  5. 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.