Terraform Döngüler (Loops)
16 min read

Terraform Döngüler (Loops)

Terraform Döngüler (loops) ile count, for ve for_each kullanımını öğreniyoruz.
Terraform Döngüler (Loops)

Terraform declarative bir dildir ve count,  for_each ve for ifadelerini sağlayarak bir döngü sayesinde işleri daha hızları ve verimli halletmemize olanak sağlar. Bu yazıda her biri farklı bir senaryoda kullanılmak üzere tasarlanmış birkaç farklı döngü yapısını göstereceğiz.

Terraform ile sunulan döngüler nedir görelim:

  • count parametresi, kaynaklar ve modüller üzerinde döngü oluşturmak için kullanılır.
  • for_each ifadeleri, kaynaklar, kaynak içindeki satır içi bloklar ve modüller üzerinde döngü oluşturmak için kullanılır.
  • for ifadesi, listeler ve mapler üzerinde döngü oluşturmak için kullanılır.
  • string directive ile, bir dize içindeki listelerin ve maplerin üzerinde döngü yapmak içi kullanılır.

Şimdi her birine sırasıyla bakalım.

1. count Parametresi İle Döngüler

Daha önceki yazılarımızda, AWS konsolunu ziyaret ederek bir AWS Identity and Access Management (IAM) kullanıcısı oluşturdunuz. Artık bu kullanıcıya sahip olduğunuza göre, Terraform ile gelecekteki tüm IAM kullanıcılarını oluşturabilir ve yönetebilirsiniz. live/global/iam/main.tf'de bulunan aşağıdaki Terraform kodu göz önünde bulundurun:

provider "aws" {
  region = "us-east-2"
}

resource "aws_iam_user" "example" {
  name = "ismet"
}

Bu kod, tek bir yeni IAM kullanıcısı oluşturmak için aws_iam_user kaynağını kullanır. Peki üç yeni IAM kullanıcısı oluşturmak istiyorsanız? Genel amaçlı bir programlama dilinde muhtemelen bir for döngüsü kullanırdınız:

# Bu sadece bir pseudo code. Bu kod Terraform'da çalışmaz.
for (i = 0; i < 3; i++) {
  resource "aws_iam_user" "example" {
    name = "ismet"
  }
}

Terraform, dilde yerleşik for-loop'lara veya diğer geleneksel prosedürel mantığa sahip değildir, bu nedenle bu sözdizimi çalışmayacaktır. Bununla birlikte, her Terraform kaynağının, kullanabileceğiniz count adı verilen bir meta parametresi vardır. count, Terraform'un en eski, en basit ve en sınırlı yineleme yapısıdır. Tek yaptığı, kaynağın kaç kopyasının oluşturulacağını belirlemektir. Üç IAM kullanıcısı oluşturmak için count u şu şekilde kullanabilirsiniz:

resource "aws_iam_user" "example" {
  count = 3
  name  = "ismet"
}

Bu kodla ilgili bir sorun, kullanıcı adlarının unique olması gerektiğinden, üç IAM kullanıcısının hepsinin aynı ada sahip olması ve bunun da bir hataya neden olmasıdır. Standart bir for döngüsüne erişiminiz varsa, her kullanıcıya benzersiz bir ad vermek için for döngüsündeki index'i (i) kullanabilirsiniz:

# Bu sadece bir pseudo code. Bu kod Terraform'da çalışmaz.
for (i = 0; i < 3; i++) {
  resource "aws_iam_user" "example" {
    name = "ismet.${i}"
  }
}

Aynı şeyi Terraform'da gerçekleştirmek ve döngüdeki her "yinelemenin" index'ini almak için count.index'i kullanabilirsiniz:

resource "aws_iam_user" "example" {
  count = 3
  name  = "ismet.${count.index}"
}

plan komutunu önceki kodda çalıştırırsanız, Terraform'un her biri farklı bir ada ("ismet.0", "ismet.1", "ismet.2") sahip üç IAM kullanıcısı oluşturmak istediğini göreceksiniz:

Terraform will perform the following actions:

  # aws_iam_user.example[0] will be created
  + resource "aws_iam_user" "example" {
      + name          = "ismet.0"
      (...)
    }

  # aws_iam_user.example[1] will be created
  + resource "aws_iam_user" "example" {
      + name          = "ismet.1"
      (...)
    }

  # aws_iam_user.example[2] will be created
  + resource "aws_iam_user" "example" {
      + name          = "ismet.2"
      (...)
    }

Plan: 3 to add, 0 to change, 0 to destroy.

Tabii ki, "ismet.0" gibi bir kullanıcı adı özellikle kullanılamaz. count.index'i Terraform'daki bazı yerleşik işlevlerle birleştirirseniz, "döngünün" her "yinelemesini" daha da özelleştirebilirsiniz.

Örneğin, live/global/iam/variables.tf'deki bir girdi değişkeninde istediğiniz tüm IAM kullanıcı adlarını tanımlayabilirsiniz:

variable "user_names" {
  description = "Create IAM users with these names"
  type        = list(string)
  default     = ["ismet", "ozlem", "furkan"]
}

Döngüler ve diziler içeren genel amaçlı bir programlama dili kullanıyorsanız, var.user_names dizisinde i indexini arayarak her IAM kullanıcısını farklı bir ad kullanacak şekilde yapılandırırsınız:

# Bu sadece bir pseudo code. Bu kod Terraform'da çalışmaz.
for (i = 0; i < 3; i++) {
  resource "aws_iam_user" "example" {
    name = vars.user_names[i]
  }
}

Terraform'da aynı şeyi, aşağıdakilerle birlikte count kullanarak gerçekleştirebilirsiniz:

resource "aws_iam_user" "example" {
  count = length(var.user_names)
  name  = var.user_names[count.index]
}

Bu kodda length(<ARRAY>) liste boyutunu verirken, ARRAY[<INDEX>] ilgili index'teki iteme ulaşmamızı sağlar.

Şimdi plan komutunu çalıştırdığınızda, Terraform'un her biri benzersiz, okunabilir bir ada sahip üç IAM kullanıcısı oluşturmak istediğini göreceksiniz:

Terraform will perform the following actions:

  # aws_iam_user.example[0] will be created
  + resource "aws_iam_user" "example" {
      + name          = "ismet"
      (...)
    }

  # aws_iam_user.example[1] will be created
  + resource "aws_iam_user" "example" {
      + name          = "ozlem"
      (...)
    }

  # aws_iam_user.example[2] will be created
  + resource "aws_iam_user" "example" {
      + name          = "furkan"
      (...)
    }

Plan: 3 to add, 0 to change, 0 to destroy.

Bir kaynakta count kullandıktan sonra, bunun tek bir kaynaktan ziyade bir dizi kaynak haline geldiğini unutmayın. aws_iam_user.example artık bir IAM kullanıcısı dizisi olduğundan, o kaynaktan (<PROVIDER>_<TYPE>.<NAME>.<ATTRIBUTE>) bir özniteliği okumak için standart sözdizimini kullanmak yerine, hangi IAM kullanıcısını kullanacağınızı belirtmelisiniz.

<PROVIDER>_<TYPE>.<NAME>[INDEX].ATTRIBUTE

Örneğin, listedeki ilk IAM kullanıcısının Amazon Resource Name (ARN) sini çıkış değişkeni olarak sağlamak istiyorsanız aşağıdakileri yapmanız gerekir:

output "first_arn" {
  value       = aws_iam_user.example[0].arn
  description = "ilk kullanıcının ARN bilgileri"
}

Tüm IAM kullanıcılarının ARN'lerini istiyorsanız, dizin yerine "*" uyarı ifadesini kullanmanız gerekir:

output "all_arns" {
  value       = aws_iam_user.example[*].arn
  description = "Tüm kullanıcıların ARN bilgileri"
}

apply komutunu çalıştırdığınızda, first_arn çıktısı yalnızca ismet için ARN'yi içerecek, all_arns çıktısı ise tüm ARN'lerin listesini içerecektir:

$ terraform apply

(...)

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Outputs:

first_arn = "arn:aws:iam::123456789012:ismet/neo"
all_arns = [
  "arn:aws:iam::123456789012:user/ismet",
  "arn:aws:iam::123456789012:user/ozlem",
  "arn:aws:iam::123456789012:user/furkan",
]

Terraform 0.13'ten itibaren count parametresi modüllerde de kullanılabilir. Örneğin, tek bir IAM kullanıcısı oluşturabilen module/landing-zone/iam-user/main.tf adresinde bir modülünüz olduğunu hayal edin:

resource "aws_iam_user" "example" {
  name = var.user_name
}

Kullanıcı adı bu modüle bir giriş değişkeni olarak iletilir:

variable "user_name" {
  description = "The user name to use"
  type        = string
}

Ve modül, oluşturulan IAM kullanıcısının ARN'sini bir çıktı değişkeni olarak döndürür:

output "user_arn" {
  value       = aws_iam_user.example.arn
  description = "The ARN of the created IAM user"
}

Bu modülü, aşağıdaki gibi üç IAM kullanıcısı oluşturmak için bir count parametresiyle kullanabilirsiniz:

module "users" {
  source = "../../../modules/landing-zone/iam-user"

  count     = length(var.user_names)
  user_name = var.user_names[count.index]
}

Yukarıdaki kod, bu kullanıcı adları listesi üzerinde dolaşmak için count kullanır:

variable "user_names" {
  description = "Create IAM users with these names"
  type        = list(string)
  default     = ["ismet", "ozlem", "furkan"]
}

Ve oluşturulan IAM kullanıcılarının ARN'lerini aşağıdaki gibi çıkarır:

output "user_arns" {
  value       = module.users[*].user_arn
  description = "The ARNs of the created IAM users"
}

Bir kaynağa sayı eklemek onu bir dizi kaynak haline getirirken, bir modüle sayı eklemek onu bir dizi modüle dönüştürür.

Bu kodda apply komutunu çalıştırırsanız, aşağıdaki çıktıyı alırsınız:

$ terraform apply

(...)

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Outputs:

all_arns = [
  "arn:aws:iam::123456789012:user/ismet",
  "arn:aws:iam::123456789012:user/ozlem",
  "arn:aws:iam::123456789012:user/furkan",
]

Gördüğünüz gibi, count, kaynaklarla ve modüllerle aşağı yukarı aynı şekilde çalışır.

Ne yazık ki, count kullanışlılığını önemli ölçüde azaltan iki sınırlaması vardır. İlk olarak, bir kaynağın tamamı üzerinde döngü yapmak için count kullanabilseniz de, satır içi bloklar üzerinde döngü yapmak için bir kaynak içinde count kullanamazsınız. Örneğin, aws_autoscaling_group kaynağında etiketlerin nasıl ayarlandığını düşünün:

resource "aws_autoscaling_group" "example" {
  launch_configuration = aws_launch_configuration.example.name
  vpc_zone_identifier  = data.aws_subnets.default.ids
  target_group_arns    = [aws_lb_target_group.asg.arn]
  health_check_type    = "ELB"

  min_size = var.min_size
  max_size = var.max_size

  tag {
    key                 = "Name"
    value               = var.cluster_name
    propagate_at_launch = true
  }
}

Her etiket; key, value ve propagate_at_launch değerlerine sahip yeni bir satır içi blok oluşturmanızı gerektirir. Önceki kod, tek bir etiketi sabit kodlar, ancak kullanıcıların özel etiketler geçmesine izin vermek isteyebilirsiniz. Bu etiketler arasında döngü yapmak ve dinamik satır içi etiket blokları oluşturmak için count parametresini kullanmayı denemek isteyebilirsiniz, ancak ne yazık ki bir satır içi blok içinde count kullanılması desteklenmez.

count ile ilgili ikinci sınırlama, onu değiştirmeye çalıştığınızda olan şeydir. Daha önce oluşturduğunuz IAM kullanıcılarının listesini düşünün:

variable "user_names" {
  description = "Create IAM users with these names"
  type        = list(string)
  default     = ["ismet", "ozlem", "furkan"]
}

Bu listeden "ozlem" ı kaldırdığınızı hayal edin. Terraform planını çalıştırdığınızda ne olur?

$ terraform plan

(...)

Terraform will perform the following actions:

  # aws_iam_user.example[1] will be updated in-place
  ~ resource "aws_iam_user" "example" {
        id            = "ozlem"
      ~ name          = "ozlem" -> "furkan"
    }

  # aws_iam_user.example[2] will be destroyed
  - resource "aws_iam_user" "example" {
      - id            = "furkan" -> null
      - name          = "furkan" -> null
    }

Plan: 0 to add, 1 to change, 1 to destroy.

Bir saniye, muhtemelen beklediğiniz bu değildi! Plan çıktısı, yalnızca "ozlem" IAM kullanıcısını silmek yerine, Terraform'un "ozlem" IAM kullanıcısını "furkan" olarak yeniden adlandırmak ve orijinal "furkan" kullanıcısını silmek istediğini gösteriyor. Neler oluyor?

Bir kaynakta count parametresini kullandığınızda, o kaynak bir dizi kaynak haline gelir. Ne yazık ki, Terraform'un dizi içindeki her bir kaynağı tanımlama şekli, o dizideki konumu (indeks) ile olur. Yani, üç kullanıcı adıyla ilk kez apply çalıştırıldıktan sonra, Terraform'un bu IAM kullanıcılarının dahili temsili şuna benzer:

aws_iam_user.example[0]: ismet
aws_iam_user.example[1]: ozlem
aws_iam_user.example[2]: furkan

Bir dizinin ortasından bir öğeyi kaldırdığınızda, ondan sonraki tüm öğeler birer birer geri kayar, bu nedenle planı yalnızca iki kullanıcı adıyla çalıştırdıktan sonra, Terraform'un dahili temsili şöyle görünecektir:

aws_iam_user.example[0]: ismet
aws_iam_user.example[2]: furkan

Furkan'ın index 2'den index 1'e nasıl geçtiğine dikkat edin. Index'i Terraform'a bir kaynağın kimliği olarak gördüğünden, bu değişiklik kabaca "index 1'deki kullanıcı adını furkan olarak yeniden adlandırın ve index 2'deki kullanıcıyı silin" anlamına gelir. Başka bir deyişle, bir kaynak listesi oluşturmak için sayımı her kullandığınızda, listenin ortasından bir öğeyi kaldırırsanız, Terraform o öğeden sonraki tüm kaynakları silecek ve ardından bu kaynakları yeniden sıfırdan oluşturacaktır. Sonuç, elbette, tam olarak istediğiniz şeydir (yani, furkan ve ismet adlı iki IAM kullanıcısı), ancak kaynakları silmek ve değiştirmek muhtemelen oraya ulaşmak istediğiniz gibi değildir.

Bu iki sınırlamayı çözmek için for_each kullanacağız.

2. for_each Parametresi İle Döngüler

for_each ifadesi, (a) tüm kaynakların birden çok kopyasını, (b) bir kaynak içindeki satır içi bloğun birden çok kopyasını veya (c) bir modülün birden çok kopyasını oluşturmak için listeler, kümeler ve mapler arasında döngü oluşturmanıza olanak tanır. Önce bir kaynağın birden çok kopyasını oluşturmak için for_each nasıl kullanılacağını inceleyelim. Sözdizimi şöyle görünür:

resource "<PROVIDER>_<TYPE>" "<NAME>" {
  for_each = <COLLECTION>

  [CONFIG ...]
}
  • PROVIDER bir sağlayıcının adıdır (ör. aws),
  • TYPE o sağlayıcıda oluşturulacak kaynak türüdür (ör. instance),
  • NAME, bu kaynağa atıfta bulunmak için Terraform kodu boyunca kullanabileceğiniz bir tanımlayıcıdır (ör. , my_instance),
  • COLLECTION, döngüye alınacak bir küme veya maptir (bir kaynakta for_each kullanılırken listeler desteklenmez!)
  • CONFIG, o kaynağa özgü bir veya daha fazla bağımsız değişkenden oluşur. CONFIG içinde, COLLECTION'daki geçerli öğenin anahtarına ve değerine erişmek için each.key ve each.value kullanabilirsiniz.

Örneğin, bir kaynakta for_each kullanarak aynı üç IAM kullanıcısını şu şekilde oluşturabilirsiniz:

resource "aws_iam_user" "example" {
  for_each = toset(var.user_names)
  name     = each.value
}

var.user_names listesini bir kümeye dönüştürmek için toset kullanımına dikkat edin. Bunun nedeni, for_each'in yalnızca bir kaynakta kullanıldığında kümeleri ve mapleri desteklemesidir. for_each bu küme üzerinde döngü yaptığında, her bir kullanıcı adının each.value ile kullanılabilir olmasını sağlar.

Bir kaynakta for_each kullandığınızda, yalnızca bir kaynak (veya count daki gibi bir dizi kaynak) yerine bir kaynak mapi haline gelir. Bunun ne anlama geldiğini görmek için orijinal all_arns ve first_arn çıktı değişkenlerini kaldırın ve yeni bir all_users çıktı değişkeni ekleyin:

output "all_users" {
  value = aws_iam_user.example
}

terraform apply'ı çalıştırdığınızda şunlar olur:

$ terraform apply

(...)

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Outputs:

all_users = {
  "morpheus" = {
    "arn" = "arn:aws:iam::123456789012:user/morpheus"
    "force_destroy" = false
    "id" = "morpheus"
    "name" = "morpheus"
    "path" = "/"
    "tags" = {}
  }
  "neo" = {
    "arn" = "arn:aws:iam::123456789012:user/neo"
    "force_destroy" = false
    "id" = "neo"
    "name" = "neo"
    "path" = "/"
    "tags" = {}
  }
  "trinity" = {
    "arn" = "arn:aws:iam::123456789012:user/trinity"
    "force_destroy" = false
    "id" = "trinity"
    "name" = "trinity"
    "path" = "/"
    "tags" = {}
  }
}

Terraform'un üç IAM kullanıcısı oluşturduğunu ve all_users çıktı değişkeninin, anahtarların for_each içindeki anahtarlar (bu durumda kullanıcı adları) olduğu ve değerlerin tümünün o kaynağın çıktıları olduğu bir harita içerdiğini görebilirsiniz. all_arns çıktı değişkenini geri getirmek istiyorsanız, values metodunu (sadece bir mapten değerleri döndürür) ve bir uyarı ifadesini kullanarak bu ARN'leri çıkarmak için biraz fazladan çalışma yapmanız gerekir:

output "all_arns" {
  value = values(aws_iam_user.example)[*].arn
}

Bu size beklenen çıktıyı verir:

$ terraform apply

(...)

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

all_arns = [
  "arn:aws:iam::123456789012:user/ismet",
  "arn:aws:iam::123456789012:user/ozlem",
  "arn:aws:iam::123456789012:user/furkan",
]

Artık, count ile olduğu gibi bir dizi kaynak yerine for_each ile bir kaynaklar mapine sahip olmanız büyük bir yetenek çünkü bu, öğeleri bir koleksiyonun ortasından güvenli bir şekilde kaldırmanıza olanak tanıyor. Örneğin, var.user_names listesinin ortasından "ozlem" i tekrar kaldırır ve terraform plan ı çalıştırırsanız, şunları göreceksiniz:

$ terraform plan

Terraform will perform the following actions:

  # aws_iam_user.example["trinity"] will be destroyed
  - resource "aws_iam_user" "example" {
      - arn           = "arn:aws:iam::123456789012:user/ozlem" -> null
      - name          = "ozlem" -> null
    }

Plan: 0 to add, 0 to change, 1 to destroy.

Bu daha doğru gibi! Artık diğerlerini kaydırmadan yalnızca tam olarak istediğiniz kaynağı siliyorsunuz. Bu nedenle, bir kaynağın birden çok kopyasını oluşturmak için hemen hemen her zaman için count yerine for_each kullanmayı tercih etmelisiniz.

for_each, modüllerle aşağı yukarı aynı şekilde çalışır. Daha önceki iam-user modülünü kullanarak, for_each kullanarak şu şekilde üç IAM kullanıcısı oluşturabilirsiniz:

module "users" {
  source = "../../../modules/landing-zone/iam-user"

  for_each  = toset(var.user_names)
  user_name = each.key
}

Ve bu kullanıcıların ARN'lerinin çıktısını aşağıdaki gibi verebilirsiniz:

output "user_arns" {
  value       = values(module.users)[*].user_arn
  description = "The ARNs of the created IAM users"
}

Bu kodda apply çalıştırdığınızda, beklenen çıktıyı alırsınız:

$ terraform apply

(...)

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Outputs:

all_arns = [
  "arn:aws:iam::123456789012:user/ismet",
  "arn:aws:iam::123456789012:user/ozlem",
  "arn:aws:iam::123456789012:user/furkan",
]

Şimdi dikkatimizi for_each'in bir başka avantajına çevirelim: Bir kaynak içinde birden çok satır içi blok oluşturma yeteneği. Örneğin, web sunucusu küme modülünde ASG için dinamik olarak etiket satır içi blokları oluşturmak için for_each kullanabilirsiniz. İlk olarak, kullanıcıların özel etiketler belirlemesine izin vermek için, modules/services/webserver-cluster/variables.tf'ye custom_tags adlı yeni bir map değişkeni ekleyin:

variable "custom_tags" {
  description = "Custom tags to set on the Instances in the ASG"
  type        = map(string)
  default     = {}
}

Ardından, production ortamında live/prod/services/webserver-cluster/main.tf'de aşağıdaki gibi bazı özel etiketler ayarlayın:

module "webserver_cluster" {
  source = "../../../../modules/services/webserver-cluster"

  cluster_name           = "webservers-prod"
  db_remote_state_bucket = "(YOUR_BUCKET_NAME)"
  db_remote_state_key    = "prod/data-stores/mysql/terraform.tfstate"

  instance_type        = "m4.large"
  min_size             = 2
  max_size             = 10

  custom_tags = {
    Owner     = "team-foo"
    ManagedBy = "terraform"
  }
}

Önceki kod, birkaç faydalı etiket belirler: Owner etiketi, bu ASG'ye hangi takımın sahip olduğunu belirtir ve ManagedBy etiketi, bu altyapının Terraform kullanılarak yönetildiğini belirtir. Ekibiniz için bir etiketleme standardı bulmak ve bu standardı kod olarak uygulayan Terraform modülleri oluşturmak genellikle iyi bir fikirdir.

Artık etiketlerinizi belirlediğinize göre, onları aws_autoscaling_group kaynağında gerçekte nasıl ayarlarsınız? İhtiyacınız olan şey, aşağıdaki pseudo koda benzer şekilde, var.custom_tags üzerinde bir for döngüsüdür:

resource "aws_autoscaling_group" "example" {
  launch_configuration = aws_launch_configuration.example.name
  vpc_zone_identifier  = data.aws_subnets.default.ids
  target_group_arns    = [aws_lb_target_group.asg.arn]
  health_check_type    = "ELB"

  min_size = var.min_size
  max_size = var.max_size

  tag {
    key                 = "Name"
    value               = var.cluster_name
    propagate_at_launch = true
  }

  # Bu sadece bir pseudo code. Bu kod Terraform'da çalışmaz.
  for (tag in var.custom_tags) {
    tag {
      key                 = tag.key
      value               = tag.value
      propagate_at_launch = true
    }
  }
}

Önceki pseudo kod çalışmayacak, ancak bir for_each ifadesi çalışacaktır. Dinamik olarak satır içi bloklar oluşturmak için for_each kullanmanın sözdizimi şöyle görünür:

dynamic "<VAR_NAME>" {
  for_each = <COLLECTION>

  content {
    [CONFIG...]
  }
}
  • VAR_NAME, her bir "yinelemenin" değerini depolayacak değişken için kullanılacak addır (each yerine),
  • COLLECTION, yinelenecek bir liste veya maptir ve content bloğu, her yinelemeden ne üretileceğidir. COLLECTION'daki geçerli öğenin sırasıyla anahtarına ve değerine erişmek için içerik bloğu içinde <VAR_NAME>.key ve <VAR_NAME>.value kullanabilirsiniz.

Bir listeyle for_each kullandığınızda, anahtarın index olacağını ve değerin o dizindeki listedeki öğe olacağını ve bir map ile for_each kullanırken, anahtar ve değerin mapteki key/value çifti olacağını unutmayın.

Bunların hepsini bir araya getirerek, aws_autoscaling_group kaynağında for_each kullanarak dinamik olarak etiket bloklarını nasıl oluşturabileceğiniz aşağıda açıklanmıştır:

resource "aws_autoscaling_group" "example" {
  launch_configuration = aws_launch_configuration.example.name
  vpc_zone_identifier  = data.aws_subnets.default.ids
  target_group_arns    = [aws_lb_target_group.asg.arn]
  health_check_type    = "ELB"

  min_size = var.min_size
  max_size = var.max_size

  tag {
    key                 = "Name"
    value               = var.cluster_name
    propagate_at_launch = true
  }

  dynamic "tag" {
    for_each = var.custom_tags

    content {
      key                 = tag.key
      value               = tag.value
      propagate_at_launch = true
    }
  }
}

Şimdi terraform plan çalıştırırsanız, şuna benzeyen bir plan görmelisiniz:

$ terraform plan

Terraform will perform the following actions:

  # aws_autoscaling_group.example will be updated in-place
  ~ resource "aws_autoscaling_group" "example" {
        (...)

        tag {
            key                 = "Name"
            propagate_at_launch = true
            value               = "webservers-prod"
        }
      + tag {
          + key                 = "Owner"
          + propagate_at_launch = true
          + value               = "team-foo"
        }
      + tag {
          + key                 = "ManagedBy"
          + propagate_at_launch = true
          + value               = "terraform"
        }
    }

Plan: 0 to add, 1 to change, 0 to destroy.

3. for İle Döngüler

Artık kaynaklar ve satır içi bloklar üzerinde nasıl döngü oluşturulacağını gördünüz. Peki ya tek bir değer oluşturmak için bir döngüye ihtiyacınız varsa? Web sunucusu kümesiyle ilgisi olmayan bazı örneklere kısaca göz atalım. Bir isim listesi alan bir Terraform kodu yazdığınızı hayal edin:

variable "names" {
  description = "A list of names"
  type        = list(string)
  default     = ["ismet", "ozlem", "furkan"]
}

Tüm bu isimleri büyük harfe nasıl çevirebilirsiniz? Python gibi genel amaçlı bir programlama dilinde aşağıdaki for döngüsünü yazabilirsiniz:

names = ["ismet", "ozlem", "furkan"]

upper_case_names = []
for name in names:
    upper_case_names.append(name.upper())

print upper_case_names

# Çıktı: ['ISMET', 'OZLEM', 'FURKAN']

Python, list comprehension olarak bilinen bir sözdizimi kullanarak aynı kodu bir satırda yazmanın başka bir yolunu sunar:

names = ["ismet", "ozlem", "furkan"]

upper_case_names = [name.upper() for name in names]

print upper_case_names

# Çıktı: ['ISMET', 'OZLEM', 'FURKAN']

Python ayrıca bir koşul belirleyerek ortaya çıkan listeyi filtrelemenize de olanak tanır:

names = ["ismet", "ozlem", "furkan"]

short_upper_case_names = [name.upper() for name in names if len(name) < 6]

print short_upper_case_names

# Çıktı: ['ISMET', 'OZLEM']

Terraform, for expression biçiminde benzer işlevsellik sunar (önceki bölümde gördüğünüz for_each expression ile karıştırılmamalıdır). Bir for ifadesinin temel sözdizimi şöyledir:

[for <ITEM> in <LIST> : <OUTPUT>]

LIST, döngü yapılacak bir liste olduğunda, ITEM, LIST içindeki her öğeye atanacak yerel değişken adıdır ve OUTPUT, ITEM'i bir şekilde dönüştüren bir ifadedir. Örneğin, var.names içindeki ad listesini büyük harfe dönüştürmek için Terraform kodu şu şekildedir:

variable "names" {
  description = "A list of names"
  type        = list(string)
  default     = ["ismet", "ozlem", "furkan"]
}

output "upper_names" {
  value = [for name in var.names : upper(name)]
}

Bu kodda terraform apply komutunu çalıştırırsanız, aşağıdaki çıktıyı alırsınız:

$ terraform apply

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

upper_names = [
  "ISMET",
  "OZLEM",
  "FURKAN",
]

Python'un list comprehension ında olduğu gibi, bir koşul belirterek ortaya çıkan listeyi filtreleyebilirsiniz:

variable "names" {
  description = "A list of names"
  type        = list(string)
  default     = ["ismet", "ozlem", "furkan"]
}

output "short_upper_names" {
  value = [for name in var.names : upper(name) if length(name) < 6]
}

Bu kodda terraform apply çalıştırmak size şunu verir:

short_upper_names = [
  "ISMET",
  "OZLEM"
]

Terraform'un for ifadesi, aşağıdaki sözdizimini kullanarak bir map üzerinde döngü oluşturmanıza da olanak tanır:

[for <KEY>, <VALUE> in <MAP> : <OUTPUT>]

Burada MAP, döngüye alınacak bir map, KEY ve VALUE, MAP'taki her bir anahtar/değer çiftine atanacak yerel değişken adlarıdır ve OUTPUT, KEY ve VALUE'u bir şekilde dönüştüren bir ifadedir. İşte bir örnek:

variable "hero_thousand_faces" {
  description = "map"
  type        = map(string)
  default     = {
    ismet     = "mühendis"
    ozlem     = "öğretmen"
    furkan    = "asker"
  }
}

output "bios" {
  value = [for name, role in var.hero_thousand_faces : "${name} bir ${role}"]
}

Bu kodda terraform apply komutunu çalıştırdığınızda, aşağıdakileri alırsınız:

bios = [
  "ismet bir mühendis",
  "ozlem bir öğretmen",
  "furkan bir asker",
]

Aşağıdaki sözdizimini kullanarak bir liste yerine bir map çıktısı almak için for ifadeleri de kullanabilirsiniz:

# Loop over a list and output a map
{for <ITEM> in <LIST> : <OUTPUT_KEY> => <OUTPUT_VALUE>}

# Loop over a map and output a map
{for <KEY>, <VALUE> in <MAP> : <OUTPUT_KEY> => <OUTPUT_VALUE>}

Tek fark, (a) ifadeyi köşeli parantezler yerine küme parantezlerine sarmanız ve (b) her yinelemede tek bir değer vermek yerine, bir okla ayrılmış bir anahtar ve değer çıktısı almanızdır. Örneğin, tüm anahtarları ve değerleri büyük harf yapmak için bir mapi nasıl dönüştürebileceğiniz aşağıda açıklanmıştır:

variable "hero_thousand_faces" {
  description = "map"
  type        = map(string)
  default     = {
    ismet     = "mühendis"
    ozlem     = "öğretmen"
    furkan    = "asker"
  }
}

output "upper_roles" {
  value = {for name, role in var.hero_thousand_faces : upper(name) => upper(role)}
}

İşte bu kodu çalıştırmanın çıktısı:

upper_roles = {
  "ISMET" = "MÜHENDİS"
  "OZLEM" = "ÖĞRETMEN"
  "FURKAN" = "ASKER"
}

4. String Directive İle Döngüler

Daha önceki yazılarımızda, String içinde Terraform koduna başvurmanıza izin veren dize enterpolasyonlarını öğrendiniz:

"Merhaba, ${var.name}"

String directive, string enterpolasyonlarına benzer bir sözdizimi kullanarak string içinde kontrol ifadeleri (örneğin, döngüler ve if ifadeleri) kullanmanıza izin verir, ancak dolar işareti (${…}) yerine yüzde işareti kullanırsınız (%{…}).

Terraform, iki tür string directive destekler: döngüler ve koşul yönergeleri. Bu bölümde, for döngülerini inceleyeceğiz; Koşullara bir sonraki yazımızda geri döneceğiz. for string directive aşağıdaki sözdizimini kullanır:

%{ for <ITEM> in <COLLECTION> }<BODY>%{ endfor }

COLLECTION, döngüye alınacak bir liste veya map olduğunda, ITEM, COLLECTION'daki her öğeye atanacak yerel değişken adıdır ve BODY, her yinelemenin (ITEM'e başvurabilir) oluşturulacağı şeydir. İşte bir örnek:

variable "names" {
  description = "Names to render"
  type        = list(string)
  default     = ["ismet", "ozlem", "furkan"]
}

output "for_directive" {
  value = "%{ for name in var.names }${name}, %{ endfor }"
}

terraform apply çalıştırdığınızda, aşağıdaki çıktıyı alırsınız:

$ terraform apply

(...)

Outputs:

for_directive = "ismet, ozlem, furkan, "

Ayrıca, for döngüsünde size indexi veren for string directive sözdiziminin bir sürümü de vardır:

%{ for <INDEX>, <ITEM> in <COLLECTION> }<BODY>%{ endfor }

İşte indexi kullanan bir örnek:

variable "names" {
  description = "Names to render"
  type        = list(string)
  default     = ["ismet", "ozlem", "furkan"]
}

output "for_directive_index" {
  value = "%{ for i, name in var.names }(${i}) ${name}, %{ endfor }"
}

terraform apply çalıştırdığınızda, aşağıdaki çıktıyı alırsınız:

$ terraform apply

(...)

Outputs:

for_directive = "(0) ismet, (1) ozlem, (2) furkan, "

Her iki çıktıda da fazladan bir virgül ve boşluk olduğuna dikkat edin. Bunu, bir sonraki yazımızda açıklandığı gibi koşullu ifadeleri, özellikle if string directive kullanarak düzeltebilirsiniz.