From c686018708c53784064a3611f1b7a4a52fd9cf84 Mon Sep 17 00:00:00 2001 From: Vishal Jaishankar Date: Wed, 15 Oct 2025 21:50:20 +0530 Subject: [PATCH] version_deferral: add new policies for crdb patch deferral Previously, there was only the FIXED_DEFERRAL policy allowing 60 days deferral. This PR adds new policy to defer the upgrades by 30,60 or 90 days. DEFERRAL_30_DAYS to defer upgrades by 30 days. DEFERRAL_60_DAYS to defer upgrades by 60 days. DEFERRAL_90_DAYS to defer upgrades by 90 days. NOT_DEFERRED to apply upgrades immediately. --- CHANGELOG.md | 3 + docs/resources/version_deferral.md | 6 +- internal/provider/version_deferral.go | 11 +- internal/provider/version_deferral_test.go | 213 +++++++++++---------- 4 files changed, 132 insertions(+), 101 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1405b73..4df40a2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/docs/resources/version_deferral.md b/docs/resources/version_deferral.md index 5776291e..ee92c0cc 100644 --- a/docs/resources/version_deferral.md +++ b/docs/resources/version_deferral.md @@ -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 diff --git a/internal/provider/version_deferral.go b/internal/provider/version_deferral.go index d4a0384b..22730883 100644 --- a/internal/provider/version_deferral.go +++ b/internal/provider/version_deferral.go @@ -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" @@ -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")}, }, } diff --git a/internal/provider/version_deferral_test.go b/internal/provider/version_deferral_test.go index a2be2f36..c8313dc4 100644 --- a/internal/provider/version_deferral_test.go +++ b/internal/provider/version_deferral_test.go @@ -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() @@ -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" @@ -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) }