冒頭まとめ
terraform planやterraform validateで次のエラーが出た場合、参照先のリソースが現在のモジュールに宣言されていません。
Error: Reference to undeclared resource
A managed resource "aws_security_group" "main" has not been declared in the root module.
最初に、説明文の型名aws_security_groupとラベル名mainを確認してください。同じモジュール内にresource "aws_security_group" "main"という宣言があるかを探します。ラベルのタイプミス、data.の付け忘れ、別モジュールのリソースを直接参照していることが主な確認点です。
クラウド上やstateにリソースが存在していても、設定内の宣言の代わりにはなりません。参照している式と、その式から見える宣言を確認する必要があります。
エラーメッセージの意味
通常の管理対象リソースは<型名>.<ラベル名>.<属性名>で参照します。たとえばaws_security_group.main.idでは、aws_security_groupが型、mainがresourceブロックのラベル、idが属性です。
ラベルはTerraformの設定内で使う名前です。AWS側の名前を設定するname = "web-sg"などの値とは別なので、そこが一致していても参照は成立しません。
Terraformの参照の公式文書は、管理対象リソース、データソース、モジュール出力を別の形式として説明しています。
| 参照先 | 参照の形式 |
|---|---|
| 管理対象リソース | aws_instance.web.id |
| データソース | data.aws_ami.ubuntu.id |
| 子モジュールの出力 | module.network.vpc_id |
| 入力変数 | var.instance_count |
| ローカル値 | local.common_tags |
本体のevaluate_valid.goでは、参照しているモジュールの設定からリソース宣言を探し、見つからなければこの診断を作ります。stateに記録されているかを調べることで、未宣言の参照を有効にする処理ではありません。
最初に型名・ラベル名・読込範囲を確認する
エラーに表示されたファイルと行は、問題の参照が書かれた場所です。そこから参照先の宣言を探してください。
resource "aws_security_group" "web"しかないのにaws_security_group.main.idを参照していれば、ラベル名が違っています。エディターの検索で、型名とラベル名をそれぞれ確認できます。
宣言が別ファイルにある場合は、そのファイルの場所も確認します。同じディレクトリの.tf・.tf.jsonは一つのモジュールとして読み込まれますが、サブディレクトリのファイルは自動では取り込まれません。この範囲は設定ファイルの公式文書に明記されています。
たとえば、main.tfと同じ場所のresources.tfへ宣言を移すだけなら同じモジュールです。modules/network/main.tfへ移した場合は別モジュールになるため、元の場所から同じ参照を続けることはできません。
CLIでは通常、コマンドを実行したディレクトリがルートモジュールです。ローカルとCIで結果が違う場合は、実行ディレクトリや-chdirの指定、対象ファイルがCIに含まれているかも確認してください。
タイプミスとdata.の付け忘れを直す
タイプミスなら、参照側を実際の宣言に合わせます。次は組み込みリソースterraform_dataを使った説明用の例です。
resource "terraform_data" "web" {
input = "example"
}
output "value" {
# 誤り:mainというラベルの宣言がない
value = terraform_data.main.output
}
宣言をそのまま使う場合は、outputの参照を次のように直します。
output "value" {
value = terraform_data.web.output
}
この例のterraform_dataはTerraform 1.4以降で利用できます。公式文書にあるとおり、外部プロバイダーの設定を必要としないリソースです。
データソースを参照している場合は、冒頭のdata.を確認します。data "aws_ami" "ubuntu"という宣言に対応する参照は、次の形式です。
# 誤り:管理対象リソースとして参照している
# ami = aws_ami.ubuntu.id
# 正しいデータソースの参照
# ami = data.aws_ami.ubuntu.id
これは既存のresourceブロック内に書く引数の抜粋です。data.を省くと、Terraformはresource "aws_ami" "ubuntu"を探します。本体実装には、同名のデータソースがある場合にDid you mean the data resource ...?と案内する処理もあります。候補の表示があるかどうかにかかわらず、宣言の種類を確認してください。
別モジュールのリソースはoutputを経由する
子モジュール内に宣言したリソースは、呼び出し側から直接参照できません。必要な値を子モジュールのoutputで公開します。
次はmodules/example/main.tfに置く子モジュールの例です。
resource "terraform_data" "web" {
input = "example"
}
output "value" {
value = terraform_data.web.output
}
呼び出し側のmain.tfには、moduleブロックと出力への参照を書きます。
module "example" {
source = "./modules/example"
}
output "value" {
value = module.example.value
}
呼び出し側でterraform_data.web.outputと書いても、そのモジュールに宣言がないためエラーになります。module.example.terraform_data.web.outputと内部のパスを連結する方法でもアクセスできません。module.exampleの後ろに指定できるのは、子モジュールが公開した出力名です。
実際のVPCなら、子モジュールでoutput "vpc_id"を宣言し、呼び出し側はmodule.network.vpc_idを参照する形になります。
宣言を削除した場合は残った参照も見直す
resourceブロックを削除した後も、outputや別のリソースに参照が残っていれば停止します。削除が意図どおりなら、その値を使う箇所も削除するか、代わりに使う値へ変更してください。誤って削除したなら宣言を戻します。
未宣言の参照をtry()で囲んでも回避できません。
output "value" {
value = try(terraform_data.missing.output, null)
}
terraform_data.missingの宣言がなければ、この式もエラーになります。tryの公式文書は、未宣言の参照など、評価前に不正と分かる式のエラーは捕捉できないと説明しています。
実際にhashicorp/terraform#24402では、Terraform 0.12.23で未宣言のIAMロールをtry()で参照し、代替値を指定してもReference to undeclared resourceになった報告があります。リソースが不要な環境を作る場合も、宣言そのものを消して参照だけを残す設計ではなく、宣言と利用側の条件を揃える必要があります。
近いエラーとの違い
宣言が見つからない場合と、宣言はあるが参照の方法が違う場合を分けてください。
| エラー | 確認する場所 |
|---|---|
Reference to undeclared resource | 型名・ラベル名に一致するリソース宣言とモジュールの範囲 |
Reference to undeclared input variable | var.*に対応するvariableブロック |
Reference to undeclared module | module.*に対応するmoduleブロック |
Missing resource instance key | countの番号、またはfor_eachのキー指定 |
Unsupported attribute | 参照先のオブジェクトが持つ属性や、子モジュールの出力名 |
Unsupported argument | ブロック内に書いた引数名 |
たとえば、for_eachで宣言したリソースの属性を参照するなら、aws_instance.web["app"].idのようにインスタンスを指定します。宣言があるのにキーを省いて属性へアクセスした場合は、未宣言ではなくMissing resource instance keyの診断になります。集合全体を参照する式では、キーを省くこと自体が誤りとは限りません。
引数名の誤りはTerraformのUnsupported argumentも参照してください。
解決手順のまとめ
エラーの説明文から型名とラベル名を取り出し、現在のモジュール内に同じ宣言があるかを確認します。宣言があるなら参照名とdata.の有無を直し、別モジュールならoutputを経由してください。宣言を削除した場合は、残っている利用側も見直します。
修正後は、対象ディレクトリで設定を検証します。
terraform validate
まだ初期化していない検証用ディレクトリでは、先に次を実行します。
terraform init -backend=false
terraform validate
validateの公式文書は、検証前に必要なモジュールとプロバイダーのインストールが必要で、バックエンドを使わず初期化する場合に-backend=falseを使えると説明しています。validate自体はリモートのリソースを変更しません。
通常の運用環境では、バックエンドなどの初期化を済ませたうえでterraform planも確認してください。検証が通ることと、意図した変更だけが計画されることは別です。この記事のコード例は公式文書と実装を照合した説明用の例で、実行結果は掲載していません。
免責事項:本記事の内容は一般的な情報提供を目的としています。実際の設定変更は、利用しているTerraformのバージョンと環境を確認したうえで行ってください。
この記事でエラーは解決しましたか?