Mert Özen Her Satırda Daha İleri
DevOps Infrastructure as Code: Terraform — #10

Terraform Kodunu Doğrulamak ve Test Etmek: fmt, validate, tflint, terraform test, mock_provider ve Policy

Mert Özen 14 Kas 2026 13 dk 17 görüntülenme
Terraform Kodunu Doğrulamak ve Test Etmek: fmt, validate, tflint, terraform test, mock_provider ve Policy

validate yalnız kodun çalışabilir olduğunu söyler; kurallarını korumak için tflint, güvenlik taraması ve mock_provider'lı terraform test katmanlarına ihtiyacın var ve hiçbiri cloud hesabı istemez. TLS 1.1'e çekilen bir Web App'i validate'in geçirip testin durdurduğunu görüyor, tflint ve Trivy bulgularını ayıklıyor, bütün Azure ortamını mock'larla apply ediyor, Docker ile gerçek provider testi yazıyor ve hepsini tek bir script'te topluyoruz.

Sürüm notu: Bu yazıdaki örnekler Terraform 1.17.2, TFLint 0.66.0 (tflint-ruleset-azurerm 0.33.0), Trivy 0.76.2, kreuzwerker/docker provider 4.7.0 ve Docker Engine 29 ile denenmiştir. AzureRM 5.14.0 kodu mock_provider'lı terraform test ile test edildi; Azure'a plan/apply yapılmadı.

Önce Tahmin Et

Bookstore'un (sitedeki serilerin ortak örnek uygulaması, bir online kitapçı) bookstore-webapp module'ünde biri minimum_tls_version'ı "1.2"'den "1.1"'e çekiyor; eski bir istemci bağlanamıyormuş. terraform fmt -check geçer mi? terraform validate geçer mi? Hiçbiri yakalamıyorsa, Azure hesabı olmadan bu değişikliği pull request'te durduracak ne yazabilirsin?

Katmanlar: Hangi Kontrol Neyi Yakalar?

İlk iki sorunun cevabı: ikisi de geçer. fmt yalnız biçime bakar. validate söz dizimini, referansları, tipleri ve provider şemasını kontrol eder; "1.1", AzureRM'in kabul ettiği değerlerden biri olduğu için geçerli. Hatalı olan şey kod değil, bizim kuralımız. Terraform kodunu doğrulamak birkaç katmanlı bir iş ve her katman bir öncekinin göremediğini yakalar:

KatmanNe yakalar?Cloud gerekir mi?
terraform fmt -checkBiçim farklarıHayır
terraform validateSöz dizimi, referans, tip, provider şemasıHayır (init -backend=false)
tflintYanlış ya da eksik kullanım, provider'a özgü kurallarHayır
Trivy, CheckovGüvenlik ayarları (açık network, şifreleme, log)Hayır
terraform test + mock_providerBizim kurallarımız, module'ün davranışıHayır
terraform test + gerçek providerKaynağın gerçekten oluşup çalışmasıEvet (test hesabı)

7. yazıda bunun bir örneğini görmüştük: prod klasöründe B1 (App Service'in en küçük Basic katmanı) kullanmayı yasaklayan validation, validate'te değil plan'da çalışıyordu. Plan'ı Azure'a gitmeden almanın yolu mock_provider.

tflint: validate'in Göremedikleri

tflint, Terraform kodunu bir linter gibi okuyan ayrı bir araç. Varsayılan olarak gelen terraform ruleset'i kullanılmayan variable'ları, tipi olmayan variable'ları, sürümü sabitlenmemiş provider'ları ve benzeri sorunları bulur. Provider'a özgü ruleset'ler eklenebilir; AzureRM için olanı geçersiz VM boyutlarını, eksik ayarları ve bazı iyi uygulama kurallarını kontrol ediyor. infra/.tflint.hcl:

plugin "terraform" {
  enabled = true
  preset  = "recommended"
}

plugin "azurerm" {
  enabled = true
  version = "0.33.0"
  source  = "github.com/terraform-linters/tflint-ruleset-azurerm"
}

# Bookstore'da bunu health check (health_check_path) karşılıyor
rule "azurerm_app_service_missing_auto_heal_setting" {
  enabled = false
}

Dosyanın sonundaki rule block'unu ilk çalıştırmadan sonra ekledim. İlk sonuç:

$ cd infra
$ tflint --init
Installing "azurerm" plugin...
Installed "azurerm" (source: github.com/terraform-linters/tflint-ruleset-azurerm, version: 0.33.0)
$ tflint --recursive --config "$(pwd)/.tflint.hcl" --format compact
8 issue(s) found:

modules/bookstore-webapp/main.tf:23:1: Warning - auto_heal_setting should be configured in site_config block for robust app services. (azurerm_app_service_missing_auto_heal_setting)
bootstrap-azure/main.tf:36:1: Warning - Resource is missing lifecycle { prevent_destroy = true }. This resource contains data that should be protected from accidental deletion. (azurerm_resources_missing_prevent_destroy)
bootstrap-azure/main.tf:60:1: Warning - Resource is missing lifecycle { prevent_destroy = true }. ...
azure/bookstore/secrets.tf:13:1: Warning - Resource is missing lifecycle { prevent_destroy = true }. ...
azure/bookstore/main.tf:45:1: Warning - Resource is missing lifecycle { prevent_destroy = true }. ...
azure/bookstore/main.tf:7:1: Warning - Resource is missing lifecycle { prevent_destroy = true }. ...
azure/bookstore/main.tf:17:1: Warning - Resource is missing lifecycle { prevent_destroy = true }. ...
azure/bookstore/main.tf:17:1: Warning - Resource is missing lifecycle { prevent_destroy = true }. ...

terraform ruleset'i hiçbir şey bulmadı. AzureRM ruleset'i iki şey söyledi. Birincisi, veri taşıyan yedi kaynakta prevent_destroy yok: Bookstore'un storage account'u ve blob container'ları, SQL Server, Key Vault secret'ı ve state'i tutan storage account ile container. 5. yazıda BookstoreDb'ye koymuştuk, gerisini atlamışız. Storage, SQL Server ve state kaynaklarına ekledim. Key Vault secret'ı için kuralı yalnız o kaynakta kapattım, çünkü rotation secret'ı silmiyor, yeni bir sürüm ekliyor:

# Her çalışmada bellekte üretilir; Terraform onu state'e ya da plan'a hiç yazmaz
ephemeral "random_password" "sql_admin" {
  length           = 32
  special          = true
  override_special = "!#%*-_=+"
  min_lower        = 2
  min_upper        = 2
  min_numeric      = 2
  min_special      = 2
}

# İnsanlar için acil durum kopyası; uygulama bu şifreyle değil managed identity'siyle bağlanır
# tflint-ignore: azurerm_resources_missing_prevent_destroy # rotation secret'ı değil değerini değiştirir
resource "azurerm_key_vault_secret" "sql_admin_password" {
  name             = "bookstore-sql-admin-password"
  key_vault_id     = data.azurerm_key_vault.shared.id
  content_type     = "password"
  value_wo         = ephemeral.random_password.sql_admin.result
  value_wo_version = var.sql_admin_password_version
}

İkincisi, Web App'te auto_heal_setting yok. Bu kuralı config'de kapattım; Bookstore'da sağlıksız instance'ı health_check_path ayarı devreden çıkarıyor. Bir kuralı kapatmak meşru bir karar, yeter ki sebebi kodda yazsın. Düzeltmelerden sonra tflint temiz çıkıyor.

terraform test ve mock_provider

terraform test, Terraform 1.6'dan beri gelen test framework'ü. Test dosyaları .tftest.hcl uzantılı, genelde module'ün tests klasöründe durur. Her dosya bir ya da birkaç run block'undan oluşur; her run bir plan ya da apply çalıştırır ve assert block'larıyla sonucu kontrol eder. Terraform 1.7 ile gelen mock_provider, provider'ın yerine geçip her kaynağa sahte değerler üretir; cloud'a hiç gitmez. Tahmin sorusundaki değişikliği durduracak test:

# Azure olmadan çalışır: her azurerm resource ve data source'u sahte değerler alır
mock_provider "azurerm" {}

variables {
  name_suffix         = "bookstore-test-weu"
  resource_group_name = "rg-bookstore-test-weu"
  location            = "westeurope"
  image               = { name = "bookstore/bookstore-api", tag = "1.1.0" }
  key_vault = {
    id   = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-platform-test-weu/providers/Microsoft.KeyVault/vaults/kv-platform-test-weu"
    name = "kv-platform-test-weu"
  }
}

run "https_and_tls" {
  command = plan

  assert {
    condition     = azurerm_linux_web_app.this.https_only == true
    error_message = "The web app must accept HTTPS only."
  }

  assert {
    condition     = azurerm_linux_web_app.this.site_config[0].minimum_tls_version == "1.2"
    error_message = "The minimum TLS version must be 1.2."
  }
}

run "open_without_ip_ranges" {
  command = plan

  assert {
    condition     = azurerm_linux_web_app.this.site_config[0].ip_restriction_default_action == "Allow"
    error_message = "Without IP ranges the app must stay reachable."
  }
}

run "deny_by_default_with_ip_ranges" {
  command = plan

  variables {
    allowed_ip_ranges = { office = "203.0.113.0/24" }
  }

  assert {
    condition     = azurerm_linux_web_app.this.site_config[0].ip_restriction_default_action == "Deny"
    error_message = "With IP ranges, everything else must be denied."
  }

  assert {
    condition     = length(azurerm_linux_web_app.this.site_config[0].ip_restriction) == 1
    error_message = "Expected one IP restriction rule."
  }
}

variables block'u bütün run'lar için ortak değerleri veriyor; son run kendi değerini ekliyor. Assertion'lar module'ün içindeki kaynağın plan'daki değerine bakıyor. Module klasöründe:

$ cd infra/modules/bookstore-webapp
$ terraform test
tests/webapp.tftest.hcl... in progress
  run "https_and_tls"... pass
  run "open_without_ip_ranges"... pass
  run "deny_by_default_with_ip_ranges"... pass
tests/webapp.tftest.hcl... tearing down
tests/webapp.tftest.hcl... pass

Success! 3 passed, 0 failed.

Bütün dosya 1,6 saniyede bitti; Azure hesabı ya da internet bağlantısı gerekmedi. Şimdi TLS (trafiği şifreleyen protokol) sürümünü "1.1"'e çekiyorum:

$ terraform test
tests/webapp.tftest.hcl... in progress
  run "https_and_tls"... fail

Error: Test assertion failed

  on tests/webapp.tftest.hcl line 24, in run "https_and_tls":
  24:     condition     = azurerm_linux_web_app.this.site_config[0].minimum_tls_version == "1.2"
    ├────────────────
    │ Diff:
    │ --- actual
    │ +++ expected
    │ - "1.1"
    │ + "1.2"


The minimum TLS version must be 1.2.
  run "open_without_ip_ranges"... pass
  run "deny_by_default_with_ip_ranges"... pass
tests/webapp.tftest.hcl... tearing down
tests/webapp.tftest.hcl... fail

Failure! 2 passed, 1 failed.

Test hangi değerin ne olması gerektiğini ve ne olduğunu gösteriyor. validate'in geçirdiği değişiklik burada duruyor. 1.17 ile mock_provider ephemeral kaynakları da destekliyor; 9. yazıdaki ephemeral random_password'lı kod da bu sayede test edilebiliyor.

Bütün Ortamı Mock'larla "Apply" Etmek

command = plan hızlı ama bazı değerler plan'da bilinmez. 7. yazıdaki prod kuralını ve ortam farklarını infra/azure/bookstore module'ünün tamamı üzerinde test etmek için bir run'ı command = apply ile, yine mock'lara karşı çalıştırdım. İlk denemede:

$ terraform test
...
Error: parsing "kkrxyvy7": parsing the StorageAccount ID: the number of segments didn't match
...
  with azurerm_storage_container.bookstore["covers"],
...
Error: parsing "obnpp4zv": parsing the SqlServer ID: the number of segments didn't match
...
Error: expected "principal_id" to be a valid UUID, got u4tk8v3c

  with module.api.azurerm_role_assignment.key_vault_secrets,
...
Failure! 0 passed, 1 failed, 2 skipped.

Mock provider sahte değerleri rastgele string olarak üretiyor, ama provider'ın kendi doğrulamaları hâlâ çalışıyor: blob container storage account ID'sini Azure biçiminde bekliyor, role assignment UUID (benzersiz kimlik biçimi) bir principal ID bekliyor. Bu yüzden ID'si başka bir kaynakta kullanılan tipler için gerçekçi varsayılanlar vermek gerekiyor:

mock_provider "azurerm" {
  # ...

  # Provider apply sırasında ID'leri yine doğrular, rastgele mock string'leri yetmez
  mock_resource "azurerm_storage_account" {
    defaults = {
      id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-bookstore-dev-weu/providers/Microsoft.Storage/storageAccounts/stbookstoredevweu"
    }
  }

  mock_resource "azurerm_mssql_server" {
    defaults = {
      id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-bookstore-dev-weu/providers/Microsoft.Sql/servers/sql-bookstore-dev-weu"
    }
  }

  # ... azurerm_service_plan, azurerm_user_assigned_identity
}

Dosyanın geri kalanı, Key Vault data source'u için de aynı biçimde bir ID veriyor, check block'undaki http data source'unu 200 döndürecek şekilde mock'luyor ve üç run tanımlıyor: dev'in küçük katmanları, prod'da B1'in reddedilmesi (expect_failures = [var.api]) ve prod'da zone yedekli storage ile S1 (Azure SQL'in Standard katmanlarından biri) veritabanı. Sonuç:

$ cd infra/azure/bookstore
$ terraform test
tests/bookstore.tftest.hcl... in progress
  run "dev_uses_small_tiers"... pass
  run "prod_rejects_b1"... pass
  run "prod_uses_zone_redundancy"... pass
tests/bookstore.tftest.hcl... tearing down
tests/bookstore.tftest.hcl... pass

Success! 3 passed, 0 failed.

Burada test edilen şey Azure'un davranışı değil, bizim kodumuzun hangi değerleri ürettiği. Mock'lu bir test, Azure'un bir SKU'yu (kaynağın boyut ve fiyat katmanı) o region'da sunup sunmadığını, isim çakışmasını ya da yetki eksikliğini yakalamaz. Onlar için gerçek bir test subscription'ında, gerçek provider'la çalışan testler gerekir.

Gerçek Provider'la Test: bookstore-service

Yerel örnekte gerçek provider'la test yazmak ucuz, çünkü Docker elimizde. bookstore-service module'ünün testi bir container ve bir volume açıp kontrol ediyor:

# Gerçek Docker provider'ını kullanır: apply run'ı bir container açıp siler
provider "docker" {}

variables {
  name          = "tfseries-test-api"
  image         = "bookstore-api:1.1.0"
  networks      = ["tfseries-shared"]
  data_volumes  = ["covers"]
  external_port = 18482
}

run "rejects_latest_tag" {
  command = plan

  variables {
    image = "bookstore-api:latest"
  }

  expect_failures = [var.image]
}

run "starts_container_with_volume" {
  assert {
    condition     = output.volume_names["covers"] == "tfseries-test-api-covers"
    error_message = "Volume names must be <name>-<data set>."
  }

  assert {
    condition     = docker_container.this.restart == "unless-stopped"
    error_message = "The default restart policy must be unless-stopped."
  }
}
$ cd infra/modules/bookstore-service
$ terraform test
tests/service.tftest.hcl... in progress
  run "rejects_latest_tag"... pass
  run "starts_container_with_volume"... pass
tests/service.tftest.hcl... tearing down
tests/service.tftest.hcl... pass

Success! 2 passed, 0 failed.
$ docker volume ls --filter name=tfseries-test
DRIVER    VOLUME NAME

İlk run, 6. yazıdaki "latest tag'i yasak" validation'ının gerçekten çalıştığını expect_failures ile doğruluyor: hata beklenen yerde çıkınca test geçiyor. İkinci run'ın command'ı yok, varsayılan apply: container gerçekten oluştu, assertion'lar gerçek değerlere baktı, test bitince Terraform her şeyi sildi. Bir ayrıntı dikkatimi çekti: module'deki volume'lerde prevent_destroy var, ama test teardown'u volume'ü sildi. Bu bir hata değil; Terraform 1.14 changelog'u test temizliğinde prevent_destroy'un yok sayıldığını açıkça yazıyor. Test framework'ünün kendi oluşturduğu kaynakları temizleyebilmesi mantıklı, ama gerçek bir ortamın state'iyle test çalıştırmanın neden tehlikeli olduğunu da gösteriyor.

Güvenlik Taraması: Trivy, Checkov ve Policy

Güvenlik tarayıcıları, kaynak ayarlarını bilinen kural setlerine göre kontrol eder: storage'ın public erişimi, şifreleme, log, TLS. Trivy'yi bütün infra klasörü üzerinde çalıştırdım:

$ trivy config --disable-telemetry infra
...
┌──────────────────────────────────┬───────────┬───────────────────┐
│              Target              │   Type    │ Misconfigurations │
├──────────────────────────────────┼───────────┼───────────────────┤
│ azure/bookstore/main.tf          │ terraform │        21         │
├──────────────────────────────────┼───────────┼───────────────────┤
│ azure/bookstore/secrets.tf       │ terraform │         3         │
├──────────────────────────────────┼───────────┼───────────────────┤
│ bootstrap-azure/main.tf          │ terraform │         5         │
├──────────────────────────────────┼───────────┼───────────────────┤
│ modules/bookstore-webapp/main.tf │ terraform │         9         │
└──────────────────────────────────┴───────────┴───────────────────┘
...
AZU-0012 (CRITICAL): No network rules defined and default action allows access.
════════════════════════════════════════
The default_action for network rules should come into effect when no other rules are matched.
The default action should be set to Deny.
See https://avd.aquasec.com/misconfig/azu-0012
────────────────────────────────────────
 azure/bookstore/main.tf:7-19
   via azure/envs/dev/main.tf:1-19 (module.bookstore)

38 bulgu çıktı; bir kısmı aynı kuralın üç ortamda tekrar etmesi, farklı kural sayısı 11. Hepsini aynı ciddiyetle ele almak yerine üçe ayırdım. Hemen düzeltilecekler: App Service'te HTTP/2'nin kapalı olması tek satırlık bir iş, http2_enabled = true ekledim. Mimari karar gerektirenler: storage account'un ve SQL Server'ın public network'e açık olması (AZU-0012 ve AZU-0022). Doğru çözüm private endpoint ve VNet; bu Cloud Temelleri serisinin konusu ve Bookstore'un network katmanı kurulunca kapanacak. Bilinçli olarak kabul edilenler: müşteri yönetimli anahtarla şifreleme, client sertifikası ve geo-redundant replikasyon gibi, maliyeti ya da karmaşıklığı Bookstore'un ihtiyacını aşan kurallar. Kabul edilen bulguları .trivyignore dosyasında gerekçesiyle tutmak, raporun bir sonraki taramada yeniden aynı listeyi göstermemesini sağlar.

Checkov aynı işi Python tabanlı ve daha geniş bir kural setiyle yapıyor; OPA (Open Policy Agent; genel amaçlı bir policy motoru) ve Conftest ise kuralları Rego dilinde kendin yazmanı sağlıyor, örneğin "her kaynakta app ve environment tag'i olmalı" gibi şirkete özgü kurallar. Bu araçların pipeline'da nereye yerleşeceği DevSecOps serisinin konusu.

Terraform'un kendi policy aracı da var: Terraform Policy, 1.17 ile GA (genel kullanıma açık, kararlı sürüm) oldu ve plan, apply ve query komutlarına -policies flag'ini getirdi. Kurallar .policy.hcl dosyalarında HCL (Terraform'un yapılandırma dili, HashiCorp Configuration Language) ile yazılıyor (resource_policy block'u ve içinde enforce). Dokümanına göre kurallar ağırlıklı olarak HCP (HashiCorp Cloud Platform; HashiCorp'un yönetilen bulut platformu) Terraform'da uygulanıyor, lokal test için ayrı bir tfpolicy CLI'ı var. Lokal plan'da denediğimde:

$ terraform plan -policies=policies
...
Error: Failed to connect to policy engine

Failed to connect to policy engine: failed to connect to plugin: exec:
"tfpolicy-plugin": executable file not found in $PATH.

Policy motoru PATH'te (çalıştırılabilir dosyaların arandığı klasör listesi) duran ayrı bir plugin (tfpolicy-plugin) olarak çalışıyor. Lokal test aracı tfpolicy ayrı bir değerlendirme lisansıyla dağıtılıyor; bu yüzden kurup çalıştırmadım, bu kısım dokümana dayanıyor.

Hepsini Tek Komutta Çalıştırmak

Bu katmanları pull request'te ve laptop'ta aynı şekilde çalıştırmak için bir script yazdım. Cloud credential'ı istemiyor; backend'e bağlanmadan init -backend=false ile çalışıyor:

#!/usr/bin/env bash
# Bütün Terraform root'larını ve module'lerini cloud credential'ı olmadan kontrol eder
set -euo pipefail
cd "$(dirname "$0")/../infra"

echo "== fmt"
terraform fmt -check -recursive

echo "== validate"
for dir in bootstrap-local bootstrap-azure local/envs/dev local/envs/staging azure/envs/dev azure/envs/staging azure/envs/prod; do
    terraform -chdir="$dir" init -backend=false -input=false -no-color >/dev/null
    printf '%-20s ' "$dir"
    terraform -chdir="$dir" validate -no-color
done

echo "== tflint"
tflint --init >/dev/null
tflint --recursive --config "$PWD/.tflint.hcl"

echo "== test (mock providers)"
for dir in modules/bookstore-webapp azure/bookstore; do
    terraform -chdir="$dir" init -input=false -no-color >/dev/null
    terraform -chdir="$dir" test -no-color
done
$ ./scripts/check-infra.sh
== fmt
== validate
bootstrap-local      Success! The configuration is valid.

bootstrap-azure      Success! The configuration is valid.

local/envs/dev       Success! The configuration is valid.

local/envs/staging   Success! The configuration is valid.

azure/envs/dev       Success! The configuration is valid.

azure/envs/staging   Success! The configuration is valid.

azure/envs/prod      Success! The configuration is valid.

== tflint
== test (mock providers)
tests/webapp.tftest.hcl... in progress
  run "https_and_tls"... pass
  run "open_without_ip_ranges"... pass
  run "deny_by_default_with_ip_ranges"... pass
tests/webapp.tftest.hcl... tearing down
tests/webapp.tftest.hcl... pass

Success! 3 passed, 0 failed.
tests/bookstore.tftest.hcl... in progress
  run "dev_uses_small_tiers"... pass
  run "prod_rejects_b1"... pass
  run "prod_uses_zone_redundancy"... pass
tests/bookstore.tftest.hcl... tearing down
tests/bookstore.tftest.hcl... pass

Success! 3 passed, 0 failed.

Gerçek Docker gerektiren bookstore-service testini bu listeye koymadım; Docker'ın olduğu bir job'da ayrı çalışıyor. 11. yazıda bu script, pull request pipeline'ının ilk job'u olacak.

Üretimde Dikkat

Gerçek provider'la test yapacaksan, ayrı bir test subscription'ı kullan ve test kaynaklarının adlarını çakışmayacak şekilde üret (örneğin run ID'sinden türeyen bir sonek). Test başarısız olursa teardown da yarıda kalabilir; test subscription'ını düzenli temizleyen bir job ve bütçe uyarısı olmadan gerçek apply testleri pahalı sürprizlere yol açar.

Testleri module'lerin yanında tut ve module'ü değiştiren her pull request'te çalıştır. Mock'lu testler saniyeler sürdüğü için bunu engelleyen bir maliyet yok. Gerçek apply testlerini ise gece ya da yalnız module'ün yeni sürümü etiketlenirken çalıştırmak yeterli olabilir.

Tarayıcıların ve tflint'in sürümlerini de sabitle. Yeni bir sürüm yeni kurallar getirir ve dün geçen bir pull request bugün hiçbir kod değişmeden kırılabilir. Kural setini yükseltmek ayrı ve bilinçli bir değişiklik olmalı.

Sık Düşülen Tuzaklar

Yalnız validate'e güvenmek. Bu yazının başındaki TLS değişikliği ve 7. yazıdaki prod kuralı, validate'ten geçip ancak plan'da ya da testte yakalandı. validate "bu kod çalışabilir" der, "bu kod doğru" demez.

Testlerde gerçek prod subscription'ı ya da prod state'i kullanmak. Gerçek provider'la çalışan bir test kaynak oluşturur ve siler; yukarıda gördüğümüz gibi teardown prevent_destroy'a takılmıyor. Test, prod'un credential'larını hiç görmemeli.

Mock'lu testi entegrasyon testi sanmak. Mock provider her şeyi kabul eder; bir SKU'nun region'da olmaması, isim çakışması, eksik role assignment gibi hatalar ancak gerçek bir apply'da ortaya çıkar.

Küçük Bir Deneme

★☆☆ bookstore-webapp testine, https_only = false yapıldığında başarısız olan bir assertion daha ekle ve testin gerçekten kırıldığını gör.

★★☆ infra/azure/bookstore testine, staging ortamında storage'ın LRS (veriyi tek bir veri merkezinde üç kopya tutan replikasyon) ve veritabanının Basic olduğunu kontrol eden bir run ekle. Hangi mock'ları yeniden kullanabildin?

★★★ Trivy bulgularından kabul ettiklerini .trivyignore dosyasına gerekçesiyle yaz ve taramanın yalnız düzeltilecek bulguları gösterdiğini doğrula. Bir bulguyu kod içinde, kaynağın yanında bir yorumla susturmak mı daha iyi, ayrı bir dosyada tutmak mı?

Bir sonraki yazıda bu kontrolleri GitHub Actions'a taşıyoruz: pull request'te fmt, validate, test ve plan; plan özetinin pull request'e yorum olarak yazılması, OIDC (OpenID Connect; pipeline'ın secret saklamadan kısa ömürlü token'la kimlik doğrulaması) ile secret'sız Azure girişi, onaylı apply ve zamanlanmış drift kontrolü.