TFLintのAWSルールセットを導入する

# はじめに
AWS環境構築で terraform を利用するにあたり、TFLint というのが環境チェックに有効そうだったので検証してみました。
今回は AWS のルールセットを導入して試してみました。


# 概要
・Terraformのコードをチェックすためのフレームワーク
・プラグインとして利用

https://github.com/terraform-linters/tflint-ruleset-aws


# 導入方法
* .tflint.hclファイルの作成
```hcl:tflint
plugin "aws" {
    enabled = true
    deep_check = true
    version = "0.31.0"
    source  = "github.com/terraform-linters/tflint-ruleset-aws"
}
```

* tflint --init の実施


# 検証
### ディレクトリ構成
```bash:ディレクトリ構成
$ tree
.
├── modules
│   └── ec2
│       ├── main.tf
│       └── variables.tf
└── ec2
    ├── main.tf
    └── versions.tf
```

### tfファイル
#### ./modules/ec2/main.tf 
```tf:resource
resource "aws_instance" "ec2" {
  ami           = var.ami
  instance_type = var.instance_type

  tags = {
    Name = "tflint-test"
  }
}
```

#### ./modules/ec2/variables.tf 
```tf:varilable
variable "ami" {
  type = string
}

variable "instance_type" {
  type = string
}
```

#### ./ec2/main.tf 
```tf:module
module "ec2" {
  source        = "../modules/ec2"
  ami           = "ami-0ca38c7440de1749a"
  instance_type = "t2.micro"
}
```

#### ./ec2/versions.tf
```tf:terraform
terraform {
  required_version = ">= 1.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.50.0"
    }
  }
}
```

#### ./ec2/.tflint.hcl
```tf:plugin
plugin "aws" {
    enabled = true
    deep_check = true
    version = "0.31.0"
    source  = "github.com/terraform-linters/tflint-ruleset-aws"
}
```

## チェック
### OK の場合
```bash:tflint
$ tflint
```
チェックが問題ない場合は何も出力されません。

### NG の場合
##### 例) 古いインスタンスタイプを指定した場合
現在は利用できないインスタンスタイプを指定した場合でも plan を実施するとエラーなどなく create 結果が表示されます。
```bash:plan結果
$ 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.ec2.aws_instance.ec2 will be created
  + resource "aws_instance" "ec2" {
      + ami                                  = "ami-0ca38c7440de1749a"
      + arn                                  = (known after apply)
      + associate_public_ip_address          = (known after apply)
      + availability_zone                    = (known after apply)
      + cpu_core_count                       = (known after apply)
      + cpu_threads_per_core                 = (known after apply)
      + disable_api_stop                     = (known after apply)
      + disable_api_termination              = (known after apply)
      + ebs_optimized                        = (known after apply)
      + get_password_data                    = false
      + host_id                              = (known after apply)
      + host_resource_group_arn              = (known after apply)
      + iam_instance_profile                 = (known after apply)
      + id                                   = (known after apply)
      + instance_initiated_shutdown_behavior = (known after apply)
      + instance_lifecycle                   = (known after apply)
      + instance_state                       = (known after apply)
      + instance_type                        = "t1.micro"
      + ipv6_address_count                   = (known after apply)
      + ipv6_addresses                       = (known after apply)
      + key_name                             = (known after apply)
      + monitoring                           = (known after apply)
      + outpost_arn                          = (known after apply)
      + password_data                        = (known after apply)
      + placement_group                      = (known after apply)
      + placement_partition_number           = (known after apply)
      + primary_network_interface_id         = (known after apply)
      + private_dns                          = (known after apply)
      + private_ip                           = (known after apply)
      + public_dns                           = (known after apply)
      + public_ip                            = (known after apply)
      + secondary_private_ips                = (known after apply)
      + security_groups                      = (known after apply)
      + source_dest_check                    = true
      + spot_instance_request_id             = (known after apply)
      + subnet_id                            = (known after apply)
      + tags                                 = {
          + "Name" = "tflint-test"
        }
      + tags_all                             = {
          + "Name" = "tflint-test"
        }
      + tenancy                              = (known after apply)
      + user_data                            = (known after apply)
      + user_data_base64                     = (known after apply)
      + user_data_replace_on_change          = false
      + vpc_security_group_ids               = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
```

tflint を実施します。
```bash:tflint
$ tflint         
1 issue(s) found:

Warning: "t1.micro" is previous generation instance type. (aws_instance_previous_type)

  on main.tf line 4:
   4:   instance_type = "t1.micro"

Callers:
   main.tf:4,19-29
   ../modules/ec2/main.tf:3,19-36

Reference: https://github.com/terraform-linters/tflint-ruleset-aws/blob/v0.31.0/docs/rules/aws_instance_previous_type.md
```

`Warning` として「t1.micro は前世代のインスタンスタイプです」と指摘されました。
また、 main.tf の 4行目ですと場所も表示されます。

##### 例) 存在しないamiを指定した場合
ここでは通常のインスタスIDに一文字足して明らかに桁が多い ami を指定してみたところ plan は問題なく通りました。
```bash:plan
$ 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.ec2.aws_instance.ec2 will be created
  + resource "aws_instance" "ec2" {
      + ami                                  = "ami-0ca38c7440de1749aa"
      + arn                                  = (known after apply)
      + associate_public_ip_address          = (known after apply)
      + availability_zone                    = (known after apply)
      + cpu_core_count                       = (known after apply)
      + cpu_threads_per_core                 = (known after apply)
      + disable_api_stop                     = (known after apply)
      + disable_api_termination              = (known after apply)
      + ebs_optimized                        = (known after apply)
      + get_password_data                    = false
      + host_id                              = (known after apply)
      + host_resource_group_arn              = (known after apply)
      + iam_instance_profile                 = (known after apply)
      + id                                   = (known after apply)
      + instance_initiated_shutdown_behavior = (known after apply)
      + instance_lifecycle                   = (known after apply)
      + instance_state                       = (known after apply)
      + instance_type                        = "t2.micro"
      + ipv6_address_count                   = (known after apply)
      + ipv6_addresses                       = (known after apply)
      + key_name                             = (known after apply)
      + monitoring                           = (known after apply)
      + outpost_arn                          = (known after apply)
      + password_data                        = (known after apply)
      + placement_group                      = (known after apply)
      + placement_partition_number           = (known after apply)
      + primary_network_interface_id         = (known after apply)
      + private_dns                          = (known after apply)
      + private_ip                           = (known after apply)
      + public_dns                           = (known after apply)
      + public_ip                            = (known after apply)
      + secondary_private_ips                = (known after apply)
      + security_groups                      = (known after apply)
      + source_dest_check                    = true
      + spot_instance_request_id             = (known after apply)
      + subnet_id                            = (known after apply)
      + tags                                 = {
          + "Name" = "tflint-test"
        }
      + tags_all                             = {
          + "Name" = "tflint-test"
        }
      + tenancy                              = (known after apply)
      + user_data                            = (known after apply)
      + user_data_base64                     = (known after apply)
      + user_data_replace_on_change          = false
      + vpc_security_group_ids               = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
```

tflint実施します。
```bash:tflint
$ tflint     
1 issue(s) found:

Error: "ami-0ca38c7440de1749aa" is invalid AMI ID. (aws_instance_invalid_ami)

  on main.tf line 3:
   3:   ami           = "ami-0ca38c7440de1749aa"

Callers:
   main.tf:3,19-43
   ../modules/ec2/main.tf:2,19-26
```

今度は `Error` が表示され「AMI ID が不正です」と指摘してくれました。


# 懸念点
### Terraformのライセンス変更による影響は受けないか？
TFLint は HashiCorp が提供するツールではないためライセンス違反などが気になりますが以下のサイトに調べてくださっている方がいました。

https://zenn.dev/erueru_tech/articles/fab5717e659dda

HashiCorp からも大丈夫と回答が来てるので将来的にも利用できなくなる確率は低いと思われます。


# おわりに
AWS を構築する上で利用できない値などがあった場合は apply 前に知りたかったので TFLint はとても役にたつツールだなと感じました。