From 965aa51c1ad3ad0b871d2a9196e8753169c84d4a Mon Sep 17 00:00:00 2001 From: iac-agent Date: Wed, 27 May 2026 16:05:04 +0800 Subject: [PATCH] feat(ga2): add tencentcloud_ga2_global_accelerator resource --- .changelog/4156.txt | 3 + go.sum | 1 + .../.openspec.yaml | 2 + .../design.md | 60 +++ .../proposal.md | 32 ++ .../ga2-global-accelerator-resource/spec.md | 99 +++++ .../tasks.md | 24 ++ .../ga2-global-accelerator-resource/spec.md | 99 +++++ tencentcloud/provider.go | 1 + tencentcloud/provider.md | 5 + .../ga2/resource_tc_ga2_global_accelerator.go | 371 ++++++++++++++++++ .../ga2/resource_tc_ga2_global_accelerator.md | 25 ++ ...resource_tc_ga2_global_accelerator_test.go | 315 +++++++++++++++ .../services/ga2/service_tencentcloud_ga2.go | 64 +++ .../tencentcloud/common/http/request.go | 1 - .../docs/r/ga2_endpoint_group.html.markdown | 119 ++++++ .../r/ga2_global_accelerator.html.markdown | 67 ++++ website/tencentcloud.erb | 17 + 18 files changed, 1304 insertions(+), 1 deletion(-) create mode 100644 .changelog/4156.txt create mode 100644 openspec/changes/archive/2026-05-27-add-ga2-global-accelerator-resource/.openspec.yaml create mode 100644 openspec/changes/archive/2026-05-27-add-ga2-global-accelerator-resource/design.md create mode 100644 openspec/changes/archive/2026-05-27-add-ga2-global-accelerator-resource/proposal.md create mode 100644 openspec/changes/archive/2026-05-27-add-ga2-global-accelerator-resource/specs/ga2-global-accelerator-resource/spec.md create mode 100644 openspec/changes/archive/2026-05-27-add-ga2-global-accelerator-resource/tasks.md create mode 100644 openspec/specs/ga2-global-accelerator-resource/spec.md create mode 100644 tencentcloud/services/ga2/resource_tc_ga2_global_accelerator.go create mode 100644 tencentcloud/services/ga2/resource_tc_ga2_global_accelerator.md create mode 100644 tencentcloud/services/ga2/resource_tc_ga2_global_accelerator_test.go create mode 100644 website/docs/r/ga2_endpoint_group.html.markdown create mode 100644 website/docs/r/ga2_global_accelerator.html.markdown diff --git a/.changelog/4156.txt b/.changelog/4156.txt new file mode 100644 index 0000000000..524aef0b98 --- /dev/null +++ b/.changelog/4156.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +tencentcloud_ga2_global_accelerator +``` diff --git a/go.sum b/go.sum index 9d29c24b56..81e65f5067 100644 --- a/go.sum +++ b/go.sum @@ -958,6 +958,7 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.89/go.mod h github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.90/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.94/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.95/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.101/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.102 h1:3n7BpLOPnE9Rv3Y9Jxgl/XaQX3GzktO+dEaAbtVbjSk= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.102/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/config v1.3.80 h1:chnsNBeJn3MieFLki4hpbzoml5NiTvLVzOTqYRVxQho= diff --git a/openspec/changes/archive/2026-05-27-add-ga2-global-accelerator-resource/.openspec.yaml b/openspec/changes/archive/2026-05-27-add-ga2-global-accelerator-resource/.openspec.yaml new file mode 100644 index 0000000000..e7c42aca70 --- /dev/null +++ b/openspec/changes/archive/2026-05-27-add-ga2-global-accelerator-resource/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-05-27 diff --git a/openspec/changes/archive/2026-05-27-add-ga2-global-accelerator-resource/design.md b/openspec/changes/archive/2026-05-27-add-ga2-global-accelerator-resource/design.md new file mode 100644 index 0000000000..996fc197d9 --- /dev/null +++ b/openspec/changes/archive/2026-05-27-add-ga2-global-accelerator-resource/design.md @@ -0,0 +1,60 @@ +## Context + +当前 Terraform Provider 已有 GA2 服务的基础设施: +- `tencentcloud/services/ga2/service_tencentcloud_ga2.go` 提供了 `Ga2Service`、`WaitForGa2TaskFinish` 等公共方法 +- `tencentcloud/services/ga2/resource_tc_ga2_endpoint_group.go` 是同产品下已有的资源实现,可作为模式参考 +- vendor 中已包含 `github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ga2/v20250115` SDK + +所有 GA2 写操作(Create/Modify/Delete)均为异步接口,返回 TaskId,需通过 `DescribeTaskResult` 轮询任务状态直到 SUCCESS。已有的 `WaitForGa2TaskFinish` 方法可直接复用。 + +## Goals / Non-Goals + +**Goals:** +- 实现 `tencentcloud_ga2_global_accelerator` 资源的完整 CRUD +- 复用已有的 `Ga2Service` 和 `WaitForGa2TaskFinish` 异步等待机制 +- 支持 Import 功能(通过 global_accelerator_id 导入) +- 在 service 层新增 `DescribeGa2GlobalAcceleratorById` 方法供 Read 使用 +- 使用 gomonkey mock 方式编写单元测试 + +**Non-Goals:** +- 不实现 tags 的独立更新(ModifyGlobalAccelerator 不支持 tags 参数) +- 不实现 instance_charge_type 的变更(仅创建时指定) +- 不实现数据源(本次仅新增 RESOURCE_KIND_GENERAL 资源) + +## Decisions + +### 1. 资源 ID 使用 GlobalAcceleratorId + +**决策**: 使用 `CreateGlobalAccelerator` 返回的 `GlobalAcceleratorId` 作为 Terraform 资源 ID(单一 ID,无需复合 ID)。 + +**理由**: 该 ID 是全球加速实例的唯一标识,Modify 和 Delete 接口均只需此 ID。 + +### 2. 异步操作使用已有的 WaitForGa2TaskFinish + +**决策**: Create/Modify/Delete 操作完成后,调用 `Ga2Service.WaitForGa2TaskFinish` 等待任务完成。 + +**理由**: 该方法已在 endpoint_group 资源中验证可用,使用 `DescribeTaskResult` 接口轮询,避免重复实现。 + +### 3. instance_charge_type 和 tags 设为 ForceNew + +**决策**: `instance_charge_type` 和 `tags` 字段设为 ForceNew,因为 ModifyGlobalAccelerator 接口不支持修改这两个字段。 + +**理由**: 云 API 的 Modify 接口仅支持 Name、Description、CrossBorderType、CrossBorderPromiseFlag 四个字段的修改。 + +### 4. Read 方法通过 DescribeGlobalAccelerators + Filter 实现 + +**决策**: 在 service 层新增 `DescribeGa2GlobalAcceleratorById` 方法,使用 `global-accelerator-id` Filter 查询单个实例。 + +**理由**: DescribeGlobalAccelerators 接口支持通过 Filters 按 ID 过滤,与 endpoint_group 的模式一致。 + +### 5. Schema 中声明 Timeouts + +**决策**: 为 Create/Update/Delete 声明 20 分钟超时,与 endpoint_group 资源保持一致。 + +**理由**: 异步操作需要超时控制,20 分钟是同产品其他资源的标准超时时间。 + +## Risks / Trade-offs + +- [Tags 不可更新] → 设为 ForceNew,用户修改 tags 会触发资源重建。这是 API 限制,无法规避。 +- [instance_charge_type 不可更新] → 设为 ForceNew,与 API 能力对齐。 +- [异步操作可能超时] → 使用 schema Timeouts 机制,用户可自定义超时时间。 diff --git a/openspec/changes/archive/2026-05-27-add-ga2-global-accelerator-resource/proposal.md b/openspec/changes/archive/2026-05-27-add-ga2-global-accelerator-resource/proposal.md new file mode 100644 index 0000000000..4c49c38f0c --- /dev/null +++ b/openspec/changes/archive/2026-05-27-add-ga2-global-accelerator-resource/proposal.md @@ -0,0 +1,32 @@ +## Why + +TencentCloud Global Accelerator (GA2) 产品需要通过 Terraform 管理全球加速实例的生命周期(创建、查询、修改、删除),当前 provider 中尚未提供该资源,用户无法通过 IaC 方式管理 GA2 全球加速实例。 + +## What Changes + +- 新增 Terraform 资源 `tencentcloud_ga2_global_accelerator`,支持全球加速实例的完整 CRUD 生命周期管理 +- 支持创建时指定名称、计费模式、描述、跨境类型、跨境承诺标志和标签 +- 支持修改名称、描述、跨境类型和跨境承诺标志 +- 支持删除全球加速实例 +- 所有写操作(Create/Modify/Delete)为异步接口,需轮询实例状态直到操作完成 +- 在 provider.go 和 provider.md 中注册新资源 + +## Capabilities + +### New Capabilities + +- `ga2-global-accelerator-resource`: 全球加速实例的 CRUD 资源管理,包括创建、读取、更新、删除操作,以及异步操作的状态轮询 + +### Modified Capabilities + +(无) + +## Impact + +- 新增文件: `tencentcloud/services/ga2/resource_tc_ga2_global_accelerator.go` +- 新增文件: `tencentcloud/services/ga2/resource_tc_ga2_global_accelerator_test.go` +- 新增文件: `tencentcloud/services/ga2/resource_tc_ga2_global_accelerator.md` +- 修改文件: `tencentcloud/provider.go`(注册资源) +- 修改文件: `tencentcloud/provider.md`(添加资源文档引用) +- 可能修改: `tencentcloud/services/ga2/service_tencentcloud_ga2.go`(添加服务层方法) +- 依赖: `github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ga2/v20250115`(已在 vendor 中) diff --git a/openspec/changes/archive/2026-05-27-add-ga2-global-accelerator-resource/specs/ga2-global-accelerator-resource/spec.md b/openspec/changes/archive/2026-05-27-add-ga2-global-accelerator-resource/specs/ga2-global-accelerator-resource/spec.md new file mode 100644 index 0000000000..7185d8c9f0 --- /dev/null +++ b/openspec/changes/archive/2026-05-27-add-ga2-global-accelerator-resource/specs/ga2-global-accelerator-resource/spec.md @@ -0,0 +1,99 @@ +## ADDED Requirements + +### Requirement: Create global accelerator instance + +The system SHALL provide a Terraform resource `tencentcloud_ga2_global_accelerator` that creates a GA2 global accelerator instance by calling the `CreateGlobalAccelerator` API with the following parameters: +- `name` (Required, string): Instance name, max 60 bytes +- `instance_charge_type` (Optional, string, ForceNew): Billing mode, valid values: `PREPAID`, `POSTPAID`. Default: `POSTPAID` +- `description` (Optional, string): Description, max 100 bytes +- `cross_border_type` (Optional, string): Cross-border type, valid values: `HighQuality`, `Unicom` +- `cross_border_promise_flag` (Optional, bool): Cross-border service promise flag +- `tags` (Optional, map of string, ForceNew): Tag information + +After successful creation, the system SHALL set the resource ID to the returned `GlobalAcceleratorId` and wait for the async task to complete by polling `DescribeTaskResult` until status is SUCCESS. + +#### Scenario: Successful creation with required parameters only + +- **WHEN** user provides a valid `name` in the Terraform configuration +- **THEN** the system calls `CreateGlobalAccelerator` API, sets the resource ID to the returned `GlobalAcceleratorId`, waits for the task to complete, and reads back the resource state + +#### Scenario: Successful creation with all parameters + +- **WHEN** user provides `name`, `instance_charge_type`, `description`, `cross_border_type`, `cross_border_promise_flag`, and `tags` +- **THEN** the system calls `CreateGlobalAccelerator` API with all provided parameters, sets the resource ID, waits for the task to complete, and reads back the resource state + +#### Scenario: Creation fails with nil response + +- **WHEN** the `CreateGlobalAccelerator` API returns a nil response or nil `GlobalAcceleratorId` +- **THEN** the system returns a NonRetryableError + +### Requirement: Read global accelerator instance + +The system SHALL read the global accelerator instance state by calling `DescribeGlobalAccelerators` API with a `global-accelerator-id` filter. The system SHALL set the following computed attributes from the response: +- `name` (string) +- `description` (string) +- `instance_charge_type` (string) +- `cross_border_type` (string) +- `create_time` (Computed, string) +- `state` (Computed, string) +- `status` (Computed, string) +- `ddos_id` (Computed, string) +- `cname` (Computed, string) + +#### Scenario: Successful read + +- **WHEN** the resource exists and `DescribeGlobalAccelerators` returns a matching instance +- **THEN** the system sets all computed and configured attributes from the `GlobalAcceleratorSet` response + +#### Scenario: Resource not found + +- **WHEN** `DescribeGlobalAccelerators` returns an empty `GlobalAcceleratorSet` for the given ID +- **THEN** the system removes the resource from state (calls `d.SetId("")`) + +### Requirement: Update global accelerator instance + +The system SHALL update the global accelerator instance by calling `ModifyGlobalAccelerator` API when any of the following fields change: `name`, `description`, `cross_border_type`, `cross_border_promise_flag`. After the API call, the system SHALL wait for the async task to complete. + +The fields `instance_charge_type` and `tags` are ForceNew and SHALL NOT be included in the update request. + +#### Scenario: Update name and description + +- **WHEN** user changes `name` or `description` in the Terraform configuration +- **THEN** the system calls `ModifyGlobalAccelerator` with the new values and the `GlobalAcceleratorId`, waits for the task to complete, and reads back the resource state + +#### Scenario: Update cross-border settings + +- **WHEN** user changes `cross_border_type` or `cross_border_promise_flag` +- **THEN** the system calls `ModifyGlobalAccelerator` with the updated values, waits for the task to complete, and reads back the resource state + +### Requirement: Delete global accelerator instance + +The system SHALL delete the global accelerator instance by calling `DeleteGlobalAccelerator` API with the `GlobalAcceleratorId`. After the API call, the system SHALL wait for the async task to complete. + +#### Scenario: Successful deletion + +- **WHEN** user destroys the Terraform resource +- **THEN** the system calls `DeleteGlobalAccelerator` with the resource ID, waits for the task to complete + +#### Scenario: Resource already deleted + +- **WHEN** `DeleteGlobalAccelerator` returns a resource not found error +- **THEN** the system treats the deletion as successful (no error) + +### Requirement: Import global accelerator instance + +The system SHALL support importing an existing global accelerator instance using its `GlobalAcceleratorId`. + +#### Scenario: Successful import + +- **WHEN** user runs `terraform import tencentcloud_ga2_global_accelerator.example ` +- **THEN** the system reads the instance state using `DescribeGlobalAccelerators` and populates all attributes + +### Requirement: Register resource in provider + +The system SHALL register `tencentcloud_ga2_global_accelerator` in `tencentcloud/provider.go` resource map and add the corresponding entry in `tencentcloud/provider.md`. + +#### Scenario: Resource is available after registration + +- **WHEN** the provider is initialized +- **THEN** `tencentcloud_ga2_global_accelerator` is available as a valid resource type diff --git a/openspec/changes/archive/2026-05-27-add-ga2-global-accelerator-resource/tasks.md b/openspec/changes/archive/2026-05-27-add-ga2-global-accelerator-resource/tasks.md new file mode 100644 index 0000000000..c0c810969e --- /dev/null +++ b/openspec/changes/archive/2026-05-27-add-ga2-global-accelerator-resource/tasks.md @@ -0,0 +1,24 @@ +## 1. Service Layer + +- [x] 1.1 Add `DescribeGa2GlobalAcceleratorById` method to `tencentcloud/services/ga2/service_tencentcloud_ga2.go` that queries a single global accelerator instance by ID using `DescribeGlobalAccelerators` API with `global-accelerator-id` filter + +## 2. Resource Implementation + +- [x] 2.1 Create `tencentcloud/services/ga2/resource_tc_ga2_global_accelerator.go` with schema definition including all input fields (`name`, `instance_charge_type`, `description`, `cross_border_type`, `cross_border_promise_flag`, `tags`) and computed fields (`create_time`, `state`, `status`, `ddos_id`, `cname`), with Timeouts block and Import support +- [x] 2.2 Implement `resourceTencentCloudGa2GlobalAcceleratorCreate` function that calls `CreateGlobalAccelerator` API, validates response, sets resource ID, and waits for async task completion via `WaitForGa2TaskFinish` +- [x] 2.3 Implement `resourceTencentCloudGa2GlobalAcceleratorRead` function that calls `DescribeGa2GlobalAcceleratorById`, handles not-found case, and sets all attributes from response +- [x] 2.4 Implement `resourceTencentCloudGa2GlobalAcceleratorUpdate` function that calls `ModifyGlobalAccelerator` API with changed fields (`name`, `description`, `cross_border_type`, `cross_border_promise_flag`) and waits for async task completion +- [x] 2.5 Implement `resourceTencentCloudGa2GlobalAcceleratorDelete` function that calls `DeleteGlobalAccelerator` API and waits for async task completion + +## 3. Provider Registration + +- [x] 3.1 Register `tencentcloud_ga2_global_accelerator` resource in `tencentcloud/provider.go` resource map +- [x] 3.2 Add `tencentcloud_ga2_global_accelerator` entry in `tencentcloud/provider.md` + +## 4. Documentation + +- [x] 4.1 Create `tencentcloud/services/ga2/resource_tc_ga2_global_accelerator.md` with Example Usage and Import sections + +## 5. Unit Tests + +- [x] 5.1 Create `tencentcloud/services/ga2/resource_tc_ga2_global_accelerator_test.go` with gomonkey-based unit tests covering Create, Read, Update, and Delete operations diff --git a/openspec/specs/ga2-global-accelerator-resource/spec.md b/openspec/specs/ga2-global-accelerator-resource/spec.md new file mode 100644 index 0000000000..7185d8c9f0 --- /dev/null +++ b/openspec/specs/ga2-global-accelerator-resource/spec.md @@ -0,0 +1,99 @@ +## ADDED Requirements + +### Requirement: Create global accelerator instance + +The system SHALL provide a Terraform resource `tencentcloud_ga2_global_accelerator` that creates a GA2 global accelerator instance by calling the `CreateGlobalAccelerator` API with the following parameters: +- `name` (Required, string): Instance name, max 60 bytes +- `instance_charge_type` (Optional, string, ForceNew): Billing mode, valid values: `PREPAID`, `POSTPAID`. Default: `POSTPAID` +- `description` (Optional, string): Description, max 100 bytes +- `cross_border_type` (Optional, string): Cross-border type, valid values: `HighQuality`, `Unicom` +- `cross_border_promise_flag` (Optional, bool): Cross-border service promise flag +- `tags` (Optional, map of string, ForceNew): Tag information + +After successful creation, the system SHALL set the resource ID to the returned `GlobalAcceleratorId` and wait for the async task to complete by polling `DescribeTaskResult` until status is SUCCESS. + +#### Scenario: Successful creation with required parameters only + +- **WHEN** user provides a valid `name` in the Terraform configuration +- **THEN** the system calls `CreateGlobalAccelerator` API, sets the resource ID to the returned `GlobalAcceleratorId`, waits for the task to complete, and reads back the resource state + +#### Scenario: Successful creation with all parameters + +- **WHEN** user provides `name`, `instance_charge_type`, `description`, `cross_border_type`, `cross_border_promise_flag`, and `tags` +- **THEN** the system calls `CreateGlobalAccelerator` API with all provided parameters, sets the resource ID, waits for the task to complete, and reads back the resource state + +#### Scenario: Creation fails with nil response + +- **WHEN** the `CreateGlobalAccelerator` API returns a nil response or nil `GlobalAcceleratorId` +- **THEN** the system returns a NonRetryableError + +### Requirement: Read global accelerator instance + +The system SHALL read the global accelerator instance state by calling `DescribeGlobalAccelerators` API with a `global-accelerator-id` filter. The system SHALL set the following computed attributes from the response: +- `name` (string) +- `description` (string) +- `instance_charge_type` (string) +- `cross_border_type` (string) +- `create_time` (Computed, string) +- `state` (Computed, string) +- `status` (Computed, string) +- `ddos_id` (Computed, string) +- `cname` (Computed, string) + +#### Scenario: Successful read + +- **WHEN** the resource exists and `DescribeGlobalAccelerators` returns a matching instance +- **THEN** the system sets all computed and configured attributes from the `GlobalAcceleratorSet` response + +#### Scenario: Resource not found + +- **WHEN** `DescribeGlobalAccelerators` returns an empty `GlobalAcceleratorSet` for the given ID +- **THEN** the system removes the resource from state (calls `d.SetId("")`) + +### Requirement: Update global accelerator instance + +The system SHALL update the global accelerator instance by calling `ModifyGlobalAccelerator` API when any of the following fields change: `name`, `description`, `cross_border_type`, `cross_border_promise_flag`. After the API call, the system SHALL wait for the async task to complete. + +The fields `instance_charge_type` and `tags` are ForceNew and SHALL NOT be included in the update request. + +#### Scenario: Update name and description + +- **WHEN** user changes `name` or `description` in the Terraform configuration +- **THEN** the system calls `ModifyGlobalAccelerator` with the new values and the `GlobalAcceleratorId`, waits for the task to complete, and reads back the resource state + +#### Scenario: Update cross-border settings + +- **WHEN** user changes `cross_border_type` or `cross_border_promise_flag` +- **THEN** the system calls `ModifyGlobalAccelerator` with the updated values, waits for the task to complete, and reads back the resource state + +### Requirement: Delete global accelerator instance + +The system SHALL delete the global accelerator instance by calling `DeleteGlobalAccelerator` API with the `GlobalAcceleratorId`. After the API call, the system SHALL wait for the async task to complete. + +#### Scenario: Successful deletion + +- **WHEN** user destroys the Terraform resource +- **THEN** the system calls `DeleteGlobalAccelerator` with the resource ID, waits for the task to complete + +#### Scenario: Resource already deleted + +- **WHEN** `DeleteGlobalAccelerator` returns a resource not found error +- **THEN** the system treats the deletion as successful (no error) + +### Requirement: Import global accelerator instance + +The system SHALL support importing an existing global accelerator instance using its `GlobalAcceleratorId`. + +#### Scenario: Successful import + +- **WHEN** user runs `terraform import tencentcloud_ga2_global_accelerator.example ` +- **THEN** the system reads the instance state using `DescribeGlobalAccelerators` and populates all attributes + +### Requirement: Register resource in provider + +The system SHALL register `tencentcloud_ga2_global_accelerator` in `tencentcloud/provider.go` resource map and add the corresponding entry in `tencentcloud/provider.md`. + +#### Scenario: Resource is available after registration + +- **WHEN** the provider is initialized +- **THEN** `tencentcloud_ga2_global_accelerator` is available as a valid resource type diff --git a/tencentcloud/provider.go b/tencentcloud/provider.go index 4036e0f4c6..fbdc10073c 100644 --- a/tencentcloud/provider.go +++ b/tencentcloud/provider.go @@ -1722,6 +1722,7 @@ func Provider() *schema.Provider { "tencentcloud_dayu_l7_rule_v2": dayuv2.ResourceTencentCloudDayuL7RuleV2(), "tencentcloud_dayu_eip": dayuv2.ResourceTencentCloudDayuEip(), "tencentcloud_ga2_endpoint_group": ga2.ResourceTencentCloudGa2EndpointGroup(), + "tencentcloud_ga2_global_accelerator": ga2.ResourceTencentCloudGa2GlobalAccelerator(), "tencentcloud_gaap_proxy": gaap.ResourceTencentCloudGaapProxy(), "tencentcloud_gaap_realserver": gaap.ResourceTencentCloudGaapRealserver(), "tencentcloud_gaap_layer4_listener": gaap.ResourceTencentCloudGaapLayer4Listener(), diff --git a/tencentcloud/provider.md b/tencentcloud/provider.md index 78f149540b..8c7f96e64a 100644 --- a/tencentcloud/provider.md +++ b/tencentcloud/provider.md @@ -2624,6 +2624,11 @@ tencentcloud_igtm_strategy tencentcloud_igtm_package_instance tencentcloud_igtm_package_task +Global Accelerator(GA2) +Resource +tencentcloud_ga2_endpoint_group +tencentcloud_ga2_global_accelerator + VCube Resource tencentcloud_vcube_application_and_video diff --git a/tencentcloud/services/ga2/resource_tc_ga2_global_accelerator.go b/tencentcloud/services/ga2/resource_tc_ga2_global_accelerator.go new file mode 100644 index 0000000000..19cc5322bf --- /dev/null +++ b/tencentcloud/services/ga2/resource_tc_ga2_global_accelerator.go @@ -0,0 +1,371 @@ +package ga2 + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + ga2v20250115 "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ga2/v20250115" + + tccommon "github.com/tencentcloudstack/terraform-provider-tencentcloud/tencentcloud/common" + "github.com/tencentcloudstack/terraform-provider-tencentcloud/tencentcloud/internal/helper" +) + +func ResourceTencentCloudGa2GlobalAccelerator() *schema.Resource { + return &schema.Resource{ + Create: resourceTencentCloudGa2GlobalAcceleratorCreate, + Read: resourceTencentCloudGa2GlobalAcceleratorRead, + Update: resourceTencentCloudGa2GlobalAcceleratorUpdate, + Delete: resourceTencentCloudGa2GlobalAcceleratorDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(20 * time.Minute), + Update: schema.DefaultTimeout(20 * time.Minute), + Delete: schema.DefaultTimeout(20 * time.Minute), + }, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + Description: "Name of the global accelerator instance. Maximum length is 60 bytes.", + }, + "instance_charge_type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "POSTPAID", + Description: "Billing mode. Valid values: `PREPAID` (prepaid, monthly subscription), `POSTPAID` (postpaid, pay-as-you-go). Default: `POSTPAID`. Currently only postpaid is supported.", + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: "Description of the global accelerator instance. Maximum length is 100 bytes.", + }, + "cross_border_type": { + Type: schema.TypeString, + Optional: true, + Description: "Cross-border type. Valid values: `HighQuality` (premium BGP-IP cross-border), `Unicom` (China Unicom dedicated line cross-border).", + }, + "cross_border_promise_flag": { + Type: schema.TypeBool, + Optional: true, + Description: "Flag indicating acceptance of cross-border service agreement. Must be set to `true` when using cross-border service.", + }, + "tags": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + Description: "Tag information.", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + // computed + "create_time": { + Type: schema.TypeString, + Computed: true, + Description: "Creation time of the global accelerator instance.", + }, + "state": { + Type: schema.TypeString, + Computed: true, + Description: "State of the global accelerator instance.", + }, + "status": { + Type: schema.TypeString, + Computed: true, + Description: "Status of the global accelerator instance.", + }, + "ddos_id": { + Type: schema.TypeString, + Computed: true, + Description: "DDoS protection ID of the global accelerator instance.", + }, + "cname": { + Type: schema.TypeString, + Computed: true, + Description: "CNAME domain of the global accelerator instance.", + }, + }, + } +} + +func resourceTencentCloudGa2GlobalAcceleratorCreate(d *schema.ResourceData, meta interface{}) error { + defer tccommon.LogElapsed("resource.tencentcloud_ga2_global_accelerator.create")() + defer tccommon.InconsistentCheck(d, meta)() + + var ( + logId = tccommon.GetLogId(tccommon.ContextNil) + ctx = tccommon.NewResourceLifeCycleHandleFuncContext(context.Background(), logId, d, meta) + request = ga2v20250115.NewCreateGlobalAcceleratorRequest() + response = ga2v20250115.NewCreateGlobalAcceleratorResponse() + ) + + if v, ok := d.GetOk("name"); ok { + request.Name = helper.String(v.(string)) + } + + if v, ok := d.GetOk("instance_charge_type"); ok { + request.InstanceChargeType = helper.String(v.(string)) + } + + if v, ok := d.GetOk("description"); ok { + request.Description = helper.String(v.(string)) + } + + if v, ok := d.GetOk("cross_border_type"); ok { + request.CrossBorderType = helper.String(v.(string)) + } + + if v, ok := d.GetOk("cross_border_promise_flag"); ok { + request.CrossBorderPromiseFlag = helper.Bool(v.(bool)) + } + + if v, ok := d.GetOk("tags"); ok { + tags := v.(map[string]interface{}) + tagList := make([]*ga2v20250115.Tag, 0, len(tags)) + for key, val := range tags { + tagList = append(tagList, &ga2v20250115.Tag{ + Key: helper.String(key), + Value: helper.String(val.(string)), + }) + } + request.Tags = tagList + } + + reqErr := resource.Retry(tccommon.WriteRetryTimeout, func() *resource.RetryError { + result, e := meta.(tccommon.ProviderMeta).GetAPIV3Conn().UseGa2V20250115Client().CreateGlobalAcceleratorWithContext(ctx, request) + if e != nil { + return tccommon.RetryError(e) + } + + log.Printf("[DEBUG]%s api[%s] success, request body [%s], response body [%s]\n", logId, request.GetAction(), request.ToJsonString(), result.ToJsonString()) + + if result == nil || result.Response == nil { + return resource.NonRetryableError(fmt.Errorf("Create ga2 global accelerator failed, Response is nil.")) + } + + response = result + return nil + }) + + if reqErr != nil { + log.Printf("[CRITAL]%s create ga2 global accelerator failed, reason:%+v", logId, reqErr) + return reqErr + } + + if response.Response.GlobalAcceleratorId == nil { + return fmt.Errorf("GlobalAcceleratorId is nil.") + } + globalAcceleratorId := *response.Response.GlobalAcceleratorId + + if response.Response.TaskId == nil { + return fmt.Errorf("TaskId is nil.") + } + taskId := *response.Response.TaskId + + service := Ga2Service{client: meta.(tccommon.ProviderMeta).GetAPIV3Conn()} + if err := service.WaitForGa2TaskFinish(ctx, taskId, d.Timeout(schema.TimeoutCreate)); err != nil { + return err + } + + d.SetId(globalAcceleratorId) + return resourceTencentCloudGa2GlobalAcceleratorRead(d, meta) +} + +func resourceTencentCloudGa2GlobalAcceleratorRead(d *schema.ResourceData, meta interface{}) error { + defer tccommon.LogElapsed("resource.tencentcloud_ga2_global_accelerator.read")() + defer tccommon.InconsistentCheck(d, meta)() + + var ( + logId = tccommon.GetLogId(tccommon.ContextNil) + ctx = tccommon.NewResourceLifeCycleHandleFuncContext(context.Background(), logId, d, meta) + service = Ga2Service{client: meta.(tccommon.ProviderMeta).GetAPIV3Conn()} + ) + + globalAcceleratorId := d.Id() + + respData, err := service.DescribeGa2GlobalAcceleratorById(ctx, globalAcceleratorId) + if err != nil { + return err + } + + if respData == nil { + log.Printf("[WARN]%s resource `tencentcloud_ga2_global_accelerator` [%s] not found, please check if it has been deleted.\n", logId, d.Id()) + d.SetId("") + return nil + } + + if respData.Name != nil { + _ = d.Set("name", respData.Name) + } + + if respData.InstanceChargeType != nil { + _ = d.Set("instance_charge_type", respData.InstanceChargeType) + } + + if respData.Description != nil { + _ = d.Set("description", respData.Description) + } + + if respData.CrossBorderType != nil { + _ = d.Set("cross_border_type", respData.CrossBorderType) + } + + if respData.CreateTime != nil { + _ = d.Set("create_time", respData.CreateTime) + } + + if respData.State != nil { + _ = d.Set("state", respData.State) + } + + if respData.Status != nil { + _ = d.Set("status", respData.Status) + } + + if respData.DdosId != nil { + _ = d.Set("ddos_id", respData.DdosId) + } + + if respData.Cname != nil { + _ = d.Set("cname", respData.Cname) + } + + if respData.TagSet != nil { + tags := make(map[string]string, len(respData.TagSet)) + for _, tag := range respData.TagSet { + if tag.Key != nil && tag.Value != nil { + tags[*tag.Key] = *tag.Value + } + } + _ = d.Set("tags", tags) + } + + return nil +} + +func resourceTencentCloudGa2GlobalAcceleratorUpdate(d *schema.ResourceData, meta interface{}) error { + defer tccommon.LogElapsed("resource.tencentcloud_ga2_global_accelerator.update")() + defer tccommon.InconsistentCheck(d, meta)() + + var ( + logId = tccommon.GetLogId(tccommon.ContextNil) + ctx = tccommon.NewResourceLifeCycleHandleFuncContext(context.Background(), logId, d, meta) + ) + + globalAcceleratorId := d.Id() + + needChange := false + request := ga2v20250115.NewModifyGlobalAcceleratorRequest() + request.GlobalAcceleratorId = helper.String(globalAcceleratorId) + + if d.HasChange("name") { + needChange = true + if v, ok := d.GetOk("name"); ok { + request.Name = helper.String(v.(string)) + } + } + + if d.HasChange("description") { + needChange = true + if v, ok := d.GetOk("description"); ok { + request.Description = helper.String(v.(string)) + } + } + + if d.HasChange("cross_border_type") { + needChange = true + if v, ok := d.GetOk("cross_border_type"); ok { + request.CrossBorderType = helper.String(v.(string)) + } + } + + if d.HasChange("cross_border_promise_flag") { + needChange = true + if v, ok := d.GetOk("cross_border_promise_flag"); ok { + request.CrossBorderPromiseFlag = helper.Bool(v.(bool)) + } + } + + if !needChange { + return resourceTencentCloudGa2GlobalAcceleratorRead(d, meta) + } + + var taskId string + reqErr := resource.Retry(tccommon.WriteRetryTimeout, func() *resource.RetryError { + result, e := meta.(tccommon.ProviderMeta).GetAPIV3Conn().UseGa2V20250115Client().ModifyGlobalAcceleratorWithContext(ctx, request) + if e != nil { + return tccommon.RetryError(e) + } + + log.Printf("[DEBUG]%s api[%s] success, request body [%s], response body [%s]\n", logId, request.GetAction(), request.ToJsonString(), result.ToJsonString()) + + if result == nil || result.Response == nil || result.Response.TaskId == nil { + return resource.NonRetryableError(fmt.Errorf("Modify ga2 global accelerator failed, Response is nil.")) + } + + taskId = *result.Response.TaskId + return nil + }) + + if reqErr != nil { + log.Printf("[CRITAL]%s update ga2 global accelerator failed, reason:%+v", logId, reqErr) + return reqErr + } + + service := Ga2Service{client: meta.(tccommon.ProviderMeta).GetAPIV3Conn()} + if err := service.WaitForGa2TaskFinish(ctx, taskId, d.Timeout(schema.TimeoutUpdate)); err != nil { + return err + } + + return resourceTencentCloudGa2GlobalAcceleratorRead(d, meta) +} + +func resourceTencentCloudGa2GlobalAcceleratorDelete(d *schema.ResourceData, meta interface{}) error { + defer tccommon.LogElapsed("resource.tencentcloud_ga2_global_accelerator.delete")() + defer tccommon.InconsistentCheck(d, meta)() + + var ( + logId = tccommon.GetLogId(tccommon.ContextNil) + ctx = tccommon.NewResourceLifeCycleHandleFuncContext(context.Background(), logId, d, meta) + request = ga2v20250115.NewDeleteGlobalAcceleratorRequest() + ) + + globalAcceleratorId := d.Id() + request.GlobalAcceleratorId = helper.String(globalAcceleratorId) + + var taskId string + reqErr := resource.Retry(tccommon.WriteRetryTimeout, func() *resource.RetryError { + result, e := meta.(tccommon.ProviderMeta).GetAPIV3Conn().UseGa2V20250115Client().DeleteGlobalAcceleratorWithContext(ctx, request) + if e != nil { + return tccommon.RetryError(e) + } + + log.Printf("[DEBUG]%s api[%s] success, request body [%s], response body [%s]\n", logId, request.GetAction(), request.ToJsonString(), result.ToJsonString()) + + if result == nil || result.Response == nil || result.Response.TaskId == nil { + return resource.NonRetryableError(fmt.Errorf("Delete ga2 global accelerator failed, Response is nil.")) + } + + taskId = *result.Response.TaskId + return nil + }) + + if reqErr != nil { + log.Printf("[CRITAL]%s delete ga2 global accelerator failed, reason:%+v", logId, reqErr) + return reqErr + } + + service := Ga2Service{client: meta.(tccommon.ProviderMeta).GetAPIV3Conn()} + if err := service.WaitForGa2TaskFinish(ctx, taskId, d.Timeout(schema.TimeoutDelete)); err != nil { + return err + } + + return nil +} diff --git a/tencentcloud/services/ga2/resource_tc_ga2_global_accelerator.md b/tencentcloud/services/ga2/resource_tc_ga2_global_accelerator.md new file mode 100644 index 0000000000..5d62b09a95 --- /dev/null +++ b/tencentcloud/services/ga2/resource_tc_ga2_global_accelerator.md @@ -0,0 +1,25 @@ +Provides a resource to create a GA2 (Global Accelerator 2) global accelerator instance. + +Example Usage + +```hcl +resource "tencentcloud_ga2_global_accelerator" "example" { + name = "tf-example" + instance_charge_type = "POSTPAID" + description = "terraform example global accelerator" + cross_border_type = "HighQuality" + cross_border_promise_flag = true + + tags = { + createdBy = "terraform" + } +} +``` + +Import + +GA2 global accelerator can be imported using the ID, e.g. + +``` +terraform import tencentcloud_ga2_global_accelerator.example ga2-xxxxxxxx +``` diff --git a/tencentcloud/services/ga2/resource_tc_ga2_global_accelerator_test.go b/tencentcloud/services/ga2/resource_tc_ga2_global_accelerator_test.go new file mode 100644 index 0000000000..7779b9e42c --- /dev/null +++ b/tencentcloud/services/ga2/resource_tc_ga2_global_accelerator_test.go @@ -0,0 +1,315 @@ +package ga2_test + +import ( + "testing" + + "github.com/agiledragon/gomonkey/v2" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/stretchr/testify/assert" + ga2v20250115 "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ga2/v20250115" + + tccommon "github.com/tencentcloudstack/terraform-provider-tencentcloud/tencentcloud/common" + "github.com/tencentcloudstack/terraform-provider-tencentcloud/tencentcloud/connectivity" + "github.com/tencentcloudstack/terraform-provider-tencentcloud/tencentcloud/services/ga2" +) + +type mockMetaGa2GlobalAccelerator struct { + client *connectivity.TencentCloudClient +} + +func (m *mockMetaGa2GlobalAccelerator) GetAPIV3Conn() *connectivity.TencentCloudClient { + return m.client +} + +var _ tccommon.ProviderMeta = &mockMetaGa2GlobalAccelerator{} + +func newMockMetaGa2GlobalAccelerator() *mockMetaGa2GlobalAccelerator { + return &mockMetaGa2GlobalAccelerator{client: &connectivity.TencentCloudClient{Region: "ap-guangzhou"}} +} + +func ptrStringGa2(s string) *string { + return &s +} + +func ptrBoolGa2(b bool) *bool { + return &b +} + +// go test ./tencentcloud/services/ga2/ -run "TestUnitGa2GlobalAccelerator" -v -count=1 -gcflags="all=-l" + +func TestUnitGa2GlobalAccelerator_Create(t *testing.T) { + patches := gomonkey.NewPatches() + defer patches.Reset() + + meta := newMockMetaGa2GlobalAccelerator() + ga2Client := &ga2v20250115.Client{} + patches.ApplyMethodReturn(meta.client, "UseGa2V20250115Client", ga2Client) + + // Patch CreateGlobalAcceleratorWithContext + patches.ApplyMethodFunc(ga2Client, "CreateGlobalAcceleratorWithContext", func(_ interface{}, request *ga2v20250115.CreateGlobalAcceleratorRequest) (*ga2v20250115.CreateGlobalAcceleratorResponse, error) { + assert.Equal(t, "tf-test-ga2", *request.Name) + assert.Equal(t, "POSTPAID", *request.InstanceChargeType) + assert.Equal(t, "test description", *request.Description) + assert.Equal(t, "HighQuality", *request.CrossBorderType) + assert.Equal(t, true, *request.CrossBorderPromiseFlag) + + resp := &ga2v20250115.CreateGlobalAcceleratorResponse{} + resp.Response = &ga2v20250115.CreateGlobalAcceleratorResponseParams{ + GlobalAcceleratorId: ptrStringGa2("ga2-test001"), + TaskId: ptrStringGa2("task-create-001"), + RequestId: ptrStringGa2("fake-request-id"), + } + return resp, nil + }) + + // Patch DescribeTaskResultWithContext for WaitForGa2TaskFinish + patches.ApplyMethodFunc(ga2Client, "DescribeTaskResultWithContext", func(_ interface{}, request *ga2v20250115.DescribeTaskResultRequest) (*ga2v20250115.DescribeTaskResultResponse, error) { + resp := &ga2v20250115.DescribeTaskResultResponse{} + resp.Response = &ga2v20250115.DescribeTaskResultResponseParams{ + Status: ptrStringGa2("SUCCESS"), + RequestId: ptrStringGa2("fake-request-id"), + } + return resp, nil + }) + + // Patch DescribeGlobalAcceleratorsWithContext for Read + patches.ApplyMethodFunc(ga2Client, "DescribeGlobalAcceleratorsWithContext", func(_ interface{}, request *ga2v20250115.DescribeGlobalAcceleratorsRequest) (*ga2v20250115.DescribeGlobalAcceleratorsResponse, error) { + resp := &ga2v20250115.DescribeGlobalAcceleratorsResponse{} + resp.Response = &ga2v20250115.DescribeGlobalAcceleratorsResponseParams{ + GlobalAcceleratorSet: []*ga2v20250115.GlobalAcceleratorSet{ + { + GlobalAcceleratorId: ptrStringGa2("ga2-test001"), + Name: ptrStringGa2("tf-test-ga2"), + Description: ptrStringGa2("test description"), + InstanceChargeType: ptrStringGa2("POSTPAID"), + CrossBorderType: ptrStringGa2("HighQuality"), + CreateTime: ptrStringGa2("2025-01-15 10:00:00"), + State: ptrStringGa2("RUNNING"), + Status: ptrStringGa2("NORMAL"), + DdosId: ptrStringGa2("ddos-001"), + Cname: ptrStringGa2("ga2-test001.example.com"), + TagSet: []*ga2v20250115.Tag{ + { + Key: ptrStringGa2("createdBy"), + Value: ptrStringGa2("terraform"), + }, + }, + }, + }, + TotalCount: func() *uint64 { v := uint64(1); return &v }(), + RequestId: ptrStringGa2("fake-request-id"), + } + return resp, nil + }) + + res := ga2.ResourceTencentCloudGa2GlobalAccelerator() + d := schema.TestResourceDataRaw(t, res.Schema, map[string]interface{}{ + "name": "tf-test-ga2", + "instance_charge_type": "POSTPAID", + "description": "test description", + "cross_border_type": "HighQuality", + "cross_border_promise_flag": true, + "tags": map[string]interface{}{ + "createdBy": "terraform", + }, + }) + + err := res.Create(d, meta) + assert.NoError(t, err) + assert.Equal(t, "ga2-test001", d.Id()) +} + +func TestUnitGa2GlobalAccelerator_Read(t *testing.T) { + patches := gomonkey.NewPatches() + defer patches.Reset() + + meta := newMockMetaGa2GlobalAccelerator() + ga2Client := &ga2v20250115.Client{} + patches.ApplyMethodReturn(meta.client, "UseGa2V20250115Client", ga2Client) + + // Patch DescribeGlobalAcceleratorsWithContext + patches.ApplyMethodFunc(ga2Client, "DescribeGlobalAcceleratorsWithContext", func(_ interface{}, request *ga2v20250115.DescribeGlobalAcceleratorsRequest) (*ga2v20250115.DescribeGlobalAcceleratorsResponse, error) { + resp := &ga2v20250115.DescribeGlobalAcceleratorsResponse{} + resp.Response = &ga2v20250115.DescribeGlobalAcceleratorsResponseParams{ + GlobalAcceleratorSet: []*ga2v20250115.GlobalAcceleratorSet{ + { + GlobalAcceleratorId: ptrStringGa2("ga2-test001"), + Name: ptrStringGa2("tf-test-ga2"), + Description: ptrStringGa2("test description"), + InstanceChargeType: ptrStringGa2("POSTPAID"), + CrossBorderType: ptrStringGa2("HighQuality"), + CreateTime: ptrStringGa2("2025-01-15 10:00:00"), + State: ptrStringGa2("RUNNING"), + Status: ptrStringGa2("NORMAL"), + DdosId: ptrStringGa2("ddos-001"), + Cname: ptrStringGa2("ga2-test001.example.com"), + TagSet: []*ga2v20250115.Tag{ + { + Key: ptrStringGa2("createdBy"), + Value: ptrStringGa2("terraform"), + }, + }, + }, + }, + TotalCount: func() *uint64 { v := uint64(1); return &v }(), + RequestId: ptrStringGa2("fake-request-id"), + } + return resp, nil + }) + + res := ga2.ResourceTencentCloudGa2GlobalAccelerator() + d := schema.TestResourceDataRaw(t, res.Schema, map[string]interface{}{}) + d.SetId("ga2-test001") + + err := res.Read(d, meta) + assert.NoError(t, err) + assert.Equal(t, "tf-test-ga2", d.Get("name")) + assert.Equal(t, "POSTPAID", d.Get("instance_charge_type")) + assert.Equal(t, "test description", d.Get("description")) + assert.Equal(t, "HighQuality", d.Get("cross_border_type")) + assert.Equal(t, "2025-01-15 10:00:00", d.Get("create_time")) + assert.Equal(t, "RUNNING", d.Get("state")) + assert.Equal(t, "NORMAL", d.Get("status")) + assert.Equal(t, "ddos-001", d.Get("ddos_id")) + assert.Equal(t, "ga2-test001.example.com", d.Get("cname")) +} + +func TestUnitGa2GlobalAccelerator_ReadNotFound(t *testing.T) { + patches := gomonkey.NewPatches() + defer patches.Reset() + + meta := newMockMetaGa2GlobalAccelerator() + ga2Client := &ga2v20250115.Client{} + patches.ApplyMethodReturn(meta.client, "UseGa2V20250115Client", ga2Client) + + // Patch DescribeGlobalAcceleratorsWithContext - return empty + patches.ApplyMethodFunc(ga2Client, "DescribeGlobalAcceleratorsWithContext", func(_ interface{}, request *ga2v20250115.DescribeGlobalAcceleratorsRequest) (*ga2v20250115.DescribeGlobalAcceleratorsResponse, error) { + resp := &ga2v20250115.DescribeGlobalAcceleratorsResponse{} + resp.Response = &ga2v20250115.DescribeGlobalAcceleratorsResponseParams{ + GlobalAcceleratorSet: []*ga2v20250115.GlobalAcceleratorSet{}, + TotalCount: func() *uint64 { v := uint64(0); return &v }(), + RequestId: ptrStringGa2("fake-request-id"), + } + return resp, nil + }) + + res := ga2.ResourceTencentCloudGa2GlobalAccelerator() + d := schema.TestResourceDataRaw(t, res.Schema, map[string]interface{}{}) + d.SetId("ga2-notexist") + + err := res.Read(d, meta) + assert.NoError(t, err) + assert.Equal(t, "", d.Id()) +} + +func TestUnitGa2GlobalAccelerator_Update(t *testing.T) { + patches := gomonkey.NewPatches() + defer patches.Reset() + + meta := newMockMetaGa2GlobalAccelerator() + ga2Client := &ga2v20250115.Client{} + patches.ApplyMethodReturn(meta.client, "UseGa2V20250115Client", ga2Client) + + // Patch ModifyGlobalAcceleratorWithContext + patches.ApplyMethodFunc(ga2Client, "ModifyGlobalAcceleratorWithContext", func(_ interface{}, request *ga2v20250115.ModifyGlobalAcceleratorRequest) (*ga2v20250115.ModifyGlobalAcceleratorResponse, error) { + assert.Equal(t, "ga2-test001", *request.GlobalAcceleratorId) + assert.Equal(t, "tf-test-ga2-updated", *request.Name) + assert.Equal(t, "updated description", *request.Description) + + resp := &ga2v20250115.ModifyGlobalAcceleratorResponse{} + resp.Response = &ga2v20250115.ModifyGlobalAcceleratorResponseParams{ + TaskId: ptrStringGa2("task-modify-001"), + RequestId: ptrStringGa2("fake-request-id"), + } + return resp, nil + }) + + // Patch DescribeTaskResultWithContext for WaitForGa2TaskFinish + patches.ApplyMethodFunc(ga2Client, "DescribeTaskResultWithContext", func(_ interface{}, request *ga2v20250115.DescribeTaskResultRequest) (*ga2v20250115.DescribeTaskResultResponse, error) { + resp := &ga2v20250115.DescribeTaskResultResponse{} + resp.Response = &ga2v20250115.DescribeTaskResultResponseParams{ + Status: ptrStringGa2("SUCCESS"), + RequestId: ptrStringGa2("fake-request-id"), + } + return resp, nil + }) + + // Patch DescribeGlobalAcceleratorsWithContext for Read after update + patches.ApplyMethodFunc(ga2Client, "DescribeGlobalAcceleratorsWithContext", func(_ interface{}, request *ga2v20250115.DescribeGlobalAcceleratorsRequest) (*ga2v20250115.DescribeGlobalAcceleratorsResponse, error) { + resp := &ga2v20250115.DescribeGlobalAcceleratorsResponse{} + resp.Response = &ga2v20250115.DescribeGlobalAcceleratorsResponseParams{ + GlobalAcceleratorSet: []*ga2v20250115.GlobalAcceleratorSet{ + { + GlobalAcceleratorId: ptrStringGa2("ga2-test001"), + Name: ptrStringGa2("tf-test-ga2-updated"), + Description: ptrStringGa2("updated description"), + InstanceChargeType: ptrStringGa2("POSTPAID"), + CrossBorderType: ptrStringGa2("HighQuality"), + CreateTime: ptrStringGa2("2025-01-15 10:00:00"), + State: ptrStringGa2("RUNNING"), + Status: ptrStringGa2("NORMAL"), + DdosId: ptrStringGa2("ddos-001"), + Cname: ptrStringGa2("ga2-test001.example.com"), + }, + }, + TotalCount: func() *uint64 { v := uint64(1); return &v }(), + RequestId: ptrStringGa2("fake-request-id"), + } + return resp, nil + }) + + res := ga2.ResourceTencentCloudGa2GlobalAccelerator() + d := schema.TestResourceDataRaw(t, res.Schema, map[string]interface{}{ + "name": "tf-test-ga2-updated", + "instance_charge_type": "POSTPAID", + "description": "updated description", + "cross_border_type": "HighQuality", + }) + d.SetId("ga2-test001") + + // Simulate HasChange by marking fields as changed + d.MarkNewResource() + + err := res.Update(d, meta) + assert.NoError(t, err) + assert.Equal(t, "tf-test-ga2-updated", d.Get("name")) + assert.Equal(t, "updated description", d.Get("description")) +} + +func TestUnitGa2GlobalAccelerator_Delete(t *testing.T) { + patches := gomonkey.NewPatches() + defer patches.Reset() + + meta := newMockMetaGa2GlobalAccelerator() + ga2Client := &ga2v20250115.Client{} + patches.ApplyMethodReturn(meta.client, "UseGa2V20250115Client", ga2Client) + + // Patch DeleteGlobalAcceleratorWithContext + patches.ApplyMethodFunc(ga2Client, "DeleteGlobalAcceleratorWithContext", func(_ interface{}, request *ga2v20250115.DeleteGlobalAcceleratorRequest) (*ga2v20250115.DeleteGlobalAcceleratorResponse, error) { + assert.Equal(t, "ga2-test001", *request.GlobalAcceleratorId) + + resp := &ga2v20250115.DeleteGlobalAcceleratorResponse{} + resp.Response = &ga2v20250115.DeleteGlobalAcceleratorResponseParams{ + TaskId: ptrStringGa2("task-delete-001"), + RequestId: ptrStringGa2("fake-request-id"), + } + return resp, nil + }) + + // Patch DescribeTaskResultWithContext for WaitForGa2TaskFinish + patches.ApplyMethodFunc(ga2Client, "DescribeTaskResultWithContext", func(_ interface{}, request *ga2v20250115.DescribeTaskResultRequest) (*ga2v20250115.DescribeTaskResultResponse, error) { + resp := &ga2v20250115.DescribeTaskResultResponse{} + resp.Response = &ga2v20250115.DescribeTaskResultResponseParams{ + Status: ptrStringGa2("SUCCESS"), + RequestId: ptrStringGa2("fake-request-id"), + } + return resp, nil + }) + + res := ga2.ResourceTencentCloudGa2GlobalAccelerator() + d := schema.TestResourceDataRaw(t, res.Schema, map[string]interface{}{}) + d.SetId("ga2-test001") + + err := res.Delete(d, meta) + assert.NoError(t, err) +} diff --git a/tencentcloud/services/ga2/service_tencentcloud_ga2.go b/tencentcloud/services/ga2/service_tencentcloud_ga2.go index 2e50d5bf0b..84a4df43a3 100644 --- a/tencentcloud/services/ga2/service_tencentcloud_ga2.go +++ b/tencentcloud/services/ga2/service_tencentcloud_ga2.go @@ -99,6 +99,70 @@ func (me *Ga2Service) DescribeGa2EndpointGroupById(ctx context.Context, gaId, li return nil, nil } +// DescribeGa2GlobalAcceleratorById queries a single global accelerator instance by ID. +// Returns (nil, nil) when the instance does not exist. +func (me *Ga2Service) DescribeGa2GlobalAcceleratorById(ctx context.Context, globalAcceleratorId string) (*ga2v20250115.GlobalAcceleratorSet, error) { + logId := tccommon.GetLogId(ctx) + + request := ga2v20250115.NewDescribeGlobalAcceleratorsRequest() + request.Filters = []*ga2v20250115.Filter{ + { + Name: helper.String("global-accelerator-id"), + Values: []*string{helper.String(globalAcceleratorId)}, + }, + } + + var ( + offset uint64 = 0 + limit uint64 = 100 + ) + + for { + request.Offset = &offset + request.Limit = &limit + + var response *ga2v20250115.DescribeGlobalAcceleratorsResponse + err := resource.Retry(tccommon.ReadRetryTimeout, func() *resource.RetryError { + result, e := me.client.UseGa2V20250115Client().DescribeGlobalAcceleratorsWithContext(ctx, request) + if e != nil { + return tccommon.RetryError(e) + } + + if result == nil || result.Response == nil { + return resource.NonRetryableError(fmt.Errorf("Describe ga2 global accelerators failed, Response is nil.")) + } + + response = result + return nil + }) + + if err != nil { + log.Printf("[CRITAL]%s describe ga2 global accelerators failed, reason:%+v", logId, err) + return nil, err + } + + set := response.Response.GlobalAcceleratorSet + for i := range set { + item := set[i] + if item == nil { + continue + } + + if item.GlobalAcceleratorId != nil && *item.GlobalAcceleratorId == globalAcceleratorId { + return item, nil + } + } + + if uint64(len(set)) < limit { + break + } + + offset += limit + } + + return nil, nil +} + // WaitForGa2TaskFinish polls DescribeTaskResult until the task reaches "SUCCESS" or the given timeout elapses. // The timeout is supplied by the caller because different async operations (create/modify/delete on // different resource types) may require very different waiting budgets. diff --git a/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http/request.go b/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http/request.go index 7033cf764f..d6327acc7b 100644 --- a/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http/request.go +++ b/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http/request.go @@ -3,7 +3,6 @@ package common import ( "context" "io" - //"log" "math/rand" "net/url" diff --git a/website/docs/r/ga2_endpoint_group.html.markdown b/website/docs/r/ga2_endpoint_group.html.markdown new file mode 100644 index 0000000000..404b56227f --- /dev/null +++ b/website/docs/r/ga2_endpoint_group.html.markdown @@ -0,0 +1,119 @@ +--- +subcategory: "Global Accelerator(GA2)" +layout: "tencentcloud" +page_title: "TencentCloud: tencentcloud_ga2_endpoint_group" +sidebar_current: "docs-tencentcloud-resource-ga2_endpoint_group" +description: |- + Provides a resource to create a GA2 (Global Accelerator 2) endpoint group. +--- + +# tencentcloud_ga2_endpoint_group + +Provides a resource to create a GA2 (Global Accelerator 2) endpoint group. + +## Example Usage + +```hcl +resource "tencentcloud_ga2_endpoint_group" "example" { + global_accelerator_id = "ga2-xxxxxxxx" + listener_id = "lis-xxxxxxxx" + endpoint_group_type = "DEFAULT" + + endpoint_group_configuration { + name = "tf-example" + endpoint_group_region = "ap-guangzhou" + description = "tf example endpoint group" + enable_health_check = true + check_type = "HTTP" + check_port = "80" + check_path = "/" + check_method = "GET" + connect_timeout = 5000 + health_check_interval = 30 + healthy_threshold = 3 + unhealthy_threshold = 3 + forward_protocol = "HTTP" + + endpoint_configurations { + endpoint_type = "PublicIp" + endpoint_service = "1.1.1.1" + weight = 10 + } + + endpoint_configurations { + endpoint_type = "Domain" + endpoint_service = "example.com" + weight = 20 + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `endpoint_group_configuration` - (Required, List) Endpoint group configuration. +* `endpoint_group_type` - (Required, String, ForceNew) Endpoint group type. Valid values: `VIRTUAL`, `DEFAULT`. +* `global_accelerator_id` - (Required, String, ForceNew) Global accelerator instance ID. +* `listener_id` - (Required, String, ForceNew) Listener ID. + +The `endpoint_configurations` object of `endpoint_group_configuration` supports the following: + +* `endpoint_service` - (Optional, String) Endpoint domain or IP. +* `endpoint_type` - (Optional, String) Endpoint type. Valid values: `Domain`, `PublicIp`. +* `weight` - (Optional, Int) Endpoint weight. + +The `endpoint_group_configuration` object supports the following: + +* `check_domain` - (Optional, String) Health check domain. +* `check_method` - (Optional, String) Health check request method. +* `check_path` - (Optional, String) Health check URL path. +* `check_port` - (Optional, String) Health check port. +* `check_recv_context` - (Optional, String) Health check expected response. +* `check_send_context` - (Optional, String) Health check request payload. +* `check_type` - (Optional, String) Health check protocol. Valid values: `TCP`, `HTTP`, `HTTPS`, `PING`, `CUSTOM`. +* `cipher_policy_id` - (Optional, String) HTTPS cipher policy ID. +* `connect_timeout` - (Optional, Int) Response timeout in milliseconds. +* `context_type` - (Optional, String) Health check content type. +* `description` - (Optional, String) Description. Maximum length is 100 bytes. +* `enable_health_check` - (Optional, Bool) Whether to enable health check. +* `endpoint_configurations` - (Optional, List) Endpoint configurations under this group. +* `endpoint_group_region` - (Optional, String) Region of the endpoint group. +* `forward_protocol` - (Optional, String) Forward protocol back to origin. +* `health_check_interval` - (Optional, Int) Health check interval in seconds. +* `healthy_threshold` - (Optional, Int) Healthy threshold count. +* `isp_type` - (Optional, String) ISP type. +* `name` - (Optional, String) Name. Maximum length is 60 bytes. +* `port_overrides` - (Optional, List) Port overrides for the endpoint group. +* `status_mask` - (Optional, List) Status code masks for health check. +* `unhealthy_threshold` - (Optional, Int) Unhealthy threshold count. + +The `port_overrides` object of `endpoint_group_configuration` supports the following: + +* `endpoint_port` - (Optional, Int) Endpoint port. +* `listener_port` - (Optional, Int) Listener port. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - ID of the resource. +* `endpoint_group_id` - Endpoint group instance ID. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts) for certain actions: + +* `create` - (Defaults to `20m`) Used when creating the resource. +* `update` - (Defaults to `20m`) Used when updating the resource. +* `delete` - (Defaults to `20m`) Used when deleting the resource. + +## Import + +GA2 endpoint group can be imported using the composite ID `##`, e.g. + +``` +terraform import tencentcloud_ga2_endpoint_group.example ga2-xxxxxxxx#lis-xxxxxxxx#eg-xxxxxxxx +``` + diff --git a/website/docs/r/ga2_global_accelerator.html.markdown b/website/docs/r/ga2_global_accelerator.html.markdown new file mode 100644 index 0000000000..96dd31d751 --- /dev/null +++ b/website/docs/r/ga2_global_accelerator.html.markdown @@ -0,0 +1,67 @@ +--- +subcategory: "Global Accelerator(GA2)" +layout: "tencentcloud" +page_title: "TencentCloud: tencentcloud_ga2_global_accelerator" +sidebar_current: "docs-tencentcloud-resource-ga2_global_accelerator" +description: |- + Provides a resource to create a GA2 (Global Accelerator 2) global accelerator instance. +--- + +# tencentcloud_ga2_global_accelerator + +Provides a resource to create a GA2 (Global Accelerator 2) global accelerator instance. + +## Example Usage + +```hcl +resource "tencentcloud_ga2_global_accelerator" "example" { + name = "tf-example" + instance_charge_type = "POSTPAID" + description = "terraform example global accelerator" + cross_border_type = "HighQuality" + cross_border_promise_flag = true + + tags = { + createdBy = "terraform" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `cross_border_promise_flag` - (Optional, Bool) Flag indicating acceptance of cross-border service agreement. Must be set to `true` when using cross-border service. +* `cross_border_type` - (Optional, String) Cross-border type. Valid values: `HighQuality` (premium BGP-IP cross-border), `Unicom` (China Unicom dedicated line cross-border). +* `description` - (Optional, String) Description of the global accelerator instance. Maximum length is 100 bytes. +* `instance_charge_type` - (Optional, String, ForceNew) Billing mode. Valid values: `PREPAID` (prepaid, monthly subscription), `POSTPAID` (postpaid, pay-as-you-go). Default: `POSTPAID`. Currently only postpaid is supported. +* `name` - (Optional, String) Name of the global accelerator instance. Maximum length is 60 bytes. +* `tags` - (Optional, Map, ForceNew) Tag information. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - ID of the resource. +* `cname` - CNAME domain of the global accelerator instance. +* `create_time` - Creation time of the global accelerator instance. +* `ddos_id` - DDoS protection ID of the global accelerator instance. +* `state` - State of the global accelerator instance. +* `status` - Status of the global accelerator instance. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts) for certain actions: + +* `create` - (Defaults to `20m`) Used when creating the resource. +* `update` - (Defaults to `20m`) Used when updating the resource. +* `delete` - (Defaults to `20m`) Used when deleting the resource. + +## Import + +GA2 global accelerator can be imported using the ID, e.g. + +``` +terraform import tencentcloud_ga2_global_accelerator.example ga2-xxxxxxxx +``` + diff --git a/website/tencentcloud.erb b/website/tencentcloud.erb index 8c336c3438..16d5adb12e 100644 --- a/website/tencentcloud.erb +++ b/website/tencentcloud.erb @@ -3009,6 +3009,23 @@ +
  • + Global Accelerator(GA2) + +
  • Global Application Acceleration(GAAP)