Lab 01 — ARM Deployment Engine

Phase: 01 — Azure Resource Manager & the Deployment Engine | Difficulty: ⭐⭐⭐☆☆ | Time: 4–6 hours

Every single write in Azure — a storage account, a subnet, a role assignment — is one idempotent PUT that ARM routes through a pipeline: authenticate, authorize, validate, then resolve a dependency graph and deploy resources in topological order. This lab builds that engine: the implicit/explicit dependency resolver, a deterministic topological sort with cycle detection, the idempotent Create/Modify/NoChange decision, the Complete-mode delete foot-gun, and the what-if dry run. Strip away the network, the JWT, and the resource providers, and what's left is a graph algorithm — the one ARM, Bicep, Terraform, and CloudFormation all share.

What you build

  • extract_dependencies(resource) — the union of explicit dependsOn and the implicit dependencies parsed out of reference('NAME') / resourceId('NAME') calls hiding in (possibly nested) property strings. This is the step everyone gets wrong by hand.
  • build_dependency_graph(resources)name → {dependencies}, raising on a dependency to a resource that isn't in the template (ARM's InvalidTemplate) and on duplicate names.
  • topological_order(graph)deterministic Kahn's algorithm, tie-breaking ready nodes by name so the same template always produces the same plan; raises ValueError("cycle: ...") on a circular dependency.
  • deploy(resources, state, mode) — process in topo order; idempotent PUTCreate / Modify / NoChange; Complete mode also Deletes un-templated resources. Returns the ordered actions and the converged state, without mutating the caller's state.
  • what_if(resources, state, mode) — the side-effect-free dry run: the same classification as deploy, returned as a list of Change objects, mutating nothing.

Key concepts

ConceptWhat to understand
Implicit vs explicit depsreference()/resourceId() create edges you didn't type; ARM finds them, so must you
Topological orderdependencies deploy before dependents; independents run in parallel (we serialise by name for determinism)
Cycle detectiona dependsOn loop is un-deployable; Kahn's leftover-nodes test finds it
Idempotent PUTdesired state, not a command → re-running converges (all NoChange), which is why IaC is safe to re-apply
Incremental vs CompleteIncremental leaves extras alone; Complete deletes anything not in the template — the classic 2 a.m. mistake
what-if is read-onlythe diff you read in the PR; mutating state in a "dry run" is a bug

Files

FilePurpose
lab.pyskeleton with # TODO markers
solution.pycomplete reference; python solution.py prints a worked deployment plan
test_lab.pythe proof — run it red, make it green
requirements.txtpytest only (pure stdlib otherwise)

Run

pip install -r requirements.txt
pytest test_lab.py -v                      # against your lab.py
LAB_MODULE=solution pytest test_lab.py -v  # against the reference
python solution.py                         # worked example

Success criteria

  • All 25 tests pass against your implementation.
  • You can explain why test_topo_order_is_deterministic_tie_break_by_name matters — real ARM parallelises independent resources; we serialise ties by name only so the plan is reproducible and testable. The determinism is a lab affordance, not how ARM schedules.
  • You can explain why test_redeploy_is_idempotent_all_no_change is the whole reason IaC works: a PUT of desired state that already matches is a safe no-op.
  • You can explain, in one breath, why test_complete_mode_deletes_extras is the most dangerous line in the suite — and when you'd ever want Complete mode.
  • Given a 5-resource template on paper, you can draw its dependency graph and write the deploy order in under two minutes.

How this maps to real Azure

The labReal ARM
extract_dependenciesARM's template expression engine evaluating reference()/resourceId() and building implicit edges, plus your literal dependsOn
build_dependency_graphthe deployment's internal DAG; an unknown reference is the deploy-time InvalidTemplate error
topological_orderthe deployment scheduler — except ARM deploys all currently-ready resources in parallel (bounded fan-out), not one-at-a-time
deploy Create/Modify/NoChangethe resource provider's idempotent PUT: it compares desired vs actual and converges; provisioningState goes Accepted → Running → Succeeded and the client polls an async operation
Complete mode deleteaz deployment group create --mode Complete — deletes every resource in the RG not in the template
what_ifaz deployment group what-if / New-AzResourceGroupDeployment -WhatIf — the dry-run diff

What the miniature leaves out (and why it's fine): real ARM also authenticates the Entra JWT, authorizes via RBAC + deny assignments, runs Azure Policy (which can deny or modify the request), registers resource providers, calls a different REST endpoint per api-version, and handles long-running async operations by returning 202 Accepted with an Azure-AsyncOperation URL the client polls. None of that changes the graph algorithm — which is exactly the part interviewers probe and incidents turn on.

Extensions (build these in your own subscription)

  • Async operations: give each resource a provisioningState that walks Accepted → Running → Succeeded|Failed over N "ticks," and make deploy poll until terminal — now you've modeled the 202/Azure-AsyncOperation loop.
  • Parallel waves: instead of a flat topo list, emit waves (all nodes whose deps are satisfied deploy together) — the real ARM scheduler. Assert that independent resources land in the same wave.
  • Bicep front-end: parse a tiny Bicep-like syntax (resource sa 'type' = { ... }) with symbolic references (sa.id) into your resource-dict format, proving Bicep is just a transpiler to the same graph.
  • Deployment stack / deny-settings: track which resources a "stack" manages; on the next deploy, Delete the ones dropped from the stack (managed deletion) and reject an out-of-band Modify to a denySettings: denyDelete resource (drift protection).
  • Wire it to a real RG: az deployment group what-if -g rg -f main.bicep and diff your predicted plan against Azure's — they should classify the same resources the same way.

Interview / resume

  • Talking points: "Walk me through what happens when I run az deployment group create." / "How does ARM know to deploy the VNet before the NIC?" / "What's the difference between Incremental and Complete mode, and which is the default?" / "Why is an ARM PUT idempotent and why does that matter for CI?"
  • Resume bullet: Built a dependency-graph deployment engine (implicit/explicit reference resolution, deterministic topological scheduling with cycle detection, idempotent PUT convergence, and a non-mutating what-if diff) modeling the Azure Resource Manager control plane.