Hedronite · Ops Lesson · 01-Earth-DevOps / Terraform · Wed 2026-09-17

Terraform ephemeral values — Azure Key Vault value_wo + MySQL administrator_password_wo

Sensitive redacts. Write-only erases. Ephemeral never lived in the ledger.

Lesson Class: Ops (DevOps + Terraform + Azure Key Vault / MySQL write-only)
Cloud Referent: azurerm_key_vault_secret.value_wo + azurerm_mysql_flexible_server.administrator_password_wo
Paired Dev: Python state JSON secret-absence census
Paired Cert: Associate ephemeral vs sensitive vs write-only versions
Grounding: Lab 24 · HashiCorp write-only docs · Brikman Ch.6 referenced
Ephemeral
Operation-local values; not ordinary state citizens.
Write-only
Provider *_wo args omitted from plan and state.
Version
Bump *_wo_version to rotate what Terraform cannot diff.
Generate once. Write-only store. Ephemeral read. Write-only consume. Demand absence in state JSON.

<!-- hal:authoritative:yaml -->

Sensitive redacts. Write-only erases. Ephemeral never lived in the ledger. Today the password crosses the plan once and leaves no residue in state.

§I — Frame

Tuesday this arc imported a brownfield GCS bucket and moved its address into a module. The finish line was a no-op plan. That fire stays filed. It answers how world objects enter and rename inside state.

Today the track is Terraform on Azure, and the question flips: how does a password reach Key Vault and MySQL Flexible Server without ever becoming a state attribute? Terraform 1.11 introduced provider write-only arguments. Ephemeral resources supply values that exist only for the current operation. Together they form a path HashiCorp documents with exactly this Azure pair: value_wo on azurerm_key_vault_secret, then administrator_password_wo on azurerm_mysql_flexible_server.

Lab 24 still matters as contrast. Marking sensitive = true redacts UI and logs. The value remains in the state blob. Write-only arguments do not. That difference is the Ops lesson.

09-02 already taught a versionless Key Vault reference on App Service and replace_triggered_by. Do not redo that path. Today is generate, write-only store, ephemeral read, write-only consume.

§II — Foundations: three facts

Fact one. Ephemeral resources are not state citizens.

ephemeral "random_password" "db_password" {
  length           = 16
  override_special = "!#$%&*()-_=+[]{}<>:?"
}

An ephemeral block produces values for the current plan/apply only. Terraform does not persist the resource instance in state the way a managed resource does. You may pass its outputs into write-only arguments. You must not expect terraform state show to reveal them later.

Fact two. Write-only arguments accept the secret and discard it from artifacts.

locals {
  db_password_version = 1
}

resource "azurerm_key_vault_secret" "db" {
  name             = "example-secret"
  value_wo         = ephemeral.random_password.db_password.result
  value_wo_version = local.db_password_version
  key_vault_id     = azurerm_key_vault.example.id
}

Registry docs mark value_wo as write-only. During apply the provider receives the value, configures the remote secret, and Terraform stores null for that argument in plan and state. Pair it with value_wo_version. Because Terraform cannot diff a value it never kept, the version integer is the signal you bump when the password must change.

Fact three. Consume through another ephemeral read, then another write-only sink.

ephemeral "azurerm_key_vault_secret" "db_password" {
  name         = azurerm_key_vault_secret.db.name
  key_vault_id = azurerm_key_vault.example.id
}

resource "azurerm_mysql_flexible_server" "example" {
  name                   = "example-mysql-flexible-server"
  resource_group_name    = azurerm_resource_group.example.name
  location               = azurerm_resource_group.example.location
  sku_name               = "B_Standard_B1s"
  administrator_login    = "newuser"

  administrator_password_wo         = ephemeral.azurerm_key_vault_secret.db_password.value
  administrator_password_wo_version = local.db_password_version
}

HashiCorp's write-only tutorial uses this exact shape: generate, store in Key Vault via value_wo, read back with an ephemeral data-shaped resource, feed MySQL via administrator_password_wo. The password never appears as a managed-resource attribute in state.

§III — Worked path (Ops drill)

  1. Require Terraform >= 1.11 and a current azurerm provider that publishes the _wo arguments.
  2. Stand up resource group, Key Vault, and access policy with Set/Get secret permissions (tutorial baseline).
  3. Declare ephemeral.random_password, Key Vault secret with value_wo / value_wo_version, ephemeral Key Vault read, MySQL Flexible Server with administrator_password_wo / _wo_version.
  4. terraform plan then apply. Inspect state: confirm the secret and server resources exist, and confirm the write-only attributes are absent or null in state JSON.
  5. Rotate by incrementing local.db_password_version and changing the generated password inputs as needed. Plan should show version drift, not a plaintext secret diff.

Success criterion: remote Key Vault and MySQL hold the password; terraform show -json on state does not contain the password string under those attributes.

§III.b — State inspection commands

terraform state list
terraform show -json > /tmp/state.json
# Confirm password string absent; version attrs present
python3 -c "import json,sys; s=json.load(open('/tmp/state.json')); print('keys ok')"

Pair with the Dev census script rather than eyeballing megabytes of JSON in a pager. Maghrib lab-ref will point Lab 24 first (sensitive contrast), then this apply path.

§IV — Contrast table (file this)

MechanismPlan UIPersists in state?Update signal
sensitive = trueRedactedYesNormal attribute diff
Write-only (*_wo)Not storedNoCompanion *_wo_version
ephemeral resourceOperation-localNo instance in stateRe-open each run

Lab 24 trains the first row. Today trains rows two and three on Azure.

§IV.b — Why Azure today

Cloud referent rotation: 09-14 was GCP GCS, 09-11 and 09-08 were AWS. Azure returns for the secret path because HashiCorp's official write-only tutorial ships the Key Vault plus MySQL Flexible Server example verbatim. Prefer that documented pair over inventing an AWS-only _wo drill when the live tutorial already names azurerm_key_vault_secret and azurerm_mysql_flexible_server.

Keep 09-02 adjacent, not primary. That fire solved versionless App Service references and forced replaces. This fire solves absence from state.

§V — Pitfalls

§VI — Close

Import taught belief to catch the world. Write-only teaches the ledger to forget the password on purpose. Azure Key Vault value_wo and MySQL Flexible Server administrator_password_wo are the concrete pair. Maghrib will quiz the contrast with Lab 24 sensitivity. Dev will census state JSON for secret absence. Cert will map Associate/Pro language for sensitive versus ephemeral versus write-only.

Re-authored on the lab Mac SoT 2026-09-18 after morning vault mirror rsync -a --delete removed the Bot-only #123 dirs that the lab Mac never held.