Skip to content

feat: Adds protected_hours and time_zone_id to mongodbatlas_maintenance_window #3195

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Apr 24, 2025
7 changes: 7 additions & 0 deletions .changelog/3195.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```release-note:enhancement
resource/mongodbatlas_maintenance_window: Adds `protected_hours` and `time_zone_id`
```

```release-note:enhancement
data-source/mongodbatlas_maintenance_window: Adds `protected_hours` and `time_zone_id`
```
8 changes: 8 additions & 0 deletions docs/data-sources/maintenance_window.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,12 @@ In addition to all arguments above, the following attributes are exported:
* `start_asap` - Flag indicating whether project maintenance has been directed to start immediately. If you request that maintenance begin immediately, this field returns true from the time the request was made until the time the maintenance event completes.
* `number_of_deferrals` - Number of times the current maintenance event for this project has been deferred, there can be a maximum of 2 deferrals.
* `auto_defer_once_enabled` - Flag that indicates whether you want to defer all maintenance windows one week they would be triggered.
* `protected_hours` - (Optional) Defines the time period during which there will be no standard updates to the clusters. See [Protected Hours](#protected-hours).
Copy link
Member

@lantoli lantoli Apr 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we document the default protected hours value if not specified? is it 0,0 the default?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I asked on the ticket.
My guess is that it will be empty (no protected hours)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also I don't know if there is a limit, e.g. can you set the 24h as protected? (i guess not because then maintenance can't happen). maybe we can reference some official doc for more info.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a link 20 lines below.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, thanks, and the answer is there: The length of your protected hours window cannot exceed 18 hours.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upstream replied that they have an internal default.

* `time_zone_id` - Identifier for the current time zone of the maintenance window. This can only be updated via the Project Settings UI.

### Protected Hours
* `start_hour_of_day` - Zero-based integer that represents the beginning hour of the day for the protected hours window.
* `end_hour_of_day` - Zero-based integer that represents the end hour of the day for the protected hours window.


For more information see: [MongoDB Atlas API Reference.](https://docs.atlas.mongodb.com/reference/api/maintenance-windows/)
11 changes: 11 additions & 0 deletions docs/resources/maintenance_window.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ Once maintenance is scheduled for your cluster, you cannot change your maintenan
project_id = "<your-project-id>"
day_of_week = 3
hour_of_day = 4

protected_hours {
start_hour_of_day = 9
end_hour_of_day = 17
}
}

```
Expand All @@ -41,14 +46,20 @@ Once maintenance is scheduled for your cluster, you cannot change your maintenan
* `defer` - Defer the next scheduled maintenance for the given project for one week.
* `auto_defer` - Defer any scheduled maintenance for the given project for one week.
* `auto_defer_once_enabled` - Flag that indicates whether you want to defer all maintenance windows one week they would be triggered.
* `protected_hours` - (Optional) Defines the time period during which there will be no standard updates to the clusters. See [Protected Hours](#protected-hours).

-> **NOTE:** The `start_asap` attribute can't be used because of breaks the Terraform flow, but you can enable via API.

### Protected Hours
* `start_hour_of_day` - Zero-based integer that represents the beginning hour of the day for the protected hours window.
- `end_hour_of_day` - Zero-based integer that represents the end hour of the day for the protected hours window.

## Attributes Reference

In addition to all arguments above, the following attributes are exported:

* `number_of_deferrals` - Number of times the current maintenance event for this project has been deferred, there can be a maximum of 2 deferrals.
* `time_zone_id` - Identifier for the current time zone of the maintenance window. This can only be updated via the Project Settings UI.

## Import

Expand Down
11 changes: 11 additions & 0 deletions examples/mongodbatlas_maintenance_window/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# MongoDB Atlas Provider - Configure Maintenance Window

This example demonstrates how to configure maintenance windows for your Atlas project in Terraform.

Required variables to set:

- `public_key`: Atlas public key
- `private_key`: Atlas private key
- `org_id`: Unique 24-hexadecimal digit string that identifies the organization that contains the project and cluster.

For additional information you can visit the [Maintenance Window Documentation](https://www.mongodb.com/docs/atlas/tutorial/cluster-maintenance-window/).
23 changes: 23 additions & 0 deletions examples/mongodbatlas_maintenance_window/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
resource "mongodbatlas_project" "example" {
name = "project-name"
org_id = var.org_id
}

resource "mongodbatlas_maintenance_window" "example" {
project_id = mongodbatlas_project.example.id
auto_defer_once_enabled = true
hour_of_day = 23
day_of_week = 1
protected_hours {
start_hour_of_day = 9
end_hour_of_day = 17
}
}

data "mongodbatlas_maintenance_window" "example" {
project_id = mongodbatlas_maintenance_window.example.project_id
}

output "time_zone_id" {
value = data.mongodbatlas_maintenance_window.example.time_zone_id
}
4 changes: 4 additions & 0 deletions examples/mongodbatlas_maintenance_window/provider.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
provider "mongodbatlas" {
public_key = var.public_key
private_key = var.private_key
}
12 changes: 12 additions & 0 deletions examples/mongodbatlas_maintenance_window/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
variable "public_key" {
description = "Public API key to authenticate to Atlas"
type = string
}
variable "private_key" {
description = "Private API key to authenticate to Atlas"
type = string
}
variable "org_id" {
description = "Atlas Organization ID"
type = string
}
9 changes: 9 additions & 0 deletions examples/mongodbatlas_maintenance_window/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
terraform {
required_providers {
mongodbatlas = {
source = "mongodb/mongodbatlas"
version = "~> 1.0"
}
}
required_version = ">= 1.0"
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package maintenancewindow

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"

"github.com/mongodb/terraform-provider-mongodbatlas/internal/config"
)

Expand Down Expand Up @@ -36,6 +38,26 @@ func DataSource() *schema.Resource {
Type: schema.TypeBool,
Computed: true,
},
"time_zone_id": {
Type: schema.TypeString,
Computed: true,
},
"protected_hours": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"end_hour_of_day": {
Type: schema.TypeInt,
Computed: true,
},
"start_hour_of_day": {
Type: schema.TypeInt,
Computed: true,
},
},
},
},
},
}
}
Expand Down Expand Up @@ -69,6 +91,16 @@ func dataSourceRead(ctx context.Context, d *schema.ResourceData, meta any) diag.
return diag.Errorf(errorMaintenanceRead, projectID, err)
}

if err := d.Set("time_zone_id", maintenance.GetTimeZoneId()); err != nil {
return diag.FromErr(fmt.Errorf(errorMaintenanceRead, projectID, err))
}

if maintenance.ProtectedHours != nil {
if err := d.Set("protected_hours", flattenProtectedHours(maintenance.GetProtectedHours())); err != nil {
return diag.FromErr(fmt.Errorf(errorMaintenanceRead, projectID, err))
}
}

d.SetId(projectID)

return nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import (
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/mongodb/terraform-provider-mongodbatlas/internal/testutil/acc"
"github.com/spf13/cast"

"github.com/mongodb/terraform-provider-mongodbatlas/internal/testutil/acc"
)

const dataSourceName = "mongodbatlas_maintenance_window.test"
Expand All @@ -32,6 +33,7 @@ func TestAccConfigDSMaintenanceWindow_basic(t *testing.T) {
resource.TestCheckResourceAttr(dataSourceName, "day_of_week", cast.ToString(dayOfWeek)),
resource.TestCheckResourceAttr(dataSourceName, "hour_of_day", cast.ToString(hourOfDay)),
resource.TestCheckResourceAttr(dataSourceName, "auto_defer_once_enabled", "true"),
resource.TestCheckResourceAttrSet(dataSourceName, "time_zone_id"),
),
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import (

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/spf13/cast"

"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion"
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/validate"
"github.com/mongodb/terraform-provider-mongodbatlas/internal/config"
"github.com/spf13/cast"
"go.mongodb.org/atlas-sdk/v20250312002/admin"
)

Expand Down Expand Up @@ -84,6 +85,27 @@ func Resource() *schema.Resource {
Optional: true,
Computed: true,
},
"time_zone_id": {
Type: schema.TypeString,
Computed: true,
},
"protected_hours": {
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"end_hour_of_day": {
Type: schema.TypeInt,
Required: true,
},
"start_hour_of_day": {
Type: schema.TypeInt,
Required: true,
},
},
},
},
},
}
}
Expand All @@ -110,6 +132,7 @@ func resourceCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.
params.AutoDeferOnceEnabled = conversion.Pointer(autoDeferOnceEnabled.(bool))
}

params.ProtectedHours = newProtectedHours(d)
_, err := connV2.MaintenanceWindowsApi.UpdateMaintenanceWindow(ctx, projectID, params).Execute()
if err != nil {
return diag.FromErr(fmt.Errorf(errorMaintenanceCreate, projectID, err))
Expand All @@ -127,6 +150,19 @@ func resourceCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.
return resourceRead(ctx, d, meta)
}

func newProtectedHours(d *schema.ResourceData) *admin.ProtectedHours {
if protectedHours, ok := d.Get("protected_hours").([]any); ok && conversion.HasElementsSliceOrMap(protectedHours) {
item := protectedHours[0].(map[string]any)

return &admin.ProtectedHours{
EndHourOfDay: conversion.IntPtr(item["end_hour_of_day"].(int)),
StartHourOfDay: conversion.IntPtr(item["start_hour_of_day"].(int)),
}
}

return nil
}

func resourceRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
connV2 := meta.(*config.MongoDBClient).AtlasV2
projectID := d.Id()
Expand Down Expand Up @@ -165,9 +201,27 @@ func resourceRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Di
return diag.FromErr(fmt.Errorf(errorMaintenanceRead, projectID, err))
}

if err := d.Set("time_zone_id", maintenanceWindow.GetTimeZoneId()); err != nil {
return diag.FromErr(fmt.Errorf(errorMaintenanceRead, projectID, err))
}

if maintenanceWindow.ProtectedHours != nil {
if err := d.Set("protected_hours", flattenProtectedHours(maintenanceWindow.GetProtectedHours())); err != nil {
return diag.FromErr(fmt.Errorf(errorMaintenanceRead, projectID, err))
}
}
return nil
}

func flattenProtectedHours(protectedHours admin.ProtectedHours) []map[string]int {
res := make([]map[string]int, 0)
res = append(res, map[string]int{
"end_hour_of_day": protectedHours.GetEndHourOfDay(),
"start_hour_of_day": protectedHours.GetStartHourOfDay(),
})
return res
}

func resourceUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
connV2 := meta.(*config.MongoDBClient).AtlasV2
projectID := d.Id()
Expand All @@ -190,6 +244,20 @@ func resourceUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.
params.AutoDeferOnceEnabled = conversion.Pointer(d.Get("auto_defer_once_enabled").(bool))
}

if oldPAny, newPAny := d.GetChange("protected_hours"); d.HasChange("protected_hours") {
oldP := oldPAny.([]any)
newP := newPAny.([]any)

if len(oldP) == 1 && len(newP) == 0 {
params.ProtectedHours = &admin.ProtectedHours{
StartHourOfDay: nil,
EndHourOfDay: nil,
}
} else {
params.ProtectedHours = newProtectedHours(d)
}
}

_, err := connV2.MaintenanceWindowsApi.UpdateMaintenanceWindow(ctx, projectID, params).Execute()
if err != nil {
return diag.FromErr(fmt.Errorf(errorMaintenanceUpdate, projectID, err))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func TestMigConfigMaintenanceWindow_basic(t *testing.T) {
projectName = acc.RandomProjectName()
dayOfWeek = 7
hourOfDay = 3
config = configBasic(orgID, projectName, dayOfWeek, conversion.Pointer(hourOfDay))
config = configBasic(orgID, projectName, dayOfWeek, conversion.Pointer(hourOfDay), nil)
)

resource.ParallelTest(t, resource.TestCase{
Expand Down
Loading