Skip to content

Commit 5a9eaf2

Browse files
feat: Add support for long running operations on mongodbatlas_cloud_provider_access_setup for GCP (#3644)
* Add GCP cloud provider access to mongodbatlas_cloud_provider_access_setup * Fix typo for function name * Add GCP configuration for schema on data source * Change optional field to computed as required per provider * Refactor function to accomodate for Cloud Provider Access - GCP * Add documentation for resource * Add GCP Cloud provider documentation for data source * Add GCP provider for data source validation * Add resource and resouce acceptance test for GCP Cloud Provider Access setup * Fix format * Add changelog * Fix issue where role ID was missing for Azure config * Fix format * Fix issues for refactor on GCP * Address PR comments * Address further PR comments * Refactor setup function to handle different cloud providers, and return error if provider used is not correct Add error handling for cloud provider access * Refactor multiple if/else statements into switch in order to avoid lint errors * Address nit comments * Substitute cloud provider string names with constants * Add implementation on long running operation for GCP Cloud provider * Fix issue where wrong role ID was being used for GCP provider * Remove duplicated client * Refactor error handling for refresh state * Add changelog * Remove GCP pre-check as it's not necessary for acceptance tests of this resource * Address PR comments * Rename constants since cloudprovideraccess package is explicit
1 parent 4887438 commit 5a9eaf2

6 files changed

+98
-38
lines changed

.changelog/3644.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
resource/mongodbatlas_cloud_provider_access_setup: Adds long running operation support for GCP
3+
```

internal/service/cloudprovideraccess/data_source_cloud_provider_access_setup.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,17 +104,17 @@ func dataSourceMongoDBAtlasCloudProviderAccessSetupRead(ctx context.Context, d *
104104

105105
role, _, err := conn.CloudProviderAccessApi.GetCloudProviderAccessRole(ctx, projectID, roleID).Execute()
106106
if err != nil {
107-
return diag.FromErr(fmt.Errorf(ErrorCloudProviderGetRead, err))
107+
return diag.FromErr(fmt.Errorf(ErrorGetRead, err))
108108
}
109109

110110
roleSchema, err := roleToSchemaSetup(role)
111111
if err != nil {
112-
return diag.FromErr(fmt.Errorf(ErrorCloudProviderGetRead, err))
112+
return diag.FromErr(fmt.Errorf(ErrorGetRead, err))
113113
}
114114

115115
for key, val := range roleSchema {
116116
if err := d.Set(key, val); err != nil {
117-
return diag.FromErr(fmt.Errorf(ErrorCloudProviderGetRead, err))
117+
return diag.FromErr(fmt.Errorf(ErrorGetRead, err))
118118
}
119119
}
120120

internal/service/cloudprovideraccess/resource_cloud_provider_access_authorization.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -125,13 +125,13 @@ func resourceCloudProviderAccessAuthorizationRead(ctx context.Context, d *schema
125125
}
126126

127127
if targetRole == nil {
128-
return diag.FromErr(fmt.Errorf(ErrorCloudProviderGetRead, "cloud provider access role not found in mongodbatlas, please create it first"))
128+
return diag.FromErr(fmt.Errorf(ErrorGetRead, "cloud provider access role not found in mongodbatlas, please create it first"))
129129
}
130130

131131
roleSchema := roleToSchemaAuthorization(targetRole)
132132
for key, val := range roleSchema {
133133
if err := d.Set(key, val); err != nil {
134-
return diag.FromErr(fmt.Errorf(ErrorCloudProviderGetRead, err))
134+
return diag.FromErr(fmt.Errorf(ErrorGetRead, err))
135135
}
136136
}
137137

@@ -158,7 +158,7 @@ func resourceCloudProviderAccessAuthorizationCreate(ctx context.Context, d *sche
158158
}
159159

160160
if targetRole == nil {
161-
return diag.FromErr(fmt.Errorf(ErrorCloudProviderGetRead, "cloud provider access role not found in mongodbatlas, please create it first"))
161+
return diag.FromErr(fmt.Errorf(ErrorGetRead, "cloud provider access role not found in mongodbatlas, please create it first"))
162162
}
163163

164164
return authorizeRole(ctx, conn, d, projectID, targetRole)
@@ -178,7 +178,7 @@ func resourceCloudProviderAccessAuthorizationUpdate(ctx context.Context, d *sche
178178
}
179179

180180
if targetRole == nil {
181-
return diag.FromErr(fmt.Errorf(ErrorCloudProviderGetRead, "cloud provider access role not found in mongodbatlas, please create it first"))
181+
return diag.FromErr(fmt.Errorf(ErrorGetRead, "cloud provider access role not found in mongodbatlas, please create it first"))
182182
}
183183

184184
if d.HasChange("aws") || d.HasChange("azure") {
@@ -240,7 +240,7 @@ func roleToSchemaAuthorization(role *admin.CloudProviderAccessRole) map[string]a
240240
func FindRole(ctx context.Context, conn *admin.APIClient, projectID, roleID string) (*admin.CloudProviderAccessRole, error) {
241241
role, _, err := conn.CloudProviderAccessApi.GetCloudProviderAccessRole(ctx, projectID, roleID).Execute()
242242
if err != nil {
243-
return nil, fmt.Errorf(ErrorCloudProviderGetRead, err)
243+
return nil, fmt.Errorf(ErrorGetRead, err)
244244
}
245245

246246
return role, nil
@@ -345,7 +345,7 @@ func authorizeRole(ctx context.Context, client *admin.APIClient, d *schema.Resou
345345

346346
for key, val := range authSchema {
347347
if err := d.Set(key, val); err != nil {
348-
return diag.FromErr(fmt.Errorf(errorCloudProviderAccessCreate, err))
348+
return diag.FromErr(fmt.Errorf(errorCreate, err))
349349
}
350350
}
351351

internal/service/cloudprovideraccess/resource_cloud_provider_access_authorization_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ func checkDestroy(s *terraform.State) error {
199199
id := ids["id"]
200200
role, _, err := acc.ConnV2().CloudProviderAccessApi.GetCloudProviderAccessRole(context.Background(), ids["project_id"], id).Execute()
201201
if err != nil {
202-
return fmt.Errorf(cloudprovideraccess.ErrorCloudProviderGetRead, err)
202+
return fmt.Errorf(cloudprovideraccess.ErrorGetRead, err)
203203
}
204204
if role.GetId() == id || role.GetRoleId() == id {
205205
return nil

internal/service/cloudprovideraccess/resource_cloud_provider_access_setup.go

Lines changed: 83 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import (
44
"context"
55
"fmt"
66
"regexp"
7+
"time"
78

89
"go.mongodb.org/atlas-sdk/v20250312006/admin"
910

1011
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
12+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
1113
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1214
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/constant"
1315
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion"
@@ -22,11 +24,12 @@ import (
2224
*/
2325

2426
const (
25-
errorCloudProviderAccessCreate = "error creating cloud provider access %s"
26-
errorCloudProviderAccessUpdate = "error updating cloud provider access %s"
27-
errorCloudProviderAccessDelete = "error deleting cloud provider access %s"
28-
errorCloudProviderAccessImporter = "error importing cloud provider access %s"
29-
ErrorCloudProviderGetRead = "error reading cloud provider access %s"
27+
errorCreate = "error creating cloud provider access %s"
28+
errorUpdate = "error updating cloud provider access %s"
29+
errorDelete = "error deleting cloud provider access %s"
30+
errorImporter = "error importing cloud provider access %s"
31+
ErrorGetRead = "error reading cloud provider access %s"
32+
defaultTimeout time.Duration = 20 * time.Minute
3033
)
3134

3235
func ResourceSetup() *schema.Resource {
@@ -130,16 +133,16 @@ func resourceCloudProviderAccessSetupRead(ctx context.Context, d *schema.Resourc
130133
return nil
131134
}
132135

133-
return diag.FromErr(fmt.Errorf(ErrorCloudProviderGetRead, err))
136+
return diag.FromErr(fmt.Errorf(ErrorGetRead, err))
134137
}
135138

136139
roleSchema, err := roleToSchemaSetup(role)
137140
if err != nil {
138-
return diag.Errorf(errorCloudProviderAccessCreate, err)
141+
return diag.Errorf(errorCreate, err)
139142
}
140143
for key, val := range roleSchema {
141144
if err := d.Set(key, val); err != nil {
142-
return diag.FromErr(fmt.Errorf(ErrorCloudProviderGetRead, err))
145+
return diag.FromErr(fmt.Errorf(ErrorGetRead, err))
143146
}
144147
}
145148

@@ -169,18 +172,26 @@ func resourceCloudProviderAccessSetupCreate(ctx context.Context, d *schema.Resou
169172

170173
role, _, err := conn.CloudProviderAccessApi.CreateCloudProviderAccessRole(ctx, projectID, requestParameters).Execute()
171174
if err != nil {
172-
return diag.FromErr(fmt.Errorf(errorCloudProviderAccessCreate, err))
175+
return diag.FromErr(fmt.Errorf(errorCreate, err))
176+
}
177+
178+
resourceID := role.GetRoleId()
179+
if role.ProviderName == constant.AZURE {
180+
resourceID = role.GetId() // For Azure, the unique identifier is in the "id" field, not "roleId".
181+
}
182+
183+
if role.ProviderName == constant.GCP {
184+
r, err := waitForGCPProviderAccessCompletion(ctx, projectID, resourceID, conn)
185+
if err != nil {
186+
return diag.FromErr(err)
187+
}
188+
role = r
173189
}
174190

175191
// once multiple providers enable here do a switch, select for provider type
176192
roleSchema, err := roleToSchemaSetup(role)
177193
if err != nil {
178-
return diag.Errorf(errorCloudProviderAccessCreate, err)
179-
}
180-
181-
resourceID := role.GetRoleId()
182-
if role.ProviderName == constant.AZURE {
183-
resourceID = role.GetId()
194+
return diag.Errorf(errorCreate, err)
184195
}
185196

186197
d.SetId(conversion.EncodeStateID(map[string]string{
@@ -191,13 +202,60 @@ func resourceCloudProviderAccessSetupCreate(ctx context.Context, d *schema.Resou
191202

192203
for key, val := range roleSchema {
193204
if err := d.Set(key, val); err != nil {
194-
return diag.FromErr(fmt.Errorf(errorCloudProviderAccessCreate, err))
205+
return diag.FromErr(fmt.Errorf(errorCreate, err))
195206
}
196207
}
197208

198209
return nil
199210
}
200211

212+
func waitForGCPProviderAccessCompletion(ctx context.Context, projectID, resourceID string, conn *admin.APIClient) (*admin.CloudProviderAccessRole, error) {
213+
requestParams := &admin.GetCloudProviderAccessRoleApiParams{
214+
RoleId: resourceID,
215+
GroupId: projectID,
216+
}
217+
218+
stateConf := retry.StateChangeConf{
219+
Pending: []string{"IN_PROGRESS", "NOT_INITIATED"},
220+
Target: []string{"COMPLETE", "FAILED"},
221+
Refresh: resourceRefreshFunc(ctx, requestParams, conn),
222+
Timeout: defaultTimeout,
223+
MinTimeout: 60 * time.Second,
224+
Delay: 30 * time.Second,
225+
}
226+
227+
finalResponse, err := stateConf.WaitForStateContext(ctx)
228+
if err != nil {
229+
return nil, err
230+
}
231+
232+
r, ok := finalResponse.(*admin.CloudProviderAccessRole)
233+
if !ok {
234+
return nil, fmt.Errorf("unexpected type for result: %T", finalResponse)
235+
}
236+
return r, nil
237+
}
238+
239+
func resourceRefreshFunc(ctx context.Context, requestParams *admin.GetCloudProviderAccessRoleApiParams, conn *admin.APIClient) retry.StateRefreshFunc {
240+
return func() (any, string, error) {
241+
role, resp, err := conn.CloudProviderAccessApi.GetCloudProviderAccessRoleWithParams(ctx, requestParams).Execute()
242+
if err != nil {
243+
if validate.StatusNotFound(resp) {
244+
return nil, "", fmt.Errorf("cloud provider access role %q not found in project %q", requestParams.RoleId, requestParams.GroupId)
245+
}
246+
return nil, "", err
247+
}
248+
249+
status := role.GetStatus()
250+
switch status {
251+
case "IN_PROGRESS", "NOT_INITIATED", "COMPLETE":
252+
return role, status, nil
253+
default:
254+
return nil, status, fmt.Errorf("cloud provider access setup failed %q for role %q", status, requestParams.RoleId)
255+
}
256+
}
257+
}
258+
201259
func resourceCloudProviderAccessSetupDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
202260
conn := meta.(*config.MongoDBClient).AtlasV2
203261
ids := conversion.DecodeStateID(d.Id())
@@ -214,7 +272,7 @@ func resourceCloudProviderAccessSetupDelete(ctx context.Context, d *schema.Resou
214272

215273
_, err := conn.CloudProviderAccessApi.DeauthorizeCloudProviderAccessRoleWithParams(ctx, req).Execute()
216274
if err != nil {
217-
return diag.FromErr(fmt.Errorf(errorCloudProviderAccessDelete, err))
275+
return diag.FromErr(fmt.Errorf(errorDelete, err))
218276
}
219277

220278
d.SetId("")
@@ -232,7 +290,7 @@ func roleToSchemaSetup(role *admin.CloudProviderAccessRole) (map[string]any, err
232290
}},
233291
"gcp_config": []any{map[string]any{}},
234292
"created_date": conversion.TimeToString(role.GetCreatedDate()),
235-
"role_id": role.GetRoleId(),
293+
"role_id": role.GetRoleId(), // For AWS, the unique identifier is in the "roleId" field
236294
}, nil
237295
case constant.AZURE:
238296
return map[string]any{
@@ -246,7 +304,7 @@ func roleToSchemaSetup(role *admin.CloudProviderAccessRole) (map[string]any, err
246304
"gcp_config": []any{map[string]any{}},
247305
"created_date": conversion.TimeToString(role.GetCreatedDate()),
248306
"last_updated_date": conversion.TimeToString(role.GetLastUpdatedDate()),
249-
"role_id": role.GetId(),
307+
"role_id": role.GetId(), // For Azure, the unique identifier is in the "id" field, not "roleId".
250308
}, nil
251309
case constant.GCP:
252310
return map[string]any{
@@ -256,7 +314,7 @@ func roleToSchemaSetup(role *admin.CloudProviderAccessRole) (map[string]any, err
256314
"service_account_for_atlas": role.GetGcpServiceAccountForAtlas(),
257315
}},
258316
"aws_config": []any{map[string]any{}},
259-
"role_id": role.GetId(),
317+
"role_id": role.GetRoleId(), // For GCP, the unique identifier is in the "roleId"
260318
"created_date": conversion.TimeToString(role.GetCreatedDate()),
261319
}, nil
262320
default:
@@ -268,7 +326,7 @@ func resourceCloudProviderAccessSetupImportState(ctx context.Context, d *schema.
268326
projectID, providerName, roleID, err := splitCloudProviderAccessID(d.Id())
269327

270328
if err != nil {
271-
return nil, fmt.Errorf(errorCloudProviderAccessImporter, err)
329+
return nil, fmt.Errorf(errorImporter, err)
272330
}
273331

274332
// searching id in internal format
@@ -281,17 +339,17 @@ func resourceCloudProviderAccessSetupImportState(ctx context.Context, d *schema.
281339
err2 := resourceCloudProviderAccessSetupRead(ctx, d, meta)
282340

283341
if err2 != nil {
284-
return nil, fmt.Errorf(errorCloudProviderAccessImporter, err)
342+
return nil, fmt.Errorf(errorImporter, err)
285343
}
286344

287345
// case of not found
288346
if d.Id() == "" {
289-
return nil, fmt.Errorf(errorCloudProviderAccessImporter, " Resource not found at the cloud please check your id")
347+
return nil, fmt.Errorf(errorImporter, " Resource not found at the cloud please check your id")
290348
}
291349

292350
// params syncing
293351
if err = d.Set("project_id", projectID); err != nil {
294-
return nil, fmt.Errorf(errorCloudProviderAccessImporter, err)
352+
return nil, fmt.Errorf(errorImporter, err)
295353
}
296354

297355
return []*schema.ResourceData{d}, nil
@@ -303,7 +361,7 @@ func splitCloudProviderAccessID(id string) (projectID, providerName, roleID stri
303361
parts := re.FindStringSubmatch(id)
304362

305363
if len(parts) != 4 {
306-
err = fmt.Errorf(errorCloudProviderAccessImporter, "format please use {project_id}-{provider-name}-{role-id}")
364+
err = fmt.Errorf(errorImporter, "format please use {project_id}-{provider-name}-{role-id}")
307365
return
308366
}
309367

internal/service/cloudprovideraccess/resource_cloud_provider_access_setup_test.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,14 @@ func TestAccCloudProviderAccessSetupAzure_basic(t *testing.T) {
6363
)
6464
}
6565
func TestAccCloudProviderAccessSetupGCP_basic(t *testing.T) {
66-
acc.SkipTestForCI(t) // Code needs to support long running operations for successful test: CLOUDP-341440
6766
var (
6867
resourceName = "mongodbatlas_cloud_provider_access_setup.test"
6968
dataSourceName = "data.mongodbatlas_cloud_provider_access_setup.test"
7069
projectID = acc.ProjectIDExecution(t)
7170
)
7271

7372
resource.ParallelTest(t, resource.TestCase{
74-
PreCheck: func() { acc.PreCheckGCPEnv(t) },
73+
PreCheck: func() { acc.PreCheckBasic(t) },
7574
ProtoV6ProviderFactories: acc.TestAccProviderV6Factories,
7675
Steps: []resource.TestStep{
7776
{
@@ -169,7 +168,7 @@ func checkExists(resourceName string) resource.TestCheckFunc {
169168

170169
role, _, err := acc.ConnV2().CloudProviderAccessApi.GetCloudProviderAccessRole(context.Background(), ids["project_id"], id).Execute()
171170
if err != nil {
172-
return fmt.Errorf(cloudprovideraccess.ErrorCloudProviderGetRead, err)
171+
return fmt.Errorf(cloudprovideraccess.ErrorGetRead, err)
173172
}
174173
if role.GetId() == id || role.GetRoleId() == id {
175174
return nil

0 commit comments

Comments
 (0)