Skip to content

Commit 7f3e3c0

Browse files
gitmknanonymous
andauthored
feat: support cam saml config (#1505)
* feat: support cam saml config * feat: add saml config unit * fix: support file path * feat: support saml output file * fix: modify field name Co-authored-by: anonymous <anonymous@mail.org>
1 parent 8357142 commit 7f3e3c0

File tree

7 files changed

+416
-0
lines changed

7 files changed

+416
-0
lines changed

tencentcloud/common.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717

1818
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
1919
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
20+
"github.com/mitchellh/go-homedir"
2021
"github.com/pkg/errors"
2122
sdkErrors "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
2223
"github.com/tencentyun/cos-go-sdk-v5"
@@ -251,6 +252,34 @@ func isExpectError(err error, expectError []string) bool {
251252
return false
252253
}
253254

255+
// IsNil Determine whether i is empty
256+
func IsNil(v interface{}) bool {
257+
258+
valueOf := reflect.ValueOf(v)
259+
260+
k := valueOf.Kind()
261+
262+
switch k {
263+
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.UnsafePointer, reflect.Interface, reflect.Slice:
264+
return valueOf.IsNil()
265+
default:
266+
return v == nil
267+
}
268+
}
269+
270+
// IsString Determine whether data is a string
271+
func IsString(data interface{}) bool {
272+
if IsNil(data) {
273+
return false
274+
}
275+
276+
if _, ok := data.(string); ok {
277+
return true
278+
}
279+
280+
return false
281+
}
282+
254283
// writeToFile write data to file
255284
func writeToFile(filePath string, data interface{}) error {
256285
if strings.HasPrefix(filePath, "~") {
@@ -277,6 +306,10 @@ func writeToFile(filePath string, data interface{}) error {
277306
}
278307
}
279308

309+
if IsString(data) {
310+
return ioutil.WriteFile(filePath, []byte(data.(string)), 0422)
311+
}
312+
280313
jsonStr, err := json.MarshalIndent(data, "", "\t")
281314
if err != nil {
282315
return fmt.Errorf("json decode error,reason %s", err.Error())
@@ -285,6 +318,21 @@ func writeToFile(filePath string, data interface{}) error {
285318
return ioutil.WriteFile(filePath, jsonStr, 0422)
286319
}
287320

321+
// ReadFromFile return file content
322+
func ReadFromFile(file string) ([]byte, error) {
323+
fileName, err := homedir.Expand(file)
324+
if err != nil {
325+
log.Printf("[CRITAL] wrong file path, error: %v", err)
326+
return nil, err
327+
}
328+
content, err := ioutil.ReadFile(fileName)
329+
if err != nil {
330+
log.Printf("[CRITAL] file read failed, error: %v", err)
331+
return nil, err
332+
}
333+
return content, nil
334+
}
335+
288336
func CheckNil(object interface{}, fields map[string]string) (nilFields []string) {
289337
// if object is a pointer, get value which object points to
290338
object = reflect.Indirect(reflect.ValueOf(object)).Interface()

tencentcloud/provider.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ Cloud Access Management(CAM)
182182
tencentcloud_cam_oidc_sso
183183
tencentcloud_cam_role_sso
184184
tencentcloud_cam_service_linked_role
185+
tencentcloud_cam_user_saml_config
185186
186187
Cloud Block Storage(CBS)
187188
Data Source
@@ -1395,6 +1396,7 @@ func Provider() terraform.ResourceProvider {
13951396
"tencentcloud_cam_group_membership": resourceTencentCloudCamGroupMembership(),
13961397
"tencentcloud_cam_saml_provider": resourceTencentCloudCamSAMLProvider(),
13971398
"tencentcloud_cam_service_linked_role": resourceTencentCloudCamServiceLinkedRole(),
1399+
"tencentcloud_cam_user_saml_config": resourceTencentCloudCamUserSamlConfig(),
13981400
"tencentcloud_scf_function": resourceTencentCloudScfFunction(),
13991401
"tencentcloud_scf_namespace": resourceTencentCloudScfNamespace(),
14001402
"tencentcloud_scf_layer": resourceTencentCloudScfLayer(),
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
/*
2+
Provides a resource to create a cam user_saml_config
3+
4+
Example Usage
5+
6+
```hcl
7+
resource "tencentcloud_cam_user_saml_config" "user_saml_config" {
8+
saml_metadata_document = "./metadataDocument.xml"
9+
# saml_metadata_document = <<-EOT
10+
# <?xml version="1.0" encoding="utf-8"?></EntityDescriptor>
11+
# EOT
12+
}
13+
```
14+
15+
Import
16+
17+
cam user_saml_config can be imported using the id, e.g.
18+
19+
```
20+
terraform import tencentcloud_cam_user_saml_config.user_saml_config user_id
21+
```
22+
*/
23+
package tencentcloud
24+
25+
import (
26+
"context"
27+
"fmt"
28+
"log"
29+
"strings"
30+
31+
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
32+
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
33+
cam "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cam/v20190116"
34+
"github.com/tencentcloudstack/terraform-provider-tencentcloud/tencentcloud/internal/helper"
35+
)
36+
37+
func resourceTencentCloudCamUserSamlConfig() *schema.Resource {
38+
return &schema.Resource{
39+
Create: resourceTencentCloudCamUserSamlConfigCreate,
40+
Read: resourceTencentCloudCamUserSamlConfigRead,
41+
Update: resourceTencentCloudCamUserSamlConfigUpdate,
42+
Delete: resourceTencentCloudCamUserSamlConfigDelete,
43+
Importer: &schema.ResourceImporter{
44+
State: schema.ImportStatePassthrough,
45+
},
46+
Schema: map[string]*schema.Schema{
47+
"saml_metadata_document": {
48+
Required: true,
49+
Type: schema.TypeString,
50+
Description: "SAML metadata document, xml format, support string content or file path.",
51+
StateFunc: func(v interface{}) string {
52+
saml := v.(string)
53+
if saml != "" {
54+
b := strings.HasSuffix(saml, ".xml")
55+
if b {
56+
metadata, _ := ReadFromFile(saml)
57+
return string(metadata)
58+
}
59+
}
60+
return saml
61+
},
62+
},
63+
64+
"status": {
65+
Computed: true,
66+
Type: schema.TypeInt,
67+
Description: "Status: `0`: not set, `11`: enabled, `2`: disabled.",
68+
},
69+
70+
"metadata_document_file": {
71+
Type: schema.TypeString,
72+
Optional: true,
73+
Description: "The path used to save the samlMetadat file.",
74+
},
75+
},
76+
}
77+
}
78+
79+
func resourceTencentCloudCamUserSamlConfigCreate(d *schema.ResourceData, meta interface{}) error {
80+
defer logElapsed("resource.tencentcloud_cam_user_saml_config.create")()
81+
defer inconsistentCheck(d, meta)()
82+
83+
logId := getLogId(contextNil)
84+
85+
var (
86+
request = cam.NewCreateUserSAMLConfigRequest()
87+
response = cam.NewCreateUserSAMLConfigResponse()
88+
)
89+
if v, ok := d.GetOk("saml_metadata_document"); ok {
90+
saml := v.(string)
91+
b := strings.HasSuffix(saml, ".xml")
92+
if b {
93+
metadata, err := ReadFromFile(v.(string))
94+
if err != nil {
95+
return err
96+
}
97+
saml = string(metadata)
98+
}
99+
request.SAMLMetadataDocument = helper.String(StringToBase64(saml))
100+
}
101+
102+
err := resource.Retry(writeRetryTimeout, func() *resource.RetryError {
103+
result, e := meta.(*TencentCloudClient).apiV3Conn.UseCamClient().CreateUserSAMLConfig(request)
104+
if e != nil {
105+
return retryError(e)
106+
} else {
107+
log.Printf("[DEBUG]%s api[%s] success, request body [%s], response body [%s]\n", logId, request.GetAction(), request.ToJsonString(), result.ToJsonString())
108+
}
109+
response = result
110+
return nil
111+
})
112+
if err != nil {
113+
log.Printf("[CRITAL]%s create cam userSamlConfig failed, reason:%+v", logId, err)
114+
return err
115+
}
116+
117+
d.SetId(*response.Response.RequestId)
118+
return resourceTencentCloudCamUserSamlConfigRead(d, meta)
119+
}
120+
121+
func resourceTencentCloudCamUserSamlConfigRead(d *schema.ResourceData, meta interface{}) error {
122+
defer logElapsed("resource.tencentcloud_cam_user_saml_config.read")()
123+
defer inconsistentCheck(d, meta)()
124+
125+
logId := getLogId(contextNil)
126+
ctx := context.WithValue(context.TODO(), logIdKey, logId)
127+
128+
service := CamService{client: meta.(*TencentCloudClient).apiV3Conn}
129+
130+
samlConfig, err := service.DescribeCamUserSamlConfigById(ctx)
131+
if err != nil {
132+
return err
133+
}
134+
135+
if samlConfig == nil || samlConfig.Response == nil || *samlConfig.Response.Status == 2 {
136+
d.SetId("")
137+
log.Printf("[WARN]%s resource `CamUserSamlConfig` status is closed, please check if it has been closed.", logId)
138+
return nil
139+
}
140+
userSamlConfig := samlConfig.Response
141+
142+
if userSamlConfig.SAMLMetadata != nil {
143+
metadata, err := Base64ToString(*userSamlConfig.SAMLMetadata)
144+
if err != nil {
145+
return fmt.Errorf("`SamlConfig.SAMLMetadata` %s does not be decoded to xml", *userSamlConfig.SAMLMetadata)
146+
}
147+
_ = d.Set("saml_metadata_document", metadata)
148+
}
149+
150+
if userSamlConfig.Status != nil {
151+
_ = d.Set("status", userSamlConfig.Status)
152+
}
153+
154+
output, ok := d.GetOk("metadata_document_files")
155+
if ok && output.(string) != "" {
156+
if err = writeToFile(output.(string), d.Get("saml_metadata_document")); err != nil {
157+
return err
158+
}
159+
}
160+
161+
return nil
162+
}
163+
164+
func resourceTencentCloudCamUserSamlConfigUpdate(d *schema.ResourceData, meta interface{}) error {
165+
defer logElapsed("resource.tencentcloud_cam_user_saml_config.update")()
166+
defer inconsistentCheck(d, meta)()
167+
168+
logId := getLogId(contextNil)
169+
ctx := context.WithValue(context.TODO(), logIdKey, logId)
170+
171+
request := cam.NewUpdateUserSAMLConfigRequest()
172+
173+
if d.HasChange("saml_metadata_document") {
174+
if v, ok := d.GetOk("saml_metadata_document"); ok {
175+
saml := v.(string)
176+
b := strings.HasSuffix(saml, ".xml")
177+
if b {
178+
metadata, err := ReadFromFile(v.(string))
179+
if err != nil {
180+
return err
181+
}
182+
saml = string(metadata)
183+
}
184+
request.SAMLMetadataDocument = helper.String(StringToBase64(saml))
185+
}
186+
}
187+
188+
service := CamService{client: meta.(*TencentCloudClient).apiV3Conn}
189+
samlConfig, describeErr := service.DescribeCamUserSamlConfigById(ctx)
190+
if describeErr != nil {
191+
return describeErr
192+
}
193+
if *samlConfig.Response.Status == 2 {
194+
request.Operate = helper.String("enable")
195+
} else {
196+
request.Operate = helper.String("updateSAML")
197+
}
198+
199+
err := resource.Retry(writeRetryTimeout, func() *resource.RetryError {
200+
result, e := meta.(*TencentCloudClient).apiV3Conn.UseCamClient().UpdateUserSAMLConfig(request)
201+
if e != nil {
202+
return retryError(e)
203+
} else {
204+
log.Printf("[DEBUG]%s api[%s] success, request body [%s], response body [%s]\n", logId, request.GetAction(), request.ToJsonString(), result.ToJsonString())
205+
}
206+
return nil
207+
})
208+
if err != nil {
209+
log.Printf("[CRITAL]%s update cam userSamlConfig failed, reason:%+v", logId, err)
210+
return err
211+
}
212+
213+
return resourceTencentCloudCamUserSamlConfigRead(d, meta)
214+
}
215+
216+
func resourceTencentCloudCamUserSamlConfigDelete(d *schema.ResourceData, meta interface{}) error {
217+
defer logElapsed("resource.tencentcloud_cam_user_saml_config.delete")()
218+
defer inconsistentCheck(d, meta)()
219+
220+
logId := getLogId(contextNil)
221+
ctx := context.WithValue(context.TODO(), logIdKey, logId)
222+
223+
service := CamService{client: meta.(*TencentCloudClient).apiV3Conn}
224+
225+
if err := service.DeleteCamUserSamlConfigById(ctx); err != nil {
226+
return err
227+
}
228+
229+
return nil
230+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package tencentcloud
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
7+
)
8+
9+
func TestAccTencentCloudNeedFixCamUserSamlConfigResource_basic(t *testing.T) {
10+
t.Parallel()
11+
resource.Test(t, resource.TestCase{
12+
PreCheck: func() {
13+
testAccPreCheck(t)
14+
},
15+
Providers: testAccProviders,
16+
Steps: []resource.TestStep{
17+
{
18+
Config: testAccCamUserSamlConfig,
19+
Check: resource.ComposeTestCheckFunc(resource.TestCheckResourceAttrSet("tencentcloud_cam_user_saml_config.user_saml_config", "id")),
20+
},
21+
{
22+
ResourceName: "tencentcloud_cam_user_saml_config.user_saml_config",
23+
ImportState: true,
24+
ImportStateVerify: true,
25+
},
26+
},
27+
})
28+
}
29+
30+
const testAccCamUserSamlConfig = `
31+
32+
resource "tencentcloud_cam_user_saml_config" "user_saml_config" {
33+
saml_metadata_document = ""
34+
}
35+
36+
`

0 commit comments

Comments
 (0)