Skip to content
Merged
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/4559.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
resource/tencentcloud_dts_sync_job: change tags from list to set to fix unordered tags response
```
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ require (
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dlc v1.3.182
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.3.124
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/domain v1.0.414
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dts v1.3.153
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dts v1.3.171
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/eb v1.1.30
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/emr v1.3.188
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/es v1.1.13
Expand Down
5 changes: 2 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -987,7 +987,6 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.130/go.mod
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.136/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.144/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.153/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.154/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.157/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.162/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
Expand Down Expand Up @@ -1039,8 +1038,8 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.3.124 h1:atQm
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.3.124/go.mod h1:zxlW9EAn0Hqx6Eub6MMocnHZz/gHundkyI7CdbIhU3o=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/domain v1.0.414 h1:egwjvOEUKBaxsoRVn/YSEhp2E8qdh77Ous9A/wftDo0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/domain v1.0.414/go.mod h1:LRWnBUNRxs/Ee8MzJJd3eHzydlqyB5qmia975Ca/Ox0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dts v1.3.153 h1:CgdZiyjk9h+kcbiPchjiDL1R533c4jHqlV5jmkBVaa4=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dts v1.3.153/go.mod h1:72IuHSGsSGDLZNRd1wDm4UBVIk6GABNCkXkCfpEvtI8=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dts v1.3.171 h1:jhJqrhIir/OOPTHSNR7CHfl0AA//yjiwXoGsRIOkAiE=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dts v1.3.171/go.mod h1:pQzZ8iRCI6B4cevn8xGMpAY+NXjJOHo9lvPxu4xeegQ=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/eb v1.1.30 h1:fuy1NhRyTbwMku0VS1/uFao77pK1HuLCqRRYVxow3Uk=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/eb v1.1.30/go.mod h1:ORMs9Hzzlrmsq0gqKdw+XBE0uMWA026Bsy3jvpCWdFA=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/emr v1.3.188 h1:GD6Vz/38zSGgRYZo8pY1IE8W+906Yddfqg4vj/3TuCM=
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-10-14
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
## Context

The `tencentcloud_dts_sync_job` resource defines its `tags` field as `schema.TypeList`. Tags are sent to the DTS cloud API via `CreateSyncJob` (as `[]*dts.TagItem`) and read back via `DescribeSyncJobs` (as `[]*dts.TagItem`). The cloud API does not guarantee tag ordering on read — the same set of tags may come back in a different order than what was sent. With `TypeList`, Terraform compares element-by-element by index, so any reordering produces a non-empty diff on every refresh. This is a well-known pattern in the codebase: other resources (e.g. MongoDB `availability_zone_list`, DTS sync_config sub-fields) already use `TypeSet` for unordered collections.

Constraints:
- The `tags` field is `ForceNew` + `Computed` + `Optional`, so it is set only at create time and refreshed on read. There is no update path that needs changing.
- The cloud API request/response types (`dts.TagItem`) are unchanged — only the Terraform-side collection type changes.
- HCL block-list syntax is identical for `TypeList` and `TypeSet`, so user `.tf` files require no modification.

## Goals / Non-Goals

**Goals:**
- Eliminate spurious diffs on the `tags` field caused by unordered cloud API responses.
- Keep the nested element schema (`tag_key`, `tag_value`) unchanged.
- Keep the HCL configuration syntax backwards-compatible (no change to user `.tf` files).

**Non-Goals:**
- Do not change the cloud API calls (`CreateSyncJob`, `DescribeSyncJobs`).
- Do not change the `dts.TagItem` SDK struct usage.
- Do not modify other fields of `tencentcloud_dts_sync_job`.
- Do not change the `tags` handling in the data source `tencentcloud_dts_sync_jobs` (it uses a different schema and is not affected by the reported issue).

## Decisions

### D1: TypeSet with default HashResource

**Decision**: Change `Type: schema.TypeList` to `Type: schema.TypeSet`, keeping `Elem: &schema.Resource{...}`. Use the default `Set` hash function (`schema.HashResource`), which hashes the full resource schema — i.e. the combination of `tag_key` + `tag_value`.

**Rationale**:
- `tag_key` + `tag_value` together form a unique identity for a tag. The default `HashResource` hashes both fields, so duplicate tags are correctly de-duplicated and order is irrelevant.
- An alternative is a custom `schema.HashResource` with a custom function, but that adds complexity for no benefit since the default already covers both fields.

**Alternatives considered**:
- Custom hash function keyed only on `tag_key`: rejected because two tags with the same key but different values would collide; the cloud API allows the same key with different values in some contexts, so hashing both fields is safer.

### D2: Create handler reads Set via `(*schema.Set).List()`

**Decision**: In `resourceTencentCloudDtsSyncJobCreate`, change `v.([]interface{})` to `v.(*schema.Set).List()` when iterating the `tags` field. The iteration body that builds `dts.TagItem` is unchanged.

**Rationale**: `d.GetOk("tags")` returns `*schema.Set` for a TypeSet field. Calling `.List()` converts it to `[]interface{}`, which is the same type the existing loop already expects.

### D3: Read handler stays as `d.Set("tags", tagsList)` with `[]interface{}`

**Decision**: The Read handler already builds `tagsList` as `[]interface{}` of `map[string]interface{}` and calls `d.Set("tags", tagsList)`. This works correctly with `TypeSet` — Terraform SDK converts the slice into the Set using the same hash function. No change needed in the read body besides keeping the existing logic.

**Rationale**: `schema.ResourceData.Set` accepts `[]interface{}` for both TypeList and TypeSet. For TypeSet it hashes each element with the configured hash function. The existing code already produces the right shape.

### D4: Test assertions updated to Set semantics

**Decision**: In `resource_tc_dts_sync_job_test.go`, replace the `resource.TestCheckResourceAttrSet(..., "tags.#")` check with `resource.TestCheckTypeSetElemNestedAttrs` to verify the specific tag key/value pair regardless of order. The HCL config stays in block syntax (valid for Set).

## Risks / Trade-offs

- **[R1] State format change** → On first `terraform plan`/`apply` after upgrade, the state representation of `tags` changes from an ordered list to a set. Users see a one-time refresh diff; no actual resource is modified. This is the standard SDKv2 List→Set migration, well-tested in this codebase (MongoDB precedent). Mitigation: document the one-time refresh in the changelog.
- **[R2] TypeSet hash collisions** → If two tags have identical `tag_key` + `tag_value`, they collapse into one Set element. The cloud API also treats identical tags as duplicates, so this matches API semantics. No risk.
- **[R3] Import compatibility** → `tags` is `ForceNew`+`Computed`, not a read-from-id field, so import (`schema.ImportStatePassthrough`) is unaffected.

## Migration Plan

1. Apply the schema + Create handler change in `resource_tc_dts_sync_job.go`.
2. Update test file assertions.
3. Run `gofmt` on modified `.go` files (finalize step).
4. Regenerate docs via `make doc` (finalize step).
5. Rollback: `git revert` the change; the state automatically reverts to List representation on next refresh with the old provider version.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## Why

The `tags` field of `tencentcloud_dts_sync_job` is currently defined as `schema.TypeList`, but the DTS cloud API `DescribeSyncJobs` returns tags in an unordered manner. This causes Terraform to detect a perpetual diff on every `terraform plan`/`refresh` — the tag order in state never matches the order returned by the API, so users see non-empty diffs even when the tag set is unchanged. Changing the field to `schema.TypeSet` eliminates order-sensitive diffs and matches the unordered semantics of the cloud API.

## What Changes

- **BREAKING** (state format only): Change the `tags` field in `tencentcloud_dts_sync_job` from `schema.TypeList` to `schema.TypeSet`. The nested `schema.Resource` element (with `tag_key` and `tag_value` fields) remains unchanged. Using `TypeSet` with the default `schema.HashResource` hashing, Terraform treats two tag sets with the same key/value pairs — regardless of order — as equal.
- Update the Create handler to read the `tags` field via `v.(*schema.Set).List()` instead of `v.([]interface{})`.
- Update the Read handler so that `d.Set("tags", tagsList)` correctly populates the Set (the existing `[]interface{}` of maps already works with TypeSet).
- Update the test file `resource_tc_dts_sync_job_test.go` to use `resource.TestCheckTypeSetElemNestedAttrs` for tag verification and update the HCL config to use the set syntax.
- Update the `.md` example doc to keep it consistent.

## Capabilities

### New Capabilities
- `dts-sync-job-tags-set-type`: Defines that the `tags` field of `tencentcloud_dts_sync_job` SHALL use `schema.TypeSet` so that tag ordering differences between configuration and cloud API responses do not produce spurious diffs.

### Modified Capabilities
<!-- (no existing capability spec to modify) -->

## Impact

- **Code**: `tencentcloud/services/dts/resource_tc_dts_sync_job.go` — schema definition (line 71–91), Create handler (line 168–180), Read handler (line 283–297).
- **Tests**: `tencentcloud/services/dts/resource_tc_dts_sync_job_test.go` — update tag assertions and HCL config.
- **Docs**: `tencentcloud/services/dts/resource_tc_dts_sync_job.md` — example syntax (tags block unchanged for users but doc regenerated via `make doc`).
- **API**: No cloud API change. `CreateSyncJob` request and `DescribeSyncJobs` response schemas are unchanged — only the Terraform-side collection type changes from List to Set.
- **State migration**: On the first `terraform plan` after provider upgrade, users will see a one-time state refresh converting the `tags` representation from List to Set. No actual cloud resource modification occurs. The HCL configuration syntax (block-list style) is identical for List and Set, so user `.tf` files require no changes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
## ADDED Requirements

### Requirement: DTS sync job tags field uses Set type

The `tags` field of the `tencentcloud_dts_sync_job` resource SHALL use `schema.TypeSet` with a nested `schema.Resource` element containing `tag_key` (string) and `tag_value` (string) sub-fields. The Set SHALL use the default `schema.HashResource` hash function so that two tag collections with identical key/value pairs — regardless of element order — are treated as equal. This eliminates spurious diffs caused by the DTS cloud API `DescribeSyncJobs` returning tags in an unordered manner.

#### Scenario: Tags are written to CreateSyncJob request from Set

- **WHEN** a user configures the `tags` block on `tencentcloud_dts_sync_job` and the provider executes the Create handler
- **THEN** the provider SHALL read the `tags` field via `v.(*schema.Set).List()` and iterate the resulting elements to build `[]*dts.TagItem` for the `CreateSyncJob` request `Tags` parameter

#### Scenario: Tags are read back into Set state

- **WHEN** the provider reads the resource via `DescribeSyncJobs` and the returned `SyncJob.Tags` is non-nil
- **THEN** the provider SHALL build a `[]interface{}` of `map[string]interface{}` (each containing `tag_key` and `tag_value`) and call `d.Set("tags", tagsList)` to populate the Set state

#### Scenario: Unordered tag response does not produce a diff

- **WHEN** the user's configuration contains tags `[{tag_key="a", tag_value="1"}, {tag_key="b", tag_value="2"}]` and the cloud API returns the same tags in reverse order `[{tag_key="b", tag_value="2"}, {tag_key="a", tag_value="1"}]`
- **THEN** `terraform plan` SHALL report no diff on the `tags` field, because the Set type is order-insensitive

#### Scenario: Tags field is omitted from configuration

- **WHEN** a user does not set the `tags` field in the `tencentcloud_dts_sync_job` configuration
- **THEN** the provider SHALL NOT set the `Tags` field on the `CreateSyncJob` request, preserving existing behavior

#### Scenario: Tags returned as nil from cloud API

- **WHEN** the provider reads the resource and the returned `SyncJob.Tags` is nil
- **THEN** the provider SHALL NOT call `d.Set("tags", ...)`, leaving existing state untouched

#### Scenario: HCL configuration syntax compatibility

- **WHEN** a user writes tags using the existing block-list HCL syntax (repeated `tags { tag_key = ... tag_value = ... }` blocks)
- **THEN** Terraform SHALL accept the configuration without error, because TypeList and TypeSet share the same HCL block syntax
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## 1. Schema 修改

- [x] 1.1 在 `tencentcloud/services/dts/resource_tc_dts_sync_job.go` 中,将 `tags` 字段的 `Type` 从 `schema.TypeList` 改为 `schema.TypeSet`(约第 72 行),保持 `Optional`/`Computed`/`ForceNew` 及 `Elem: &schema.Resource{...}`(含 `tag_key`、`tag_value`)不变
- [x] 1.2 确认无需显式设置 `Set` 字段,使用默认 `schema.HashResource` 哈希 `tag_key` + `tag_value` 组合

## 2. Create handler 修改

- [x] 2.1 在 `resourceTencentCloudDtsSyncJobCreate` 函数中,将读取 `tags` 的类型断言从 `v.([]interface{})` 改为 `v.(*schema.Set).List()`(约第 169 行),循环体内构建 `dts.TagItem` 的逻辑不变

## 3. Read handler 确认

- [x] 3.1 确认 `resourceTencentCloudDtsSyncJobRead` 中的 `d.Set("tags", tagsList)` 逻辑(约第 283–297 行)对 TypeSet 兼容,`tagsList` 仍为 `[]interface{}` of `map[string]interface{}`,无需修改

## 4. 测试修改

- [x] 4.1 在 `tencentcloud/services/dts/resource_tc_dts_sync_job_test.go` 中,将 `resource.TestCheckResourceAttrSet("tencentcloud_dts_sync_job.sync_job", "tags.#")` 替换为 `resource.TestCheckTypeSetElemNestedAttrs`,验证 tag 的 `tag_key`/`tag_value` 键值对(与顺序无关)
- [x] 4.2 确认 HCL 配置块 `testAccDtsSyncJob` 中的 `tags { ... }` 块语法无需修改(TypeList/TypeSet 共用块语法)

## 5. 文档更新

- [x] 5.1 确认 `tencentcloud/services/dts/resource_tc_dts_sync_job.md` 示例中的 `tags` 块语法保持不变(Set 与 List 语法相同,无需修改)

## 6. 收尾(tfpacer-finalize 阶段执行)

- [ ] 6.1 对修改的 `.go` 文件执行 `gofmt` 格式化
- [ ] 6.2 执行 `make doc` 生成 `website/docs/` 下的文档
- [ ] 6.3 创建 `.changelog/` 条目记录本次变更

## 7. 验证

- [x] 7.1 确认 `tags` 字段在 schema 中为 TypeSet
- [x] 7.2 确认 Create handler 使用 `(*schema.Set).List()` 读取 tags
- [x] 7.3 确认无其他文件引用 `tags` 字段的 List 类型断言
39 changes: 39 additions & 0 deletions openspec/specs/dts-sync-job-tags-set-type/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# dts-sync-job-tags-set-type Specification

## Purpose
TBD - created by archiving change fix-dts-sync-job-tags-list-to-set. Update Purpose after archive.
## Requirements
### Requirement: DTS sync job tags field uses Set type

The `tags` field of the `tencentcloud_dts_sync_job` resource SHALL use `schema.TypeSet` with a nested `schema.Resource` element containing `tag_key` (string) and `tag_value` (string) sub-fields. The Set SHALL use the default `schema.HashResource` hash function so that two tag collections with identical key/value pairs — regardless of element order — are treated as equal. This eliminates spurious diffs caused by the DTS cloud API `DescribeSyncJobs` returning tags in an unordered manner.

#### Scenario: Tags are written to CreateSyncJob request from Set

- **WHEN** a user configures the `tags` block on `tencentcloud_dts_sync_job` and the provider executes the Create handler
- **THEN** the provider SHALL read the `tags` field via `v.(*schema.Set).List()` and iterate the resulting elements to build `[]*dts.TagItem` for the `CreateSyncJob` request `Tags` parameter

#### Scenario: Tags are read back into Set state

- **WHEN** the provider reads the resource via `DescribeSyncJobs` and the returned `SyncJob.Tags` is non-nil
- **THEN** the provider SHALL build a `[]interface{}` of `map[string]interface{}` (each containing `tag_key` and `tag_value`) and call `d.Set("tags", tagsList)` to populate the Set state

#### Scenario: Unordered tag response does not produce a diff

- **WHEN** the user's configuration contains tags `[{tag_key="a", tag_value="1"}, {tag_key="b", tag_value="2"}]` and the cloud API returns the same tags in reverse order `[{tag_key="b", tag_value="2"}, {tag_key="a", tag_value="1"}]`
- **THEN** `terraform plan` SHALL report no diff on the `tags` field, because the Set type is order-insensitive

#### Scenario: Tags field is omitted from configuration

- **WHEN** a user does not set the `tags` field in the `tencentcloud_dts_sync_job` configuration
- **THEN** the provider SHALL NOT set the `Tags` field on the `CreateSyncJob` request, preserving existing behavior

#### Scenario: Tags returned as nil from cloud API

- **WHEN** the provider reads the resource and the returned `SyncJob.Tags` is nil
- **THEN** the provider SHALL NOT call `d.Set("tags", ...)`, leaving existing state untouched

#### Scenario: HCL configuration syntax compatibility

- **WHEN** a user writes tags using the existing block-list HCL syntax (repeated `tags { tag_key = ... tag_value = ... }` blocks)
- **THEN** Terraform SHALL accept the configuration without error, because TypeList and TypeSet share the same HCL block syntax

11 changes: 7 additions & 4 deletions tencentcloud/services/dts/resource_tc_dts_sync_job.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,10 @@ func ResourceTencentCloudDtsSyncJob() *schema.Resource {
},

"tags": {
Type: schema.TypeList,
Type: schema.TypeSet,
Optional: true,
Computed: true,
ForceNew: true,
Description: "Tag information.",
Description: "Tag information.Cannot be modified.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"tag_key": {
Expand Down Expand Up @@ -166,7 +165,7 @@ func resourceTencentCloudDtsSyncJobCreate(d *schema.ResourceData, meta interface
}

if v, ok := d.GetOk("tags"); ok {
for _, item := range v.([]interface{}) {
for _, item := range v.(*schema.Set).List() {
dMap := item.(map[string]interface{})
tagItem := dts.TagItem{}
if v, ok := dMap["tag_key"]; ok {
Expand Down Expand Up @@ -324,6 +323,10 @@ func resourceTencentCloudDtsSyncJobUpdate(d *schema.ResourceData, meta interface
syncJobId = d.Id()
)

if d.HasChange("tags") {
return fmt.Errorf("tags cannot be updated, please delete and recreate the resource to modify tags")
}

if d.HasChange("instance_class") {
request := dts.NewResizeSyncJobRequest()
if v, ok := d.GetOk("instance_class"); ok {
Expand Down
Loading
Loading