はじめに
業務でCloudAutomatorをTerraformで管理している際に遭遇したパニックエラーを調査し、最終的にOSSへのコントリビュート(PR #142)に至った経験を共有します。
正直に言うと、Go言語の経験はほぼゼロです。Hello Worldを書いたことがある程度。それでも、Claude CodeとCursorというAIツールを活用することで、バグの原因特定から修正、PRの作成まで完遂できました。
この記事では、AIを活用して未経験言語のOSSにコントリビュートした過程も含めて紹介します。
発生した問題
terragrunt planを実行した際、以下のパニックエラーが発生しました。
panic: interface conversion: interface {} is nil, not map[string]interface {}
goroutine 81 [running]:
terraform-provider-cloudautomator/internal/provider.buildActionValue(0x14000527320, 0x140001869a0)
terraform-provider-cloudautomator/internal/provider/resource_job.go:989 +0x49c
terraform-provider-cloudautomator/internal/provider.resourceJobRead(...)
terraform-provider-cloudautomator/internal/provider/resource_job.go:841 +0x648
対象のリソースはaction_type = "bulk_delete_images"のジョブで、CloudAutomator UI上では正しく設定されているにもかかわらず、Terraform stateではbulk_delete_images_action_value {}と空ブロックとして表示されていました。
原因調査
Claude Codeでの初期分析
パニックエラーに遭遇した際、まずClaude Codeにエラーメッセージとスタックトレースを渡して分析を依頼しました。
Claude Codeは以下の点を指摘してくれました
-
interface conversion: interface {} is nil→ nilを型アサーションしようとしている -
resource_job.go:989→ プロバイダーの特定の関数で発生 - 「APIレスポンスは正常で、プロバイダー側のパース処理にバグがある可能性が高い」
この初期分析により、問題の切り分け方針(API側 vs プロバイダー側)が明確になりました。
APIレスポンスの確認
Claude Codeの指摘に従い、まずCloudAutomator APIを直接叩いてレスポンスを確認しました。
curl -s -X GET \
"https://manager.cloudautomator.com/api/v1/jobs/{JOB_ID}" \
-H "Authorization: Bearer ${API_KEY}" \
-H "Content-Type: application/json" | jq '.data.attributes.action_value'
結果、APIレスポンスには正しくデータが含まれていることが判明。
{
"before_days": 15,
"specify_base_date": "before_days",
"exclude_by_tag_bulk_delete_images": true,
"exclude_by_tag_key_bulk_delete_images": "autoDelete",
"exclude_by_tag_value_bulk_delete_images": "false"
}
APIは正常 → 問題はプロバイダー側にあることが特定できました。
プロバイダーコードの解析(Cursorとの対話)
ここからはCursorを使って、プロバイダーのソースコードを読み解いていきました。
Go言語の知見がほぼないため、以下のような質問をCursorに投げながら理解を深めました
- 「この
.(map[string]interface{})って何をしている?」 - 「
d.Get()は何を返す?nilになるケースは?」 - 「Terraform Providerの
Read関数はいつ呼ばれる?」
Cursorは該当コードの文脈を理解した上で、Go言語の型アサーションの仕組みやTerraform SDKの動作を丁寧に説明してくれました。
terraform-provider-cloudautomatorのソースコードを確認したところ、buildActionValue関数に問題を発見しました。
問題の構造
-
resourceJobRead関数が既存リソースを読み取る際、buildActionValue関数を呼び出す -
buildActionValue関数はTerraform stateから値を取得しようとする - 初回Read時はstateに値が設定されていないため、
nilが返される -
nilをmap[string]interface{}にキャストしようとしてパニック発生
// 問題のあったコード(概念)
func buildActionValue(d *schema.ResourceData, job *cloudautomator.Job) {
// Terraform stateから取得を試みる
actionValue := d.Get("bulk_delete_images_action_value")
// nilチェックなしで型アサーション → パニック!
actionMap := actionValue.(map[string]interface{})
}
修正内容
Cursorと一緒に修正方針を検討
問題の原因が特定できたところで、Cursorに修正方針を相談しました。
「stateに値がない場合にnilになるのが問題なら、どう修正すべき?」と聞いたところ、以下の方針を提案してくれました
- まずTerraform stateから取得を試みる
- stateに値がなければ、APIレスポンスから取得する
- どちらもnilなら安全に処理をスキップする
Go言語の経験がない自分でも、Cursorが提案してくれたコードの意図を理解し、既存コードとの整合性を確認しながら修正を進めることができました。
修正のポイント
- Terraform stateから取得を試みる(Update時などに有効)
- stateに値がない場合は、APIレスポンス(
job.ActionValue)から取得する job.ActionValueがnilの場合のチェックを追加
// 修正後のコード(概念)
func buildActionValue(d *schema.ResourceData, job *cloudautomator.Job) {
// 1. Terraform stateから取得を試みる
actionValue := d.Get("bulk_delete_images_action_value")
// 2. stateに値がない場合、APIレスポンスから取得
if actionValue == nil || len(actionValue.([]interface{})) == 0 {
if job.ActionValue != nil {
actionValue = job.ActionValue
}
}
// 3. nilチェック後に処理続行
if actionValue == nil {
return
}
// 安全に型アサーション
actionMap := actionValue.(map[string]interface{})
// ...
}
検証
修正後、以下のテストを実施しました。
| テスト項目 | 結果 |
|---|---|
| ユニットテスト | ✅ 全件成功 |
| 既存テストへの影響 | ✅ なし |
| リンターエラー | ✅ なし |
実環境でのterragrunt plan
|
✅ 正常実行 |
PRの結果
PR #142を作成したところ、メンテナーの方から以下のフィードバックをいただきました。
コードを見て気になったので手元で確認したところ、bulk_delete_images 以外のアクションでも同様の問題が再現しました。いただいた修正で他のアクションの問題も合わせて解決することを確認しましたので、このままマージさせていただきます 👍
実は他のアクションタイプでも同様の問題が潜在していたことが判明し、この修正で一括して解決できました。
PRはマージされ、v0.5.2としてリリースされました 🎉
学んだこと
1. AIを活用すれば未経験言語でもコントリビュートできる
今回最も実感したのは、AIツールを適切に活用すれば、未経験の言語でもOSSにコントリビュートできるということです。
- Claude Code: エラー分析、問題の切り分け方針の提案
- Cursor: コードの読解、修正方針の検討、Go言語の文法解説
ただし、AIの提案を鵜呑みにするのではなく、自分で理解した上で修正することが重要です。「なぜこの修正が必要なのか」「既存の処理フローとどう整合するのか」を自分の言葉で説明できるレベルまで理解してからPRを出しました。
2. パニックスタックトレースの読み方
Go言語のパニックスタックトレースは、問題の発生箇所を特定する重要な手がかりになります。resource_job.go:989という情報から、該当のコードを直接確認できました。
3. Terraform Providerの読み取りフロー
Terraform ProviderのRead操作では、stateとAPIレスポンスの両方を考慮する必要があることを理解しました。
- Create/Update後のRead: stateに値がある
- Import時のRead: stateに値がない(APIから取得が必要)
- 既存リソースの初回Read: stateに値がない場合がある
4. OSSコントリビュートの流れ
- 問題を再現・調査
- 原因を特定
- ローカルで修正・テスト
- PRを作成(問題・原因・修正内容を明確に記載)
- メンテナーのレビュー
- マージ → リリース
特に、PRの説明文には問題・原因・修正内容・テスト結果を明確に記載することで、レビュアーの負担を軽減できます。
まとめ
業務で遭遇したバグをきっかけに、Go言語未経験ながらOSSへのコントリビュートを行うことができました。
- AI活用のコツ: Claude Codeで問題を分析し、Cursorでコードを読み解く。役割分担が効果的
- 調査のコツ:APIレスポンスとプロバイダーの処理を分けて検証
- 修正のコツ:nilチェックとフォールバック処理の追加
- PRのコツ:問題・原因・修正内容・テスト結果を明確に記載
「その言語を知らないから」と諦める必要はありません。AIを活用しつつ、自分で理解することを怠らなければ、未経験言語のOSSにも貢献できます。
今回の修正は小さなものでしたが、同じ問題で困っている人の助けになれば幸いです。
