Skip to content

Commit 3a7177f

Browse files
tongyimingmikatong
and
mikatong
authored
feat(vpc): [123456789] support reserve ip address (#2972)
* support reserve ip address * add changelog --------- Co-authored-by: mikatong <[email protected]>
1 parent 76a060b commit 3a7177f

File tree

13 files changed

+1673
-101
lines changed

13 files changed

+1673
-101
lines changed

.changelog/2972.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:new-resource
2+
tencentcloud_reserve_ip_address
3+
```

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ require (
4646
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1034
4747
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cloudaudit v1.0.1033
4848
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cls v1.0.970
49-
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1038
49+
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1045
5050
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.1014
5151
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cwp v1.0.762
5252
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cynosdb v1.0.692
@@ -96,7 +96,7 @@ require (
9696
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tse v1.0.857
9797
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tsf v1.0.674
9898
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.0.860
99-
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc v1.0.1018
99+
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc v1.0.1045
100100
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.0.1037
101101
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/wedata v1.0.792
102102
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/wss v1.0.199

go.sum

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -928,10 +928,12 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1034 h1:T7e
928928
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1034/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
929929
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1036 h1:B3GO+IBOrjrq8sN5bT9e8GMHWguHkyyGdNEos6cp5cE=
930930
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1036/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
931-
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1038 h1:tB3DLzyQXavvGTI+JUvEcuYh0EtTn1rvh9W6xxFHL38=
932-
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1038/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
933931
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1037 h1:cXerqxVAnEhEryXYgeBVFAJR8zjG2AsPWVnLtqJfkmY=
934932
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1037/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
933+
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1038 h1:tB3DLzyQXavvGTI+JUvEcuYh0EtTn1rvh9W6xxFHL38=
934+
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1038/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
935+
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1045 h1:3vvovAezER+/2FycWT4Wplkis3y3OHsRg2mDfcv08MI=
936+
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1045/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
935937
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/controlcenter v1.0.993 h1:WlPgXldQCxt7qi5Xrc6j6zTrsXWzN5BcOGs7Irq7fwQ=
936938
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/controlcenter v1.0.993/go.mod h1:Z9U8zNtyuyKhjS0698wqsrG/kLx1TQ5CEixXBwVe7xY=
937939
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/csip v1.0.860 h1:F3esKBIT3HW9+7Gt8cVgf8X06VdGIczpgLBUECzSEzU=
@@ -1054,6 +1056,8 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.0.860 h1:vW2NgAH
10541056
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vod v1.0.860/go.mod h1:uCkDh/AW/tb8JGq5b2kqLjqZuhCFR+6oTsq1SrrvT44=
10551057
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc v1.0.1018 h1:+ATJA5XAYJVLRm0IjPhl6UlyXc2eh7FTJ+I42uSz0B4=
10561058
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc v1.0.1018/go.mod h1:LmBUb+v6kdY+Jr+VRsU28SF8H5ga7Tqh+wpoVYmSofw=
1059+
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc v1.0.1045 h1:7+rfyxftJ/C85yMQfs7ALj0RHkSlyboMj6gqCVKYa58=
1060+
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc v1.0.1045/go.mod h1:yn4I7fSwpiM6ltjqksS1JtZZAHkZ35ywN+kLm6AJPzc=
10571061
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.0.833 h1:avaBlZ+Qqv7bfMg/u0jlRsbEBbE18CfqKWbfGc84PLg=
10581062
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.0.833/go.mod h1:fUWG217b//46Oa3VXxC5mgeYeewVAbF+lc81uET89EM=
10591063
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/waf v1.0.1037 h1:sgHOHqVFcO266dnoh0KJ0CoxrRglRZYKW78iBh41Giw=

tencentcloud/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2164,6 +2164,7 @@ func Provider() *schema.Provider {
21642164
"tencentcloud_vpc_peer_connect_manager": vpc.ResourceTencentCloudVpcPeerConnectManager(),
21652165
"tencentcloud_vpc_peer_connect_accept_operation": vpc.ResourceTencentCloudVpcPeerConnectAcceptOperation(),
21662166
"tencentcloud_vpc_peer_connect_reject_operation": vpc.ResourceTencentCloudVpcPeerConnectRejectOperation(),
2167+
"tencentcloud_reserve_ip_address": vpc.ResourceTencentCloudReserveIpAddress(),
21672168
"tencentcloud_csip_risk_center": csip.ResourceTencentCloudCsipRiskCenter(),
21682169
"tencentcloud_organization_org_share_unit_member": tco.ResourceTencentCloudOrganizationOrgShareUnitMember(),
21692170
"tencentcloud_organization_org_share_unit": tco.ResourceTencentCloudOrganizationOrgShareUnit(),
Lines changed: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
1+
package vpc
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"strings"
8+
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
11+
vpc "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc/v20170312"
12+
13+
tccommon "github.com/tencentcloudstack/terraform-provider-tencentcloud/tencentcloud/common"
14+
"github.com/tencentcloudstack/terraform-provider-tencentcloud/tencentcloud/internal/helper"
15+
svctag "github.com/tencentcloudstack/terraform-provider-tencentcloud/tencentcloud/services/tag"
16+
)
17+
18+
func ResourceTencentCloudReserveIpAddress() *schema.Resource {
19+
return &schema.Resource{
20+
Create: resourceTencentCloudReserveIpAddressCreate,
21+
Read: resourceTencentCloudReserveIpAddressRead,
22+
Update: resourceTencentCloudReserveIpAddressUpdate,
23+
Delete: resourceTencentCloudReserveIpAddressDelete,
24+
Importer: &schema.ResourceImporter{
25+
State: schema.ImportStatePassthrough,
26+
},
27+
Schema: map[string]*schema.Schema{
28+
"vpc_id": {
29+
Type: schema.TypeString,
30+
Required: true,
31+
Description: "VPC unique ID.",
32+
},
33+
34+
"ip_address": {
35+
Type: schema.TypeString,
36+
Optional: true,
37+
Computed: true,
38+
Description: "Specify the reserved IP address of the intranet for which the IP application is requested.",
39+
},
40+
41+
"subnet_id": {
42+
Type: schema.TypeString,
43+
Optional: true,
44+
Description: "Subnet ID.",
45+
},
46+
47+
"name": {
48+
Type: schema.TypeString,
49+
Optional: true,
50+
Description: "The IP name is reserved for the intranet.",
51+
},
52+
53+
"description": {
54+
Type: schema.TypeString,
55+
Optional: true,
56+
Description: "The IP description is retained on the intranet.",
57+
},
58+
59+
"tags": {
60+
Type: schema.TypeMap,
61+
Optional: true,
62+
Description: "Tags.",
63+
},
64+
"reserve_ip_id": {
65+
Type: schema.TypeString,
66+
Computed: true,
67+
Description: "Reserve ip ID.",
68+
},
69+
"resource_id": {
70+
Type: schema.TypeString,
71+
Computed: true,
72+
Description: "The intranet retains the resource instance ID bound to the IPs.",
73+
},
74+
"ip_type": {
75+
Type: schema.TypeInt,
76+
Computed: true,
77+
Description: "Ip type for product application.",
78+
},
79+
"state": {
80+
Type: schema.TypeString,
81+
Computed: true,
82+
Description: "Binding status.",
83+
},
84+
"created_time": {
85+
Type: schema.TypeString,
86+
Computed: true,
87+
Description: "Created time.",
88+
},
89+
},
90+
}
91+
}
92+
93+
func resourceTencentCloudReserveIpAddressCreate(d *schema.ResourceData, meta interface{}) error {
94+
defer tccommon.LogElapsed("resource.tencentcloud_reserve_ip_address.create")()
95+
defer tccommon.InconsistentCheck(d, meta)()
96+
97+
logId := tccommon.GetLogId(tccommon.ContextNil)
98+
99+
ctx := tccommon.NewResourceLifeCycleHandleFuncContext(context.Background(), logId, d, meta)
100+
101+
var (
102+
vpcId string
103+
reserveIpId string
104+
)
105+
var (
106+
request = vpc.NewCreateReserveIpAddressesRequest()
107+
response = vpc.NewCreateReserveIpAddressesResponse()
108+
)
109+
110+
if v, ok := d.GetOk("vpc_id"); ok {
111+
vpcId = v.(string)
112+
request.VpcId = helper.String(vpcId)
113+
}
114+
115+
if v, ok := d.GetOk("ip_address"); ok {
116+
ipAddress := v.(string)
117+
request.IpAddresses = append(request.IpAddresses, helper.String(ipAddress))
118+
} else {
119+
request.IpAddressCount = helper.IntUint64(1)
120+
}
121+
122+
if v, ok := d.GetOk("subnet_id"); ok {
123+
request.SubnetId = helper.String(v.(string))
124+
}
125+
126+
if v, ok := d.GetOk("name"); ok {
127+
request.Name = helper.String(v.(string))
128+
}
129+
130+
if v, ok := d.GetOk("description"); ok {
131+
request.Description = helper.String(v.(string))
132+
}
133+
134+
err := resource.Retry(tccommon.WriteRetryTimeout, func() *resource.RetryError {
135+
result, e := meta.(tccommon.ProviderMeta).GetAPIV3Conn().UseVpcClient().CreateReserveIpAddressesWithContext(ctx, request)
136+
if e != nil {
137+
return tccommon.RetryError(e)
138+
} else {
139+
log.Printf("[DEBUG]%s api[%s] success, request body [%s], response body [%s]\n", logId, request.GetAction(), request.ToJsonString(), result.ToJsonString())
140+
}
141+
if len(result.Response.ReserveIpAddressSet) > 0 {
142+
reserveIpId = *result.Response.ReserveIpAddressSet[0].ReserveIpId
143+
}
144+
return nil
145+
})
146+
if err != nil {
147+
log.Printf("[CRITAL]%s create reserve ip addresses failed, reason:%+v", logId, err)
148+
return err
149+
}
150+
151+
_ = response
152+
153+
if tags := helper.GetTags(d, "tags"); len(tags) > 0 {
154+
tcClient := meta.(tccommon.ProviderMeta).GetAPIV3Conn()
155+
tagService := svctag.NewTagService(tcClient)
156+
resourceName := tccommon.BuildTagResourceName("vpc", "rsvip", tcClient.Region, reserveIpId)
157+
if err := tagService.ModifyTags(ctx, resourceName, tags, nil); err != nil {
158+
return err
159+
}
160+
}
161+
162+
d.SetId(strings.Join([]string{vpcId, reserveIpId}, tccommon.FILED_SP))
163+
164+
return resourceTencentCloudReserveIpAddressRead(d, meta)
165+
}
166+
167+
func resourceTencentCloudReserveIpAddressRead(d *schema.ResourceData, meta interface{}) error {
168+
defer tccommon.LogElapsed("resource.tencentcloud_reserve_ip_address.read")()
169+
defer tccommon.InconsistentCheck(d, meta)()
170+
171+
logId := tccommon.GetLogId(tccommon.ContextNil)
172+
173+
ctx := tccommon.NewResourceLifeCycleHandleFuncContext(context.Background(), logId, d, meta)
174+
175+
service := VpcService{client: meta.(tccommon.ProviderMeta).GetAPIV3Conn()}
176+
177+
idSplit := strings.Split(d.Id(), tccommon.FILED_SP)
178+
if len(idSplit) != 2 {
179+
return fmt.Errorf("id is broken,%s", d.Id())
180+
}
181+
reserveIpId := idSplit[1]
182+
183+
respData, err := service.DescribeReserveIpAddressesById(ctx, reserveIpId)
184+
if err != nil {
185+
return err
186+
}
187+
188+
if respData == nil {
189+
d.SetId("")
190+
log.Printf("[WARN]%s resource `reserve_ip_addresses` [%s] not found, please check if it has been deleted.\n", logId, d.Id())
191+
return nil
192+
}
193+
if len(respData.ReserveIpAddressSet) < 1 {
194+
d.SetId("")
195+
log.Printf("[WARN]%s resource `reserve_ip_addresses` [%s] not found, please check if it has been deleted.\n", logId, d.Id())
196+
return nil
197+
}
198+
reserveIpAddress := respData.ReserveIpAddressSet[0]
199+
200+
_ = d.Set("vpc_id", reserveIpAddress.VpcId)
201+
_ = d.Set("ip_address", reserveIpAddress.ReserveIpAddress)
202+
_ = d.Set("name", reserveIpAddress.Name)
203+
_ = d.Set("description", reserveIpAddress.Description)
204+
_ = d.Set("reserve_ip_id", reserveIpAddress.ReserveIpId)
205+
_ = d.Set("resource_id", reserveIpAddress.ResourceId)
206+
_ = d.Set("ip_type", reserveIpAddress.IpType)
207+
_ = d.Set("state", reserveIpAddress.State)
208+
_ = d.Set("created_time", reserveIpAddress.CreatedTime)
209+
210+
tcClient := meta.(tccommon.ProviderMeta).GetAPIV3Conn()
211+
tagService := svctag.NewTagService(meta.(tccommon.ProviderMeta).GetAPIV3Conn())
212+
tags, err := tagService.DescribeResourceTags(ctx, "vpc", "rsvip", tcClient.Region, reserveIpId)
213+
if err != nil {
214+
return err
215+
}
216+
_ = d.Set("tags", tags)
217+
_ = reserveIpId
218+
return nil
219+
}
220+
221+
func resourceTencentCloudReserveIpAddressUpdate(d *schema.ResourceData, meta interface{}) error {
222+
defer tccommon.LogElapsed("resource.tencentcloud_reserve_ip_address.update")()
223+
defer tccommon.InconsistentCheck(d, meta)()
224+
225+
logId := tccommon.GetLogId(tccommon.ContextNil)
226+
227+
ctx := tccommon.NewResourceLifeCycleHandleFuncContext(context.Background(), logId, d, meta)
228+
229+
immutableArgs := []string{"vpc_id", "ip_addresses", "subnet_id"}
230+
for _, v := range immutableArgs {
231+
if d.HasChange(v) {
232+
return fmt.Errorf("argument `%s` cannot be changed", v)
233+
}
234+
}
235+
idSplit := strings.Split(d.Id(), tccommon.FILED_SP)
236+
if len(idSplit) != 2 {
237+
return fmt.Errorf("id is broken,%s", d.Id())
238+
}
239+
vpcId := idSplit[0]
240+
reserveIpId := idSplit[1]
241+
242+
needChange := false
243+
mutableArgs := []string{"name", "description"}
244+
for _, v := range mutableArgs {
245+
if d.HasChange(v) {
246+
needChange = true
247+
break
248+
}
249+
}
250+
251+
if needChange {
252+
request := vpc.NewModifyReserveIpAddressRequest()
253+
254+
request.VpcId = helper.String(vpcId)
255+
256+
request.ReserveIpId = helper.String(reserveIpId)
257+
258+
if v, ok := d.GetOk("name"); ok {
259+
request.Name = helper.String(v.(string))
260+
}
261+
262+
if v, ok := d.GetOk("description"); ok {
263+
request.Description = helper.String(v.(string))
264+
}
265+
266+
err := resource.Retry(tccommon.WriteRetryTimeout, func() *resource.RetryError {
267+
result, e := meta.(tccommon.ProviderMeta).GetAPIV3Conn().UseVpcClient().ModifyReserveIpAddressWithContext(ctx, request)
268+
if e != nil {
269+
return tccommon.RetryError(e)
270+
} else {
271+
log.Printf("[DEBUG]%s api[%s] success, request body [%s], response body [%s]\n", logId, request.GetAction(), request.ToJsonString(), result.ToJsonString())
272+
}
273+
return nil
274+
})
275+
if err != nil {
276+
log.Printf("[CRITAL]%s update reserve ip addresses failed, reason:%+v", logId, err)
277+
return err
278+
}
279+
}
280+
281+
if d.HasChange("tags") {
282+
tcClient := meta.(tccommon.ProviderMeta).GetAPIV3Conn()
283+
tagService := svctag.NewTagService(tcClient)
284+
oldTags, newTags := d.GetChange("tags")
285+
replaceTags, deleteTags := svctag.DiffTags(oldTags.(map[string]interface{}), newTags.(map[string]interface{}))
286+
resourceName := tccommon.BuildTagResourceName("vpc", "rsvip", tcClient.Region, reserveIpId)
287+
if err := tagService.ModifyTags(ctx, resourceName, replaceTags, deleteTags); err != nil {
288+
return err
289+
}
290+
}
291+
292+
return resourceTencentCloudReserveIpAddressRead(d, meta)
293+
}
294+
295+
func resourceTencentCloudReserveIpAddressDelete(d *schema.ResourceData, meta interface{}) error {
296+
defer tccommon.LogElapsed("resource.tencentcloud_reserve_ip_address.delete")()
297+
defer tccommon.InconsistentCheck(d, meta)()
298+
299+
logId := tccommon.GetLogId(tccommon.ContextNil)
300+
ctx := tccommon.NewResourceLifeCycleHandleFuncContext(context.Background(), logId, d, meta)
301+
302+
idSplit := strings.Split(d.Id(), tccommon.FILED_SP)
303+
if len(idSplit) != 2 {
304+
return fmt.Errorf("id is broken,%s", d.Id())
305+
}
306+
vpcId := idSplit[0]
307+
reserveIpId := idSplit[1]
308+
309+
var (
310+
request = vpc.NewDeleteReserveIpAddressesRequest()
311+
response = vpc.NewDeleteReserveIpAddressesResponse()
312+
)
313+
314+
request.VpcId = helper.String(vpcId)
315+
request.ReserveIpIds = []*string{&reserveIpId}
316+
317+
err := resource.Retry(tccommon.WriteRetryTimeout, func() *resource.RetryError {
318+
result, e := meta.(tccommon.ProviderMeta).GetAPIV3Conn().UseVpcClient().DeleteReserveIpAddressesWithContext(ctx, request)
319+
if e != nil {
320+
return tccommon.RetryError(e)
321+
} else {
322+
log.Printf("[DEBUG]%s api[%s] success, request body [%s], response body [%s]\n", logId, request.GetAction(), request.ToJsonString(), result.ToJsonString())
323+
}
324+
response = result
325+
return nil
326+
})
327+
if err != nil {
328+
log.Printf("[CRITAL]%s delete reserve ip addresses failed, reason:%+v", logId, err)
329+
return err
330+
}
331+
332+
_ = response
333+
_ = reserveIpId
334+
return nil
335+
}

0 commit comments

Comments
 (0)