Criação de múltiplos objetos utilizando locals e loops
Tags: terraform, sre, devops, hashicorp
Published: 2022-06-01 18:12:02

Se você já se encontrou em uma situação onde teve que gerenciar uma grande quantidade de recursos com os mesmos argumentos, e quando olha para o lado, você está lá editando um arquivo `.tf` com pelo menos 1500 linhas, chato, repetitivo, cheio de recursos iguais, na base do copia-e-cola bem, talvez este artigo seja para você. Nele apresentarei uma forma para escrever um código menor e mais dinâmico com o uso de *locals* e *loops* `for_each` com o objetivo de tornar a nossa vida um pouco melhor. Chega mais!

Vamos pegar o código abaixo como exemplo:

```hcl
resource "aws_sqs_queue" "fila_01_dlq" {
  name = "fila-01-dlq"

  message_retention_seconds = 172800
  delay_seconds = 0
  max_message_size = 256000
  visibility_timeout_seconds = 40
  receive_wait_time_seconds = 20
}

resource "aws_sqs_queue" "fila_01" {
  name = "fila-01"

  message_retention_seconds = 172800
  delay_seconds = 0
  max_message_size = 256000
  visibility_timeout_seconds = 40
  receive_wait_time_seconds = 20

  redrive_policy = jsonencode({
    deadLetterTargetArn = aws_sqs_queue.fila_01_dlq.arn
    maxReceiveCount = 3
  })
}

resource "aws_sqs_queue" "fila_02_dlq" {
  name = "fila-02-dlq"

  message_retention_seconds = 172800
  delay_seconds = 0
  max_message_size = 256000
  visibility_timeout_seconds = 40
  receive_wait_time_seconds = 20
}

resource "aws_sqs_queue" "fila_02" {
  name = "fila-02"

  message_retention_seconds = 172800
  delay_seconds = 0
  max_message_size = 256000
  visibility_timeout_seconds = 40
  receive_wait_time_seconds = 20

  redrive_policy = jsonencode({
    deadLetterTargetArn = aws_sqs_queue.fila_02_dlq.arn
    maxReceiveCount = 3
  })
}

resource "aws_sqs_queue" "fila_03_dlq" {
  name = "fila-03-dlq"

  message_retention_seconds = 172800
  delay_seconds = 0
  max_message_size = 256000
  visibility_timeout_seconds = 40
  receive_wait_time_seconds = 20
}

resource "aws_sqs_queue" "fila_03" {
  name = "fila-03"

  message_retention_seconds = 172800
  delay_seconds = 0
  max_message_size = 256000
  visibility_timeout_seconds = 40
  receive_wait_time_seconds = 20

  redrive_policy = jsonencode({
    deadLetterTargetArn = aws_sqs_queue.fila_03_dlq.arn
    maxReceiveCount = 3
  })
}
```

Nele, nós temos o seguinte:

- São definidas 6 filas SQS, sendo 3 normais e suas respectivas DLQs (*Dead-Letter Queues*), ou seja, filas para onde as mensagens são enviadas caso não processadas;
  
- Os atributos das 3 filas normais são os mesmos, assim como os das filas DLQs. Isso é importante porque nós temos um padrão de recurso para criação de um *loop*;
  
- Temos vários valores de argumentos que se repetem, e nós podemos utilizar para eles variáveis, mantendo o nosso código DRY (*Don't Repeat Yourself*).

Vamos aplicar essas mudanças aos poucos para podermos analisar a melhoria gradual do arquivo. O primeiro desses passos será a substituição dos valores dos argumentos por variáveis:

```hcl
variable "sqs_message_retention_seconds" {
  type = number
  default = 172800
}

variable "sqs_delay_seconds" {
  type = number
  default = 0
}

variable "sqs_max_message_size" {
  type = number
  default = 256000
}

variable "sqs_visibility_timeout_seconds" {
  type = number
  default = 40
}

variable "sqs_receive_wait_time_seconds" {
  type = number
  default = 20
}

variable "sqs_maxReceiveCount" {
  type = number
  default = 3
}

resource "aws_sqs_queue" "fila_01_dlq" {
  name = "fila-01-dlq"

  message_retention_seconds = var.sqs_message_retention_seconds
  defay_seconds = var.sqs_delay_seconds
  max_message_size = var.sqs_max_message_size
  visibility_timeout_seconds = var.sqs_visibility_timeout_seconds
  receive_wait_time_seconds = var.sqs_receive_wait_time_seconds
}

resource "aws_sqs_queue" "fila_01" {
  name = "fila-01"

  message_retention_seconds = var.message_retention_seconds
  defay_seconds = var.sqs_delay_seconds
  max_message_size = var.sqs_max_message_size
  visibility_timeout_seconds = var.sqs_visibility_timeout_seconds
  receive_wait_time_seconds = var.sqs_receive_wait_time_seconds

  redrive_policy = jsonencode({
    deadLetterTargetArn = aws_sqs_queue.fila_01_dlq.arn
    maxReceiveCount = var.sqs_maxReceiveCount
  })
}

resource "aws_sqs_queue" "fila_02_dlq" {
  name = "fila-02-dlq"

  message_retention_seconds = var.message_retention_seconds
  defay_seconds = var.sqs_delay_seconds
  max_message_size = var.sqs_max_message_size
  visibility_timeout_seconds = var.sqs_visibility_timeout_seconds
  receive_wait_time_seconds = var.sqs_receive_wait_time_seconds
}

resource "aws_sqs_queue" "fila_02" {
  name = "fila-02"

  message_retention_seconds = var.message_retention_seconds
  defay_seconds = var.sqs_delay_seconds
  max_message_size = var.sqs_max_message_size
  visibility_timeout_seconds = var.sqs_visibility_timeout_seconds
  receive_wait_time_seconds = var.sqs_receive_wait_time_seconds

  redrive_policy = jsonencode({
    deadLetterTargetArn = aws_sqs_queue.fila_02_dlq.arn
    maxReceiveCount = var.sqs_maxReceiveCount
  })
}

resource "aws_sqs_queue" "fila_03_dlq" {
  name = "fila-03-dlq"

  message_retention_seconds = var.message_retention_seconds
  defay_seconds = var.sqs_delay_seconds
  max_message_size = var.sqs_max_message_size
  visibility_timeout_seconds = var.sqs_visibility_timeout_seconds
  receive_wait_time_seconds = var.sqs_receive_wait_time_seconds
}

resource "aws_sqs_queue" "fila_03" {
  name = "fila-03"

  message_retention_seconds = var.message_retention_seconds
  defay_seconds = var.sqs_delay_seconds
  max_message_size = var.sqs_max_message_size
  visibility_timeout_seconds = var.sqs_visibility_timeout_seconds
  receive_wait_time_seconds = var.sqs_receive_wait_time_seconds

  redrive_policy = jsonencode({
    deadLetterTargetArn = aws_sqs_queue.fila_03_dlq.arn
    maxReceiveCount = var.sqs_maxReceiveCount
  })
}
```

O nosso principal ganho com esta nova versão é que caso desejemos modificar o valor de um argumento, não precisamos fazê-lo em todos os *resources*, mas apenas na variável.

Agora vamos criar um bloco `locals` para armazenar os nossos dados das filas normais e DLQ, assim como a criação dos  *resources* padrão:

```hcl
variable "sqs_message_retention_seconds" {
  type = number
  default = 172800
}

variable "sqs_delay_seconds" {
  type = number
  default = 0
}

variable "sqs_max_message_size" {
  type = number
  default = 256000
}

variable "sqs_visibility_timeout_seconds" {
  type = number
  default = 40
}

variable "sqs_receive_wait_time_seconds" {
  type = number
  default = 20
}

variable "sqs_maxReceiveCount" {
  type = number
  default = 3
}

locals {
  aws_sqs_queues_dlq = {
    fila_01_dlq = {
      name = "fila-01-dlq"
    }
    fila_02_dlq = {
      name = "fila-02-dlq"
    }
    fila_03_dlq = {
      name = "fila-03-dlq"
    }
    fila_01 = {
      name = "fila-01"
      deadLetterTargetArn = aws_sqs_queue.aws_sqs_queues_dlq["fila_01_dlq"].arn
    }
    fila_02 = {
      name = "fila-02"
      deadLetterTargetArn = aws_sqs_queue.aws_sqs_queues_dlq["fila_02_dlq"].arn
    }
    fila_03 = {
      name = "fila-03"
      deadLetterTargetArn = aws_sqs_queue.aws_sqs_queues_dlq["fila_03_dlq"].arn
    }
  }
}

resource "aws_sqs_queue" "aws_sqs_queues_dlq" {
  for_each = local.aws_sqs_queues_dlq
  name = each.value.name

  message_retention_seconds = try(each.value.message_retention_seconds, var.sqs_message_retention_seconds)
  delay_seconds = try(each.value.delay_seconds, var.sqs_delay_seconds)
  max_message_size = try(each.value.delay_seconds, var.sqs_max_message_size)
  visibility_timeout_seconds = try(each.value.visibility_timeout_seconds, var.sqs_visibility_timeout_seconds)
  receive_wait_time_seconds = try(each.value.receive_wait_time_seconds, var.sqs_receive_wait_time_seconds)
}

resource "aws_sqs_queue" "aws_sqs_queues" {
  for_each = local.aws_sqs_queue
  name = each.value.name

  message_retention_seconds = try(each.value.message_retention_seconds, var.sqs_message_retention_seconds)
  delay_seconds = try(each.value.delay_seconds, var.sqs_delay_seconds)
  max_message_size = try(each.value.delay_seconds, var.sqs_max_message_size)
  visibility_timeout_seconds = try(each.value.visibility_timeout_seconds, var.sqs_visibility_timeout_seconds)
  receive_wait_time_seconds = try(each.value.receive_wait_time_seconds, var.sqs_receive_wait_time_seconds)

  redrive_policy = jsonencode({
    deadLetterTargetArn = each.value.deadLetterTargetArn
    maxReceiveCount = try(each.value.maxReceiveCount, var.sqs_maxReceiveCount)
  })
}
```

Pontos sobre esta nova versão:

- Dentro do bloco `locals` foram criados dois *maps*: um para armazenar as nossas filas normais chamado `aws_sqs_queues`, e outro chamado `aws_sqs_queues_dlq` para as filas DLQ. Dentro de cada um estamos definindo os atributos que serão consumidos pelos *resources* (`name`, `deadLetterTargetArn`, etc.). A escolha pelo uso de *locals* sobre variáveis normais dá-se devido ao fato de que diferentemente de linguagens de programação normais, o Terraform não suporta que variáveis tenham como seus valores resultados de expressões, atributos de *resources* ou que os mesmos sejam sobreescritos ao longo da execução do Terraform;
- Em cada um dos dois *resources*, temos um *loop* `for_each` iterando sobre o respectivo *map*. A nossa preocupação aqui é definir um *resource* padrão, onde tentamos resgatar os valores do *map* com o uso da função `try()`, e caso não consigamos, ele tentará pegar o valor da variável.

É possível melhorar ainda mais esta estrutura com a utilização de módulos, abstraindo ainda mais a infraestrutura, mas fica para um próximo artigo.