-
Notifications
You must be signed in to change notification settings - Fork 7
/
common.go
287 lines (250 loc) · 9.48 KB
/
common.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
/* Copyright (c) Huawei Technologies Co., Ltd. 2021. All rights resvered. */
/*
The common package defines some common functions, which are mainly used for the functions of the following services.
The difference between common package and utils:
1. Common functions under common are related to the project, and common functions are placed here.
2. Utils are some stored tool functions, which are not related to the project.
Such as: date conversion, type conversion.
*/
package common
import (
"context"
"encoding/json"
"fmt"
"github.com/huaweicloud/terraform-provider-hcs/huaweicloudstack/sdk/huaweicloud/openstack/eps/v1/enterpriseprojects"
"net/http"
"strconv"
"strings"
"time"
"github.com/huaweicloud/terraform-provider-hcs/huaweicloudstack/sdk/huaweicloud"
"github.com/huaweicloud/terraform-provider-hcs/huaweicloudstack/sdk/huaweicloud/openstack/bss/v2/orders"
"github.com/huaweicloud/terraform-provider-hcs/huaweicloudstack/sdk/huaweicloud/openstack/bss/v2/resources"
"github.com/huaweicloud/terraform-provider-hcs/huaweicloudstack/sdk/huaweicloud/openstack/networking/v1/eips"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/sdkerr"
"github.com/huaweicloud/terraform-provider-hcs/huaweicloudstack/config"
"github.com/huaweicloud/terraform-provider-hcs/huaweicloudstack/utils/fmtp"
)
// ErrorResp is the response when API failed
type ErrorResp struct {
ErrorCode string `json:"error_code"`
ErrorMsg string `json:"error_msg"`
}
func ParseErrorMsg(body []byte) (ErrorResp, error) {
resp := ErrorResp{}
err := json.Unmarshal(body, &resp)
return resp, err
}
// GetRegion returns the region that was specified ina the resource. If a
// region was not set, the provider-level region is checked. The provider-level
// region can either be set by the region argument or by HCS_REGION_NAME.
func GetRegion(d *schema.ResourceData, config *config.HcsConfig) string {
if v, ok := d.GetOk("region"); ok {
return v.(string)
}
return config.Region
}
// GetEnterpriseProjectID returns the enterprise_project_id that was specified in the resource.
// If it was not set, the provider-level value is checked. The provider-level value can
// either be set by the `enterprise_project_id` argument or by HCS_ENTERPRISE_PROJECT_ID.
func GetEnterpriseProjectID(d *schema.ResourceData, config *config.HcsConfig) string {
if v, ok := d.GetOk("enterprise_project_id"); ok {
return v.(string)
}
return config.EnterpriseProjectID
}
func MigrateEnterpriseProject(client *golangsdk.ServiceClient, region, targetEPSId, resourceType, resourceID string) error {
if targetEPSId == "" {
targetEPSId = "0"
} else {
// check enterprise_project_id existed
if result := enterpriseprojects.Get(client, targetEPSId); result.Err != nil {
return fmt.Errorf("failed to query the target enterprise project %s: %s", targetEPSId, result.Err)
}
}
migrateOpts := enterpriseprojects.MigrateResourceOpts{
RegionId: region,
ProjectId: client.ProjectID,
ResourceType: resourceType,
ResourceId: resourceID,
}
migrateResult := enterpriseprojects.Migrate(client, migrateOpts, targetEPSId)
if err := migrateResult.Err; err != nil {
return fmt.Errorf("failed to migrate %s to enterprise project %s, err: %s", resourceID, targetEPSId, err)
}
return nil
}
// GetEipIDbyAddress returns the EIP ID of address when success.
func GetEipIDbyAddress(client *golangsdk.ServiceClient, address, epsID string) (string, error) {
listOpts := &eips.ListOpts{
PublicIp: []string{address},
EnterpriseProjectId: epsID,
}
pages, err := eips.List(client, listOpts).AllPages()
if err != nil {
return "", err
}
allEips, err := eips.ExtractPublicIPs(pages)
if err != nil {
return "", fmtp.Errorf("Unable to retrieve eips: %s ", err)
}
total := len(allEips)
if total == 0 {
return "", fmtp.Errorf("queried none results with %s", address)
} else if total > 1 {
return "", fmtp.Errorf("queried more results with %s", address)
}
return allEips[0].ID, nil
}
// CheckDeleted checks the error to see if it's a 404 (Not Found) and, if so,
// sets the resource ID to the empty string instead of throwing an error.
func CheckDeleted(d *schema.ResourceData, err error, msg string) error {
if _, ok := err.(golangsdk.ErrDefault404); ok {
d.SetId("")
return nil
}
return fmtp.Errorf("%s: %s", msg, err)
}
// CheckDeletedDiag checks the error to see if it's a 404 (Not Found) and, if so,
// sets the resource ID to the empty string instead of throwing an error.
func CheckDeletedDiag(d *schema.ResourceData, err error, msg string) diag.Diagnostics {
var statusCode int
// check if the error is raised by **golangsdk**
if _, ok := err.(golangsdk.ErrDefault404); ok {
statusCode = http.StatusNotFound
} else if responseErr, ok := err.(*sdkerr.ServiceResponseError); ok {
// check if the error is raised by **huaweicloudstack-sdk-go-v3**
statusCode = responseErr.StatusCode
}
if statusCode == http.StatusNotFound {
resourceID := d.Id()
d.SetId("")
return diag.Diagnostics{
diag.Diagnostic{
Severity: diag.Warning,
Summary: "Resource not found",
Detail: fmt.Sprintf("the resource %s is gone and will be removed in Terraform state.", resourceID),
},
}
}
return fmtp.DiagErrorf("%s: %s", msg, err)
}
// UnsubscribePrePaidResource impl the action of unsubscribe resource
func UnsubscribePrePaidResource(d *schema.ResourceData, config *config.HcsConfig, resourceIDs []string) error {
bssV2Client, err := config.BssV2Client(GetRegion(d, config))
if err != nil {
return fmtp.Errorf("Error creating HuaweiCloudStack bss V2 client: %s", err)
}
unsubscribeOpts := orders.UnsubscribeOpts{
ResourceIds: resourceIDs,
UnsubscribeType: 1,
}
_, err = orders.Unsubscribe(bssV2Client, unsubscribeOpts).Extract()
return err
}
func CheckForRetryableError(err error) *resource.RetryError {
switch errCode := err.(type) {
case golangsdk.ErrDefault500:
return resource.RetryableError(err)
case golangsdk.ErrUnexpectedResponseCode:
switch errCode.Actual {
case 409, 503:
return resource.RetryableError(err)
default:
return resource.NonRetryableError(err)
}
default:
return resource.NonRetryableError(err)
}
}
func WaitOrderComplete(ctx context.Context, client *golangsdk.ServiceClient, orderId string,
timeout time.Duration) error {
stateConf := &resource.StateChangeConf{
Pending: []string{"3", "6"}, // 3: Processing; 6: Pending payment.
Target: []string{"5"}, // 5: Completed.
Refresh: refreshOrderStatusFunc(client, orderId),
Timeout: timeout,
Delay: 5 * time.Second,
PollInterval: 10 * time.Second,
}
_, err := stateConf.WaitForStateContext(ctx)
if err != nil {
return fmt.Errorf("error waiting for the order (%s) to complete payment: %#v", orderId, err)
}
return nil
}
func refreshOrderStatusFunc(client *golangsdk.ServiceClient, orderId string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
r, err := orders.Get(client, orderId).Extract()
if err != nil {
return nil, "Error", err
}
return r, strconv.Itoa(r.OrderInfo.Status), nil
}
}
// WaitOrderResourceComplete is the method to wait for the resource to be generated.
// Notes: Note that this method needs to be used in conjunction with method "WaitOrderComplete", because the ID of some
// resources may not be generated when the order is not completed.
func WaitOrderResourceComplete(ctx context.Context, client *golangsdk.ServiceClient, orderId string,
timeout time.Duration) (string, error) {
stateConf := &resource.StateChangeConf{
Pending: []string{"PENDING"},
Target: []string{"DONE"},
Refresh: refreshOrderResourceStatusFunc(client, orderId),
Timeout: timeout,
Delay: 5 * time.Second,
PollInterval: 10 * time.Second,
}
res, err := stateConf.WaitForStateContext(ctx)
if err != nil {
return "", fmt.Errorf("error while waiting for the order (%s) to complete: %#v", orderId, err)
}
r := res.(resources.Resource)
return r.ResourceId, nil
}
func refreshOrderResourceStatusFunc(client *golangsdk.ServiceClient, orderId string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
listOpts := resources.ListOpts{
OrderId: orderId,
OnlyMainResource: 1,
}
resp, err := resources.List(client, listOpts)
if err != nil || resp == nil {
return nil, "ERROR", fmt.Errorf("error waiting for the order (%s) to complete: %#v", orderId, err)
}
if resp.TotalCount < 1 {
return nil, "PENDING", nil
}
return resp.Resources[0], "DONE", nil
}
}
func CaseInsensitiveFunc() schema.SchemaDiffSuppressFunc {
return func(k, old, new string, d *schema.ResourceData) bool {
return strings.EqualFold(old, new)
}
}
// GetAutoPay is a method to return whether order is auto pay according to the user input.
// auto_pay parameter inputs and returns:
//
// false: false
// true, empty: true
//
// Before using this function, make sure the parameter behavior is auto pay (the default value is "true").
func GetAutoPay(d *schema.ResourceData) string {
if val, ok := d.GetOk("auto_pay"); ok && val.(string) == "false" {
return "false"
}
return "true"
}
func UpdateAutoRenew(c *golangsdk.ServiceClient, enabled, resourceId string) error {
if enabled == "true" {
return resources.EnableAutoRenew(c, resourceId)
}
return resources.DisableAutoRenew(c, resourceId)
}
func HasFilledOpt(d *schema.ResourceData, param string) bool {
_, b := d.GetOk(param)
return b
}