Skip to content

Commit 392af97

Browse files
gitmknanonymoushellertang
authored
Feat/cam service linked role (#1436)
* feat: support ServiceLinkedRole * docs: add doc * feat: add changelog * remove import Co-authored-by: anonymous <anonymous@mail.org> Co-authored-by: hellertang <hellertang@tencent.com>
1 parent 89350e0 commit 392af97

File tree

7 files changed

+549
-0
lines changed

7 files changed

+549
-0
lines changed

.changelog/1436.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_cam_service_linked_role
3+
```

tencentcloud/provider.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ Cloud Access Management(CAM)
174174
tencentcloud_cam_saml_provider
175175
tencentcloud_cam_oidc_sso
176176
tencentcloud_cam_role_sso
177+
tencentcloud_cam_service_linked_role
177178
178179
Cloud Block Storage(CBS)
179180
Data Source
@@ -1299,6 +1300,7 @@ func Provider() terraform.ResourceProvider {
12991300
"tencentcloud_cam_role_sso": resourceTencentCloudCamRoleSSO(),
13001301
"tencentcloud_cam_group_membership": resourceTencentCloudCamGroupMembership(),
13011302
"tencentcloud_cam_saml_provider": resourceTencentCloudCamSAMLProvider(),
1303+
"tencentcloud_cam_service_linked_role": resourceTencentCloudCamServiceLinkedRole(),
13021304
"tencentcloud_scf_function": resourceTencentCloudScfFunction(),
13031305
"tencentcloud_scf_namespace": resourceTencentCloudScfNamespace(),
13041306
"tencentcloud_scf_layer": resourceTencentCloudScfLayer(),
Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
/*
2+
Provides a resource to create a cam service_linked_role
3+
4+
Example Usage
5+
6+
```hcl
7+
resource "tencentcloud_cam_service_linked_role" "service_linked_role" {
8+
qcs_service_name = "postgreskms.postgres.cloud.tencent.com"
9+
custom_suffix = "x-1"
10+
description = "desc cam"
11+
tags = {
12+
"createdBy" = "terraform"
13+
}
14+
}
15+
16+
```
17+
*/
18+
package tencentcloud
19+
20+
import (
21+
"context"
22+
"fmt"
23+
"log"
24+
"strings"
25+
26+
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
27+
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
28+
cam "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cam/v20190116"
29+
"github.com/tencentcloudstack/terraform-provider-tencentcloud/tencentcloud/internal/helper"
30+
)
31+
32+
func resourceTencentCloudCamServiceLinkedRole() *schema.Resource {
33+
return &schema.Resource{
34+
Read: resourceTencentCloudCamServiceLinkedRoleRead,
35+
Create: resourceTencentCloudCamServiceLinkedRoleCreate,
36+
Update: resourceTencentCloudCamServiceLinkedRoleUpdate,
37+
Delete: resourceTencentCloudCamServiceLinkedRoleDelete,
38+
Schema: map[string]*schema.Schema{
39+
"qcs_service_name": {
40+
Type: schema.TypeString,
41+
Required: true,
42+
Description: "Authorization service, the Tencent Cloud service principal with this role attached.",
43+
},
44+
45+
"custom_suffix": {
46+
Type: schema.TypeString,
47+
Optional: true,
48+
Description: "The custom suffix, based on the string you provide, is combined with the prefix provided by the service to form the full role name.",
49+
},
50+
51+
"description": {
52+
Type: schema.TypeString,
53+
Optional: true,
54+
Description: "role description.",
55+
},
56+
57+
"tags": {
58+
Type: schema.TypeMap,
59+
Optional: true,
60+
Description: "Tag description list.",
61+
},
62+
},
63+
}
64+
}
65+
66+
func resourceTencentCloudCamServiceLinkedRoleCreate(d *schema.ResourceData, meta interface{}) error {
67+
defer logElapsed("resource.tencentcloud_cam_service_linked_role.create")()
68+
defer inconsistentCheck(d, meta)()
69+
70+
logId := getLogId(contextNil)
71+
72+
var (
73+
request = cam.NewCreateServiceLinkedRoleRequest()
74+
response *cam.CreateServiceLinkedRoleResponse
75+
roleId string
76+
qcsServiceName = ""
77+
customSuffix = ""
78+
)
79+
80+
if v, ok := d.GetOk("qcs_service_name"); ok {
81+
qcsServiceName = v.(string)
82+
request.QCSServiceName = helper.Strings([]string{v.(string)})
83+
}
84+
85+
if v, ok := d.GetOk("custom_suffix"); ok {
86+
customSuffix = v.(string)
87+
request.CustomSuffix = helper.String(v.(string))
88+
}
89+
90+
if v, ok := d.GetOk("description"); ok {
91+
request.Description = helper.String(v.(string))
92+
}
93+
94+
if tags := helper.GetTags(d, "tags"); len(tags) > 0 {
95+
for k, v := range tags {
96+
key := k
97+
value := v
98+
request.Tags = append(request.Tags, &cam.RoleTags{
99+
Key: &key,
100+
Value: &value,
101+
})
102+
}
103+
}
104+
105+
err := resource.Retry(writeRetryTimeout, func() *resource.RetryError {
106+
result, e := meta.(*TencentCloudClient).apiV3Conn.UseCamClient().CreateServiceLinkedRole(request)
107+
if e != nil {
108+
return retryError(e)
109+
} else {
110+
log.Printf("[DEBUG]%s api[%s] success, request body [%s], response body [%s]\n",
111+
logId, request.GetAction(), request.ToJsonString(), result.ToJsonString())
112+
}
113+
response = result
114+
return nil
115+
})
116+
117+
if err != nil {
118+
log.Printf("[CRITAL]%s create cam serviceLinkedRole failed, reason:%+v", logId, err)
119+
return err
120+
}
121+
122+
roleId = *response.Response.RoleId
123+
124+
d.SetId(roleId + FILED_SP + qcsServiceName + FILED_SP + customSuffix)
125+
ctx := context.WithValue(context.TODO(), logIdKey, logId)
126+
if tags := helper.GetTags(d, "tags"); len(tags) > 0 {
127+
tagService := TagService{client: meta.(*TencentCloudClient).apiV3Conn}
128+
region := meta.(*TencentCloudClient).apiV3Conn.Region
129+
resourceName := fmt.Sprintf("qcs::cam:%s:uin/:RoleId/%s", region, roleId)
130+
if err := tagService.ModifyTags(ctx, resourceName, tags, nil); err != nil {
131+
return err
132+
}
133+
}
134+
return resourceTencentCloudCamServiceLinkedRoleRead(d, meta)
135+
}
136+
137+
func resourceTencentCloudCamServiceLinkedRoleRead(d *schema.ResourceData, meta interface{}) error {
138+
defer logElapsed("resource.tencentcloud_cam_service_linked_role.read")()
139+
defer inconsistentCheck(d, meta)()
140+
141+
logId := getLogId(contextNil)
142+
ctx := context.WithValue(context.TODO(), logIdKey, logId)
143+
service := CamService{client: meta.(*TencentCloudClient).apiV3Conn}
144+
145+
resourceId := d.Id()
146+
items := strings.Split(resourceId, FILED_SP)
147+
if len(items) != 3 {
148+
return fmt.Errorf("invalid ID %s", resourceId)
149+
}
150+
roleId := items[0]
151+
qcsServiceName := items[1]
152+
customSuffix := items[2]
153+
154+
serviceLinkedRole, err := service.DescribeCamServiceLinkedRole(ctx, roleId)
155+
if err != nil {
156+
return err
157+
}
158+
159+
if serviceLinkedRole == nil {
160+
d.SetId("")
161+
return fmt.Errorf("resource `serviceLinkedRole` %s does not exist", roleId)
162+
}
163+
164+
if qcsServiceName != "" {
165+
_ = d.Set("qcs_service_name", qcsServiceName)
166+
}
167+
168+
if customSuffix != "" {
169+
_ = d.Set("custom_suffix", customSuffix)
170+
}
171+
172+
if serviceLinkedRole.Description != nil {
173+
_ = d.Set("description", serviceLinkedRole.Description)
174+
}
175+
176+
tcClient := meta.(*TencentCloudClient).apiV3Conn
177+
tagService := &TagService{client: tcClient}
178+
tags, err := tagService.DescribeResourceTags(ctx, "cam", "RoleId", tcClient.Region, roleId)
179+
if err != nil {
180+
return err
181+
}
182+
_ = d.Set("tags", tags)
183+
184+
return nil
185+
}
186+
187+
func resourceTencentCloudCamServiceLinkedRoleUpdate(d *schema.ResourceData, meta interface{}) error {
188+
defer logElapsed("resource.tencentcloud_cam_service_linked_role.update")()
189+
defer inconsistentCheck(d, meta)()
190+
191+
logId := getLogId(contextNil)
192+
ctx := context.WithValue(context.TODO(), logIdKey, logId)
193+
request := cam.NewUpdateRoleDescriptionRequest()
194+
195+
resourceId := d.Id()
196+
items := strings.Split(resourceId, FILED_SP)
197+
if len(items) != 3 {
198+
return fmt.Errorf("invalid ID %s", resourceId)
199+
}
200+
roleId := items[0]
201+
// qcsServiceName := items[1]
202+
// customSuffix := items[2]
203+
204+
request.RoleId = &roleId
205+
206+
if d.HasChange("qcs_service_name") {
207+
return fmt.Errorf("`qcs_service_name` do not support change now.")
208+
}
209+
210+
if d.HasChange("custom_suffix") {
211+
return fmt.Errorf("`custom_suffix` do not support change now.")
212+
}
213+
214+
if d.HasChange("description") {
215+
if v, ok := d.GetOk("description"); ok {
216+
request.Description = helper.String(v.(string))
217+
}
218+
}
219+
220+
err := resource.Retry(writeRetryTimeout, func() *resource.RetryError {
221+
result, e := meta.(*TencentCloudClient).apiV3Conn.UseCamClient().UpdateRoleDescription(request)
222+
if e != nil {
223+
return retryError(e)
224+
} else {
225+
log.Printf("[DEBUG]%s api[%s] success, request body [%s], response body [%s]\n",
226+
logId, request.GetAction(), request.ToJsonString(), result.ToJsonString())
227+
}
228+
return nil
229+
})
230+
if err != nil {
231+
log.Printf("[CRITAL]%s create cam serviceLinkedRole failed, reason:%+v", logId, err)
232+
return err
233+
}
234+
235+
if d.HasChange("tags") {
236+
tcClient := meta.(*TencentCloudClient).apiV3Conn
237+
tagService := &TagService{client: tcClient}
238+
oldTags, newTags := d.GetChange("tags")
239+
replaceTags, deleteTags := diffTags(oldTags.(map[string]interface{}), newTags.(map[string]interface{}))
240+
resourceName := BuildTagResourceName("cam", "RoleId", tcClient.Region, d.Id())
241+
if err := tagService.ModifyTags(ctx, resourceName, replaceTags, deleteTags); err != nil {
242+
return err
243+
}
244+
}
245+
246+
return resourceTencentCloudCamServiceLinkedRoleRead(d, meta)
247+
}
248+
249+
func resourceTencentCloudCamServiceLinkedRoleDelete(d *schema.ResourceData, meta interface{}) error {
250+
defer logElapsed("resource.tencentcloud_cam_service_linked_role.delete")()
251+
defer inconsistentCheck(d, meta)()
252+
253+
logId := getLogId(contextNil)
254+
ctx := context.WithValue(context.TODO(), logIdKey, logId)
255+
256+
service := CamService{client: meta.(*TencentCloudClient).apiV3Conn}
257+
258+
resourceId := d.Id()
259+
items := strings.Split(resourceId, FILED_SP)
260+
if len(items) != 3 {
261+
return fmt.Errorf("invalid ID %s", resourceId)
262+
}
263+
roleId := items[0]
264+
// qcsServiceName := items[1]
265+
// customSuffix := items[2]
266+
267+
serviceLinkedRole, err := service.DescribeCamServiceLinkedRole(ctx, roleId)
268+
if err != nil {
269+
return err
270+
}
271+
if serviceLinkedRole == nil || serviceLinkedRole.RoleName == nil {
272+
return fmt.Errorf("When querying serviceLinkedRole, an error occurs")
273+
}
274+
275+
deletionTaskId, err := service.DeleteCamServiceLinkedRoleById(ctx, *serviceLinkedRole.RoleName)
276+
if err != nil {
277+
return err
278+
}
279+
280+
err = resource.Retry(3*readRetryTimeout, func() *resource.RetryError {
281+
response, _ := service.DescribeCamServiceLinkedRoleDeleteStatus(ctx, deletionTaskId)
282+
// if errRet != nil {
283+
// return retryError(errRet, InternalError)
284+
// }
285+
if response == nil || response.Response == nil {
286+
return resource.NonRetryableError(fmt.Errorf("When querying the deletion status, an error occurred"))
287+
}
288+
289+
instance := response.Response
290+
if *instance.Status == "SUCCEEDED" {
291+
return nil
292+
}
293+
if *instance.Status == "FAILED" {
294+
return resource.NonRetryableError(fmt.Errorf("serviceLinkedRole status is %v, operate failed.", *instance.Status))
295+
}
296+
return resource.RetryableError(fmt.Errorf("serviceLinkedRole status is %v, retry...", *instance.Status))
297+
})
298+
if err != nil {
299+
return err
300+
}
301+
return nil
302+
}

0 commit comments

Comments
 (0)