重複コードは極力減らしつつ複数のECSサービスをTerraformの管理下としたい

## はじめに
前提として、AWSマネジメントコンソール上で作成した各種ECSサービスを
Terraform管理下とするために実施した内容となります。
今回はTerraform importを活用していきます。
また、コード記述時にfor_eachを活用することで重複コードも極力減らしていきます。
※key名、各種名称および定義の詳細は本来とは違う内容を記載してます。

## Terraform importとは
Terraformで既存のインフラストラクチャを管理するための機能です。 この機能を使用すると、既存のインフラストラクチャをTerraformの管理下に置くことができます。

## tfファイルに定義を記載する
各種サービスをimportするために定義を配列で記載します。
importした定義を紐づけるため、最低限のservice_nameは記載する。
その後resourceを定義し、先ほどの配列をループ処理するためfor_eachを記載します。
今回は例として4つのサービスをimportしていきます。

```terraform:aws_ecs_service_fargate.tf
# locals:
locals {
    service_map_fargate = [
        {
            service_name                    = "test-development"
        },
        {
            service_name                    = "test-scheduler-development"
        },
        {
            service_name                    = "test-api-development"
        },
        {
            service_name                    = "test-api-queue-development"
        },
    ]
}

resource "aws_ecs_service" "ecs-test-fargate" {
    for_each                           = { for i in local.service_map_fargate : i.service_name => i }
}
```

上記定義が完了したら、importコマンドを実行します。import時も配列のように記載します。

```terminal
terraform import 'aws_ecs_service.ecs-test-fargate["test-development"]' [クラスター名/サービス名]
terraform import 'aws_ecs_service.ecs-test-fargate["test-scheduler-development"]' [クラスター名/サービス名]
terraform import 'aws_ecs_service.ecs-test-fargate["test-api-development"]' [クラスター名/サービス名]
terraform import 'aws_ecs_service.ecs-test-fargate["test-api-queue-development"]' [クラスター名/サービス名]
```

importが成功しているか確認するためにterraform state listコマンドを実行します。
importした定義が表示されれば成功しています。
```terminal
terraform state list
aws_ecs_service.ecs-test-fargate["test-development"]
aws_ecs_service.ecs-test-fargate["test-scheduler-development"]
aws_ecs_service.ecs-test-fargate["test-api-development"]
aws_ecs_service.ecs-test-fargate["test-api-queue-development"]
```

次にimportした定義に対してterraform state showをすることで詳細情報を取得します。
例としてtest-developmentの情報を取得しresource上に追記しています。
```terminal
terraform state show 'aws_ecs_service.ecs-test-fargate["test-development"]'
```

```terraform:aws_ecs_service_fargate.tf
resource "aws_ecs_service" "ecs-test-fargate" {
    for_each                           = { for i in local.service_map_fargate : i.service_name => i }
    cluster                            = "test-cluster"
    deployment_maximum_percent         = 200
    deployment_minimum_healthy_percent = 100
    desired_count                      = 1
    enable_ecs_managed_tags            = true
    enable_execute_command             = true
    health_check_grace_period_seconds  = 0
    name                               = "test-development"
    platform_version                   = "LATEST"
    propagate_tags                     = "NONE"
    scheduling_strategy                = "REPLICA"
    tags                               = {}
    task_definition                    = "test-development-fargate:1"
    triggers                           = {}

    capacity_provider_strategy {
        base              = 0
        capacity_provider = "FARGATE_SPOT"
        weight            = 1
    }

    deployment_circuit_breaker {
        enable   = true
        rollback = true
    }

    deployment_controller {
        type = "ECS"
    }

    load_balancer {
        container_name   = "web"
        container_port   = 8080
        target_group_arn = "target_group.test-fargate.arn"
    }

    network_configuration {
        assign_public_ip = false
        security_groups  = [
            "data.security_groups.test.id",
        ]
        subnets          = [
            "data.subnet.1atest.id",
            "data.subnet.1ctest.id",
            "data.subnet.1dtest.id",
        ]
    }
}
```

サービス毎に差分がある項目に関して、詳細情報を確認しながらservice_map_fargateに定義し、resourceも合わせて変更します。
※細かい文法などに関しましては本題とは関係ないため割愛します。以下一例となります。
```terraform:aws_ecs_service_fargate.tf
locals {
    service_map_fargate = [
        {
            service_name                    = "test-development"
            task_name                       = "test-development-fargate"
            enable_execute_command          = true
            container_name                  = "web"
            container_port_lb               = 8080
            target_group_arn                = target_group.test-fargate.arn
        },
        {
            service_name                    = "test-scheduler-development"
            task_name                       = "test-scheduler-development-fargate"
            enable_execute_command          = true
        },
        {
            service_name                    = "test-api-development"
            task_name                       = "test-api-development-fargate"
            enable_execute_command          = true
            container_name                  = "web"
            container_port_lb               = 80
            target_group_arn                = target_group.test-api-fargate.arn
        },
        {
            service_name                    = "test-api-queue-development"
            task_name                       = "test-api-queue-development-fargate"
            enable_execute_command          = false
        },
    ]
}

data "aws_ecs_task_definition" "ecs-test-fargate" {
    for_each                           = { for i in local.service_map_fargate : i.service_name => i }
    task_definition = each.value
}

resource "aws_ecs_service" "ecs-test-fargate" {
    for_each                           = { for i in local.ecs_service_map_fargate : i.service_name => i }
    cluster                            = "test-cluster"
    deployment_maximum_percent         = 200
    deployment_minimum_healthy_percent = 100
    desired_count                      = 1
    enable_ecs_managed_tags            = true
    enable_execute_command             = lookup(each.value, "enable_execute_command", false)
    health_check_grace_period_seconds  = 0
    name                               = each.value.service_name
    platform_version                   = "LATEST"
    propagate_tags                     = "NONE"
    scheduling_strategy                = "REPLICA"
    tags                               = {}
    task_definition                    = data.aws_ecs_task_definition.ecs-test-fargate[each.value.task_name].arn
    triggers                           = {}

    capacity_provider_strategy {
        base              = 0
        capacity_provider = "FARGATE_SPOT"
        weight            = 1
    }

    deployment_circuit_breaker {
        enable   = true
        rollback = true
    }

    deployment_controller {
        type = "ECS"
    }

    dynamic "load_balancer" {
        for_each = can(each.value.target_group_arn) ? [1] : []
        content {
            container_name   = each.value.container_name
            container_port   = each.value.container_port_lb
            target_group_arn = each.value.target_group_arn
        }
    }

    network_configuration {
        assign_public_ip = false
        security_groups  = [
            "data.security_groups.test.id",
        ]
        subnets          = [
            "data.subnet.1atest.id",
            "data.subnet.1ctest.id",
            "data.subnet.1dtest.id",
        ]
    }
    # 以下省略
}
```

後はterraform planを実行し、構成に問題がないか確認しましょう。
差分がある場合は確認して修正が必要です。
差分がないようでしたらterraform applyで反映して完了です。
以降に修正が必要な場合は、Terraform上から修正するようにしましょう。

## まとめ
全体のコード量が減ると可読性が向上するため管理しやすいと感じました。
また、保守なども考えるとコード化できるメリットは大きいと思います。
今回は一例を紹介しましたがTerraform1.5からはimportブロックなども活用できるかと思います。