Enables one named fault on an onboarded Chaos Studio target, built for
hashicorp/azurerm ~> 4.0. One capability, one fault β enable only what an experiment actually exercises.
This module manages a single Azure Chaos Studio Capability (azurerm_chaos_studio_capability):
- π₯ Enables exactly one named fault β
Shutdown-1.0,CPUPressure-1.0,NetworkDisconnect-1.1,PodChaos-2.1β on an already-onboarded Chaos Studio target. - π Attaches to a target by
chaos_studio_target_id, so it is always a child ofterraform-azurerm-chaos-studio-target. - π Emits the capability's
urn, which is the value an experiment's fault block references. That output is the whole reason this resource exists in a composition. - π Grants permission to run a fault, not a run. Nothing is injected until an experiment executes and its identity holds a role on the target resource.
π‘ Why it matters: Capabilities are the real blast-radius control in Chaos Studio. Each one is a specific fault a future experiment is allowed to invoke, so keeping them in code β one resource per fault, added and removed deliberately β makes the answer to "what could an experiment do to this resource?" a reviewable list.
If this module saves you time, consider supporting the work:
- β Star the repository on GitHub
- π€ Connect on LinkedIn: linkedin.com/in/microsoftexpert
- β Buy me a coffee: buymeacoffee.com/microsoftexpert
flowchart LR
RES["Azure resource under test (VM / AKS / Storage)"]
TGT["terraform-azurerm-chaos-studio-target"]
CAP["terraform-azurerm-chaos-studio-capability"]
EXP["terraform-azurerm-chaos-studio-experiment"]
FAULT["Experiment fault referencing the capability urn"]
RES -->|"onboarded as a target"| TGT
TGT -->|"id feeds chaos_studio_target_id"| CAP
CAP -->|"enables one named fault on the target"| FAULT
FAULT -->|"declared in an experiment step"| EXP
EXP -->|"selector names the same target"| TGT
classDef self fill:#0078D4,stroke:#004578,color:#fff;
classDef keystone fill:#004578,stroke:#001d33,color:#fff;
classDef neutral fill:#eef2f6,stroke:#b9c4d0,color:#1b2733;
class CAP self;
class TGT keystone;
class RES,EXP,FAULT neutral;
The target is the keystone this capability hangs off. The capability's urn then travels into an experiment's fault definition, while the experiment's selector independently names the same target.
flowchart TB
subgraph IN["Inputs"]
N["chaos_studio_target_id (force-new)"]
C["capability_type (force-new)"]
end
THIS["azurerm_chaos_studio_capability.this"]
subgraph BLK["Nested blocks"]
B1["timeouts (create / read / delete only)"]
end
subgraph OUT["Outputs"]
O1["id"]
O2["urn (referenced by an experiment fault)"]
O3["capability_type / chaos_studio_target_id"]
end
N --> THIS
C --> THIS
THIS --> B1
THIS -->|"emits"| O1
THIS --> O2
THIS --> O3
classDef self fill:#0078D4,stroke:#004578,color:#fff;
classDef keystone fill:#004578,stroke:#001d33,color:#fff;
classDef neutral fill:#eef2f6,stroke:#b9c4d0,color:#1b2733;
class THIS keystone;
class N,C,B1,O1,O2,O3 neutral;
Resource inventory
| Resource | Count | Role |
|---|---|---|
azurerm_chaos_studio_capability.this |
1 | The keystone capability record enabling one fault on one target. |
Nested blocks rendered from typed inputs: timeouts (single, with create / read / delete only β this resource has no update operation).
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
| azurerm provider | ~> 4.0 |
| Provider block | None β the caller configures provider "azurerm" { features {} }, auth, and the subscription |
| API provider | Microsoft.Chaos (API version 2023-11-01) |
Schema notes that bite (verified against the live provider schema):
- π Both fields are force-new.
chaos_studio_target_idandcapability_typeeach replace the capability. There is no in-place update path. - β±οΈ Because nothing is updatable, the provider exposes no
updatetimeout. This module'stimeoutsobject carriescreate,read, anddeleteonly. Passingupdateis not an error -- Terraform's object-type conversion silently discards the undeclared key, so the value is dropped with no diagnostic at all. - π·οΈ
capability_typeincludes a version suffix (Shutdown-1.0, notShutdown) and must be one the fault library publishes for that target'starget_type. EnablingPodChaos-2.1on aMicrosoft-VirtualMachinetarget is rejected at apply, not at plan. - π The
urnis a computed attribute β you cannot set it, and an experiment fault needs it, so wire it from this module's output rather than hand-writing it. - 𧨠Replacing the parent target destroys this capability, since it is a child of the target's ID.
- π« This resource type supports no
tags, nolocation, and nonameβ the capability name is derived fromcapability_type. None is a module variable. - π§© A capability grants permission for a fault. It does not schedule or run anything.
Microsoft.Chaos/targets/capabilities/write(plus/read,/delete) at the target resource's scope β capabilities are grandchildren of the resource being tested, so permission is needed there.Microsoft.Chaos/targets/readon the parent target.- Separately, at experiment time the experiment's managed identity needs a role on the target resource that permits the specific fault (e.g. Virtual Machine Contributor for
Shutdown-1.0). That assignment belongs to the experiment composition, not here. - No data-plane or key permissions are needed; the capability carries no secrets.
- An existing Chaos Studio target β create it with
terraform-azurerm-chaos-studio-targetfirst; a capability cannot exist without one. - The
Microsoft.Chaosresource provider registered on the target subscription. - The chosen
capability_typepublished for the parent target'starget_typeby the Chaos Studio fault library. - For agent-based faults: the Chaos Studio agent installed on the VM with an associated user-assigned identity.
- For AKS Chaos Mesh faults: Chaos Mesh installed in the cluster.
- The caller configures
provider "azurerm" { features {} }, auth, and the subscription; the module declares none of these.
terraform-azurerm-chaos-studio-capability/
βββ providers.tf # required_version >= 1.12.0; azurerm ~> 4.0; no provider block
βββ variables.tf # chaos_studio_target_id, capability_type; timeouts tail (no update)
βββ main.tf # keystone azurerm_chaos_studio_capability.this; dynamic timeouts
βββ outputs.tf # id first, then urn, then capability_type/target id
βββ README.md # this document
βββ SCOPE.md # the cross-module contract
βββ LICENSE # MIT, Copyright (c) 2026 Casey Wood
βββ .gitignore # canonical library ignore set
provider "azurerm" {
features {}
# auth + subscription configured here (az login, OIDC, managed identity, or a service principal)
}
module "chaos_capability_shutdown" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-chaos-studio-capability.git?ref=v1.0.0"
chaos_studio_target_id = module.chaos_target.id
capability_type = "Shutdown-1.0"
}βΉοΈ The caller owns the provider:
features {}, authentication, and which subscription the provider points at are all root-module concerns. The module never declares them.
π This makes one fault available. It does not run it β that needs an experiment plus a role assignment on the target resource.
Consumes
| Input | Type | Typical source |
|---|---|---|
chaos_studio_target_id |
string |
terraform-azurerm-chaos-studio-target (id) |
Emits
| Output | Description | Consumed by |
|---|---|---|
id |
Chaos Studio Capability Resource ID (first) | drift / review references |
urn |
Unique Resource Name of the capability | terraform-azurerm-chaos-studio-experiment β a fault block's urn |
capability_type |
The enabled fault | review / drift checks |
chaos_studio_target_id |
The parent target | composition wiring |
The examples take the IDs of already-onboarded Chaos Studio targets as inputs. A capability always attaches to
a target that exists first, created by terraform-azurerm-chaos-studio-target.
variable "chaos_studio_target_ids" {
description = "Map of a stable key to the Resource ID of an existing Chaos Studio target."
type = map(string)
}1 Β· Minimal call (a VM shutdown fault)
Both fields are required, so the minimal call is the whole call.
module "chaos_capability" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-chaos-studio-capability.git?ref=v1.0.0"
chaos_studio_target_id = module.chaos_target.id
capability_type = "Shutdown-1.0"
}π One capability, one fault. Nothing runs until an experiment invokes it.
2 Β· CPU pressure on an agent-based target
module "chaos_capability_cpu" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-chaos-studio-capability.git?ref=v1.0.0"
chaos_studio_target_id = var.chaos_studio_target_ids["agent"]
capability_type = "CPUPressure-1.0"
}
β οΈ In-guest faults like CPU pressure require aMicrosoft-Agenttarget with the Chaos Studio agent installed. Enabling this against a service-directMicrosoft-VirtualMachinetarget is rejected at apply.
3 Β· Network disconnect on an agent target
module "chaos_capability_network" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-chaos-studio-capability.git?ref=v1.0.0"
chaos_studio_target_id = var.chaos_studio_target_ids["agent"]
capability_type = "NetworkDisconnect-1.1"
}π‘ Note the
-1.1version suffix. Fault versions are part of the type name and are not interchangeable.
4 Β· AKS Chaos Mesh pod fault
module "chaos_capability_pod" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-chaos-studio-capability.git?ref=v1.0.0"
chaos_studio_target_id = var.chaos_studio_target_ids["aks"]
capability_type = "PodChaos-2.1"
}
β οΈ Chaos Mesh faults need Chaos Mesh installed in the cluster and aMicrosoft-AzureKubernetesServiceChaosMeshtarget.
5 Β· Several capabilities on one target with `for_each`
locals {
vm_faults = ["CPUPressure-1.0", "PhysicalMemoryPressure-1.0", "DiskIOPressure-1.1"]
}
module "chaos_capabilities" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-chaos-studio-capability.git?ref=v1.0.0"
for_each = toset(local.vm_faults)
chaos_studio_target_id = var.chaos_studio_target_ids["agent"]
capability_type = each.value
}π‘
for_eachover a set keyed by the fault name means each capability is individually addressable β removing one fault from the list removes exactly that capability.
6 Β· Capabilities across several targets
locals {
# target module key β the one fault that target needs
target_faults = {
web_vm = { target_id = var.chaos_studio_target_ids["web"], capability = "Shutdown-1.0" }
api_vm = { target_id = var.chaos_studio_target_ids["api"], capability = "Shutdown-1.0" }
cluster = { target_id = var.chaos_studio_target_ids["aks"], capability = "PodChaos-2.1" }
}
}
module "chaos_capabilities" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-chaos-studio-capability.git?ref=v1.0.0"
for_each = local.target_faults
chaos_studio_target_id = each.value.target_id
capability_type = each.value.capability
}βΉοΈ Each capability must name a fault its own target type publishes β the map keeps that pairing explicit.
7 Β· Wiring the `urn` into an experiment fault
module "chaos_capability" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-chaos-studio-capability.git?ref=v1.0.0"
chaos_studio_target_id = module.chaos_target.id
capability_type = "Shutdown-1.0"
}
output "fault_urn" {
description = "Feed this into an experiment step's fault block."
value = module.chaos_capability.urn
}π‘
urnis computed by the service. Wire it from this output rather than hand-writing a URN string β the format is not a stable contract.
8 Β· Least-privilege capability set for one game day
# The exercise tests only whether the app survives losing one VM.
module "chaos_capability" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-chaos-studio-capability.git?ref=v1.0.0"
chaos_studio_target_id = module.chaos_target.id
capability_type = "Shutdown-1.0"
}π Resist enabling a broad fault set "while we're here". Every extra capability widens what any future experiment against that target could do, with no benefit to the test you are actually running.
9 Β· Explicit timeouts (no `update` β the schema has none)
module "chaos_capability" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-chaos-studio-capability.git?ref=v1.0.0"
chaos_studio_target_id = module.chaos_target.id
capability_type = "Shutdown-1.0"
timeouts = {
create = "30m"
read = "5m"
delete = "30m"
}
}
β οΈ Both fields are force-new, so the provider exposes noupdatetimeout. Passing one is a plan error, which is why this module'stimeoutsobject omits it.
10 Β· Changing the fault version is a replacement
# Moving from NetworkDisconnect-1.0 to -1.1 destroys and re-creates the capability.
module "chaos_capability" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-chaos-studio-capability.git?ref=v1.0.0"
chaos_studio_target_id = var.chaos_studio_target_ids["agent"]
capability_type = "NetworkDisconnect-1.1"
}
β οΈ Any experiment referencing the oldurnneeds updating in the same change β the URN moves with the capability.
11 Β· Retiring a capability after an exercise
locals {
# Flip to false once the game day is over; the capability is destroyed and the
# fault is no longer available to any experiment.
chaos_exercise_active = false
}
module "chaos_capability" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-chaos-studio-capability.git?ref=v1.0.0"
count = local.chaos_exercise_active ? 1 : 0
chaos_studio_target_id = module.chaos_target.id
capability_type = "Shutdown-1.0"
}π Capabilities are cheap to remove and worth removing. Treating them as time-boxed keeps standing blast radius at zero between exercises.
12 Β· Target, capability, and the RBAC that makes the fault land
module "chaos_target" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-chaos-studio-target.git?ref=v1.0.0"
target_resource_id = module.vm.id
target_type = "Microsoft-VirtualMachine"
location = module.rg.location
}
module "chaos_capability" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-chaos-studio-capability.git?ref=v1.0.0"
chaos_studio_target_id = module.chaos_target.id
capability_type = "Shutdown-1.0"
}
module "chaos_rbac" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-role-assignments.git?ref=v1.0.0"
scope = module.chaos_target.target_resource_id
role_assignments = {
chaos_experiment = {
role_definition_name = "Virtual Machine Contributor"
principal_id = module.chaos_experiment.identity_principal_id
}
}
}π Scope the role assignment to the individual target resource, never to the resource group or subscription.
13 Β· ποΈ End-to-end composition
A resource group, a VM, its Chaos Studio target, exactly one capability, the experiment that uses it, and the RBAC that lets the fault land.
provider "azurerm" {
features {}
}
module "rg" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-resource-group.git?ref=v1.0.0"
name = "rg-resilience-test"
location = "eastus"
}
module "vnet" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-network.git?ref=v1.0.0"
name = "vnet-resilience"
resource_group_name = module.rg.name
location = module.rg.location
address_space = ["10.40.0.0/16"]
subnets = {
app = { address_prefixes = ["10.40.1.0/24"] }
}
}
module "nic" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-network-interface.git?ref=v1.0.0"
name = "nic-app-01"
resource_group_name = module.rg.name
location = module.rg.location
ip_configurations = [{
name = "internal"
subnet_id = module.vnet.subnet_ids["app"]
}]
}
module "vm" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-linux-virtual-machine.git?ref=v1.0.0"
name = "vm-app-01"
resource_group_name = module.rg.name
location = module.rg.location
size = "Standard_D2s_v5"
admin_username = "azureadmin"
network_interface_ids = [module.nic.id]
}
module "chaos_target" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-chaos-studio-target.git?ref=v1.0.0"
target_resource_id = module.vm.id
target_type = "Microsoft-VirtualMachine"
location = module.rg.location
}
module "chaos_capability" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-chaos-studio-capability.git?ref=v1.0.0"
chaos_studio_target_id = module.chaos_target.id
capability_type = "Shutdown-1.0"
}
module "chaos_experiment" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-chaos-studio-experiment.git?ref=v1.0.0"
name = "exp-vm-shutdown"
resource_group_name = module.rg.name
location = module.rg.location
selectors = {
app_vm = {
chaos_studio_target_ids = [module.chaos_target.id]
}
}
# The fault references this capability's computed urn.
# steps β branches β actions β { urn = module.chaos_capability.urn }
steps = [{
name = "shutdown"
branch = [{
name = "branch-1"
actions = [{
action_type = "continuous"
urn = "urn:csci:microsoft:virtualMachine:shutdown/1.0"
selector_name = "app_vm"
duration = "PT10M"
}]
}]
}]
}
module "chaos_rbac" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-role-assignments.git?ref=v1.0.0"
scope = module.vm.id
role_assignments = {
chaos_experiment = {
role_definition_name = "Virtual Machine Contributor"
principal_id = module.chaos_experiment.identity_principal_id
}
}
}π Four things gate a fault: the target is onboarded, this capability is enabled, the experiment selector names the target, and the experiment identity holds a role on the resource. Missing any one produces a silent no-op or a run-time authorization failure β never a plan-time error.
Required
| Name | Type | Description |
|---|---|---|
chaos_studio_target_id |
string |
Resource ID of the parent Chaos Studio target. Force-new. |
capability_type |
string |
The fault to enable, including its version suffix, e.g. Shutdown-1.0. Force-new. |
Optional
| Name | Type | Default | Description |
|---|---|---|---|
timeouts |
object |
null |
create/read/delete durations β no update. |
Full object() schemas
variable "timeouts" {
type = object({
create = optional(string)
read = optional(string)
delete = optional(string)
})
default = null
}Validation: capability_type is deliberately not constrained to a fixed list. The Chaos Studio fault library grows over time, and the legal set additionally depends on the parent target's target_type, so an enum here would silently reject values the service accepts. The service adjudicates the value at apply. The timeouts object omits update because the resource has no update operation.
| Output | Description | Kind |
|---|---|---|
id |
Passthrough | |
urn |
Passthrough | |
capability_type |
The fault capability enabled on the target, exactly as supplied | Passthrough |
chaos_studio_target_id |
The Resource ID of the Chaos Studio Target this capability is enabled on | Passthrough |
capability_name |
Passthrough | |
target_type |
Derived | |
target_resource_id |
Derived | |
capability_type_is_validated_at_apply_not_at_plan |
Constant | |
is_deleted_when_the_parent_target_is_deleted |
Constant | |
grants_fault_permission_not_execution |
Constant | |
is_upstream_of_experiment_faults |
Constant | |
urn_is_known_only_after_apply |
Constant | |
both_arguments_are_force_new |
Constant | |
capability_type_is_documented |
Passthrough | |
capability_type_matches_documented_casing |
Passthrough | |
is_agent_based_capability |
Derived | |
requires_chaos_mesh_installed_on_the_cluster |
Derived | |
resource_provider_to_register |
Derived | |
chaos_api_version |
Derived |
There is no name output β this resource type has no name attribute; the capability name derives from capability_type. No output is sensitive.
- Fully immutable, so no update timeout. Both fields force replacement, which is why
timeoutscarries onlycreate,read, anddelete. Includingupdatewould failterraform validateagainst the real schema. - The
urnis the point.idis the Terraform-side identity;urnis what an experiment's fault block consumes. It is computed by the service, so it must be wired from this output β hand-writing a URN couples your configuration to an undocumented format. - Capability validity is a two-way constraint. A
capability_typemust exist in the fault library and be published for the parent target'starget_type. Neither condition is checkable at plan time, so both surface at apply. Thetarget_type/capability_typepairing is the single most common source of an apply-time rejection here. capability_typeis intentionally unconstrained. Unlike a closed enum such as a SKU name, the fault library is a growing service-side table whose legal values vary by target type. Acontains()list would reject valid new faults, so the module accepts the string and lets the service decide.- Lifecycle is nested under the target. The capability is a child of the target's ID, so replacing the target destroys the capability. A change to the target's
target_resource_idproduces a cascading plan. - Capabilities are the blast-radius dial. A target with no capabilities is inert. Each capability is a specific fault a future experiment may invoke, which makes "one module instance per fault you actually test" a security posture, not just a style.
- No tags, no location, no name. This ARM resource supports none of them, so the universal tail here is
timeoutsonly β a deliberate omission, not an oversight.
| Concern | Secure default (empty call) | Opt-out (caller types it) |
|---|---|---|
| Blast radius | exactly one named fault per module instance | add more instances, deliberately, one fault each |
| Fault execution | never from this module | requires an experiment plus RBAC on the target resource |
| Standing capability | none between exercises (remove when done) | leave enabled |
capability_type correctness |
adjudicated by the service | not constrained locally, to avoid rejecting valid new faults |
| Mutability | none β every change is a replacement | not permitted |
| Secrets | none accepted, none emitted | not permitted |
terraform init -backend=false
terraform validate
terraform fmt -check- Pin the module with
?ref=v1.0.0β never a branch. - This is plan-only during authoring; a human runs
terraform plan/applyfrom CI against real credentials. - The caller supplies
provider "azurerm" { features {} }, auth, and the target subscription.
The offline proof gate β no cloud, no backend:
terraform init -backend=falseresolves the pinnedazurerm ~> 4.0provider.terraform validateproves the configuration is type-correct against the provider schema β including that both inputs are required strings and thattimeoutshas noupdatemember.terraform fmt -checkenforces canonical formatting.
What only terraform plan / apply (run by a human, from CI) exercises: whether capability_type exists in the fault library, whether it is published for the parent target's target_type, the parent target's existence, RBAC on the target resource, and prerequisites such as the Chaos Studio agent or Chaos Mesh.
$ terraform output
capability_type = "Shutdown-1.0"
chaos_studio_target_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-resilience-test/providers/Microsoft.Compute/virtualMachines/vm-app-01/providers/Microsoft.Chaos/targets/Microsoft-VirtualMachine"
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-resilience-test/providers/Microsoft.Compute/virtualMachines/vm-app-01/providers/Microsoft.Chaos/targets/Microsoft-VirtualMachine/capabilities/Shutdown-1.0"
urn = "urn:csci:microsoft:virtualMachine:shutdown/1.0"| Symptom | Cause | Fix |
|---|---|---|
| Apply fails: capability not found | capability_type misspelled or missing its version suffix |
Use the exact published name, e.g. Shutdown-1.0, not Shutdown. |
| Apply fails: capability not valid for this target | The fault is not published for the parent target's target_type |
Check the fault library for that target type; agent faults need a Microsoft-Agent target. |
An update timeout you set is silently ignored, with no error |
This module's timeouts is a typed object with no update attribute, and Terraform's object-type conversion discards undeclared keys without raising anything. The Unsupported argument: update diagnostic exists, but only inside a resource block's own timeouts -- which a module caller never writes. |
Do not set update; there is no update operation to time out. Nothing will warn you, so check the key is absent rather than relying on a plan error. |
| Capability replaces on a small edit | Both fields are force-new | Expected; update any experiment referencing the old urn in the same change. |
| Capability disappears from state after a target change | The target was replaced; capabilities are its children | Re-create the capability alongside the new target. |
| Experiment cannot find the fault | Experiment references a stale or hand-written URN | Wire the fault from module.<this>.urn. |
| Experiment fails with an authorization error | Experiment identity has no role on the target resource | Assign a role permitting the fault, scoped to the target resource. |
| Agent-based fault never starts | Chaos Studio agent or its user-assigned identity missing on the VM | Install the agent and associate the identity. |
| Chaos Mesh fault never starts | Chaos Mesh not installed in the AKS cluster | Install Chaos Mesh; onboarding the target does not install it. |
- azurerm_chaos_studio_capability (provider registry)
- Microsoft Learn β Chaos Studio fault library
- Microsoft Learn β Chaos Studio fault providers and target types
- Sibling module:
terraform-azurerm-chaos-studio-target - Sibling module:
terraform-azurerm-chaos-studio-experiment - Sibling module:
terraform-azurerm-role-assignments - This module's
SCOPE.md
π "Infrastructure as Code should be standardized, consistent, and secure."