Terraform AWS Provider 数据源 aws_ec2_hosts 完全指南:按条件批量查询 EC2 Dedicated Hosts
【免费下载链接】terraform-provider-awsThe AWS Provider enables Terraform to manage AWS resources.项目地址: https://gitcode.com/GitHub_Trending/te/terraform-provider-aws
本指南围绕 Terraform AWS Provider 提供的aws_ec2_hosts数据源展开,讲解如何通过filter、tags、outpost_arn等参数批量查询 EC2 Dedicated Hosts(专用主机)的 ID 列表,并深入源码层面剖析其底层实现原理(DescribeHostsAPI 调用、分页逻辑与客户端过滤机制)。读完本文,你将能够在 Terraform 配置中精准筛选专用主机,并理解该数据源在 terraform-provider-aws 仓库中的完整实现链路。
数据源概述:什么是aws_ec2_hosts
aws_ec2_hosts是一个框架(Plugin Framework)实现的数据源,其功能是返回一组与给定过滤器匹配的 EC2 Dedicated Host 的 ID 列表。它与单实例查询的数据源(如aws_ec2_host)不同,专为"批量发现"场景设计:例如你需要获取某个可用区、某个实例类型下所有可用状态的专用主机 ID,供后续资源引用或循环创建实例使用。
该数据源的官方说明位于 website/docs/d/ec2_hosts.html.markdown,其 frontmatter 中的描述为"Provides details about multiple EC2 Dedicated Hosts"。关于 Dedicated Host 的更多背景,可参考 AWS 官方 EC2 User Guide 中的 Dedicated Hosts 章节(文档内链接为外部站点,此处仅提示语义)。
示例用法
按实例类型过滤
最常见的用法是同时传入多个filter块,数据源会返回同时满足所有过滤器(AND 语义)的主机 ID:
data "aws_ec2_hosts" "example" { filter { name = "instance-type" values = ["c5.large"] } filter { name = "state" values = ["available"] } }这段配置的含义是:查询所有实例类型为c5.large且状态为available(可用)的 Dedicated Host。查询结果会导出到data.aws_ec2_hosts.example.ids,供其他资源引用。
按 Outpost ARN 过滤
outpost_arn参数用于筛选部署在指定 AWS Outpost 上的主机。需要特别注意的是:DescribeHostsAPI 并不支持将outpost-arn作为服务端过滤条件,因此该参数是在 Provider 内部通过客户端侧过滤实现的:
data "aws_ec2_hosts" "outpost" { outpost_arn = data.aws_outposts_outpost.example.arn filter { name = "state" values = ["available"] } }Argument Reference(参数参考)
以下是aws_ec2_hosts支持的参数,全部为可选:
| 参数 | 是否可选 | 说明 |
|---|---|---|
filter | 可选 | 一个或多个配置块,用于按名称-值过滤。支持的过滤器名称见 AWS EC2 API 参考中DescribeHosts的 Filter 列表。详细说明见下文 |
outpost_arn | 可选 | AWS Outpost 的 ARN。客户端侧过滤,仅返回分配在该 Outpost 上的主机 |
region | 可选 | 该数据源执行查询的 AWS 区域,默认使用 Provider 配置中设置的区域 |
tags | 可选 | 键值对形式的资源标签映射,每一对标签都必须与目标 Dedicated Host 上的标签精确匹配 |
filter块
每个filter配置块支持以下参数:
| 参数 | 是否必填 | 说明 |
|---|---|---|
name | 必填 | 过滤器名称,对应DescribeHostsAPI 支持的过滤字段 |
values | 必填 | 一个或多个过滤器值组成的列表 |
从源码看,filter块被建模为集合嵌套块(SetNestedBlock),其中的name为必填字符串属性,values为必填字符串集合,定义在 internal/service/ec2/filters.go#L136-L152:
func customFiltersBlock(ctx context.Context) datasourceschema.Block { return datasourceschema.SetNestedBlock{ CustomType: fwtypes.NewSetNestedObjectTypeOfcustomFilterModel, NestedObject: datasourceschema.NestedBlockObject{ Attributes: map[string]datasourceschema.Attribute{ names.AttrName: datasourceschema.StringAttribute{Required: true}, names.AttrValues: datasourceschema.SetAttribute{ CustomType: fwtypes.SetOfStringType, ElementType: types.StringType, Required: true, }, }, }, } }值得注意的一个实现细节:values在 schema 中是一个Set(无序集合),而对应的customFilterModel结构体(filters.go#L155-L158)使用fwtypes.SetOfString承载值,这意味着在配置中多个filter块的顺序不影响查询结果。
Attribute Reference(属性参考)
除上述参数外,该数据源还会导出以下属性:
| 属性 | 说明 |
|---|---|
ids | EC2 Dedicated Host 标识符(Host ID)的列表 |
ids在 schema 中被声明为 Computed(计算属性)的字符串列表(ec2_hosts_data_source.go#L37-L41):
names.AttrIDs: schema.ListAttribute{ CustomType: fwtypes.ListOfStringType, ElementType: types.StringType, Computed: true, },源码级实现原理
数据源的框架化注册
该数据源使用 Terraform Plugin Framework 实现,通过@FrameworkDataSource注解注册,对应源码为 internal/service/ec2/ec2_hosts_data_source.go:
// @FrameworkDataSource("aws_ec2_hosts", name="Hosts") func newHostsDataSource(context.Context) (datasource.DataSourceWithConfigure, error) { return &hostsDataSource{}, nil }数据源的模型结构体hostsDataSourceModel继承自framework.WithRegionModel(用于注入region参数),并声明了filter、ids、outpost_arn、tags四个字段(ec2_hosts_data_source.go#L92-L98)。
Read 调用链:从配置到DescribeHosts
数据源的核心逻辑在Read方法(ec2_hosts_data_source.go#L53-L90),完整流程如下:
- 从请求配置中解析出用户定义的过滤器、标签与
outpost_arn; - 通过
d.Meta().EC2Client(ctx)获取 EC2 客户端; - 构造
ec2.DescribeHostsInput,将用户过滤器转换为 API Filter 列表:
input := ec2.DescribeHostsInput{ Filter: append(newCustomFilterListFramework(ctx, data.Filters), newTagFilterList(svcTags(tftags.New(ctx, data.Tags)))...), }这里体现了两个关键转换函数:
newCustomFilterListFramework(filters.go#L209-L236):把 Terraform 侧的filter集合逐条转换为awstypes.Filter{Name, Values},跳过未设置或未知的条目;newTagFilterList(filters.go#L53-L57):把tags映射转换为tag:<Key>格式的过滤器,实现"标签必须精确匹配"的服务端过滤:
func newTagFilterList(tags []awstypes.Tag) []awstypes.Filter { return tfslices.ApplyToAll(tags, func(tag awstypes.Tag) awstypes.Filter { return newFilter("tag:"+aws.ToString(tag.Key), []string{aws.ToString(tag.Value)}) }) }- 调用
findHosts执行查询(详见下文分页逻辑); - 客户端侧过滤 Outpost ARN:由于 API 不支持
outpost-arn服务端过滤,代码在拿到全部主机列表后,用tfslices.Filter逐条比对Host.OutpostArn与用户传入的outpost_arn:
if !data.OutpostARN.IsNull() && !data.OutpostARN.IsUnknown() { outpostARN := data.OutpostARN.ValueString() output = tfslices.Filter(output, func(v awstypes.Host) bool { return aws.ToString(v.OutpostArn) == outpostARN }) }- 最后通过
fwflex.FlattenFrameworkStringValueListOfString把每条主机的HostId展平为字符串列表,写入ids并保存到状态:
data.IDs = fwflex.FlattenFrameworkStringValueListOfString(ctx, tfslices.ApplyToAll(output, func(v awstypes.Host) string { return aws.ToString(v.HostId) }))分页与错误处理:findHosts
查询动作最终落到 internal/service/ec2/find.go#L355-L376 的findHosts函数,它使用 EC2 SDK 的DescribeHostsPaginator自动分页拉取全部结果,避免单次 API 响应大小限制导致数据缺失:
func findHosts(ctx context.Context, conn *ec2.Client, input *ec2.DescribeHostsInput) ([]awstypes.Host, error) { var output []awstypes.Host pages := ec2.NewDescribeHostsPaginator(conn, input) for pages.HasMorePages() { page, err := pages.NextPage(ctx) if tfawserr.ErrCodeEquals(err, errCodeInvalidHostIDNotFound) { return nil, &retry.NotFoundError{LastError: err} } if err != nil { return nil, err } output = append(output, page.Hosts...) } return output, nil }该实现还处理了特殊错误码InvalidHostID.NotFound(定义于 internal/service/ec2/errors.go#L51),将其归一化为retry.NotFoundError,便于上层统一处理"查询对象不存在"的场景。
整体执行流程图
Terraform 配置 (filter / tags / outpost_arn) │ ▼ Read() 解析模型 (hostsDataSourceModel) │ ├──► newCustomFilterListFramework ──► []awstypes.Filter(用户 filter 块) ├──► newTagFilterList ─────────────► []awstypes.Filter(tag:<Key> 格式) │ ▼ DescribeHostsInput ──► findHosts()(DescribeHostsPaginator 分页拉取) │ ▼ 服务端返回全部 Host 列表 │ ▼ outpost_arn 非空?──► tfslices.Filter 客户端侧按 OutpostArn 精确匹配 │ ▼ 展平 HostId ──► ids 属性写入 State测试用例佐证
仓库为aws_ec2_hosts提供了三组接受测试(Acceptance Tests),位于 internal/service/ec2/ec2_hosts_data_source_test.go,可以作为完整可运行的实战模板:
TestAccEC2HostsDataSource_filter:先创建aws_ec2_host资源(实例类型c5.large,指定可用区),再用instance-type+availability-zone两个过滤器查询,断言ids.#大于 0;TestAccEC2HostsDataSource_outpostARN:在 Outpost 上创建主机(实例族r5d),配合aws_outposts_outposts/aws_outposts_outpost数据源获取 Outpost ARN,验证outpost_arn客户端过滤后ids.#恰好为 1;TestAccEC2HostsDataSource_tags:创建带Name标签的主机,然后通过tags参数精确匹配,断言ids.#为 1。
其中 filter 场景的配置片段展示了数据源与资源depends_on的正确配合方式:
resource "aws_ec2_host" "test" { availability_zone = data.aws_availability_zones.available.names[0] instance_type = "c5.large" tags = { Name = "tf-acc-test-..." } } data "aws_ec2_hosts" "test" { filter { name = "instance-type" values = ["c5.large"] } filter { name = "availability-zone" values = [aws_ec2_host.test.availability_zone] } }注意测试中使用了acctest.PreCheckOutpostsOutposts做前置检查(ec2_hosts_data_source_test.go#L41),说明 Outpost 相关用例只有在测试账户具备 Outposts 资源时才可运行——这是实际环境中使用outpost_arn参数前需要确认的前提条件。
实践要点与注意事项
filter与tags可组合使用:两者最终都会转换成DescribeHosts的 Filter 数组合并下发,语义为 AND,即结果必须同时匹配所有条件。outpost_arn是客户端过滤:它发生在 API 返回全部主机之后,因此若账户内主机数量巨大,该参数无法减少 API 数据传输量,仅影响最终返回的ids结果。values支持通配符:从测试配置可见values = ["r5d.*"]这类通配符写法(ec2_hosts_data_source_test.go#L96-L99),这与 EC2 过滤语法保持一致。- 分页自动处理:底层
findHosts使用 Paginator,无需关心主机数量超过单页上限的问题。 - 结果集为空时的表现:当没有任何主机匹配时,
ids为空列表而非报错(除非 API 返回InvalidHostID.NotFound等异常),下游引用ids的for_each等表达式会自然得到空集合,便于做条件化处理。 - 可用区域(region):查询默认在 Provider 配置的区域执行,跨区域查询需显式设置
region参数,与 Provider 区域化管理的整体设计一致。
【免费下载链接】terraform-provider-awsThe AWS Provider enables Terraform to manage AWS resources.项目地址: https://gitcode.com/GitHub_Trending/te/terraform-provider-aws
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考