😸

terraform importするときにちょっと便利なスクリプトを作ってみた

に公開

経緯

最近インフラ周りを触る機会が増えて、とうとうterraformまで触るようになりました。
見よう見まねでterraformコマンドを実行している時に「これって頻繁に使う割に実行するのめんどくさいし、スクリプトにしちゃえばいいのでは?」と思って作ったやつを、せっかくなので公開してみようというコンセプトの記事になります。
めちゃくちゃterraform初学者が書いてるので、もっといいやり方をご存知の方がいたら是非コメントで教えてください🙏

対象者

  • terraform importの書式を毎回忘れちゃう方
  • terraform import済みかどうかわからなくなってモヤモヤしたことのある方

この記事に出てくる用語

用語 説明
HCL terraformのコード
ローカルリソースアドレス terraformでリソースを定義するためのアドレス
例:module.vpc.google_compute_network.vpc
リソースURL リソースの正式名みたいなもの。公式ドキュメントから確認できる
例:projects/{{project}}/locations/{{location}}/services/{{name}}

初学者すぎて正式な用語が使えているかわからないのですが、もし間違ってたり一般的じゃなかったりしたらコメントで教えていただけると嬉しいです、、

前提

作ったスクリプトは、以下のようなディレクトリ構成で動作しています。
もしコピペして使ってみたいという方は、ご自身の環境のディレクトリ構成に合わせて多少スクリプトの修正が必要になる場合があるのでご注意ください。

environments/
├ prod/
│ └ main.tf
└ stg/
  └ main.tf
modules/
└ vpc/
  └ main.tf
scripts/
└ import.sh

scripts/import.shが今回紹介するスクリプトです。

environments/prodとenvironments/stgのmain.tfは、以下のようなコードになる想定です。

environments/prod/main.tf
provider "google" {
  project = "test-project"
  region  = "asia-northeast1"
}

module "vpc" {
  source     = "../../modules/vpc"
  # 省略
}

modulesのmain.tfは、以下のようなコードになる想定です。

modules/vpc/main.tf
resource "google_compute_network" "vpc" {
  name                    = "hogehoge"
  auto_create_subnetworks = false
  project                 = "test-project"
}

本題

以下が、作ったスクリプトです。

import.sh
#!/bin/bash

APP_ENV="${1:?USAGE: $0 <prod|stg>}"
PROJECT_ID='test-project'
REGION='asia-northeast1'
ZONE='asia-northeast1-a'

terraform -chdir="environments/${APP_ENV}" init

import_if_missing () {
  local addr="$1"
  local id="$2"

  if terraform -chdir="environments/${APP_ENV}" state list | fgrep -x "$addr"; then
    echo "Skipping import of $addr, already in state"
  else
    terraform -chdir="environments/${APP_ENV}" import "$addr" "$id"
  fi
}

# import_if_missing()の使い方の例
import_if_missing module.vpc.google_compute_network.vpc "projects/${PROJECT_ID}/global/networks/hogehoge"

メインはimport_if_missing()関数です。
この関数が、terraform importを実行しています。

実行する時は以下のようにします。

# environments/prodに対して実行したい時
$ . scripts/import.sh prod

# environments/stgに対して実行したい時
$ . scripts/import.sh stg

実行結果は以下のような感じになると思います。

$ . scripts/import.sh prod

Initializing the backend...
Initializing modules...

Initializing provider plugins...
- Reusing previous version of hashicorp/google from the dependency lock file
- Reusing previous version of hashicorp/google-beta from the dependency lock file
- Using previously-installed hashicorp/google v6.38.0
- Using previously-installed hashicorp/google-beta v7.1.1

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.
# ↓importされてなかったリソース(terraform importを実行してる)
module.load_balancer.google_compute_region_network_endpoint_group.cloud_run_neg_ws: Import prepared!
  Prepared google_compute_region_network_endpoint_group for import
module.load_balancer.google_compute_region_network_endpoint_group.cloud_run_neg_ws: Refreshing state... [id=******]
data.google_project.project: Read complete after 2s [id=******]
# ↓import済みだったリソース(スキップされてる)
module.vpc.google_compute_network.vpc
Skipping import of module.vpc.google_compute_network.vpc, already in state

このスクリプトは何をしているの?

上から見ていきます。

APP_ENV="${1:?USAGE: $0 <prod|stg>}"
PROJECT_ID="test-project"
REGION='asia-northeast1'
ZONE='asia-northeast1-a'

ここは、環境変数を設定しています。
APP_ENVは、スクリプト実行時の引数を使うので、${1:?USAGE: $0 <prod|stg>}として必ずprodかstgのどちらかが来るようにしています。

terraform -chdir="environments/${APP_ENV}" init

ここでterraform initしています。
-chdirは、コマンドを実行するディレクトリを指定しています。

terraform initについての解説はここではしませんので、初心者の方は公式ドキュメントなどを読んでみてください。
これ以降に出てくるterraformコマンドについても同様に解説はしませんので、ご了承ください。

import_if_missing () {
  local addr="$1"
  local id="$2"

  if terraform -chdir="environments/${APP_ENV}" state list | fgrep -x "$addr"; then
    echo "Skipping import of $addr, already in state"
  else
    terraform -chdir="environments/${APP_ENV}" import "$addr" "$id"
  fi
}

先ほども書きましたが、ここがこのスクリプトの肝です。
ここでは、「まだterraform importされていないリソースにのみterraform importを実行し、そうでなければスキップする」という関数の定義をしています。

以下は、解説コメントを入れたバージョンの同じ関数です。

import_if_missing () {
  local addr="$1"  # ローカルリソースアドレス
  local id="$2"    # リソースURL

  # `terraform state list`コマンドでimport済みのローカルリソースアドレス一覧を表示し、
  # その中に$addrと同じものがあればimport済みなのでスキップ
  if terraform -chdir="environments/${APP_ENV}" state list | fgrep -x "$addr"; then
    echo "Skipping import of $addr, already in state"
  else
    terraform -chdir="environments/${APP_ENV}" import "$addr" "$id"
  fi
}

実際に何度かterraform importしたことある方はご存知だと思いますが、すでにimport済みのリソースに対して実行してしまうと「もうimportされてるよ!」というエラーが出ます。
どれをimportしたか覚えてたら問題ないんですが、管理するリソースの数が増えたり減ったりしてガチャガチャやっているうちに大体よくわからなくなります。
こういう時、terraform state listというコマンドを実行すると、現時点でimportされているリソースの一覧が確認できるんですが、そもそもリソースの数が多いと探すのも大変です。

エラーが気にならないなら何度でもterraform importすれば済む話ですが、個人的何度もそれをやってるとウザくなってきてしまうんですね、、
そこで、
「import済みか確認」→「importされてなければterraform importを実行」or「import済みならスキップ」
という流れを自動化できたら楽だなと思いました。

その後は簡単で、terraform state listの実行結果に対してローカルリソースアドレスでfgrepすればimport済みかどうか判定できるので、それを利用した条件分岐でいい感じに自動化できたというところです。

ちなみに、現在の僕の環境では、import.shは以下のようになっています。
管理してるリソースの数が多いので、素でterraform importしてると結構しんどかったです。


最後に

もっと便利にもできるかなと思うんですが、個人的にはこれくらいシンプルな方が小回りがきいて使いやすいよな〜というのが念頭にあったので、こういったスクリプトになりました。

本文ではスクリプトを普通に実行することだけ紹介しましたが、僕は普段terraform apply -target=hogehogeのように-targetオプションを使いたい時に、hogehogeの部分(ローカルリソースアドレス)のコピペ用としても使ってます。

カラビナテクノロジー デベロッパーブログ

Discussion