Published on
·4 min read

Terraform Upgrade sa 0.12 i Glavobolje sa $ u Nazivu

Ovaj tekst je dostupan i na engleskom →

TL;DR: Ako dobijaš "Provider produces inconsistent result after apply" grešku pri migraciji AzureRM resursa iz Terraforma sa 0.12 na 0.13, proveri da li naziv nekog resursa počinje sa $. Izbriši ga i nastavi.


Za nas u developmentu, svaki upgrade verzija donosi sa sobom obavezne glavobolje i niz misli gde se preispituješ da li si promašio struku i zašto ti je ovo trebalo u životu. U toku svoje karijere sam odradio dovoljan broj upgradeova, uglavnom u .NET svetu, ali me ništa nije spremilo za upgrade Terraforma.

Terraform kroz verzije

Terraform je alat koji ti omogucava da infrastrukturu opises kao kod, umesto da klikces po Azure portalu. Svi resursi se čuvaju u state koji prati promene resursa (dodavanje, brisanje, update atributa) i pri svakom terraform apply menja samo ono što se stvarno promenilo — u suprotnom bi sve resurse brisao i rekreirao iz početka (odnosno Configuration drift [^1]).

Terraform kao takav je prošao dugačak put u razvoju (state pogotovo, pročitaj malo dalje), ali se malo priča o tome koliko su veliki skokovi u minor verzijama, konkretno u verzijama od 0.11 do 0.14.

0.12 — HashiCorp je doneo prelaz sa stringly typed na typed verzije. Sintaksa za reference je promenjena:

# Pre (0.11)
"${var.foo}"

# Posle (0.12)
var.foo

State je dobio eksplicitni tip metadata:

# Before (0.11)
"count": "3",
"enabled": "true",
"tags": "map[env:prod]"

# After (0.12)
"count": 3,
"enabled": true,
"tags": { "env": "prod" }

uvedeni su for_each i dynamic blokovi.

0.13 — Najveća izmena je bila dodavanje adrese (laički rečeno, namespace-a) za svaki provider. Na primer, ako smo imali dva azurerm providera (originalni HashiCorp i nečiji drugi), pre 0.13 je bilo gotovo nemoguće jasno precizirati koji provider treba:

# 0.13 — eksplicitni provider namespace
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
  }
}

0.14 — Sensitive atribut se počeo pratiti u state-u, i dodat je dependency lock file za providere (.terraform.lock.hcl), slično kao package-lock.json u Node.js svetu.

Problem

Ovo znači da ako je verzija Terraforma u repou koji upgradeujete manja ili jednaka 0.11, da biste upgradovali na poslednju verziju (u mom slučaju, 1.9.5) morate odraditi 4 deploymenta. Odnosno, pokrenuti terraform apply četiri puta:

0.11 -> 0.12 -> 0.13 -> 0.14 -> 1.9.5

Razlog je što svaka verzija zna da migrira state samo iz prethodne - 0.14 jednostavno ne razume state fajl od 0.12. Ovo je svesna odluka HashiCorp-a.

I to je bio moj zadatak: upgradeuj Terraform sa 0.11 na 1.9.5. Prvi deployment sa 0.11 na 0.12 je prošao bez problema, ali upgrade sa 0.12 na 0.13 doneo je bug koji je tema ovog posta.

Prilikom terraform apply koraka događao se exception sa sledećom sadržinom:

Error: Provider produces inconsistent result after apply

When applying changes to azurerm_servicebus_subscription_rule.example,
provider "registry.terraform.io/hashicorp/azurerm" produced an unexpected new
value: Root resource was present, but not absent.

This is a bug in the provider, which should be reported in the provider's own
issue tracker.

Resurs sam po sebi nije ukazivao ni na kakav problem:

resource "azurerm_servicebus_subscription_rule" "example" {
  name            = "$Default"
  subscription_id = azurerm_servicebus_subscription.example.id
  filter_type     = "SqlFilter"
  sql_filter      = "colour = 'red'"
}

Guglanje i ChatGPT nisu pomogli, dakle ostalo je samo još try and error pristup — a istini za volju, imao sam i prostora i vremena da se igram.

Zašto se ovo dešava

Počeo sam da menjam atribute jedan po jedan. Kad sam promenio naziv sa $Default na test-name, terraform apply je prošao. Dakle, problem je ili u $ ili u Default - koji je rezervisana reč. Sledeći pokušaj je bio deploy sa nazivom Default (bez $), i bingo.

Zašto? AzureRM automatski pravi $Default pravilo za svaki Service Bus subscription - to je pravilo koje hvata sve poruke kad ne definišeš nijedno drugo pravilo. $ prefiks je rezervisan za sistemske resurse, što znači da Azure odbija da kreira bilo koji korisnički resurs sa tim prefiksom. I tu dolazi do konflikta.

Rešenje

resource "azurerm_servicebus_subscription_rule" "example" {
  - name            = "$Default" # <--- Before
  + name            = "default-rule" # <--- After
  subscription_id = azurerm_servicebus_subscription.example.id
  filter_type     = "SqlFilter"
  sql_filter      = "colour = 'red'"
}

Naredna dva deploymenta su, srećom, prošla bez problema.

Rešenje je prosto - izbriši $ iz naziva i nastavi sa migracijom.

[^1]: Test fus nota