Skip to content

Commit c686018

Browse files
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.
1 parent 9086c64 commit c686018

File tree

4 files changed

+132
-101
lines changed

4 files changed

+132
-101
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- Added support for creating egress private endpoints domain names via
1313
the `cockroach_egress_private_endpoint_domain_names` resource.
1414

15+
- Added support for configurable 30, 60, or 90 day patch upgrade deferrals in
16+
`cockroach_version_deferral` resource.
17+
1518
## [1.15.2] - 2025-10-16
1619

1720
### Fixed

docs/resources/version_deferral.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@ resource "cockroach_version_deferral" "example" {
3434

3535
### Required
3636

37-
- `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.
37+
- `deferral_policy` (String) The policy for delaying automated patch version upgrades after their release.
38+
- Set to `DEFERRAL_30_DAYS` to defer each upgrade by 30 days.
39+
- Set to `DEFERRAL_60_DAYS` to defer each upgrade by 60 days.
40+
- Set to `DEFERRAL_90_DAYS` to defer each upgrade by 90 days.
41+
- Set to `NOT_DEFERRED` to apply each upgrade soon after the patch is released.
3842
- `id` (String) Cluster ID.
3943

4044
## Import

internal/provider/version_deferral.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ package provider
1616
import (
1717
"context"
1818
"fmt"
19+
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
20+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
1921
"net/http"
2022

2123
"github.com/cockroachdb/cockroach-cloud-sdk-go/v6/pkg/client"
@@ -33,8 +35,13 @@ var versionDeferralAttributes = map[string]schema.Attribute{
3335
MarkdownDescription: "Cluster ID.",
3436
},
3537
"deferral_policy": schema.StringAttribute{
36-
Required: true,
37-
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.",
38+
Required: true,
39+
MarkdownDescription: "The policy for delaying automated patch version upgrades after their release.\n" +
40+
" - Set to `DEFERRAL_30_DAYS` to defer each upgrade by 30 days.\n" +
41+
" - Set to `DEFERRAL_60_DAYS` to defer each upgrade by 60 days.\n" +
42+
" - Set to `DEFERRAL_90_DAYS` to defer each upgrade by 90 days.\n" +
43+
" - Set to `NOT_DEFERRED` to apply each upgrade soon after the patch is released.",
44+
Validators: []validator.String{stringvalidator.OneOf("DEFERRAL_30_DAYS", "DEFERRAL_60_DAYS", "DEFERRAL_90_DAYS", "NOT_DEFERRED", "FIXED_DEFERRAL")},
3845
},
3946
}
4047

internal/provider/version_deferral_test.go

Lines changed: 115 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,17 @@ import (
2828

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

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

52-
clusterInfo := &client.Cluster{
53-
Id: clusterID,
54-
Name: clusterName,
55-
CockroachVersion: "v22.2.0",
56-
Plan: "ADVANCED",
57-
CloudProvider: "GCP",
58-
State: "CREATED",
59-
Config: client.ClusterConfig{
60-
Dedicated: &client.DedicatedHardwareConfig{
61-
MachineType: "m5.xlarge",
62-
NumVirtualCpus: 4,
63-
StorageGib: 35,
64-
MemoryGib: 8,
65-
},
66-
},
67-
Regions: []client.Region{
68-
{
69-
Name: "us-east1",
70-
NodeCount: 3,
71-
},
72-
},
73-
}
74-
createdVersionDeferralInfo := &client.ClusterVersionDeferral{
75-
DeferralPolicy: client.CLUSTERVERSIONDEFERRALPOLICYTYPE_FIXED_DEFERRAL,
76-
}
77-
updatedVersionDeferralInfo := &client.ClusterVersionDeferral{
78-
DeferralPolicy: client.CLUSTERVERSIONDEFERRALPOLICYTYPE_NOT_DEFERRED,
79-
}
80-
deletedVersionDeferralInfo := &client.ClusterVersionDeferral{
81-
DeferralPolicy: client.CLUSTERVERSIONDEFERRALPOLICYTYPE_NOT_DEFERRED,
55+
clusterInfo := getClusterInfo(clusterID, clusterName)
56+
57+
policies := []string{"FIXED_DEFERRAL", "DEFERRAL_30_DAYS", "DEFERRAL_60_DAYS", "DEFERRAL_90_DAYS", "NOT_DEFERRED"}
58+
setupMockExpectationsForPolicyTransitions(s, clusterInfo, clusterID, policies)
59+
60+
testVersionDeferralResourceMultiStep(t, clusterName, true, policies)
61+
}
62+
63+
// Helper function to set up mock expectations for policy transitions
64+
func setupMockExpectationsForPolicyTransitions(s *mock_client.MockService, clusterInfo *client.Cluster, clusterID string, policies []string) {
65+
// Map policy strings to SDK types
66+
policyTypeMap := map[string]client.ClusterVersionDeferralPolicyType{
67+
"FIXED_DEFERRAL": client.CLUSTERVERSIONDEFERRALPOLICYTYPE_FIXED_DEFERRAL,
68+
"DEFERRAL_30_DAYS": client.CLUSTERVERSIONDEFERRALPOLICYTYPE_DEFERRAL_30_DAYS,
69+
"DEFERRAL_60_DAYS": client.CLUSTERVERSIONDEFERRALPOLICYTYPE_DEFERRAL_60_DAYS,
70+
"DEFERRAL_90_DAYS": client.CLUSTERVERSIONDEFERRALPOLICYTYPE_DEFERRAL_90_DAYS,
71+
"NOT_DEFERRED": client.CLUSTERVERSIONDEFERRALPOLICYTYPE_NOT_DEFERRED,
8272
}
8373

84-
// Create
85-
s.EXPECT().CreateCluster(gomock.Any(), gomock.Any()).
86-
Return(clusterInfo, nil, nil)
74+
// Allow any number of GetCluster and GetBackupConfiguration calls throughout the test
8775
s.EXPECT().GetCluster(gomock.Any(), clusterID).
8876
Return(clusterInfo, &http.Response{Status: http.StatusText(http.StatusOK)}, nil).
89-
Times(3)
77+
AnyTimes()
9078
s.EXPECT().GetBackupConfiguration(gomock.Any(), clusterID).
9179
Return(initialBackupConfig, httpOk, nil).AnyTimes()
92-
s.EXPECT().SetClusterVersionDeferral(gomock.Any(), clusterID, createdVersionDeferralInfo).
93-
Return(createdVersionDeferralInfo, nil, nil)
94-
s.EXPECT().GetClusterVersionDeferral(gomock.Any(), clusterID).
95-
Return(createdVersionDeferralInfo, nil, nil)
9680

97-
// Update
98-
s.EXPECT().GetCluster(gomock.Any(), clusterID).
81+
// Create cluster - this happens first
82+
s.EXPECT().CreateCluster(gomock.Any(), gomock.Any()).
9983
Return(clusterInfo, nil, nil).
100-
Times(3)
101-
s.EXPECT().GetClusterVersionDeferral(gomock.Any(), clusterID).
102-
Return(createdVersionDeferralInfo, nil, nil)
103-
s.EXPECT().SetClusterVersionDeferral(gomock.Any(), clusterID, updatedVersionDeferralInfo).
104-
Return(updatedVersionDeferralInfo, nil, nil)
105-
s.EXPECT().GetClusterVersionDeferral(gomock.Any(), clusterID).
106-
Return(updatedVersionDeferralInfo, nil, nil).
107-
Times(2)
108-
109-
// Delete
110-
s.EXPECT().DeleteCluster(gomock.Any(), clusterID)
111-
s.EXPECT().SetClusterVersionDeferral(gomock.Any(), clusterID, deletedVersionDeferralInfo)
112-
113-
testVersionDeferralResource(t, clusterName, true)
84+
Times(1)
85+
86+
// Set up policy transitions using InOrder to ensure sequential execution
87+
var calls []*gomock.Call
88+
89+
// First policy (creation)
90+
firstPolicy := &client.ClusterVersionDeferral{
91+
DeferralPolicy: policyTypeMap[policies[0]],
92+
}
93+
calls = append(calls,
94+
s.EXPECT().SetClusterVersionDeferral(gomock.Any(), clusterID, firstPolicy).
95+
Return(firstPolicy, nil, nil).
96+
Times(1))
97+
calls = append(calls,
98+
s.EXPECT().GetClusterVersionDeferral(gomock.Any(), clusterID).
99+
Return(firstPolicy, nil, nil).
100+
Times(2))
101+
102+
// Each subsequent policy update
103+
for i := 1; i < len(policies); i++ {
104+
currentPolicy := &client.ClusterVersionDeferral{
105+
DeferralPolicy: policyTypeMap[policies[i]],
106+
}
107+
108+
calls = append(calls,
109+
s.EXPECT().SetClusterVersionDeferral(gomock.Any(), clusterID, currentPolicy).
110+
Return(currentPolicy, nil, nil).
111+
Times(1))
112+
calls = append(calls,
113+
s.EXPECT().GetClusterVersionDeferral(gomock.Any(), clusterID).
114+
Return(currentPolicy, nil, nil).
115+
Times(2))
116+
}
117+
118+
gomock.InOrder(calls...)
119+
120+
// Delete expectations
121+
s.EXPECT().DeleteCluster(gomock.Any(), clusterID).Times(1)
122+
lastPolicy := &client.ClusterVersionDeferral{
123+
DeferralPolicy: policyTypeMap[policies[len(policies)-1]],
124+
}
125+
s.EXPECT().SetClusterVersionDeferral(gomock.Any(), clusterID, lastPolicy).Times(1)
114126
}
115127

116-
func testVersionDeferralResource(t *testing.T, clusterName string, useMock bool) {
128+
// Helper function to test version deferral resource with multiple policy transitions
129+
func testVersionDeferralResourceMultiStep(t *testing.T, clusterName string, useMock bool, policies []string) {
117130
var (
118131
clusterResourceName = "cockroach_cluster.test"
119132
versionDeferralResourceName = "cockroach_version_deferral.test"
120133
)
121134

135+
// Build test steps for each policy
136+
steps := make([]resource.TestStep, 0, len(policies)+1)
137+
for _, policy := range policies {
138+
steps = append(steps, resource.TestStep{
139+
Config: getTestVersionDeferralConfig(clusterName, policy),
140+
Check: resource.ComposeTestCheckFunc(
141+
testCheckCockroachClusterExists(clusterResourceName),
142+
resource.TestCheckResourceAttr(versionDeferralResourceName, "deferral_policy", policy),
143+
),
144+
})
145+
}
146+
147+
// Add import state verification step at the end
148+
steps = append(steps, resource.TestStep{
149+
ResourceName: versionDeferralResourceName,
150+
ImportState: true,
151+
ImportStateVerify: true,
152+
})
153+
122154
resource.Test(t, resource.TestCase{
123155
IsUnitTest: useMock,
124156
PreCheck: func() { testAccPreCheck(t) },
125157
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
126-
Steps: []resource.TestStep{
127-
{
128-
Config: getTestVersionDeferralResourceCreateConfig(clusterName),
129-
Check: resource.ComposeTestCheckFunc(
130-
testCheckCockroachClusterExists(clusterResourceName),
131-
resource.TestCheckResourceAttr(versionDeferralResourceName, "deferral_policy", "FIXED_DEFERRAL"),
132-
),
133-
},
134-
{
135-
Config: getTestVersionDeferralResourceUpdateConfig(clusterName),
136-
Check: resource.ComposeTestCheckFunc(
137-
testCheckCockroachClusterExists(clusterResourceName),
138-
resource.TestCheckResourceAttr(versionDeferralResourceName, "deferral_policy", "NOT_DEFERRED"),
139-
),
158+
Steps: steps,
159+
})
160+
}
161+
162+
func getClusterInfo(clusterID string, clusterName string) *client.Cluster {
163+
clusterInfo := &client.Cluster{
164+
Id: clusterID,
165+
Name: clusterName,
166+
CockroachVersion: "v22.2.0",
167+
Plan: "ADVANCED",
168+
CloudProvider: "GCP",
169+
State: "CREATED",
170+
Config: client.ClusterConfig{
171+
Dedicated: &client.DedicatedHardwareConfig{
172+
MachineType: "m5.xlarge",
173+
NumVirtualCpus: 4,
174+
StorageGib: 35,
175+
MemoryGib: 8,
140176
},
177+
},
178+
Regions: []client.Region{
141179
{
142-
ResourceName: versionDeferralResourceName,
143-
ImportState: true,
144-
ImportStateVerify: true,
180+
Name: "us-east1",
181+
NodeCount: 3,
145182
},
146183
},
147-
})
148-
}
149-
150-
func getTestVersionDeferralResourceCreateConfig(name string) string {
151-
return fmt.Sprintf(`
152-
resource "cockroach_cluster" "test" {
153-
name = "%s"
154-
cloud_provider = "GCP"
155-
dedicated = {
156-
storage_gib = 35
157-
num_virtual_cpus = 4
158-
}
159-
regions = [{
160-
name = "us-east1"
161-
node_count: 3
162-
}]
163-
}
164-
resource "cockroach_version_deferral" "test" {
165-
id = cockroach_cluster.test.id
166-
deferral_policy = "FIXED_DEFERRAL"
167-
}
168-
`, name)
184+
}
185+
return clusterInfo
169186
}
170187

171-
func getTestVersionDeferralResourceUpdateConfig(name string) string {
188+
func getTestVersionDeferralConfig(name string, policy string) string {
172189
return fmt.Sprintf(`
173190
resource "cockroach_cluster" "test" {
174191
name = "%s"
@@ -184,7 +201,7 @@ resource "cockroach_cluster" "test" {
184201
}
185202
resource "cockroach_version_deferral" "test" {
186203
id = cockroach_cluster.test.id
187-
deferral_policy = "NOT_DEFERRED"
204+
deferral_policy = "%s"
188205
}
189-
`, name)
206+
`, name, policy)
190207
}

0 commit comments

Comments
 (0)