FOLIO特有のロジックが入ってますが、参考になればと思います。
terraformのディレクトリ構成
FOLIOのterraformはこんなディレクトリ構成になってます。(一部抜粋)
── envs ├── mobile-prod ├── provider.tf ├── ecs ├── iam ├── route53 └── vpc ├── backend.tf ├── provider.tf ├── route_table.tf ├── variables.tf └── vpc.tf ├── mobile-stg ├── sandbox ├── web-prod └── web-stg ── global.tfvars ── backend_tf_template
global.tfvarsは全体の変数定義。(CIDRやIPリストなど)
provider.tfは各環境内の設定です。(subnet_idなど)
planの実行
terraformのオペレーションはMakefileで管理されてます。
下記のコマンドで特定のディレクトリに対してplanが実行できます。
$ make plan CONFIG=envs/mobile-prod/vpc # initializeした後、terraform plan -var-file=global.tfvarsを打ってるだけです
今回作ったもの
- 各ディレクトリを再帰的に検索して、xxx.tfがある場所でmake planを並列実行する
- backend.tfとprovider.tf以外が対象
- 差分がある or 問題が起こったら出力する
- 具体的には、
No changes. Infrastructure is up-to-date
というワードがなかったら出力
- 具体的には、
-n
で並列数を選べる-t
でディレクトリ指定も出来る- 常に差分が発生するものに対しては、ignorePlanで除外出来る
- (そんなに変更するものじゃないので、ハードコーディング)
$ ./check-all-plan --help Usage of ./check-all-plan: -n int 並列数 (default 20) -t string ターゲット。ex: -t mobile-stg $ ./check-all-plan check-all-plan: 2018/09/21 13:49:47 [INFO] Check start...... check-all-plan: 2018/09/21 13:52:37 [INFO] Result: you need check below make plan CONFIG=envs/mobile-stg/kinesis make plan CONFIG=envs/web-prod/elasticache make plan CONFIG=envs/web-prod/teleport make plan CONFIG=envs/web-stg/iam make plan CONFIG=envs/web-prod/instances make plan CONFIG=envs/web-stg/network make plan CONFIG=envs/web-stg/kinesis
ソースコード
package main import ( "flag" "fmt" "log" "os" "os/exec" "path/filepath" "strings" "sync" ) const ( APP = "check-all-plan" ) var ( // parallelNum ... 適当に20並列にしてる。 parallelNum = 20 // target ... envs/mobile-stgのように、ターゲット単位で指定出来るようにしてる target string ) // init ... set flag func init() { flag.IntVar(¶llelNum, "n", 20, "並列数") flag.StringVar(&target, "t", "", "ターゲット。ex: -t mobile-stg") flag.Parse() } func main() { log.SetOutput(os.Stderr) log.SetPrefix(APP + ": ") os.Exit(Run()) } // Run ... 実行 func Run() int { // 結果を格納するslice var results []string // skipするディレクトリ。必要だったら修正してね! ignorePlan := []string{ "sandbox", "web-prod/apps", "web-stg/apps", } // planを実行するdirの取得 targetDirs := GetDir("envs", ignorePlan) // default: 20並列 var m sync.Mutex wg := &sync.WaitGroup{} semaphore := make(chan struct{}, parallelNum) log.Println("[INFO] Check start......") for _, targetdir := range targetDirs { wg.Add(1) semaphore <- struct{}{} go func(d string, m *sync.Mutex) { defer func() { <-semaphore defer wg.Done() }() out := RunTerraformPlan(d) if !strings.Contains(out, "No changes. Infrastructure is up-to-date") { m.Lock() defer m.Unlock() results = append(results, d) } }(targetdir, &m) } wg.Wait() log.Println("[INFO] Result: you need check below") for _, result := range results { fmt.Printf("make plan CONFIG=%s\n", result) } return 0 } // RunTerraformPlan ...make plan CONFIG=xxxxを実行する func RunTerraformPlan(targetDir string) string { dir := fmt.Sprintf("CONFIG=%s", targetDir) // make plan打って差分 or 問題が起こったかを検知したいので、 // ここではエラーハンドリングしない out, _ := exec.Command("make", "plan", dir).Output() return string(out) } // GetDir ...envs/配下の実行対象のディレクトリを取得する func GetDir(parentDir string, ignorePlan []string) []string { var dirs []string err := filepath.Walk(parentDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } // 隠しディレクトリはskip if info.IsDir() && strings.HasPrefix(info.Name(), ".") { return filepath.SkipDir } // ignorePlanに書かれてるディレクトリ以下をskip for _, v := range ignorePlan { if info.IsDir() && path == fmt.Sprintf("envs/%s", v) { return filepath.SkipDir } } // backend.tfとprovider.tf以外のtfを持つディレクトリを抽出 switch info.Name() { case "provider.tf": break case "backend.tf": break default: if strings.HasSuffix(info.Name(), ".tf") { dirs = append(dirs, filepath.Dir(path)) } } return nil }) if err != nil { log.Printf("[WARN] filepath.Walk: %s\n", err) } return getUniqueDirs(dirs) } // getUniqueDirs ...重複したデータを削除。targetが指定されてたら指定されたものだけをチョイス func getUniqueDirs(src []string) []string { dst := make([]string, 0, len(src)) m := make(map[string]bool) for _, s := range src { if _, ok := m[s]; !ok { m[s] = true // targetが指定されたら、指定されたものだけをappendする if target != "" { if strings.HasPrefix(s, fmt.Sprintf("envs/%s", target)) { dst = append(dst, s) } } else { dst = append(dst, s) } } } return dst }