flatten の応用 — ネスト構造の実践パターン

1. 概要

  • flattenの基本的な動作
  • ネストされたリストの展開パターン
  • for式との組み合わせ
  • 実際のユースケース(複数VPCのサブネット管理など)

flatten(list_of_lists)はネストされたリストを1次元のリストに変換します。複数のリソースから生成したIDのリストをまとめたり、for式で生成したネストリストを展開したりする場面で頻繁に使います。


2. 基本的な動作

# terraform consoleで確認
> flatten([[1, 2], [3, 4], [5]])
[1, 2, 3, 4, 5]

> flatten(["a", ["b", "c"], ["d", ["e", "f"]]])
["a", "b", "c", "d", "e", "f"]

# 空リストも展開される
> flatten([[1, 2], [], [3]])
[1, 2, 3]

3. for式との組み合わせ(最重要パターン)

flattenfor式の組み合わせは、Terraformで最も重要なパターンの1つです。複数の親リソースに紐づく子リソースのリストを1次元化します。

terraform {
  required_version = ">= 1.9"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

variable "environment" {
  description = "環境名"
  type        = string
  default     = "dev"
}

variable "vpcs" {
  description = "VPCと各VPCのサブネット設定"
  type = list(object({
    name    = string
    subnets = list(string)
  }))
  default = [
    {
      name    = "web"
      subnets = ["10.0.1.0/24", "10.0.2.0/24"]
    },
    {
      name    = "db"
      subnets = ["10.1.1.0/24", "10.1.2.0/24"]
    }
  ]
}

locals {
  # VPC × サブネットの組み合わせを1次元のリストに展開
  subnet_list = flatten([
    for vpc in var.vpcs : [
      for subnet_cidr in vpc.subnets : {
        vpc_name = vpc.name
        cidr     = subnet_cidr
      }
    ]
  ])
  # 結果:
  # [
  #   { vpc_name = "web", cidr = "10.0.1.0/24" },
  #   { vpc_name = "web", cidr = "10.0.2.0/24" },
  #   { vpc_name = "db",  cidr = "10.1.1.0/24" },
  #   { vpc_name = "db",  cidr = "10.1.2.0/24" },
  # ]
}

4. 実践例: 複数環境のセキュリティグループルール

variable "ingress_rules" {
  description = "サービスごとのインバウンドルール"
  type = list(object({
    service = string
    ports   = list(number)
  }))
  default = [
    { service = "web",    ports = [80, 443] },
    { service = "api",    ports = [8080, 8443] },
    { service = "admin",  ports = [9090] }
  ]
}

locals {
  # サービス × ポートの組み合わせを展開
  all_rules = flatten([
    for rule in var.ingress_rules : [
      for port in rule.ports : {
        service = rule.service
        port    = port
      }
    ]
  ])
}

# tomap → for_each で各ルールを独立したリソースとして管理
resource "aws_security_group_rule" "ingress" {
  for_each = {
    for rule in local.all_rules :
    "${rule.service}-${rule.port}" => rule
  }

  type              = "ingress"
  from_port         = each.value.port
  to_port           = each.value.port
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.main.id
  description       = "${each.value.service} port ${each.value.port}"
}

resource "aws_security_group" "main" {
  name        = "${var.environment}-main"
  description = "${var.environment} main security group"

  tags = {
    Name        = "${var.environment}-main"
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

5. サブネットIDのマージ

data "aws_availability_zones" "available" {
  state = "available"
}

resource "aws_subnet" "public" {
  count             = 2
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet("10.0.0.0/16", 8, count.index)
  availability_zone = data.aws_availability_zones.available.names[count.index]

  tags = {
    Name        = "${var.environment}-public-${count.index + 1}"
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

resource "aws_subnet" "private" {
  count             = 2
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet("10.0.0.0/16", 8, count.index + 10)
  availability_zone = data.aws_availability_zones.available.names[count.index]

  tags = {
    Name        = "${var.environment}-private-${count.index + 1}"
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
  tags = {
    Name        = "${var.environment}-vpc"
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

locals {
  # publicとprivateサブネットのIDを1つのリストにまとめる
  all_subnet_ids = flatten([
    aws_subnet.public[*].id,
    aws_subnet.private[*].id
  ])
}

6. 関連記事


7. まとめ

  • flatten(list_of_lists) — ネストされたリストを1次元のリストに展開する
  • 最重要パターン: flatten([for x in list : [for y in x.items : {...}]]) でクロス積を展開
  • for_eachに渡すmapを生成する前のリスト展開として定番
  • aws_subnet.public[*].idなどのスプラット式と組み合わせてIDリストをマージできる

動作確認バージョン: Terraform >= 1.9 公式ドキュメント: https://developer.hashicorp.com/terraform/language/functions/flatten