冒頭まとめ

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 variablevar.*に対応するvariableブロック
Reference to undeclared modulemodule.*に対応するmoduleブロック
Missing resource instance keycountの番号、または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のバージョンと環境を確認したうえで行ってください。