Terraformのリファクタリングツールのtfirmgを公開しました

目次

はじめに

色々と忙しい日々を過ごしているゲインです。

今回はterraformのimport blockとremoved block(と将来的にはmoved block)を自動で生成するツールを雑に公開したので紹介します。

モチベーション

terraformを日々追加していると別tfstateに分けたいということは多々発生するでしょう。

terraform v1.5.0以前であればひたすらリソースを terraform import hoge && terraform state rm hoge実行するという辛いリファクタリングをしていた方もいるのではないでしょうか。

また複数人が絡むチーム開発ではtfmigrate を利用してCIフレンドリーにリファクタリングしている方もいらっしゃるでしょう。

しかし、シンプルに数が多くなってくるとコマンドを大量に実行するのも疲れますし自身が雑な人間なのでミスも増えてしまいます。

そのため実際のコードのリファクタリングに合わせてtfstateも移動してくれるツールが欲しいなと思っていました。

そんな中 terraformのアップデートによりv1.5.0からは Import Block1が、 v.1.7.0からは Removed Block2がサポートされました。

このアップデートによりterraformのstateを行う際には、import blockで移動させたいリソースをimportし、removed blockで移動させいたいリソースをコードベースで定義することが可能になりました。

そのため2つのstateにまたがってこれらのコードを生成すればstate跨ぎのリファクタリングが可能になるということです。

また、import blockの構文の id はtfstateに値があることを発見しこれを利用することでコードベースのリソース移動によるリファクタリングが可能なのではないかということで今回作成してみました。

tfirmg

こちらで公開しています

https://github.com/gainings/tfirmg

tfstateの中身はまったくいじらないツールなのでお気軽に試していただければと思います。

使用例

以下の例だとネットワークとec2のリソースのみがあります。

これぐらいだったら手動でimportしても問題はありませんが、大量に書かれたリソースがあると想像してください。

#current_state/main.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.47.0"
    }
  }
}

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

module "vpc" {
  source = "terraform-aws-modules/vpc/aws"

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

  azs             = ["ap-northeast-1a", "ap-northeast-1c", "ap-northeast-1d"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]

  enable_nat_gateway = false
  enable_vpn_gateway = false

  tags = {
    Terraform   = "true"
    Environment = "dev"
  }
}

data "aws_ami" "ubuntu" {
  most_recent = true

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  owners = ["099720109477"]
}

resource "aws_instance" "my_perfect_app" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"

  tags = {
    Terraform = "true"
    Name      = "HelloWorld"
  }
}

このterraformを以下のように分割したいと考えます。

#current_state/main.tf

...

module "vpc" {
  source = "terraform-aws-modules/vpc/aws"

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

  azs             = ["ap-northeast-1a", "ap-northeast-1c", "ap-northeast-1d"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]

  enable_nat_gateway = false
  enable_vpn_gateway = false

  tags = {
    Terraform   = "true"
    Environment = "dev"
  }
}
#app_state/main.tf

...

data "aws_ami" "ubuntu" {
  most_recent = true

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  owners = ["099720109477"]
}

resource "aws_instance" "my_perfect_app" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"

  tags = {
    Terraform = "true"
    Name      = "HelloWorld"
  }
}

そこで以下のコマンドを実行することで import blockとremoved blockを生成します。

tfirmg generate --src-dir ./current_state --dst-dir ./app_state --src-tfstate-path file://$(PWD)/current_state/terraform.tfstate

このコマンドの結果により、 current_state/removed.tfapp_state/import.tf を生成します。

リソースの移動によって参照先が無くなる場合があるのでそれは普通にリファクタリングを行い、通常通りterraform applyを行えば無事にstate跨ぎの移動が完了します。

この方法はCIに新規のツールを導入する必要もなく、Terraformの機能に乗っかることができる点が利点だと思っています。

課題

現状では僕の仕事のユースケースのためlocal backendとs3 backendのみにしか対応していません。

gcsぐらいは個人で検証できるとは思いますが、Azure、Terraform Cloudやその他はまったく見識がないのでPRお待ちしています。

またこちらはすべてのリソースに対応しているわけではありません。

というのも各種providerのリソースはtfstateに記載されているidで必ずimport出来るわけでもなく、そもそもimportに対応していないリソースもあるためです。

それを回避するために愚直にid生成のコードを書いていたりもしておりなんとかならないかなーと思っています。

https://github.com/gainings/tfirmg/tree/main/internal/rules/providers/aws

とはいえ対応してないリソースについては生成された後にimport blockを手動で削除したり修正したりすれば良いためまぁとりあえずはこのほうほうでもんだいないかな〜と思っています。

まとめ

terraformのリファクタリングを行うツールを公開しました。

現状ではimport / removed blockを生成する機能しかありませんが、暇なタイミングでmodule間のリソース移動などなど作成していきたいと思っています。

使ってみて感想などいただけたら幸いです。

Footnotes

  1. https://www.hashicorp.com/blog/terraform-1-5-brings-config-driven-import-and-checks

  2. https://www.hashicorp.com/blog/terraform-1-7-adds-test-mocking-and-config-driven-remove