Skip to content

Commit 08c76e5

Browse files
authored
feat: Long-running operation improvements for mongodbatlas_push_based_log_export resource (#3570)
* implement delete_on_create_timeout * aws provider in test
1 parent 7f01e61 commit 08c76e5

File tree

9 files changed

+103
-25
lines changed

9 files changed

+103
-25
lines changed

.changelog/3570.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_push_based_log_export: Adds `delete_on_create_timeout` attribute to indicate whether to delete the resource if its creation times out
3+
```

docs/resources/push_based_log_export.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ output "test" {
5959

6060
### Optional
6161

62+
- `delete_on_create_timeout` (Boolean) Indicates whether to delete the resource being created if a timeout is reached when waiting for completion. When set to `true` and timeout occurs, it triggers the deletion and returns immediately without waiting for deletion to complete. When set to `false`, the timeout will not trigger resource deletion. If you suspect a transient error when the value is `true`, wait before retrying to allow resource deletion to finish. Default is `true`.
6263
- `prefix_path` (String) S3 directory in which vector writes in order to store the logs. An empty string denotes the root directory.
6364
- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts))
6465

internal/common/cleanup/handle_timeout.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
1010
"github.com/hashicorp/terraform-plugin-framework/diag"
11+
"github.com/hashicorp/terraform-plugin-framework/types"
1112
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
1213
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/constant"
1314
)
@@ -99,3 +100,14 @@ func ResolveTimeout(ctx context.Context, t *timeouts.Value, operationName string
99100
}
100101
return timeoutDuration
101102
}
103+
104+
// ResolveDeleteOnCreateTimeout returns true if delete_on_create_timeout should be enabled.
105+
// Default behavior is true when not explicitly set to false.
106+
func ResolveDeleteOnCreateTimeout(deleteOnCreateTimeout types.Bool) bool {
107+
// If null or unknown, default to true
108+
if deleteOnCreateTimeout.IsNull() || deleteOnCreateTimeout.IsUnknown() {
109+
return true
110+
}
111+
// Otherwise use the explicit value
112+
return deleteOnCreateTimeout.ValueBool()
113+
}

internal/service/pushbasedlogexport/data_source.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func (d *pushBasedLogExportDS) Read(ctx context.Context, req datasource.ReadRequ
4545
return
4646
}
4747

48-
newTFModel, diags := NewTFPushBasedLogExport(ctx, projectID, logConfig, nil)
48+
newTFModel, diags := NewTFPushBasedLogExport(ctx, projectID, logConfig, nil, nil)
4949
if diags.HasError() {
5050
resp.Diagnostics.Append(diags...)
5151
return

internal/service/pushbasedlogexport/model.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion"
1313
)
1414

15-
func NewTFPushBasedLogExport(ctx context.Context, projectID string, apiResp *admin.PushBasedLogExportProject, timeout *timeouts.Value) (*TFPushBasedLogExportRSModel, diag.Diagnostics) {
15+
func NewTFPushBasedLogExport(ctx context.Context, projectID string, apiResp *admin.PushBasedLogExportProject, timeout *timeouts.Value, deleteOnCreateTimeout *types.Bool) (*TFPushBasedLogExportRSModel, diag.Diagnostics) {
1616
tfModel := &TFPushBasedLogExportRSModel{
1717
ProjectID: types.StringPointerValue(&projectID),
1818
BucketName: types.StringPointerValue(apiResp.BucketName),
@@ -25,6 +25,9 @@ func NewTFPushBasedLogExport(ctx context.Context, projectID string, apiResp *adm
2525
if timeout != nil {
2626
tfModel.Timeouts = *timeout
2727
}
28+
if deleteOnCreateTimeout != nil {
29+
tfModel.DeleteOnCreateTimeout = *deleteOnCreateTimeout
30+
}
2831
return tfModel, nil
2932
}
3033

internal/service/pushbasedlogexport/model_test.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@ var (
2323
)
2424

2525
type sdkToTFModelTestCase struct {
26-
apiResp *admin.PushBasedLogExportProject
27-
timeout *timeouts.Value
28-
expectedTFModel *pushbasedlogexport.TFPushBasedLogExportRSModel
29-
name string
30-
projectID string
26+
apiResp *admin.PushBasedLogExportProject
27+
timeout *timeouts.Value
28+
deleteOnCreateTimeout *types.Bool
29+
expectedTFModel *pushbasedlogexport.TFPushBasedLogExportRSModel
30+
name string
31+
projectID string
3132
}
3233

3334
func TestNewTFPushBasedLogExport(t *testing.T) {
@@ -76,7 +77,7 @@ func TestNewTFPushBasedLogExport(t *testing.T) {
7677

7778
for _, tc := range testCases {
7879
t.Run(tc.name, func(t *testing.T) {
79-
resultModel, _ := pushbasedlogexport.NewTFPushBasedLogExport(t.Context(), tc.projectID, tc.apiResp, tc.timeout)
80+
resultModel, _ := pushbasedlogexport.NewTFPushBasedLogExport(t.Context(), tc.projectID, tc.apiResp, tc.timeout, tc.deleteOnCreateTimeout)
8081
if !assert.Equal(t, tc.expectedTFModel, resultModel) {
8182
t.Errorf("result model does not match expected output: expected %+v, got %+v", tc.expectedTFModel, resultModel)
8283
}

internal/service/pushbasedlogexport/resource.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/hashicorp/terraform-plugin-framework/path"
1212
"github.com/hashicorp/terraform-plugin-framework/resource"
1313

14+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/cleanup"
1415
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion"
1516
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/retrystrategy"
1617
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/validate"
@@ -74,6 +75,15 @@ func (r *pushBasedLogExportRS) Create(ctx context.Context, req resource.CreateRe
7475

7576
logExportConfigResp, err := WaitStateTransition(ctx, projectID, connV2.PushBasedLogExportApi,
7677
retryTimeConfig(timeout, minTimeoutCreateUpdate))
78+
79+
err = cleanup.HandleCreateTimeout(cleanup.ResolveDeleteOnCreateTimeout(tfPlan.DeleteOnCreateTimeout), err, func(ctx context.Context) error {
80+
cleanResp, cleanErr := connV2.PushBasedLogExportApi.DeletePushBasedLogConfiguration(ctx, projectID).Execute()
81+
if validate.StatusNotFound(cleanResp) {
82+
return nil
83+
}
84+
return cleanErr
85+
})
86+
7787
if err != nil {
7888
resp.Diagnostics.AddError("Error when creating push-based log export configuration", err.Error())
7989

@@ -84,7 +94,7 @@ func (r *pushBasedLogExportRS) Create(ctx context.Context, req resource.CreateRe
8494
return
8595
}
8696

87-
newTFModel, diags := NewTFPushBasedLogExport(ctx, projectID, logExportConfigResp, &tfPlan.Timeouts)
97+
newTFModel, diags := NewTFPushBasedLogExport(ctx, projectID, logExportConfigResp, &tfPlan.Timeouts, &tfPlan.DeleteOnCreateTimeout)
8898
if diags.HasError() {
8999
resp.Diagnostics.Append(diags...)
90100
return
@@ -111,7 +121,7 @@ func (r *pushBasedLogExportRS) Read(ctx context.Context, req resource.ReadReques
111121
return
112122
}
113123

114-
newTFModel, diags := NewTFPushBasedLogExport(ctx, projectID, logConfig, &tfState.Timeouts)
124+
newTFModel, diags := NewTFPushBasedLogExport(ctx, projectID, logConfig, &tfState.Timeouts, &tfState.DeleteOnCreateTimeout)
115125
if diags.HasError() {
116126
resp.Diagnostics.Append(diags...)
117127
return
@@ -148,7 +158,7 @@ func (r *pushBasedLogExportRS) Update(ctx context.Context, req resource.UpdateRe
148158
return
149159
}
150160

151-
newTFModel, diags := NewTFPushBasedLogExport(ctx, projectID, logExportConfigResp, &tfPlan.Timeouts)
161+
newTFModel, diags := NewTFPushBasedLogExport(ctx, projectID, logExportConfigResp, &tfPlan.Timeouts, &tfPlan.DeleteOnCreateTimeout)
152162
if diags.HasError() {
153163
resp.Diagnostics.Append(diags...)
154164
return

internal/service/pushbasedlogexport/resource_schema.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
1313
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
1414
"github.com/hashicorp/terraform-plugin-framework/types"
15+
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/customplanmodifier"
1516
)
1617

1718
func ResourceSchema(ctx context.Context) schema.Schema {
@@ -55,16 +56,24 @@ func ResourceSchema(ctx context.Context) schema.Schema {
5556
Update: true,
5657
Delete: true,
5758
}),
59+
"delete_on_create_timeout": schema.BoolAttribute{
60+
Optional: true,
61+
PlanModifiers: []planmodifier.Bool{
62+
customplanmodifier.CreateOnlyBoolPlanModifier(),
63+
},
64+
MarkdownDescription: "Indicates whether to delete the resource being created if a timeout is reached when waiting for completion. When set to `true` and timeout occurs, it triggers the deletion and returns immediately without waiting for deletion to complete. When set to `false`, the timeout will not trigger resource deletion. If you suspect a transient error when the value is `true`, wait before retrying to allow resource deletion to finish. Default is `true`.",
65+
},
5866
},
5967
}
6068
}
6169

6270
type TFPushBasedLogExportRSModel struct {
63-
BucketName types.String `tfsdk:"bucket_name"`
64-
CreateDate types.String `tfsdk:"create_date"`
65-
ProjectID types.String `tfsdk:"project_id"`
66-
IamRoleID types.String `tfsdk:"iam_role_id"`
67-
PrefixPath types.String `tfsdk:"prefix_path"`
68-
State types.String `tfsdk:"state"`
69-
Timeouts timeouts.Value `tfsdk:"timeouts"`
71+
BucketName types.String `tfsdk:"bucket_name"`
72+
CreateDate types.String `tfsdk:"create_date"`
73+
ProjectID types.String `tfsdk:"project_id"`
74+
IamRoleID types.String `tfsdk:"iam_role_id"`
75+
PrefixPath types.String `tfsdk:"prefix_path"`
76+
State types.String `tfsdk:"state"`
77+
Timeouts timeouts.Value `tfsdk:"timeouts"`
78+
DeleteOnCreateTimeout types.Bool `tfsdk:"delete_on_create_timeout"`
7079
}

internal/service/pushbasedlogexport/resource_test.go

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func basicTestCase(tb testing.TB) *resource.TestCase {
4444
CheckDestroy: checkDestroy,
4545
Steps: []resource.TestStep{
4646
{
47-
Config: configBasic(projectID, s3BucketName1, s3BucketName2, s3BucketPolicyName, awsIAMRoleName, awsIAMRolePolicyName, nonEmptyPrefixPath, true),
47+
Config: configBasic(projectID, s3BucketName1, s3BucketName2, s3BucketPolicyName, awsIAMRoleName, awsIAMRolePolicyName, nonEmptyPrefixPath, true, "", nil),
4848
Check: resource.ComposeAggregateTestCheckFunc(commonChecks(s3BucketName1, nonEmptyPrefixPath)...),
4949
},
5050
{
@@ -86,7 +86,7 @@ func noPrefixPathTestCase(tb testing.TB) *resource.TestCase {
8686
CheckDestroy: checkDestroy,
8787
Steps: []resource.TestStep{
8888
{
89-
Config: configBasic(projectID, s3BucketName1, s3BucketName2, s3BucketPolicyName, awsIAMRoleName, awsIAMRolePolicyName, defaultPrefixPath, false),
89+
Config: configBasic(projectID, s3BucketName1, s3BucketName2, s3BucketPolicyName, awsIAMRoleName, awsIAMRolePolicyName, defaultPrefixPath, false, "", nil),
9090
Check: resource.ComposeAggregateTestCheckFunc(commonChecks(s3BucketName1, defaultPrefixPath)...),
9191
},
9292
},
@@ -116,6 +116,39 @@ func createFailure(tb testing.TB) *resource.TestCase {
116116
}
117117
}
118118

119+
func TestAccPushBasedLogExport_createTimeoutWithDeleteOnCreateTimeout(t *testing.T) {
120+
resource.Test(t, *createTimeoutWithDeleteOnCreateTimeout(t))
121+
}
122+
123+
func createTimeoutWithDeleteOnCreateTimeout(tb testing.TB) *resource.TestCase {
124+
tb.Helper()
125+
126+
var (
127+
projectID = acc.ProjectIDExecution(tb)
128+
s3BucketNamePrefix = acc.RandomS3BucketName()
129+
s3BucketName1 = fmt.Sprintf("%s-1", s3BucketNamePrefix)
130+
s3BucketName2 = fmt.Sprintf("%s-2", s3BucketNamePrefix)
131+
s3BucketPolicyName = fmt.Sprintf("%s-s3-policy", s3BucketNamePrefix)
132+
awsIAMRoleName = acc.RandomIAMRole()
133+
awsIAMRolePolicyName = fmt.Sprintf("%s-policy", awsIAMRoleName)
134+
createTimeout = "1s"
135+
deleteOnCreateTimeout = true
136+
)
137+
138+
return &resource.TestCase{
139+
PreCheck: func() { acc.PreCheckBasic(tb) },
140+
ExternalProviders: acc.ExternalProvidersOnlyAWS(),
141+
ProtoV6ProviderFactories: acc.TestAccProviderV6Factories,
142+
CheckDestroy: checkDestroy,
143+
Steps: []resource.TestStep{
144+
{
145+
Config: configBasic(projectID, s3BucketName1, s3BucketName2, s3BucketPolicyName, awsIAMRoleName, awsIAMRolePolicyName, nonEmptyPrefixPath, true, acc.TimeoutConfig(&createTimeout, nil, nil, true), &deleteOnCreateTimeout),
146+
ExpectError: regexp.MustCompile("will run cleanup because delete_on_create_timeout is true"),
147+
},
148+
},
149+
}
150+
}
151+
119152
func pushBasedLogExportInvalidConfig(projectID string) string {
120153
return fmt.Sprintf(`resource "mongodbatlas_push_based_log_export" "test" {
121154
project_id = %[1]q
@@ -140,7 +173,7 @@ func addAttrChecks(checks []resource.TestCheckFunc, mapChecks map[string]string)
140173
return acc.AddAttrChecks(datasourceName, checks, mapChecks)
141174
}
142175

143-
func configBasic(projectID, s3BucketName1, s3BucketName2, s3BucketPolicyName, awsIAMRoleName, awsIAMRolePolicyName, prefixPath string, usePrefixPath bool) string {
176+
func configBasic(projectID, s3BucketName1, s3BucketName2, s3BucketPolicyName, awsIAMRoleName, awsIAMRolePolicyName, prefixPath string, usePrefixPath bool, timeoutConfig string, deleteOnCreateTimeout *bool) string {
144177
test := fmt.Sprintf(`
145178
locals {
146179
project_id = %[1]q
@@ -155,7 +188,7 @@ func configBasic(projectID, s3BucketName1, s3BucketName2, s3BucketPolicyName, aw
155188
156189
%[8]s
157190
`, projectID, s3BucketName1, s3BucketName2, s3BucketPolicyName, awsIAMRoleName, awsIAMRolePolicyName,
158-
awsIAMroleAuthAndS3Config(s3BucketName1, s3BucketName2), pushBasedLogExportConfig(false, usePrefixPath, prefixPath))
191+
awsIAMroleAuthAndS3Config(s3BucketName1, s3BucketName2), pushBasedLogExportConfig(false, usePrefixPath, prefixPath, timeoutConfig, deleteOnCreateTimeout))
159192
return test
160193
}
161194

@@ -174,13 +207,17 @@ func configBasicUpdated(projectID, s3BucketName1, s3BucketName2, s3BucketPolicyN
174207
175208
%[8]s
176209
`, projectID, s3BucketName1, s3BucketName2, s3BucketPolicyName, awsIAMRoleName, awsIAMRolePolicyName,
177-
awsIAMroleAuthAndS3Config(s3BucketName1, s3BucketName2), pushBasedLogExportConfig(true, usePrefixPath, prefixPath)) // updating the S3 bucket to use for push-based log config
210+
awsIAMroleAuthAndS3Config(s3BucketName1, s3BucketName2), pushBasedLogExportConfig(true, usePrefixPath, prefixPath, "", nil)) // updating the S3 bucket to use for push-based log config
178211
return test
179212
}
180213

181214
// pushBasedLogExportConfig returns config for mongodbatlas_push_based_log_export resource and data source.
182215
// This method uses the project and S3 bucket created in awsIAMroleAuthAndS3Config()
183-
func pushBasedLogExportConfig(useBucket2, usePrefixPath bool, prefixPath string) string {
216+
func pushBasedLogExportConfig(useBucket2, usePrefixPath bool, prefixPath, timeoutConfig string, deleteOnCreateTimeout *bool) string {
217+
deleteOnCreateTimeoutAttr := ""
218+
if deleteOnCreateTimeout != nil {
219+
deleteOnCreateTimeoutAttr = fmt.Sprintf("delete_on_create_timeout = %[1]t", *deleteOnCreateTimeout)
220+
}
184221
bucketNameAttr := "bucket_name = aws_s3_bucket.log_bucket_1.bucket"
185222
if useBucket2 {
186223
bucketNameAttr = "bucket_name = aws_s3_bucket.log_bucket_2.bucket"
@@ -191,10 +228,12 @@ func pushBasedLogExportConfig(useBucket2, usePrefixPath bool, prefixPath string)
191228
%[1]s
192229
iam_role_id = mongodbatlas_cloud_provider_access_authorization.auth_role.role_id
193230
prefix_path = %[2]q
231+
%[4]s
232+
%[5]s
194233
}
195234
196235
%[3]s
197-
`, bucketNameAttr, prefixPath, pushBasedLogExportDataSourceConfig())
236+
`, bucketNameAttr, prefixPath, pushBasedLogExportDataSourceConfig(), deleteOnCreateTimeoutAttr, timeoutConfig)
198237
}
199238

200239
return fmt.Sprintf(`resource "mongodbatlas_push_based_log_export" "test" {

0 commit comments

Comments
 (0)