terraform consoleは便利なお話

# はじめに
Terraformでインフラを記述してapplyしてみたら思ったoutputと違う値が出ている、planでエラー出ているけどmoduleのoutputがどんな感じかわからない。「printfデバッグみたいなことができたらいいのに…」みたいなことを思った人はありませんか？
そんなあなたに、Terraformには`terraform console`というコマンドをお勧めします。

# terraform consoleを使ってみよう
terraform moduleのoutputを別のmoduleの引数として参照したいというシナリオを想定していきます。
貴方はvpc moduleのoutputを使ってsecurity_groupを定義していきたいがどのようなoutputがあるか確認したいです。

- 与えられたコード
```
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.5.1"

  name = "my-vpc"
  cidr = "10.0.0.0/16"

  azs            = ["ap-northeast-1a", "ap-northeast-1c"]
  public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]

  tags = {
    Environment = "staging"
  }
}

# security_groupでvpcのmoduleのoutputを活用したいが何を書けばいいかわからない
# module "security_group" {
#   source  = "terraform-aws-modules/security-group/aws"
#   version = "5.1.0"

#   name        = "web-sg"
#   description = "Security group for web servers"
#   vpc_id      = ????

#   ingress_with_cidr_blocks = [
#     {
#       from_port   = 80
#       to_port     = 80
#       protocol    = "tcp"
#       cidr_blocks = ???
#     }
#   ]
# }
```

- まずvpc moduleのリソースが作成差分として出るかを確認する
```
> terraform init
# 各々のproviderによりこれより上の差分は違うので省略
Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
> terraform plan
Terraform used the selected providers to generate the following execution plan.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.vpc.aws_default_network_acl.this[0] will be created
  + resource "aws_default_network_acl" "this" {
      + arn                    = (known after apply)
      + default_network_acl_id = (known after apply)
      + id                     = (known after apply)
      + owner_id               = (known after apply)
      + tags                   = {
          + "Environment" = "staging"
          + "Name"        = "my-vpc-default"
        }
      + tags_all               = {
          + "AwsInfraDir" = "arase-rails/development/aws/"
          + "CreatedBy"   = "Terraform"
          + "Environment" = "staging"
          + "Name"        = "my-vpc-default"
          + "Project"     = (known after apply)
        }
      + vpc_id                 = (known after apply)

      + egress {
          + action          = "allow"
          + from_port       = 0
          + ipv6_cidr_block = "::/0"
          + protocol        = "-1"
          + rule_no         = 101
          + to_port         = 0
            # (1 unchanged attribute hidden)
        }
      + egress {
          + action          = "allow"
          + cidr_block      = "0.0.0.0/0"
          + from_port       = 0
          + protocol        = "-1"
          + rule_no         = 100
          + to_port         = 0
            # (1 unchanged attribute hidden)
        }

      + ingress {
          + action          = "allow"
          + from_port       = 0
          + ipv6_cidr_block = "::/0"
          + protocol        = "-1"
          + rule_no         = 101
          + to_port         = 0
            # (1 unchanged attribute hidden)
        }
      + ingress {
          + action          = "allow"
          + cidr_block      = "0.0.0.0/0"
          + from_port       = 0
          + protocol        = "-1"
          + rule_no         = 100
          + to_port         = 0
            # (1 unchanged attribute hidden)
        }
    }

  # module.vpc.aws_default_route_table.default[0] will be created
  + resource "aws_default_route_table" "default" {
      + arn                    = (known after apply)
      + default_route_table_id = (known after apply)
      + id                     = (known after apply)
      + owner_id               = (known after apply)
      + route                  = (known after apply)
      + tags                   = {
          + "Environment" = "staging"
          + "Name"        = "my-vpc-default"
        }
      + tags_all               = {
          + "AwsInfraDir" = "arase-rails/development/aws/"
          + "CreatedBy"   = "Terraform"
          + "Environment" = "staging"
          + "Name"        = "my-vpc-default"
          + "Project"     = (known after apply)
        }
      + vpc_id                 = (known after apply)

      + timeouts {
          + create = "5m"
          + update = "5m"
        }
    }

  # module.vpc.aws_default_security_group.this[0] will be created
  + resource "aws_default_security_group" "this" {
      + arn                    = (known after apply)
      + description            = (known after apply)
      + egress                 = (known after apply)
      + id                     = (known after apply)
      + ingress                = (known after apply)
      + name                   = (known after apply)
      + name_prefix            = (known after apply)
      + owner_id               = (known after apply)
      + revoke_rules_on_delete = false
      + tags                   = {
          + "Environment" = "staging"
          + "Name"        = "my-vpc-default"
        }
      + tags_all               = {
          + "AwsInfraDir" = "arase-rails/development/aws/"
          + "CreatedBy"   = "Terraform"
          + "Environment" = "staging"
          + "Name"        = "my-vpc-default"
          + "Project"     = (known after apply)
        }
      + vpc_id                 = (known after apply)
    }

  # module.vpc.aws_internet_gateway.this[0] will be created
  + resource "aws_internet_gateway" "this" {
      + arn      = (known after apply)
      + id       = (known after apply)
      + owner_id = (known after apply)
      + tags     = {
          + "Environment" = "staging"
          + "Name"        = "my-vpc"
        }
      + tags_all = {
          + "AwsInfraDir" = "arase-rails/development/aws/"
          + "CreatedBy"   = "Terraform"
          + "Environment" = "staging"
          + "Name"        = "my-vpc"
          + "Project"     = (known after apply)
        }
      + vpc_id   = (known after apply)
    }

  # module.vpc.aws_route.public_internet_gateway[0] will be created
  + resource "aws_route" "public_internet_gateway" {
      + destination_cidr_block = "0.0.0.0/0"
      + gateway_id             = (known after apply)
      + id                     = (known after apply)
      + instance_id            = (known after apply)
      + instance_owner_id      = (known after apply)
      + network_interface_id   = (known after apply)
      + origin                 = (known after apply)
      + route_table_id         = (known after apply)
      + state                  = (known after apply)

      + timeouts {
          + create = "5m"
        }
    }

  # module.vpc.aws_route_table.public[0] will be created
  + resource "aws_route_table" "public" {
      + arn              = (known after apply)
      + id               = (known after apply)
      + owner_id         = (known after apply)
      + propagating_vgws = (known after apply)
      + route            = (known after apply)
      + tags             = {
          + "Environment" = "staging"
          + "Name"        = "my-vpc-public"
        }
      + tags_all         = {
          + "AwsInfraDir" = "arase-rails/development/aws/"
          + "CreatedBy"   = "Terraform"
          + "Environment" = "staging"
          + "Name"        = "my-vpc-public"
          + "Project"     = (known after apply)
        }
      + vpc_id           = (known after apply)
    }

  # module.vpc.aws_route_table_association.public[0] will be created
  + resource "aws_route_table_association" "public" {
      + id             = (known after apply)
      + route_table_id = (known after apply)
      + subnet_id      = (known after apply)
    }

  # module.vpc.aws_route_table_association.public[1] will be created
  + resource "aws_route_table_association" "public" {
      + id             = (known after apply)
      + route_table_id = (known after apply)
      + subnet_id      = (known after apply)
    }

  # module.vpc.aws_subnet.public[0] will be created
  + resource "aws_subnet" "public" {
      + arn                                            = (known after apply)
      + assign_ipv6_address_on_creation                = false
      + availability_zone                              = "ap-northeast-1a"
      + availability_zone_id                           = (known after apply)
      + cidr_block                                     = "10.0.1.0/24"
      + enable_dns64                                   = false
      + enable_resource_name_dns_a_record_on_launch    = false
      + enable_resource_name_dns_aaaa_record_on_launch = false
      + id                                             = (known after apply)
      + ipv6_cidr_block_association_id                 = (known after apply)
      + ipv6_native                                    = false
      + map_public_ip_on_launch                        = false
      + owner_id                                       = (known after apply)
      + private_dns_hostname_type_on_launch            = (known after apply)
      + tags                                           = {
          + "Environment" = "staging"
          + "Name"        = "my-vpc-public-ap-northeast-1a"
        }
      + tags_all                                       = {
          + "AwsInfraDir" = "arase-rails/development/aws/"
          + "CreatedBy"   = "Terraform"
          + "Environment" = "staging"
          + "Name"        = "my-vpc-public-ap-northeast-1a"
          + "Project"     = (known after apply)
        }
      + vpc_id                                         = (known after apply)
    }

  # module.vpc.aws_subnet.public[1] will be created
  + resource "aws_subnet" "public" {
      + arn                                            = (known after apply)
      + assign_ipv6_address_on_creation                = false
      + availability_zone                              = "ap-northeast-1c"
      + availability_zone_id                           = (known after apply)
      + cidr_block                                     = "10.0.2.0/24"
      + enable_dns64                                   = false
      + enable_resource_name_dns_a_record_on_launch    = false
      + enable_resource_name_dns_aaaa_record_on_launch = false
      + id                                             = (known after apply)
      + ipv6_cidr_block_association_id                 = (known after apply)
      + ipv6_native                                    = false
      + map_public_ip_on_launch                        = false
      + owner_id                                       = (known after apply)
      + private_dns_hostname_type_on_launch            = (known after apply)
      + tags                                           = {
          + "Environment" = "staging"
          + "Name"        = "my-vpc-public-ap-northeast-1c"
        }
      + tags_all                                       = {
          + "AwsInfraDir" = "arase-rails/development/aws/"
          + "CreatedBy"   = "Terraform"
          + "Environment" = "staging"
          + "Name"        = "my-vpc-public-ap-northeast-1c"
          + "Project"     = (known after apply)
        }
      + vpc_id                                         = (known after apply)
    }

  # module.vpc.aws_vpc.this[0] will be created
  + resource "aws_vpc" "this" {
      + arn                                  = (known after apply)
      + cidr_block                           = "10.0.0.0/16"
      + default_network_acl_id               = (known after apply)
      + default_route_table_id               = (known after apply)
      + default_security_group_id            = (known after apply)
      + dhcp_options_id                      = (known after apply)
      + enable_dns_hostnames                 = true
      + enable_dns_support                   = true
      + enable_network_address_usage_metrics = (known after apply)
      + id                                   = (known after apply)
      + instance_tenancy                     = "default"
      + ipv6_association_id                  = (known after apply)
      + ipv6_cidr_block                      = (known after apply)
      + ipv6_cidr_block_network_border_group = (known after apply)
      + main_route_table_id                  = (known after apply)
      + owner_id                             = (known after apply)
      + tags                                 = {
          + "Environment" = "staging"
          + "Name"        = "my-vpc"
        }
      + tags_all                             = {
          + "AwsInfraDir" = "arase-rails/development/aws/"
          + "CreatedBy"   = "Terraform"
          + "Environment" = "staging"
          + "Name"        = "my-vpc"
          + "Project"     = (known after apply)
        }
    }

Plan: 11 to add, 0 to change, 0 to destroy.
```

- terraform consoleを起動

```
> terraform console
> module.vpc # outputに何が存在しているかを確認
{
  "azs" = tolist([
    "ap-northeast-1a",
    "ap-northeast-1c",
  ])
  "cgw_arns" = (known after apply)
  "cgw_ids" = (known after apply)
  "database_internet_gateway_route_id" = (known after apply)
  "database_ipv6_egress_route_id" = (known after apply)
  "database_nat_gateway_route_ids" = (known after apply)
  "database_network_acl_arn" = (known after apply)
  "database_network_acl_id" = (known after apply)
  "database_route_table_association_ids" = (known after apply)
  "database_route_table_ids" = (known after apply)
  "database_subnet_arns" = (known after apply)
  "database_subnet_group" = (known after apply)
  "database_subnet_group_name" = (known after apply)
  "database_subnets" = (known after apply)
  "database_subnets_cidr_blocks" = (known after apply)
  "database_subnets_ipv6_cidr_blocks" = (known after apply)
  "default_network_acl_id" = (known after apply)
  "default_route_table_id" = (known after apply)
  "default_security_group_id" = (known after apply)
  "default_vpc_arn" = (known after apply)
  "default_vpc_cidr_block" = (known after apply)
  "default_vpc_default_network_acl_id" = (known after apply)
  "default_vpc_default_route_table_id" = (known after apply)
  "default_vpc_default_security_group_id" = (known after apply)
  "default_vpc_enable_dns_hostnames" = (known after apply)
  "default_vpc_enable_dns_support" = (known after apply)
  "default_vpc_id" = (known after apply)
  "default_vpc_instance_tenancy" = (known after apply)
  "default_vpc_main_route_table_id" = (known after apply)
  "dhcp_options_id" = (known after apply)
  "egress_only_internet_gateway_id" = (known after apply)
  "elasticache_network_acl_arn" = (known after apply)
  "elasticache_network_acl_id" = (known after apply)
  "elasticache_route_table_association_ids" = (known after apply)
  "elasticache_route_table_ids" = (known after apply)
  "elasticache_subnet_arns" = (known after apply)
  "elasticache_subnet_group" = (known after apply)
  "elasticache_subnet_group_name" = (known after apply)
  "elasticache_subnets" = (known after apply)
  "elasticache_subnets_cidr_blocks" = (known after apply)
  "elasticache_subnets_ipv6_cidr_blocks" = (known after apply)
  "igw_arn" = (known after apply)
  "igw_id" = (known after apply)
  "intra_network_acl_arn" = (known after apply)
  "intra_network_acl_id" = (known after apply)
  "intra_route_table_association_ids" = (known after apply)
  "intra_route_table_ids" = (known after apply)
  "intra_subnet_arns" = (known after apply)
  "intra_subnets" = (known after apply)
  "intra_subnets_cidr_blocks" = (known after apply)
  "intra_subnets_ipv6_cidr_blocks" = (known after apply)
  "name" = "my-vpc"
  "nat_ids" = (known after apply)
  "nat_public_ips" = (known after apply)
  "natgw_ids" = (known after apply)
  "natgw_interface_ids" = (known after apply)
  "outpost_network_acl_arn" = (known after apply)
  "outpost_network_acl_id" = (known after apply)
  "outpost_subnet_arns" = (known after apply)
  "outpost_subnets" = (known after apply)
  "outpost_subnets_cidr_blocks" = (known after apply)
  "outpost_subnets_ipv6_cidr_blocks" = (known after apply)
  "private_ipv6_egress_route_ids" = (known after apply)
  "private_nat_gateway_route_ids" = (known after apply)
  "private_network_acl_arn" = (known after apply)
  "private_network_acl_id" = (known after apply)
  "private_route_table_association_ids" = (known after apply)
  "private_route_table_ids" = (known after apply)
  "private_subnet_arns" = (known after apply)
  "private_subnets" = (known after apply)
  "private_subnets_cidr_blocks" = (known after apply)
  "private_subnets_ipv6_cidr_blocks" = (known after apply)
  "public_internet_gateway_ipv6_route_id" = (known after apply)
  "public_internet_gateway_route_id" = (known after apply)
  "public_network_acl_arn" = (known after apply)
  "public_network_acl_id" = (known after apply)
  "public_route_table_association_ids" = (known after apply)
  "public_route_table_ids" = (known after apply)
  "public_subnet_arns" = (known after apply)
  "public_subnets" = (known after apply)
  "public_subnets_cidr_blocks" = (known after apply)
  "public_subnets_ipv6_cidr_blocks" = (known after apply)
  "redshift_network_acl_arn" = (known after apply)
  "redshift_network_acl_id" = (known after apply)
  "redshift_public_route_table_association_ids" = (known after apply)
  "redshift_route_table_association_ids" = (known after apply)
  "redshift_route_table_ids" = (known after apply)
  "redshift_subnet_arns" = (known after apply)
  "redshift_subnet_group" = (known after apply)
  "redshift_subnets" = (known after apply)
  "redshift_subnets_cidr_blocks" = (known after apply)
  "redshift_subnets_ipv6_cidr_blocks" = (known after apply)
  "this_customer_gateway" = (known after apply)
  "vgw_arn" = (known after apply)
  "vgw_id" = (known after apply)
  "vpc_arn" = (known after apply)
  "vpc_cidr_block" = (known after apply)
  "vpc_enable_dns_hostnames" = (known after apply)
  "vpc_enable_dns_support" = (known after apply)
  "vpc_flow_log_cloudwatch_iam_role_arn" = ""
  "vpc_flow_log_deliver_cross_account_role" = (known after apply)
  "vpc_flow_log_destination_arn" = ""
  "vpc_flow_log_destination_type" = "cloud-watch-logs"
  "vpc_flow_log_id" = (known after apply)
  "vpc_id" = (known after apply)
  "vpc_instance_tenancy" = (known after apply)
  "vpc_ipv6_association_id" = (known after apply)
  "vpc_ipv6_cidr_block" = (known after apply)
  "vpc_main_route_table_id" = (known after apply)
  "vpc_owner_id" = (known after apply)
  "vpc_secondary_cidr_blocks" = (known after apply)
}
> module.vpc.azs
tolist([
  "ap-northeast-1a",
  "ap-northeast-1c",
])
> module.vpc.vpc_id # 参照エラーが起きないかを確認
(known after apply)
> module.vpc.vpc_cidr_block # 参照エラーが起きないかを確認
(known after apply)
```
- 結果を参照して実際にコードを修正する
先ほどのoutputを元に元コードを修正します。

```
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.5.1"

  name = "my-vpc"
  cidr = "10.0.0.0/16"

  azs            = ["ap-northeast-1a", "ap-northeast-1c"]
  public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]

  tags = {
    Environment = "staging"
  }
}

module "security_group" {
  source  = "terraform-aws-modules/security-group/aws"
  version = "5.1.0"

  name        = "web-sg"
  description = "Security group for web servers"
  vpc_id      = module.vpc.vpc_id

  ingress_with_cidr_blocks = [
    {
      from_port   = 80
      to_port     = 80
      protocol    = "tcp"
      cidr_blocks = module.vpc.vpc_cidr_block
    }
  ]
}

```

# まとめ
今回はterraform consoleの使い方について書いてみました。
取り上げたシナリオは記述段階でしたが、実際にはデバッグなどにも使える便利な子です。moduleの仕様などを確認する時もちろん公式のdocを見に行くのも一つの手ですが、中には悲しいことにdocが更新されていなかったり不確かなREADMEしか存在しなかったりする場合もあるので自分は実際にconsoleで確認している時も多かったです。
terraform consoleを活用して快適なterraform lifeを送りましょう。