Heredocとテンプレート文字列 — 複数行文字列の書き方

1. 概要

  • Heredoc構文(<<-EOT)とは何か
  • インデント対応のHeredoc(<<-
  • テンプレート文字列(${...}%{...}
  • templatefile関数との使い分け
  • 実際のユースケース(IAMポリシー・ユーザーデータなど)

Terraformでは複数行の文字列を書くためにHeredoc構文とテンプレート文字列が使えます。IAMポリシーのJSON、EC2のユーザーデータ、設定ファイルの内容など、長い文字列を扱うときに重宝します。


2. Heredoc基本構文

# 基本形(インデントは除去されない)
variable = <<EOT
1行目
2行目
3行目
EOT

# インデント対応(<<- を使うとインデントが自動除去される)
variable = <<-EOT
  1行目
  2行目
  3行目
EOT

<<-EOT(ハイフン付き)を使うと、全行の中で最も少ない先頭スペース数を検出し、その分だけ全行から除去します。行ごとの相対的なインデント差は保持されます。コードのインデントを揃えたい場合はこちらを使います。


3. IAMポリシーの記述

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 "bucket_name" {
  description = "S3バケット名"
  type        = string
}

# Heredocで複数行のJSONを記述
resource "aws_iam_policy" "s3_read" {
  name        = "${var.environment}-s3-read-policy"
  description = "S3読み取り専用ポリシー"

  policy = <<-EOT
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": [
            "s3:GetObject",
            "s3:ListBucket"
          ],
          "Resource": [
            "arn:aws:s3:::${var.bucket_name}",
            "arn:aws:s3:::${var.bucket_name}/*"
          ]
        }
      ]
    }
  EOT

  tags = {
    Name        = "${var.environment}-s3-read-policy"
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

推奨: IAMポリシーのJSONにはjsonencode()関数を使う方がより安全です。Heredocの場合、文字列のエスケープミスがあってもTerraformは検知できませんが、jsonencode()なら構文エラーを検出できます。


4. EC2ユーザーデータ

EC2起動時に実行するスクリプトのユーザーデータもHeredocで書くと読みやすくなります。

data "aws_ami" "amazon_linux_2023" {
  most_recent = true
  owners      = ["amazon"]
  filter {
    name   = "name"
    values = ["al2023-ami-*-x86_64"]
  }
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.amazon_linux_2023.id
  instance_type = "t3.micro"

  # Heredocでユーザーデータを記述
  user_data = <<-EOT
    #!/bin/bash
    set -e

    # システム更新
    dnf update -y

    # Nginxインストール
    dnf install -y nginx

    # 環境変数を設定
    echo "ENVIRONMENT=${var.environment}" >> /etc/environment

    # Nginx起動
    systemctl enable nginx
    systemctl start nginx
  EOT

  root_block_device {
    volume_size = 20
    volume_type = "gp3"
    encrypted   = true
  }

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

5. テンプレート文字列(%{if}・%{for})

%{if}%{for}を使うと、文字列の中に条件分岐やループを埋め込めます。

locals {
  servers = ["web-01", "web-02", "web-03"]

  # %{for} — ループを文字列内に展開
  hosts_file = <<-EOT
    # Managed by Terraform
    %{ for server in local.servers ~}
    10.0.0.${index(local.servers, server) + 1} ${server}.internal
    %{ endfor ~}
  EOT

  # %{if} — 条件分岐を文字列内に展開
  nginx_config = <<-EOT
    server {
      listen 80;
      %{ if var.environment == "prd" ~}
      server_name www.example.com;
      %{ else ~}
      server_name ${var.environment}.example.com;
      %{ endif ~}
    }
  EOT
}

6. templatefile関数との使い分け

比較Heredoctemplatefile
テンプレートの場所.tfファイル内外部ファイル(.tftpl
再利用しにくいしやすい
向いている用途短い・シンプルな内容長い・複雑な内容
バージョン管理.tfと一体別ファイルで管理

7. 関連記事


8. まとめ

  • <<-EOT ... EOTはインデント対応のHeredoc構文(全行中の最小インデント数分を除去、相対差は保持)
  • Heredoc内では${var.名前}で変数を参照できる
  • %{for}%{if}でループや条件分岐を文字列内に埋め込める
  • IAMポリシーにはjsonencode()を使う方がより安全
  • 長い・再利用するテンプレートはtemplatefile関数で外部ファイル化が推奨

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