11
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

terraformAdvent Calendar 2020

Day 21

tflint の --deep オプション便利

Last updated at Posted at 2020-12-21

2020-01-09 追記

--deep オプションは v0.23.0 で廃止されました。.tflint.hcl で設定するようです。
https://github.com/terraform-linters/tflint/releases/tag/v0.23.0

tflint

便利ですよね。

まず軽く動かします。

リポジトリはこちら。

tflint の example にあるように、こういう tf file の plan は通ってしまいますが

resource "aws_instance" "foo" {
  ami           = "ami-0ff8a91507f77f867"
  instance_type = "t1.2xlarge" # invalid type!
}

plan

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.foo will be created
  + resource "aws_instance" "foo" {
      + ami                          = "ami-0ff8a91507f77f867"
      + 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)
      + get_password_data            = false
      + host_id                      = (known after apply)
      + id                           = (known after apply)
      + instance_state               = (known after apply)
      + instance_type                = "t1.2xlarge"
      + ipv6_address_count           = (known after apply)
      + ipv6_addresses               = (known after apply)
      + key_name                     = (known after apply)
      + outpost_arn                  = (known after apply)
      + password_data                = (known after apply)
      + placement_group              = (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
      + subnet_id                    = (known after apply)
      + tenancy                      = (known after apply)
      + volume_tags                  = (known after apply)
      + vpc_security_group_ids       = (known after apply)

      + ebs_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + snapshot_id           = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }

      + ephemeral_block_device {
          + device_name  = (known after apply)
          + no_device    = (known after apply)
          + virtual_name = (known after apply)
        }

      + metadata_options {
          + http_endpoint               = (known after apply)
          + http_put_response_hop_limit = (known after apply)
          + http_tokens                 = (known after apply)
        }

      + network_interface {
          + delete_on_termination = (known after apply)
          + device_index          = (known after apply)
          + network_interface_id  = (known after apply)
        }

      + root_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }

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

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

ちゃんと怒ってくれるわけですね。

$ tflint
1 issue(s) found:

Error: "t1.2xlarge" is an invalid value as instance_type (aws_instance_invalid_type)

  on main.tf line 3:
   3:   instance_type = "t1.2xlarge" # invalid type!

--deep option

--deep Enable deep check mode

deep check mode なるものがあるんですね。

apply のときにこける系のやつで、api 問い合わせをしないとわからないようなやつをチェックしてくれるみたいです。

さて、instance type を valid のものにします。こうすると tflint は通りますね。

$ cat main.tf
resource "aws_instance" "foo" {
  ami           = "ami-0ff8a91507f77f867"
  instance_type = "t3.small"
}
$ tflint
$ echo $?
0 # ok

ところで tflint はどういった仕組みで instance_type をチェックしているかというと、今回の例でいえば tflint/rules/awsrules/models/aws_instance_invalid_type.go に 定義されていますね。新しいインスタンスタイプがサポートされた場合はコントリビューションチャンスかもしれませんね。

ざっとコードを見ると、resource type と attribute name と valid な list として enum を定義して model Rules ととして呼び出すだけでいいんですね。ルールの追加も簡単そうで良い。

さて、次に ami を不正なものに変えてみます。

resource "aws_instance" "foo" {
  ami           = "ami-1234xxxx1234yyyy" # invalid ami!
  instance_type = "t3.small"
}

引数なしだと通っちゃいますね。

$ tflint
$ echo $?
0 # ok

deep option つけてみましょう。

$ tflint --deep
1 issue(s) found:

Error: "ami-1234xxxx1234yyyy" is invalid AMI ID. (aws_instance_invalid_ami)

  on main.tf line 2:
   2:   ami           = "ami-1234xxxx1234yyyy" # invalid ami!

ちゃんと怒ってくれました!すごい!

deep check mode では api を call するためなんらかの方法で credential を渡してあげる必要があります。

普通に Terraform で plan や apply を実行するように、こんな感じに環境変数でもいいですし、他のパターンもサポートしているようです。

$ cat .envrc
export AWS_ACCESS_KEY_ID="secret-secret"
export AWS_SECRET_ACCESS_KEY="secret-secret-secret-secret"
export AWS_DEFAULT_REGION="ap-northeast-1"

ref: https://github.com/terraform-linters/tflint/blob/9844546c5ae6391e42a8a746f9af7ea1325d946c/docs/guides/credentials.md#credentials

今回適用されたルールは aws_instance_invalid_ami ですね。

ちなみに tflint は loglevel をオプションで指定できます。挙動に対してソースコードを追うのに便利でいいですね。

$ tflint --deep --loglevel=debug                                                                                                                                                               [main]
14:58:53 config.go:98: [INFO] Load config: .tflint.hcl
14:58:53 config.go:110: [INFO] Default config file is not found. Ignored
14:58:53 config.go:119: [INFO] Load fallback config: /Users/chaspy/.tflint.hcl
14:58:53 config.go:127: [INFO] Fallback config file is not found. Ignored
14:58:53 config.go:129: [INFO] Use default config
14:58:53 option.go:54: [DEBUG] CLI Options
14:58:53 option.go:55: [DEBUG]   Module: false
14:58:53 option.go:56: [DEBUG]   DeepCheck: true
14:58:53 option.go:57: [DEBUG]   Force: false
14:58:53 option.go:58: [DEBUG]   IgnoreModules: map[string]bool{}
14:58:53 option.go:59: [DEBUG]   EnableRules: []string(nil)
14:58:53 option.go:60: [DEBUG]   DisableRules: []string(nil)
14:58:53 option.go:61: [DEBUG]   Only: []string(nil)
14:58:53 option.go:62: [DEBUG]   Varfiles: []string{}
14:58:53 option.go:63: [DEBUG]   Variables: []string{}
14:58:53 loader.go:57: [INFO] Initialize new loader
14:58:53 loader.go:82: [INFO] Load configurations under .
14:58:53 loader.go:90: [INFO] Module inspection is disabled. Building a root module without children...
14:58:53 loader.go:170: [INFO] Load values files
14:58:53 runner.go:52: [INFO] Initialize new runner for root
14:58:53 provider.go:48: [INFO] `access_key` is not found in the provider block.
14:58:53 provider.go:48: [INFO] `secret_key` is not found in the provider block.
14:58:53 provider.go:48: [INFO] `profile` is not found in the provider block.
14:58:53 provider.go:48: [INFO] `shared_credentials_file` is not found in the provider block.
14:58:53 runner_eval.go:67: [WARN] Unevaluable expression found in provider.tf:2; TFLint ignores an unevaluable expression.
14:58:53 provider.go:76: [INFO] `assume_role` is not found in the provider block.
14:58:53 aws.go:83: [INFO] Initialize AWS Client
14:58:53 awsauth.go:242: [INFO] AWS Auth provider used: "EnvProvider"
14:58:53 awsauth.go:143: [DEBUG] Trying to get account information via sts:GetCallerIdentity
14:58:54 provider.go:87: [INFO] Prepare rules
14:58:54 provider.go:93: [DEBUG] Deep check mode is enabled. Add deep check rules
14:58:54 provider.go:123: [INFO]   794 rules enabled
14:58:54 runner_walk.go:36: [DEBUG] Walk `aws_instance.foo.instance_type` attribute
14:58:54 runner_walk.go:36: [DEBUG] Walk `aws_instance.foo.instance_type` attribute
14:58:54 runner_walk.go:36: [DEBUG] Walk `aws_instance.foo.ami` attribute
14:58:54 aws_instance_invalid_ami.go:60: [DEBUG] Fetch AMI images: ami-1234xxxx1234yyyy
1 issue(s) found:

Error: "ami-1234xxxx1234yyyy" is invalid AMI ID. (aws_instance_invalid_ami)

  on main.tf line 2:
   2:   ami           = "ami-1234xxxx1234yyyy" # invalid ami!

trace はあまりに多かったので debug のものを載せてみました。

valid な ami かどうかを確かめるために api call しているのはここですね。

おわりに

tflint, デフォルトだと credentrial 不要というのもイケてますし、--deep 多少 api call に時間かかるものの、CI で実行してあげると Apply 時に爆死するパターンが減りそうです。

どんどん導入して使っていきたいと思いました。

11
4
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?