Skip to content

terraform query:批量查询与导入已有资源

代码重构一章中,我们介绍了 import 块的基本用法和 for_each 批量导入模式。但那种方式需要事先知道每个资源的 ID,并手动维护一个 ID 映射表——当需要导入几十甚至上百个资源时,这非常繁琐。

自 Terraform v1.12 起,terraform query 命令和 .tfquery.hcl 配置文件提供了一种全新的工作流:先查询已有基础设施中的资源,再自动生成 import + resource,实现真正的批量导入。

完整工作流

批量查询导入分为四个步骤:

1. 编写查询 (.tfquery.hcl)

2. terraform query  →  查看发现的资源列表

3. terraform query -generate-config-out=generated.tf
       ↓  → 自动生成 import + resource 块
4. 将生成的配置合并到主配置 → terraform apply → 导入完成

.tfquery.hcl 文件与 list 块

查询配置使用 .tfquery.hcl 扩展名,核心构建块是 list。每个 list 块定义一次资源查询:

hcl
# discover.tfquery.hcl

list "aws_s3_bucket" "all_buckets" {
  provider = aws
}

list 块的第一个标签是资源类型,第二个标签是查询名称(在当前目录中唯一)。

list 块属性

属性说明默认值
provider使用哪个 Provider 配置执行查询(必填)
configProvider 特定的过滤条件
limit返回结果的最大数量100
include_resource是否返回完整的资源属性(而非仅标识)false
count创建多个查询实例(与 for_each 互斥)
for_each基于集合创建多个查询实例(与 count 互斥)

使用 config 过滤

config 块中的参数是 Provider 特定的,用于缩小查询范围。以 AWS 为例:

hcl
list "aws_instance" "prod_servers" {
  provider = aws
  limit    = 50

  config {
    filter {
      name   = "tag:Environment"
      values = ["prod"]
    }
    filter {
      name   = "instance-state-name"
      values = ["running"]
    }
  }
}

不同 Provider 支持的 config 参数各不相同,需要查阅对应的 Provider 文档。

使用 for_each 多维查询

代码重构中我们介绍过 import 块的 for_eachlist 块同样支持 for_eachcount。例如,用 count 创建多个查询实例:

hcl
variable "subnet_ids" {
  type = list(string)
}

list "aws_instance" "server" {
  provider = aws
  count    = length(var.subnet_ids)
}

也可以用 for_each 基于集合创建多个查询:

hcl
variable "environments" {
  type    = set(string)
  default = ["prod", "staging"]
}

list "aws_s3_bucket" "by_env" {
  for_each = var.environments
  provider = aws
}

terraform query 命令

基本查询

bash
# 执行查询,将结果打印到终端
terraform query

# JSON 格式输出
terraform query -json

输出包含每个发现的资源的标识信息,格式为 list.<类型>.<标签>

传递变量

如果 .tfquery.hcl 中定义了 variable,可以通过命令行传入:

bash
terraform query -var 'env=prod'
terraform query -var-file=query-vars.tfvars

生成导入配置

这是最核心的功能——自动生成 importresource 块:

bash
terraform query -generate-config-out=generated.tf

Terraform 会:

  1. 执行 .tfquery.hcl 中定义的所有查询
  2. 对每个发现的资源,生成一个 import 块(含资源标识)和一个 resource 块(含当前属性)
  3. 将所有生成的配置写入 generated.tf

WARNING

目标文件不能已存在,否则命令会报错。需要先删除旧文件再重新生成。

命令选项

选项说明
-generate-config-out=FILE生成 import + resource 块到指定文件
-jsonJSON 格式输出
-var 'KEY=VALUE'设置查询变量
-var-file=FILE从文件加载变量
-no-color禁用颜色输出

生成的配置结构

terraform query -generate-config-out 生成的文件内容类似:

hcl
# generated.tf

resource "aws_s3_bucket" "all_buckets_0" {
  bucket        = "my-data-bucket"
  force_destroy = null
  region        = "us-east-1"
  tags          = {}
  tags_all      = {}
}

import {
  to       = aws_s3_bucket.all_buckets_0
  provider = aws
  identity = {
    account_id = ""
    bucket     = "my-data-bucket"
    region     = "us-east-1"
  }
}

resource "aws_s3_bucket" "all_buckets_1" {
  bucket        = "my-logs-bucket"
  # ...
}

import {
  to       = aws_s3_bucket.all_buckets_1
  provider = aws
  identity = {
    bucket     = "my-logs-bucket"
    region     = "us-east-1"
  }
}

注意几个特点:

  • 资源使用数字后缀命名(all_buckets_0all_buckets_1...),而非以资源名为 key
  • import 块使用 identity(而非旧版的 id 参数),这是 Terraform v1.12+ 的新格式
  • resource 块包含所有属性(包括 null 值和空集合),需要手动清理冗余内容

导入资源

将生成的配置合并到主配置中(通常需要手动调整):

  1. 检查生成的配置 — 移除 null 值、空 tagstimeouts 块等冗余属性
  2. 重命名资源 — 生成的 _0_1 后缀不够直观,按需重命名
  3. importresource 块复制到主配置
  4. 执行导入
bash
terraform plan    # 确认显示 "X to import, 0 to add"
terraform apply   # 执行导入
  1. 清理 — 导入完成后可以删除 import 块(或保留作为历史记录)和 generated.tf

与 import 块 for_each 的关系

代码重构中,我们讲过使用 import 块的 for_each 手动批量导入:

hcl
locals {
  buckets = {
    data = "my-data-bucket"
    logs = "my-logs-bucket"
  }
}

import {
  for_each = local.buckets
  to       = aws_s3_bucket.this[each.key]
  id       = each.value
}

这种方式需要你已经知道所有资源的 IDterraform query 的优势在于:

对比import + for_eachterraform query
前提已知所有资源 ID不需要事先知道
发现能力自动发现未管理的资源
配置生成需手动编写 resource自动生成
适用规模少量已知资源大量未知资源
过滤能力手动筛选Provider 原生过滤
版本要求Terraform v1.5+Terraform v1.12+

两者可以结合使用——先用 terraform query 发现资源并生成初始配置,然后根据需要重构为 for_each 模式管理。

参数化查询

.tfquery.hcl 文件支持 variablelocals 块,让查询可复用:

hcl
# discover.tfquery.hcl

variable "environment" {
  type    = string
  default = "prod"
}

variable "region" {
  type    = string
  default = "us-east-1"
}

list "aws_s3_bucket" "env_buckets" {
  provider = aws

  config {
    filter {
      name   = "tag:Environment"
      values = [var.environment]
    }
  }
}
bash
# 查询 prod 环境
terraform query -var 'environment=prod'

# 查询 staging 环境
terraform query -var 'environment=staging'

最佳实践

建议的工作流

  1. 先用 terraform query(不带 -generate-config-out)预览发现的资源数量和类型
  2. 调整 config 过滤条件和 limit,确保查询范围合理
  3. 使用 -generate-config-out 生成配置
  4. 仔细审查生成的配置——移除不需要的资源、调整属性、统一命名
  5. 合并到主配置后先 terraform plan,确认只有 import 操作
  6. 执行 terraform apply 完成导入

版本要求

terraform query 对版本有严格要求:

  • Terraform CLI v1.12 或更高版本
  • Provider 必须实现 resource identity 接口 — 这是 query 功能依赖的底层 API,并非所有 Provider 或资源类型都支持
  • 对于 AWS Provider,需要 v6.x~> 6.0);v5.x 不支持 query 功能

如果你的环境尚未升级到 Terraform v1.12+ 或 AWS Provider v6.x,可以使用 import 块的 for_each 搭配 AWS CLI 手动收集资源 ID 来实现类似效果(参见动手实验第一步)。

其他注意事项

  • include_resource = true 会返回完整属性,可能影响大批量查询的性能
  • 生成的配置可能包含只读属性或由 Provider 计算的值,需要手动清理
  • 导入操作不会影响实际基础设施——只是将状态信息写入 Terraform 状态文件

动手实验