Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/4322.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-data-source
tencentcloud_teo_billing_data
```
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ require (
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.3.105
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cloudaudit v1.0.1033
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cls v1.3.131
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.132
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.136
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/config v1.3.80
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.3.130
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cwp v1.3.30
Expand Down Expand Up @@ -93,7 +93,7 @@ require (
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tdcpg v1.0.533
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tdmq v1.3.113
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tem v1.0.578
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.3.121
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.3.136
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tke v1.3.107
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/trocket v1.1.0
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tse v1.0.857
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -996,16 +996,16 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.112/go.mod
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.113/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.115/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.118/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.121/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.122/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.123/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.124/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.125/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.127/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.130/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.131/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.132 h1:42RfJnwzMprfKEJrM9fyhHu1jXyg8xvSJ1A1qb9Qun8=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.132/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.136 h1:06MPEiR1oZzRyNux2jshwMh67C0Zp4kRWvRfj46EcYs=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.136/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/config v1.3.80 h1:chnsNBeJn3MieFLki4hpbzoml5NiTvLVzOTqYRVxQho=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/config v1.3.80/go.mod h1:B/ezGtlvDhZlleNM+2QdvZccrUi+J+fN6vMnDDsZnuM=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/controlcenter v1.1.51 h1:pGwrfCBBCt1u+EDHwfNj9NLQpvk5MVKVMcsE7SvwqM4=
Expand Down Expand Up @@ -1121,8 +1121,8 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tdmq v1.3.113 h1:2sulWz
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tdmq v1.3.113/go.mod h1:uCUT6rfNap46k8JHzdPIz3WvYjiQPDkOBZ+b2GrIpUE=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tem v1.0.578 h1:vBpQhUroO+FAslUmsDWGi8nvczsqZBWVgQwlnyT0Aj8=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tem v1.0.578/go.mod h1:UlojGQh/9wb7/uXPNi7PvMral1CNAskVDNgqJEV83l0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.3.121 h1:SdW1CsEOUSABdz3YHXSw7nESExvD4fR43ZfBFYaD2OQ=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.3.121/go.mod h1:r8LzDVp59qZs+Pe65TiXnGKZSu05QB7cYd6XpK6XZ0s=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.3.136 h1:rgjFH09Z/PH6v7C6uum5pqli+d3AxzSH1gdhqLYiw1s=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.3.136/go.mod h1:hROu6uk1E9l58lfZKQ15BEb9EF/rc3PP8Uxo1M7WdJg=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/thpc v1.0.998 h1:f4/n0dVKQTD06xJ84B5asHViNJHrZmGojdAWEPIsITM=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/thpc v1.0.998/go.mod h1:fyi/HUwCwVe2NCCCjz8k/C5GwPu3QazCZO+OBJ3MhLk=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tke v1.3.107 h1:Yky7eL8FnkO89MvxERN3tniBQjstZzjIX8I91A4la/k=
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-07-20
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
## Context

Terraform Provider for TencentCloud 通过 `tencentcloud-sdk-go` 调用云 API 管理腾讯云资源。EdgeOne (TEO) 服务已存在多个数据源(如 `tencentcloud_teo_zones`、`tencentcloud_teo_plans` 等),均位于 `tencentcloud/services/teo/` 目录下,遵循 `data_source_tc_teo_<name>.go` 命名规范。

当前 TEO 数据源覆盖了站点、套餐、配置组、安全模板等查询场景,但尚未覆盖计费数据查询。云 API `DescribeBillingData`(teo v20220901)提供了按时间范围、站点、指标、时间粒度、过滤条件、分组维度查询计费用的能力,返回数据点列表(`BillingData`),每个数据点包含时间戳、数值、站点 ID、域名、四层代理实例 ID、计费大区 ID 等字段。

本变更新增 `tencentcloud_teo_billing_data` 数据源(RESOURCE_KIND_DATASOURCE),封装该 API 的查询能力。该数据源为只读查询,不涉及任何资源创建/更新/删除操作。

### 云 API 结构(来自 vendor 校验)

- 请求 `DescribeBillingDataRequest`:
- `StartTime *string`(起始时间)
- `EndTime *string`(结束时间,范围 ≤ 31 天)
- `ZoneIds []*string`(站点 ID 集合,必填,最多 100 个;`*` 表示账号级别)
- `MetricName *string`(指标名)
- `Interval *string`(时间粒度:`5min` / `hour` / `day`)
- `Filters []*BillingDataFilter`(过滤条件,每项含 `Type`、`Value`)
- `GroupBy []*string`(分组维度:`zone-id` / `host` / `proxy-id` / `region-id`,最多两个)
- 响应 `DescribeBillingDataResponse`:
- `Response.Data []*BillingData`(数据点列表,可能返回 null)
- `BillingData` 结构:`Time *string`、`Value *uint64`、`ZoneId *string`、`Host *string`、`ProxyId *string`、`RegionId *string`

该接口为同步接口(无异步轮询需求),无分页字段。

## Goals / Non-Goals

**Goals:**
- 新增 `tencentcloud_teo_billing_data` 数据源,支持通过 `DescribeBillingData` API 查询 TEO 计费数据。
- 完整映射入参(StartTime/EndTime/ZoneIds/MetricName/Interval/Filters/GroupBy)与出参(Data 列表展开字段)。
- 在 `provider.go` 和 `provider.md` 中注册数据源。
- 提供基于 gomonkey mock 的单元测试(不使用 Terraform 测试套件)。
- 生成 `.md` 文档样例。

**Non-Goals:**
- 不实现计费数据的写入、修改或删除(该 API 仅为查询接口)。
- 不对接其他计费相关 API。
- 不变更任何现有 TEO 数据源/资源的 schema 或行为。
- 不处理异步轮询(本接口为同步接口)。
- 不暴露分页参数(该 API 无分页字段)。

## Decisions

### 决策 1:数据源 ID 生成方式

数据源为纯查询接口,无服务端持久化 ID。采用查询关键参数组合生成确定性 ID,格式为 `metric_name#start_time#end_time`,使用 `tccommon.FIELD_SP`(`#`)作为分隔符。

**理由**:与项目中其他查询型数据源的惯例一致;该组合在单次 `terraform plan/apply` 范围内稳定,满足 Terraform state 标识需求。

**备选方案**:使用 `RequestId`。否决,因为每次查询 RequestId 不同,会导致 state 频繁变更。

### 决策 2:出参 data 列表字段展开

按照代码生成规范要求,`response.Response.Data` 是一个列表,将列表中元素的字段(`time`、`value`、`zone_id`、`host`、`proxy_id`、`region_id`)平铺到 `data` 这个 list 类型的 schema 中,每个元素为一个 object,object 内部包含上述字段。不在 schema 顶层再嵌套一层"列表型数据"包裹结构。

**理由**:遵循项目规范,使每个字段都可被 Terraform 单独 set/read,且与云 API 返回结构直接对应。

### 决策 3:空响应处理

在 Read 方法的 retry 块内,若云 API 返回空(`response == nil` / `response.Response == nil`),不直接 `d.SetId("")`,而是返回 `NonRetryableError`,让外层 retry 继续尝试,并在 retry 失败路径上保留 `log.Printf("[DATASOURCE] read empty, skip SetId")` 提示。

**理由**:遵循 DATASOURCE 资源代码生成规范第 14 条,避免因云 API 短暂波动导致本地 state 中的 id 被清空,造成数据丢失。

### 决策 4:filters 参数结构

`filters` 为 list of object,每个 object 含 `type`(string)和 `value`(string),与云 API `BillingDataFilter` 结构一一对应。设为 Optional。

### 决策 5:retry 与超时

调用 `DescribeBillingData` 时使用 `tccommon.ReadRetryTimeout` 作为超时时间,通过 `helper.Retry()` 包装。失败时使用 `tccommon.RetryError()` 包装错误返回。retry 块内仅执行 API 调用,不执行 set 等操作。

### 决策 6:单元测试方式

使用 gomonkey 对 `DescribeBillingData` 进行 mock,仅测试业务逻辑(参数组装、响应解析、字段 set),不使用 Terraform 测试套件,不依赖真实云环境。使用 `go test -gcflags=all=-l` 运行。

## Risks / Trade-offs

- **[指标名取值范围广]** → `metric_name` 为自由字符串,云 API 会在服务端校验取值。Terraform schema 不做枚举限制,避免与云 API 取值变化不同步;非法值由云 API 返回错误,经 `RetryError` 透传给用户。
- **[ZoneIds 用 `*` 表示账号级别]** → 保留原样透传,文档中说明该特殊用法。
- **[Data 可能为 null]** → Read 中对 `Data == nil` 进行判空处理,set 空列表而非报错,保证查询无数据时 Terraform 不报错。
- **[查询时间范围限制 31 天]** → 由云 API 服务端校验,schema 层不强加校验,错误经 retry 包装透传。
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
## Why

Terraform Provider for TencentCloud 当前不支持查询 EdgeOne (TEO) 的计费数据。用户在以基础设施即代码方式管理 EdgeOne 站点时,无法通过 Terraform 数据源获取流量、带宽、请求数等计费指标数据,导致需要在 Terraform 之外使用额外工具来核对用量。新增 `tencentcloud_teo_billing_data` 数据源可填补这一空白,使用户能在 Terraform 中直接查询计费数据并用于配置编排或对账。

## What Changes

- 新增数据源 `tencentcloud_teo_billing_data`(RESOURCE_KIND_DATASOURCE),通过调用云 API `DescribeBillingData`(teo v20220901)读取计费数据。
- 新增资源代码文件 `tencentcloud/services/teo/data_source_tc_teo_billing_data.go`,实现数据源 Read 逻辑(仅查询,不涉及创建/更新/删除)。
- 新增单元测试文件 `tencentcloud/services/teo/data_source_tc_teo_billing_data_test.go`,使用 gomonkey mock 云 API 进行业务逻辑测试。
- 新增文档样例文件 `tencentcloud/services/teo/data_source_tc_teo_billing_data.md`。
- 在 `tencentcloud/provider.go` 和 `tencentcloud/provider.md` 中注册该数据源。

### Schema 参数说明

入参(均为查询条件,Optional):
- `start_time` (string, Required): 起始时间。
- `end_time` (string, Required): 结束时间,查询范围需 ≤ 31 天。
- `zone_ids` (list of string, Required): 站点 ID 集合,最多 100 个;用 `*` 表示查询账号级别数据。
- `metric_name` (string, Required): 计费指标名,如 `acc_flux`、`acc_bandwidth` 等。
- `interval` (string, Optional): 查询时间粒度,取值 `5min` / `hour` / `day`。
- `filters` (list of object, Optional): 过滤条件,每项包含 `type` 与 `value`,支持 `host`、`proxy-id`、`region-id`。
- `group_by` (list of string, Optional): 分组聚合维度,取值 `zone-id`、`host`、`proxy-id`、`region-id`,最多两个维度。

出参:
- `data` (list of object, Computed): 计费数据点列表,每项包含 `time`、`value`、`zone_id`、`host`、`proxy_id`、`region_id`。
- `id` (string, Computed): 数据源 ID,由查询参数组合生成,用于 Terraform 状态标识。

## Capabilities

### New Capabilities
- `teo-billing-data`: 查询 EdgeOne 计费数据的数据源能力,封装 `DescribeBillingData` 云 API,支持按时间范围、站点、指标、时间粒度、过滤条件、分组维度查询用量数据。

### Modified Capabilities
<!-- 无 -->

## Impact

- 新增代码:`data_source_tc_teo_billing_data.go`、`data_source_tc_teo_billing_data_test.go`、`data_source_tc_teo_billing_data.md`。
- 修改文件:`tencentcloud/provider.go`(注册数据源)、`tencentcloud/provider.md`(文档注册)。
- 依赖:使用 vendor 目录下已有的 `github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo/v20220901` 包,无需新增第三方依赖。
- 不影响任何现有资源/数据源的 schema 与行为,向后完全兼容。
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
## ADDED Requirements

### Requirement: 数据源注册与声明

系统 SHALL 在 Terraform Provider 中注册名为 `tencentcloud_teo_billing_data` 的数据源,类型为 RESOURCE_KIND_DATASOURCE,并在 `tencentcloud/provider.go` 中通过 `dataProvider` 工厂注册,同时在 `tencentcloud/provider.md` 中声明对应文档条目。

#### Scenario: Provider 中注册数据源
- **WHEN** 用户在 Terraform 配置中声明 `data "tencentcloud_teo_billing_data" "example"`
- **THEN** Provider 能识别该数据源类型并调用其 Read 函数

#### Scenario: 文档注册
- **WHEN** 执行 `make doc` 文档生成
- **THEN** `provider.md` 中包含 `tencentcloud_teo_billing_data` 数据源条目

### Requirement: 查询计费数据

数据源 SHALL 调用云 API `DescribeBillingData`(teo v20220901)查询 EdgeOne 计费数据,将入参映射到请求字段,将响应 `Data` 列表映射到 `data` 输出参数。

#### Scenario: 带完整参数查询成功
- **WHEN** 用户配置 `start_time`、`end_time`、`zone_ids`、`metric_name`、`interval`、`filters`、`group_by` 并执行 `terraform plan`
- **THEN** 数据源将参数组装为 `DescribeBillingDataRequest` 调用云 API,并将响应中 `Data` 列表每项的 `Time`、`Value`、`ZoneId`、`Host`、`ProxyId`、`RegionId` 映射到 `data` 列表输出字段 `time`、`value`、`zone_id`、`host`、`proxy_id`、`region_id`

#### Scenario: 仅必填参数查询成功
- **WHEN** 用户仅配置 `start_time`、`end_time`、`zone_ids`、`metric_name`
- **THEN** 数据源以 Optional 参数为空调用云 API,仍能正确返回数据并设置 `data` 输出

#### Scenario: 查询返回空数据
- **WHEN** 云 API 返回 `Data` 为 null 或空列表
- **THEN** 数据源将 `data` 设置为空列表,且不报错

### Requirement: 入参 Schema 定义

数据源 SHALL 定义以下入参 schema 字段:
- `start_time`(string, Required):起始时间。
- `end_time`(string, Required):结束时间,查询范围 ≤ 31 天。
- `zone_ids`(list of string, Required):站点 ID 集合,最多 100 个;`*` 表示账号级别。
- `metric_name`(string, Required):计费指标名。
- `interval`(string, Optional):查询时间粒度,取值 `5min` / `hour` / `day`。
- `filters`(list of object, Optional):过滤条件,每项含 `type`(string, Required)、`value`(string, Required)。
- `group_by`(list of string, Optional):分组聚合维度,取值 `zone-id` / `host` / `proxy-id` / `region-id`,最多两个维度。

#### Scenario: Required 字段缺失
- **WHEN** 用户配置缺少 `start_time` 或 `metric_name` 等必填字段
- **THEN** Terraform 在 plan 阶段报错提示字段缺失

#### Scenario: filters 结构
- **WHEN** 用户配置 `filters` 包含多个 `{ type = "host", value = "test.example.com" }` 项
- **THEN** 数据源将每个项映射为 `BillingDataFilter`(`Type`、`Value`)并传入请求 `Filters` 字段

### Requirement: 出参 Schema 定义

数据源 SHALL 定义以下出参 schema 字段:
- `id`(string, Computed):数据源 ID,由查询参数组合生成。
- `data`(list of object, Computed):计费数据点列表,每个 object 含 `time`(string)、`value`(integer)、`zone_id`(string)、`host`(string)、`proxy_id`(string)、`region_id`(string)。

#### Scenario: id 生成
- **WHEN** 数据源 Read 成功执行
- **THEN** `id` 被设置为 `metric_name#start_time#end_time` 格式(使用 `FIELD_SP` 分隔符)

#### Scenario: data 字段展开
- **WHEN** 云 API 返回 `Data` 列表包含多个 `BillingData` 项
- **THEN** `data` 输出列表每项对应一个 `BillingData`,且字段 `Time`/`Value`/`ZoneId`/`Host`/`ProxyId`/`RegionId` 分别映射到 `time`/`value`/`zone_id`/`host`/`proxy_id`/`region_id`

### Requirement: 重试与错误处理

数据源 Read 调用 `DescribeBillingData` 时 SHALL 使用 `tccommon.ReadRetryTimeout` 作为超时时间,通过 `helper.Retry()` 包装;失败时使用 `tccommon.RetryError()` 包装错误返回。retry 块内仅执行 API 调用,不执行 set 等操作。

#### Scenario: 云 API 短暂失败重试
- **WHEN** `DescribeBillingData` 调用返回临时性错误
- **THEN** 数据源在 `ReadRetryTimeout` 内重试调用,直至成功或超时

### Requirement: 空响应保护

数据源 Read 的 retry 块内 SHALL 检查云 API 返回是否为空(`response == nil` 或 `response.Response == nil`),若为空则返回 `NonRetryableError` 而非直接 `d.SetId("")`,并在 retry 失败路径保留 `log.Printf("[DATASOURCE] read empty, skip SetId")` 提示。

#### Scenario: 云 API 返回 nil 响应
- **WHEN** `DescribeBillingData` 返回 `response == nil` 或 `response.Response == nil`
- **THEN** 数据源返回 `NonRetryableError`,不清空 state 中的 id,并在日志中打印 `[DATASOURCE] read empty, skip SetId` 提示

### Requirement: 单元测试

数据源 SHALL 提供单元测试文件 `data_source_tc_teo_billing_data_test.go`,使用 gomonkey mock `DescribeBillingData` 云 API,测试业务逻辑(参数组装、响应解析、字段 set),不使用 Terraform 测试套件,通过 `go test -gcflags=all=-l` 运行。

#### Scenario: mock 云 API 返回数据并验证字段映射
- **WHEN** 运行单元测试,gomonkey mock `DescribeBillingData` 返回包含多个 `BillingData` 项的响应
- **THEN** 测试验证 Read 函数正确组装请求参数并将响应字段设置到 `data` 输出

#### Scenario: mock 云 API 返回空数据
- **WHEN** 运行单元测试,gomonkey mock `DescribeBillingData` 返回 `Data` 为空
- **THEN** 测试验证 Read 函数将 `data` 设置为空列表且不报错
Loading
Loading