Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added support for creating egress private endpoints domain names via
the `cockroach_egress_private_endpoint_domain_names` resource.

- Added support for configurable 30, 60, or 90 day patch upgrade deferrals in
`cockroach_version_deferral` resource.

## [1.15.2] - 2025-10-16

### Fixed
Expand Down
6 changes: 5 additions & 1 deletion docs/resources/version_deferral.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ resource "cockroach_version_deferral" "example" {

### Required

- `deferral_policy` (String) The policy for managing automated minor version upgrades. Set to FIXED_DEFERRAL to defer upgrades by 60 days or NOT_DEFERRED to apply upgrades immediately.
- `deferral_policy` (String) The policy for delaying automated patch version upgrades after their release.
- Set to `DEFERRAL_30_DAYS` to defer each upgrade by 30 days.
- Set to `DEFERRAL_60_DAYS` to defer each upgrade by 60 days.
- Set to `DEFERRAL_90_DAYS` to defer each upgrade by 90 days.
- Set to `NOT_DEFERRED` to apply each upgrade soon after the patch is released.
- `id` (String) Cluster ID.

## Import
Expand Down
11 changes: 9 additions & 2 deletions internal/provider/version_deferral.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ package provider
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"net/http"

"github.com/cockroachdb/cockroach-cloud-sdk-go/v6/pkg/client"
Expand All @@ -33,8 +35,13 @@ var versionDeferralAttributes = map[string]schema.Attribute{
MarkdownDescription: "Cluster ID.",
},
"deferral_policy": schema.StringAttribute{
Required: true,
MarkdownDescription: "The policy for managing automated minor version upgrades. Set to FIXED_DEFERRAL to defer upgrades by 60 days or NOT_DEFERRED to apply upgrades immediately.",
Required: true,
MarkdownDescription: "The policy for delaying automated patch version upgrades after their release.\n" +
" - Set to `DEFERRAL_30_DAYS` to defer each upgrade by 30 days.\n" +
" - Set to `DEFERRAL_60_DAYS` to defer each upgrade by 60 days.\n" +
" - Set to `DEFERRAL_90_DAYS` to defer each upgrade by 90 days.\n" +
" - Set to `NOT_DEFERRED` to apply each upgrade soon after the patch is released.",
Validators: []validator.String{stringvalidator.OneOf("DEFERRAL_30_DAYS", "DEFERRAL_60_DAYS", "DEFERRAL_90_DAYS", "NOT_DEFERRED", "FIXED_DEFERRAL")},
},
}

Expand Down
213 changes: 115 additions & 98 deletions internal/provider/version_deferral_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,17 @@ import (

// TestAccVersionDeferralResource attempts to create, check, and destroy a
// real cluster. It will be skipped if TF_ACC isn't set.
// This test covers all deferral policy transitions in a single test to minimize resource creation.
func TestAccVersionDeferralResource(t *testing.T) {
t.Parallel()
clusterName := fmt.Sprintf("%s-version-deferral-%s", tfTestPrefix, GenerateRandomString(4))
testVersionDeferralResource(t, clusterName, false)
policies := []string{"FIXED_DEFERRAL", "DEFERRAL_30_DAYS", "DEFERRAL_60_DAYS", "DEFERRAL_90_DAYS", "NOT_DEFERRED"}
testVersionDeferralResourceMultiStep(t, clusterName, false, policies)
}

// TestIntegrationVersionDeferralResource attempts to create, check, and
// destroy a cluster, but uses a mocked API service.
// This test covers all deferral policy transitions: FIXED_DEFERRAL → 30 → 60 → 90 → NOT_DEFERRED
func TestIntegrationVersionDeferralResource(t *testing.T) {
clusterName := fmt.Sprintf("%s-deferral-%s", tfTestPrefix, GenerateRandomString(4))
clusterID := uuid.Nil.String()
Expand All @@ -49,126 +52,140 @@ func TestIntegrationVersionDeferralResource(t *testing.T) {
return s
})()

clusterInfo := &client.Cluster{
Id: clusterID,
Name: clusterName,
CockroachVersion: "v22.2.0",
Plan: "ADVANCED",
CloudProvider: "GCP",
State: "CREATED",
Config: client.ClusterConfig{
Dedicated: &client.DedicatedHardwareConfig{
MachineType: "m5.xlarge",
NumVirtualCpus: 4,
StorageGib: 35,
MemoryGib: 8,
},
},
Regions: []client.Region{
{
Name: "us-east1",
NodeCount: 3,
},
},
}
createdVersionDeferralInfo := &client.ClusterVersionDeferral{
DeferralPolicy: client.CLUSTERVERSIONDEFERRALPOLICYTYPE_FIXED_DEFERRAL,
}
updatedVersionDeferralInfo := &client.ClusterVersionDeferral{
DeferralPolicy: client.CLUSTERVERSIONDEFERRALPOLICYTYPE_NOT_DEFERRED,
}
deletedVersionDeferralInfo := &client.ClusterVersionDeferral{
DeferralPolicy: client.CLUSTERVERSIONDEFERRALPOLICYTYPE_NOT_DEFERRED,
clusterInfo := getClusterInfo(clusterID, clusterName)

policies := []string{"FIXED_DEFERRAL", "DEFERRAL_30_DAYS", "DEFERRAL_60_DAYS", "DEFERRAL_90_DAYS", "NOT_DEFERRED"}
setupMockExpectationsForPolicyTransitions(s, clusterInfo, clusterID, policies)

testVersionDeferralResourceMultiStep(t, clusterName, true, policies)
}

// Helper function to set up mock expectations for policy transitions
func setupMockExpectationsForPolicyTransitions(s *mock_client.MockService, clusterInfo *client.Cluster, clusterID string, policies []string) {
// Map policy strings to SDK types
policyTypeMap := map[string]client.ClusterVersionDeferralPolicyType{
"FIXED_DEFERRAL": client.CLUSTERVERSIONDEFERRALPOLICYTYPE_FIXED_DEFERRAL,
"DEFERRAL_30_DAYS": client.CLUSTERVERSIONDEFERRALPOLICYTYPE_DEFERRAL_30_DAYS,
"DEFERRAL_60_DAYS": client.CLUSTERVERSIONDEFERRALPOLICYTYPE_DEFERRAL_60_DAYS,
"DEFERRAL_90_DAYS": client.CLUSTERVERSIONDEFERRALPOLICYTYPE_DEFERRAL_90_DAYS,
"NOT_DEFERRED": client.CLUSTERVERSIONDEFERRALPOLICYTYPE_NOT_DEFERRED,
}

// Create
s.EXPECT().CreateCluster(gomock.Any(), gomock.Any()).
Return(clusterInfo, nil, nil)
// Allow any number of GetCluster and GetBackupConfiguration calls throughout the test
s.EXPECT().GetCluster(gomock.Any(), clusterID).
Return(clusterInfo, &http.Response{Status: http.StatusText(http.StatusOK)}, nil).
Times(3)
AnyTimes()
s.EXPECT().GetBackupConfiguration(gomock.Any(), clusterID).
Return(initialBackupConfig, httpOk, nil).AnyTimes()
s.EXPECT().SetClusterVersionDeferral(gomock.Any(), clusterID, createdVersionDeferralInfo).
Return(createdVersionDeferralInfo, nil, nil)
s.EXPECT().GetClusterVersionDeferral(gomock.Any(), clusterID).
Return(createdVersionDeferralInfo, nil, nil)

// Update
s.EXPECT().GetCluster(gomock.Any(), clusterID).
// Create cluster - this happens first
s.EXPECT().CreateCluster(gomock.Any(), gomock.Any()).
Return(clusterInfo, nil, nil).
Times(3)
s.EXPECT().GetClusterVersionDeferral(gomock.Any(), clusterID).
Return(createdVersionDeferralInfo, nil, nil)
s.EXPECT().SetClusterVersionDeferral(gomock.Any(), clusterID, updatedVersionDeferralInfo).
Return(updatedVersionDeferralInfo, nil, nil)
s.EXPECT().GetClusterVersionDeferral(gomock.Any(), clusterID).
Return(updatedVersionDeferralInfo, nil, nil).
Times(2)

// Delete
s.EXPECT().DeleteCluster(gomock.Any(), clusterID)
s.EXPECT().SetClusterVersionDeferral(gomock.Any(), clusterID, deletedVersionDeferralInfo)

testVersionDeferralResource(t, clusterName, true)
Times(1)

// Set up policy transitions using InOrder to ensure sequential execution
var calls []*gomock.Call

// First policy (creation)
firstPolicy := &client.ClusterVersionDeferral{
DeferralPolicy: policyTypeMap[policies[0]],
}
calls = append(calls,
s.EXPECT().SetClusterVersionDeferral(gomock.Any(), clusterID, firstPolicy).
Return(firstPolicy, nil, nil).
Times(1))
calls = append(calls,
s.EXPECT().GetClusterVersionDeferral(gomock.Any(), clusterID).
Return(firstPolicy, nil, nil).
Times(2))

// Each subsequent policy update
for i := 1; i < len(policies); i++ {
currentPolicy := &client.ClusterVersionDeferral{
DeferralPolicy: policyTypeMap[policies[i]],
}

calls = append(calls,
s.EXPECT().SetClusterVersionDeferral(gomock.Any(), clusterID, currentPolicy).
Return(currentPolicy, nil, nil).
Times(1))
calls = append(calls,
s.EXPECT().GetClusterVersionDeferral(gomock.Any(), clusterID).
Return(currentPolicy, nil, nil).
Times(2))
}

gomock.InOrder(calls...)

// Delete expectations
s.EXPECT().DeleteCluster(gomock.Any(), clusterID).Times(1)
lastPolicy := &client.ClusterVersionDeferral{
DeferralPolicy: policyTypeMap[policies[len(policies)-1]],
}
s.EXPECT().SetClusterVersionDeferral(gomock.Any(), clusterID, lastPolicy).Times(1)
}

func testVersionDeferralResource(t *testing.T, clusterName string, useMock bool) {
// Helper function to test version deferral resource with multiple policy transitions
func testVersionDeferralResourceMultiStep(t *testing.T, clusterName string, useMock bool, policies []string) {
var (
clusterResourceName = "cockroach_cluster.test"
versionDeferralResourceName = "cockroach_version_deferral.test"
)

// Build test steps for each policy
steps := make([]resource.TestStep, 0, len(policies)+1)
for _, policy := range policies {
steps = append(steps, resource.TestStep{
Config: getTestVersionDeferralConfig(clusterName, policy),
Check: resource.ComposeTestCheckFunc(
testCheckCockroachClusterExists(clusterResourceName),
resource.TestCheckResourceAttr(versionDeferralResourceName, "deferral_policy", policy),
),
})
}

// Add import state verification step at the end
steps = append(steps, resource.TestStep{
ResourceName: versionDeferralResourceName,
ImportState: true,
ImportStateVerify: true,
})

resource.Test(t, resource.TestCase{
IsUnitTest: useMock,
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: getTestVersionDeferralResourceCreateConfig(clusterName),
Check: resource.ComposeTestCheckFunc(
testCheckCockroachClusterExists(clusterResourceName),
resource.TestCheckResourceAttr(versionDeferralResourceName, "deferral_policy", "FIXED_DEFERRAL"),
),
},
{
Config: getTestVersionDeferralResourceUpdateConfig(clusterName),
Check: resource.ComposeTestCheckFunc(
testCheckCockroachClusterExists(clusterResourceName),
resource.TestCheckResourceAttr(versionDeferralResourceName, "deferral_policy", "NOT_DEFERRED"),
),
Steps: steps,
})
}

func getClusterInfo(clusterID string, clusterName string) *client.Cluster {
clusterInfo := &client.Cluster{
Id: clusterID,
Name: clusterName,
CockroachVersion: "v22.2.0",
Plan: "ADVANCED",
CloudProvider: "GCP",
State: "CREATED",
Config: client.ClusterConfig{
Dedicated: &client.DedicatedHardwareConfig{
MachineType: "m5.xlarge",
NumVirtualCpus: 4,
StorageGib: 35,
MemoryGib: 8,
},
},
Regions: []client.Region{
{
ResourceName: versionDeferralResourceName,
ImportState: true,
ImportStateVerify: true,
Name: "us-east1",
NodeCount: 3,
},
},
})
}

func getTestVersionDeferralResourceCreateConfig(name string) string {
return fmt.Sprintf(`
resource "cockroach_cluster" "test" {
name = "%s"
cloud_provider = "GCP"
dedicated = {
storage_gib = 35
num_virtual_cpus = 4
}
regions = [{
name = "us-east1"
node_count: 3
}]
}
resource "cockroach_version_deferral" "test" {
id = cockroach_cluster.test.id
deferral_policy = "FIXED_DEFERRAL"
}
`, name)
}
return clusterInfo
}

func getTestVersionDeferralResourceUpdateConfig(name string) string {
func getTestVersionDeferralConfig(name string, policy string) string {
return fmt.Sprintf(`
resource "cockroach_cluster" "test" {
name = "%s"
Expand All @@ -184,7 +201,7 @@ resource "cockroach_cluster" "test" {
}
resource "cockroach_version_deferral" "test" {
id = cockroach_cluster.test.id
deferral_policy = "NOT_DEFERRED"
deferral_policy = "%s"
}
`, name)
`, name, policy)
}
Loading