Skip to content

Commit 089e8e7

Browse files
committed
Merge 'publish/v0.0.60' into 'main'
publish v0.0.60 See merge request: !88
2 parents 7654cbb + 1024a59 commit 089e8e7

15 files changed

Lines changed: 336 additions & 23 deletions

File tree

docs/data-sources/ecs_instance.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Data Source schema for Volcengine::ECS::Instance
4343
- Currently, only g3al, c3al, r3al, g4i, c4i, r4i, g4ie, c4ie, r4ie instances support this parameter. For base/turbo frequencies and more information, see [Instance Specifications Introduction](https://www.volcengine.com/docs/6396/70840).
4444
- This feature is in invitation-only testing. To use it, please contact your account manager.
4545
- `cpu_memory` (Attributes) The CPU options for the instance. (see [below for nested schema](#nestedatt--cpu_memory))
46+
- `cpu_options` (Attributes) CPU configuration options for the instance (see [below for nested schema](#nestedatt--cpu_options))
4647
- `created_at` (String) Instance creation time.
4748
- `credit_specification` (String) Burstable instance operating mode. Values:
4849
- Standard: Standard mode.
@@ -167,6 +168,19 @@ Read-Only:
167168
- `threads_per_core` (Number) Threads per core for the instance.
168169

169170

171+
<a id="nestedatt--cpu_options"></a>
172+
### Nested Schema for `cpu_options`
173+
174+
Read-Only:
175+
176+
- `topology_type` (String) CPU topology mode. Available values:
177+
- ContinuousCoreToHTMapping: Continuous HT mode
178+
- DiscreteCoreToHTMapping (default): Discrete HT mode
179+
180+
**Note:**
181+
This feature is currently in invitation-only testing. To use it, please contact your account manager to apply.
182+
183+
170184
<a id="nestedatt--data_volumes"></a>
171185
### Nested Schema for `data_volumes`
172186

docs/resources/ecs_instance.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ resource "volcenginecc_ecs_instance" "EcsInstanceDemo" {
5757
delete_with_instance = true
5858
volume_type = "ESSD_FlexPL"
5959
}
60+
cpu_options = {
61+
topology_type = "DiscreteCoreToHTMapping"
62+
}
6063
}
6164
```
6265

@@ -99,6 +102,7 @@ resource "volcenginecc_ecs_instance" "EcsInstanceDemo" {
99102
**Note:**
100103
- Currently, only g3al, c3al, r3al, g4i, c4i, r4i, g4ie, c4ie, r4ie instances support this parameter. For base/turbo frequencies and more information, see [Instance Specifications Introduction](https://www.volcengine.com/docs/6396/70840).
101104
- This feature is in invitation-only testing. To use it, please contact your account manager.
105+
- `cpu_options` (Attributes) CPU configuration options for the instance (see [below for nested schema](#nestedatt--cpu_options))
102106
- `credit_specification` (String) Burstable instance operating mode. Values:
103107
- Standard: Standard mode.
104108
- Unlimited: Unlimited performance mode (not supported yet).
@@ -270,6 +274,19 @@ Read-Only:
270274
- `volume_id` (String) Instance volume ID.
271275

272276

277+
<a id="nestedatt--cpu_options"></a>
278+
### Nested Schema for `cpu_options`
279+
280+
Optional:
281+
282+
- `topology_type` (String) CPU topology mode. Available values:
283+
- ContinuousCoreToHTMapping: Continuous HT mode
284+
- DiscreteCoreToHTMapping (default): Discrete HT mode
285+
286+
**Note:**
287+
This feature is currently in invitation-only testing. To use it, please contact your account manager to apply.
288+
289+
273290
<a id="nestedatt--data_volumes"></a>
274291
### Nested Schema for `data_volumes`
275292

examples/resources/volcenginecc_ecs_instance/ecs_instance.tf

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,7 @@ resource "volcenginecc_ecs_instance" "EcsInstanceDemo" {
4343
delete_with_instance = true
4444
volume_type = "ESSD_FlexPL"
4545
}
46+
cpu_options = {
47+
topology_type = "DiscreteCoreToHTMapping"
48+
}
4649
}

internal/common/common.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ package common
22

33
const (
44
TerraformProviderName = "terraform-provider-volcenginecc"
5-
TerraformProviderVersion = "0.0.59"
5+
TerraformProviderVersion = "0.0.60"
66
)

internal/generic/resource.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,7 @@ func (r *genericResource) Read(ctx context.Context, request resource.ReadRequest
548548

549549
if tfresource.NotFound(err) {
550550
response.Diagnostics.Append(ResourceNotFoundWarningDiag(err))
551-
//response.State.RemoveResource(ctx)
551+
response.State.RemoveResource(ctx)
552552

553553
return
554554
}
@@ -879,6 +879,11 @@ func (r *genericResource) Delete(ctx context.Context, request resource.DeleteReq
879879

880880
err = tfcloudcontrol.DeleteResource(ctx, conn, r.provider.Region(ctx), "", r.ccTypeName, id)
881881

882+
if tfresource.NotFound(err) {
883+
response.State.RemoveResource(ctx)
884+
return
885+
}
886+
882887
if err != nil {
883888
response.Diagnostics.Append(ServiceOperationErrorDiag("Cloud Control API", "DeleteResource", err))
884889

internal/service/cloudcontrol/delete.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/hashicorp/terraform-plugin-log/tflog"
1313
"github.com/volcengine/terraform-provider-volcenginecc/internal/base"
1414
"github.com/volcengine/terraform-provider-volcenginecc/internal/cloudcontrol"
15+
"github.com/volcengine/terraform-provider-volcenginecc/internal/tfresource"
1516
"github.com/volcengine/terraform-provider-volcenginecc/internal/util"
1617
)
1718

@@ -28,7 +29,7 @@ func DeleteResource(ctx context.Context, cloudControlClient *cloudcontrol.CloudC
2829
ClientToken: util.StringPtr(util.GenerateToken(32)),
2930
})
3031
if err != nil {
31-
return err
32+
return wrapCloudControlNotFound(err)
3233
}
3334
if resp == nil || resp.OperationStatus == nil {
3435
return fmt.Errorf("call DeleteResource failed,resp:%s,err:%v ", util.JsonString(resp), err)
@@ -37,7 +38,11 @@ func DeleteResource(ctx context.Context, cloudControlClient *cloudcontrol.CloudC
3738
taskId := ""
3839
status := *resp.OperationStatus
3940
if status == base.FAILED {
40-
return fmt.Errorf("invoke DeleteResource handler failed,resp:%s ", util.JsonString(resp))
41+
err := fmt.Errorf("invoke DeleteResource handler failed,resp:%s ", util.JsonString(resp))
42+
if isCloudControlNotFoundProgressEvent(&resp.ProgressEvent) {
43+
return &tfresource.NotFoundError{LastError: err}
44+
}
45+
return err
4146
} else if status == base.SUCCESS {
4247
return nil
4348
} else if status == base.IN_PROGRESS || status == base.PENDING {
@@ -88,7 +93,11 @@ func AwaitTask(ctx context.Context, client *cloudcontrol.CloudControl, taskId st
8893

8994
status := *output.OperationStatus
9095
if status == base.FAILED {
91-
return nil, false, fmt.Errorf("invoke get task failed,resp:%s,err:%v ", util.JsonString(output), err)
96+
err := fmt.Errorf("invoke get task failed,resp:%s ", util.JsonString(output))
97+
if isCloudControlNotFoundProgressEvent(output) {
98+
return nil, false, &tfresource.NotFoundError{LastError: err}
99+
}
100+
return nil, false, err
92101
} else if status == base.SUCCESS {
93102
return output, true, nil
94103
} else if status == base.IN_PROGRESS || status == base.PENDING {
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package cloudcontrol
5+
6+
import (
7+
"errors"
8+
"net/http"
9+
"strings"
10+
11+
"github.com/volcengine/terraform-provider-volcenginecc/internal/cloudcontrol"
12+
"github.com/volcengine/terraform-provider-volcenginecc/internal/tfresource"
13+
"github.com/volcengine/volcengine-go-sdk/volcengine/volcengineerr"
14+
)
15+
16+
const cloudControlNotFoundHandlerErrorCode = "(HandlerErrorCode: NotFound)"
17+
18+
// wrapCloudControlNotFound converts errors that explicitly identify a missing
19+
// Cloud Control resource into the provider's shared NotFoundError type.
20+
func wrapCloudControlNotFound(err error) error {
21+
if err == nil || tfresource.NotFound(err) {
22+
return err
23+
}
24+
25+
if isCloudControlNotFoundError(err) {
26+
return &tfresource.NotFoundError{LastError: err}
27+
}
28+
29+
return err
30+
}
31+
32+
// isCloudControlNotFoundError checks structured SDK error data first and uses
33+
// the exact Cloud Control handler marker only as a compatibility fallback.
34+
func isCloudControlNotFoundError(err error) bool {
35+
var requestFailure volcengineerr.RequestFailure
36+
if errors.As(err, &requestFailure) && requestFailure.StatusCode() == http.StatusNotFound {
37+
return true
38+
}
39+
40+
var sdkError volcengineerr.Error
41+
if errors.As(err, &sdkError) && isCloudControlNotFoundCode(sdkError.Code()) {
42+
return true
43+
}
44+
45+
return strings.Contains(err.Error(), cloudControlNotFoundHandlerErrorCode)
46+
}
47+
48+
// isCloudControlNotFoundProgressEvent reports whether a failed asynchronous
49+
// operation explicitly returned a resource-not-found handler error.
50+
func isCloudControlNotFoundProgressEvent(event *cloudcontrol.ProgressEvent) bool {
51+
if event == nil {
52+
return false
53+
}
54+
55+
if event.ErrorCode != nil && isCloudControlNotFoundCode(*event.ErrorCode) {
56+
return true
57+
}
58+
59+
return event.StatusMessage != nil && strings.Contains(*event.StatusMessage, cloudControlNotFoundHandlerErrorCode)
60+
}
61+
62+
// isCloudControlNotFoundCode accepts the generic Cloud Control NotFound code
63+
// and service-specific codes whose final, complete segment is NotFound.
64+
func isCloudControlNotFoundCode(code string) bool {
65+
return code == "NotFound" || strings.HasSuffix(code, ".NotFound")
66+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package cloudcontrol
5+
6+
import (
7+
"errors"
8+
"net/http"
9+
"testing"
10+
11+
ccsdk "github.com/volcengine/terraform-provider-volcenginecc/internal/cloudcontrol"
12+
"github.com/volcengine/terraform-provider-volcenginecc/internal/tfresource"
13+
"github.com/volcengine/volcengine-go-sdk/volcengine/volcengineerr"
14+
)
15+
16+
// TestWrapCloudControlNotFound verifies that only explicit Cloud Control
17+
// resource-not-found responses are converted to tfresource.NotFoundError.
18+
func TestWrapCloudControlNotFound(t *testing.T) {
19+
t.Parallel()
20+
21+
tests := map[string]struct {
22+
err error
23+
expected bool
24+
}{
25+
"exact SDK code": {
26+
err: volcengineerr.New("NotFound", "resource does not exist", nil),
27+
expected: true,
28+
},
29+
"HTTP 404": {
30+
err: volcengineerr.NewRequestFailure(
31+
volcengineerr.New("UnknownError", "resource does not exist", nil),
32+
http.StatusNotFound,
33+
"request-id",
34+
),
35+
expected: true,
36+
},
37+
"exact handler marker": {
38+
err: errors.New("handler failed (HandlerErrorCode: NotFound)"),
39+
expected: true,
40+
},
41+
"service-specific NotFound code": {
42+
err: volcengineerr.New("InvalidNatGateway.NotFound", "resource does not exist", nil),
43+
expected: true,
44+
},
45+
"partial service code suffix is not enough": {
46+
err: volcengineerr.New("InvalidNatGateway.NotFoundExtra", "different service error", nil),
47+
},
48+
"partial handler marker is not enough": {
49+
err: errors.New("handler failed (HandlerErrorCode: NotFoundExtra)"),
50+
},
51+
"ordinary error": {
52+
err: errors.New("permission denied"),
53+
},
54+
}
55+
56+
for name, test := range tests {
57+
t.Run(name, func(t *testing.T) {
58+
t.Parallel()
59+
60+
err := wrapCloudControlNotFound(test.err)
61+
if got := tfresource.NotFound(err); got != test.expected {
62+
t.Fatalf("tfresource.NotFound() = %t, want %t; error: %v", got, test.expected, err)
63+
}
64+
})
65+
}
66+
}
67+
68+
// TestIsCloudControlNotFoundProgressEvent verifies the strict ErrorCode and
69+
// StatusMessage boundaries used for failed synchronous and asynchronous tasks.
70+
func TestIsCloudControlNotFoundProgressEvent(t *testing.T) {
71+
t.Parallel()
72+
73+
notFound := "NotFound"
74+
serviceCode := "InvalidNatGateway.NotFound"
75+
partialServiceCode := "InvalidNatGateway.NotFoundExtra"
76+
exactMarker := "handler failed (HandlerErrorCode: NotFound)"
77+
partialMarker := "handler failed (HandlerErrorCode: NotFoundExtra)"
78+
79+
tests := map[string]struct {
80+
event *ccsdk.ProgressEvent
81+
expected bool
82+
}{
83+
"nil event": {},
84+
"exact error code": {
85+
event: &ccsdk.ProgressEvent{ErrorCode: &notFound},
86+
expected: true,
87+
},
88+
"service-specific NotFound code": {
89+
event: &ccsdk.ProgressEvent{ErrorCode: &serviceCode},
90+
expected: true,
91+
},
92+
"partial service code suffix is not enough": {
93+
event: &ccsdk.ProgressEvent{ErrorCode: &partialServiceCode},
94+
},
95+
"exact status marker": {
96+
event: &ccsdk.ProgressEvent{StatusMessage: &exactMarker},
97+
expected: true,
98+
},
99+
"partial status marker is not enough": {
100+
event: &ccsdk.ProgressEvent{StatusMessage: &partialMarker},
101+
},
102+
}
103+
104+
for name, test := range tests {
105+
t.Run(name, func(t *testing.T) {
106+
t.Parallel()
107+
108+
if got := isCloudControlNotFoundProgressEvent(test.event); got != test.expected {
109+
t.Fatalf("isCloudControlNotFoundProgressEvent() = %t, want %t", got, test.expected)
110+
}
111+
})
112+
}
113+
}

internal/service/cloudcontrol/find.go

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,7 @@ func FindResourceByTypeNameAndIDWithSysTag(ctx context.Context, client *cloudcon
2626
Identifier: &id,
2727
})
2828
if err != nil {
29-
if strings.Contains(err.Error(), "HandlerErrorCode: NotFound") {
30-
return nil, &tfresource.NotFoundError{LastError: err}
31-
32-
}
33-
return nil, err
29+
return nil, wrapCloudControlNotFound(err)
3430
}
3531
if output == nil || output.ResourceDescription == nil {
3632
return nil, &tfresource.NotFoundError{Message: "Empty result"}
@@ -48,18 +44,14 @@ func FindResourceByTypeNameAndID(ctx context.Context, client *cloudcontrol.Cloud
4844
Identifier: &id,
4945
})
5046
if err != nil {
51-
if strings.Contains(err.Error(), "HandlerErrorCode: NotFound") {
52-
return nil, &tfresource.NotFoundError{LastError: err}
53-
54-
}
55-
return nil, err
47+
return nil, wrapCloudControlNotFound(err)
5648
}
5749
if output == nil || output.ResourceDescription == nil {
5850
return nil, &tfresource.NotFoundError{Message: "Empty result"}
5951
}
6052
err = NormalizeResourceDescription(output.ResourceDescription)
6153
if err != nil {
62-
return nil, &tfresource.NotFoundError{Message: "normalize resource description err", LastError: err}
54+
return nil, fmt.Errorf("normalize resource description: %w", err)
6355
}
6456
return output, nil
6557
}

internal/service/cloudcontrol/find_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package cloudcontrol
22

33
import (
44
"encoding/json"
5+
"strings"
56
"testing"
67

78
ccsdk "github.com/volcengine/terraform-provider-volcenginecc/internal/cloudcontrol"
9+
"github.com/volcengine/terraform-provider-volcenginecc/internal/tfresource"
810
"github.com/volcengine/volcengine-go-sdk/volcengine"
911
)
1012

@@ -35,3 +37,23 @@ func TestNormalizeResourceDescriptionPreservesTagFields(t *testing.T) {
3537
t.Fatalf("Tags[0] = %#v, want Key/Value/Type preserved", tags[0])
3638
}
3739
}
40+
41+
// TestNormalizeResourceDescriptionMalformedPropertiesIsNotNotFound verifies
42+
// malformed Properties remain a normalization error rather than NotFound.
43+
func TestNormalizeResourceDescriptionMalformedPropertiesIsNotNotFound(t *testing.T) {
44+
t.Parallel()
45+
46+
properties := "{"
47+
description := &ccsdk.ResourceDescriptionForGetResourceOutput{Properties: &properties}
48+
49+
err := NormalizeResourceDescription(description)
50+
if err == nil {
51+
t.Fatal("NormalizeResourceDescription() returned nil, want an error")
52+
}
53+
if tfresource.NotFound(err) {
54+
t.Fatalf("NormalizeResourceDescription() error must not be NotFound: %v", err)
55+
}
56+
if !strings.Contains(err.Error(), "failed to unmarshal ResourceDescription.Properties") {
57+
t.Fatalf("NormalizeResourceDescription() error = %q, want unmarshal context", err)
58+
}
59+
}

0 commit comments

Comments
 (0)