diff --git a/docs/data-sources/cluster.md b/docs/data-sources/cluster.md index 4ab57ca5..da6d92ac 100644 --- a/docs/data-sources/cluster.md +++ b/docs/data-sources/cluster.md @@ -69,6 +69,7 @@ Read-Only: - `num_virtual_cpus` (Number) Number of virtual CPUs per node in the cluster. - `private_network_visibility` (Boolean) Indicates whether private IP addresses are assigned to nodes. Required for CMEK and other advanced networking features. - `storage_gib` (Number) Storage amount per node in GiB. +- `support_physical_cluster_replication` (Boolean) Indicates whether to create a cluster using an architecture that supports physical cluster replication. diff --git a/docs/resources/cluster.md b/docs/resources/cluster.md index f647865c..18e30e2a 100644 --- a/docs/resources/cluster.md +++ b/docs/resources/cluster.md @@ -144,6 +144,7 @@ Optional: - `num_virtual_cpus` (Number) Number of virtual CPUs per node in the cluster. - `private_network_visibility` (Boolean) Set to true to assign private IP addresses to nodes. Required for CMEK and other advanced networking features. Clusters created with this flag will have advanced security features enabled. This cannot be changed after cluster creation and incurs additional charges. See [Create an Advanced Cluster](https://www.cockroachlabs.com/docs/cockroachcloud/create-an-advanced-cluster.html#step-6-configure-advanced-security-features) and [Pricing](https://www.cockroachlabs.com/pricing/) for more information. - `storage_gib` (Number) Storage amount per node in GiB. +- `support_physical_cluster_replication` (Boolean) Specifies whether a cluster should be started using an architecture that supports physical cluster replication. Read-Only: diff --git a/examples/workflows/cockroach_advanced_cluster/main.tf b/examples/workflows/cockroach_advanced_cluster/main.tf index 3bdd1c5f..2eaf2331 100644 --- a/examples/workflows/cockroach_advanced_cluster/main.tf +++ b/examples/workflows/cockroach_advanced_cluster/main.tf @@ -80,6 +80,11 @@ variable "cidr_prefix_length" { default = 0 } +variable "support_physical_cluster_replication" { + type = bool + nullable = true +} + terraform { required_providers { cockroach = { @@ -98,7 +103,8 @@ resource "cockroach_cluster" "example" { dedicated = { storage_gib = var.storage_gib num_virtual_cpus = var.num_virtual_cpus - cidr_range = "172.28.0.0/14" + # cidr_range = "172.28.0.0/14" + support_physical_cluster_replication = var.support_physical_cluster_replication } regions = [ for r in var.cloud_provider_regions : { diff --git a/internal/provider/cluster_data_source.go b/internal/provider/cluster_data_source.go index 67093c4a..04af7ced 100644 --- a/internal/provider/cluster_data_source.go +++ b/internal/provider/cluster_data_source.go @@ -126,6 +126,10 @@ func (d *clusterDataSource) Schema( Computed: true, Description: "The IPv4 range in CIDR format that is in use by the cluster. It is only set on GCP clusters and is otherwise empty.", }, + "support_physical_cluster_replication": schema.BoolAttribute{ + Computed: true, + Description: "Indicates whether to create a cluster using an architecture that supports physical cluster replication.", + }, }, }, "regions": schema.ListNestedAttribute{ @@ -191,11 +195,11 @@ func (d *clusterDataSource) Schema( Description: "Indicates whether backups are enabled.", }, "retention_days": schema.Int64Attribute{ - Computed: true, + Computed: true, MarkdownDescription: "The number of days to retain backups for.", }, "frequency_minutes": schema.Int64Attribute{ - Computed: true, + Computed: true, Description: "The frequency of backups in minutes.", }, }, @@ -281,7 +285,6 @@ func (d *clusterDataSource) Read( return } - // The concept of a plan doesn't apply to data sources. // Using a nil plan means we won't try to re-sort the region list. var newState CockroachCluster diff --git a/internal/provider/cluster_resource.go b/internal/provider/cluster_resource.go index c31af80b..f0ca9efa 100644 --- a/internal/provider/cluster_resource.go +++ b/internal/provider/cluster_resource.go @@ -217,9 +217,9 @@ func (r *clusterResource) Schema( Description: "Storage amount per node in GiB.", }, "disk_iops": schema.Int64Attribute{ - Optional: true, - Computed: true, - Validators: []validator.Int64{ + Optional: true, + Computed: true, + Validators: []validator.Int64{ // If supplied this value must be non-zero. 0 is a // valid api value indicating the default being // returned but it causes a provider inconsistency. @@ -232,8 +232,8 @@ func (r *clusterResource) Schema( Description: "Memory per node in GiB.", }, "machine_type": schema.StringAttribute{ - Optional: true, - Computed: true, + Optional: true, + Computed: true, MarkdownDescription: "Machine type identifier within the given cloud provider, e.g., m6.xlarge, n2-standard-4. This attribute requires a feature flag to be enabled. It is recommended to leave this empty and use `num_virtual_cpus` to control the machine type.", }, "num_virtual_cpus": schema.Int64Attribute{ @@ -257,6 +257,14 @@ func (r *clusterResource) Schema( stringplanmodifier.UseStateForUnknown(), }, }, + "support_physical_cluster_replication": schema.BoolAttribute{ + Optional: true, + Computed: true, + Description: "Specifies whether a cluster should be started using an architecture that supports physical cluster replication.", + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.UseStateForUnknown(), + }, + }, }, }, "regions": schema.ListNestedAttribute{ @@ -267,7 +275,7 @@ func (r *clusterResource) Schema( NestedObject: regionSchema, }, "state": schema.StringAttribute{ - Computed: true, + Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, @@ -302,32 +310,32 @@ func (r *clusterResource) Schema( Description: "Set to true to enable delete protection on the cluster. If unset, the server chooses the value on cluster creation, and preserves the value on cluster update.", }, "backup_config": schema.SingleNestedAttribute{ - Computed: true, - Optional: true, + Computed: true, + Optional: true, MarkdownDescription: "The backup settings for a cluster.\n Each cluster has backup settings that determine if backups are enabled, how frequently they are taken, and how long they are retained for. Use this attribute to manage those settings.", PlanModifiers: []planmodifier.Object{ objectplanmodifier.UseStateForUnknown(), }, Attributes: map[string]schema.Attribute{ "enabled": schema.BoolAttribute{ - Optional: true, - Computed: true, + Optional: true, + Computed: true, PlanModifiers: []planmodifier.Bool{ boolplanmodifier.UseStateForUnknown(), }, Description: "Indicates whether backups are enabled. If set to false, no backups will be created.", }, "retention_days": schema.Int64Attribute{ - Optional: true, - Computed: true, + Optional: true, + Computed: true, PlanModifiers: []planmodifier.Int64{ int64planmodifier.UseStateForUnknown(), }, MarkdownDescription: "The number of days to retain backups for. Valid values are [2, 7, 30, 90, 365]. Can only be set once, further changes require opening a support ticket. See [Updating backup retention](../guides/updating-backup-retention) for more information.", }, "frequency_minutes": schema.Int64Attribute{ - Optional: true, - Computed: true, + Optional: true, + Computed: true, PlanModifiers: []planmodifier.Int64{ int64planmodifier.UseStateForUnknown(), }, @@ -556,6 +564,9 @@ func (r *clusterResource) Create( if cfg.CidrRange.ValueString() != "" { dedicated.CidrRange = ptr(cfg.CidrRange.ValueString()) } + if cfg.SupportPhysicalClusterReplication.ValueBool() { + dedicated.SupportPhysicalClusterReplication = ptr(cfg.SupportPhysicalClusterReplication.ValueBool()) + } } clusterSpec.SetDedicated(dedicated) } @@ -632,7 +643,7 @@ func (r *clusterResource) Create( backupUpdateRequest.FrequencyMinutes = ptr(int32(planBackupConfig.FrequencyMinutes.ValueInt64())) } - if backupUpdateRequest != (client.UpdateBackupConfigurationSpec{}) { + if backupUpdateRequest != (client.UpdateBackupConfigurationSpec{}) { traceAPICall("UpdateBackupConfiguration") remoteBackupConfig, _, err = r.provider.service.UpdateBackupConfiguration(ctx, clusterObj.Id, &backupUpdateRequest) if err != nil { @@ -790,6 +801,12 @@ func (r *clusterResource) ModifyPlan( "isn't allowed. Please explicitly destroy this cluster before changing "+ "cidr range.") } + if dedicated := plan.DedicatedConfig; dedicated != nil && dedicated.SupportPhysicalClusterReplication != state.DedicatedConfig.SupportPhysicalClusterReplication { + resp.Diagnostics.AddError("Cannot update support_physical_cluster_replication", + "To prevent accidental deletion of data, changing a cluster's "+ + "support_physical_cluster_replication field isn't allowed. "+ + "Please explicitly destroy this cluster before changing this field.") + } } if req.Plan.Raw.IsNull() { @@ -1087,7 +1104,7 @@ func (r *clusterResource) Update( backupUpdateRequest.FrequencyMinutes = ptr(int32(planBackupConfig.FrequencyMinutes.ValueInt64())) } - if backupUpdateRequest != (client.UpdateBackupConfigurationSpec{}) { + if backupUpdateRequest != (client.UpdateBackupConfigurationSpec{}) { traceAPICall("UpdateBackupConfiguration") remoteBackupConfig, _, err = r.provider.service.UpdateBackupConfiguration(ctx, clusterObj.Id, &backupUpdateRequest) if err != nil { @@ -1309,6 +1326,10 @@ func loadClusterToTerraformState( PrivateNetworkVisibility: types.BoolValue(clusterObj.GetNetworkVisibility() == client.NETWORKVISIBILITYTYPE_PRIVATE), CidrRange: types.StringValue(clusterObj.CidrRange), } + + if plan != nil && plan.DedicatedConfig != nil && IsKnown(plan.DedicatedConfig.SupportPhysicalClusterReplication) { + state.DedicatedConfig.SupportPhysicalClusterReplication = plan.DedicatedConfig.SupportPhysicalClusterReplication + } } var diags diag.Diagnostics @@ -1332,12 +1353,11 @@ var backupConfigElementTypes = map[string]attr.Type{ "retention_days": types.Int64Type, } -func unknownBackupConfig( -) (basetypes.ObjectValue, diag.Diagnostics) { +func unknownBackupConfig() (basetypes.ObjectValue, diag.Diagnostics) { elements := map[string]attr.Value{ - "enabled": types.BoolUnknown(), + "enabled": types.BoolUnknown(), "frequency_minutes": types.Int64Unknown(), - "retention_days": types.Int64Unknown(), + "retention_days": types.Int64Unknown(), } objectValue, diags := types.ObjectValue(backupConfigElementTypes, elements) return objectValue, diags @@ -1347,9 +1367,9 @@ func clientBackupConfigToProviderBackupConfig( apiBackupConfig *client.BackupConfiguration, ) (basetypes.ObjectValue, diag.Diagnostics) { elements := map[string]attr.Value{ - "enabled": types.BoolValue(apiBackupConfig.GetEnabled()), + "enabled": types.BoolValue(apiBackupConfig.GetEnabled()), "frequency_minutes": types.Int64Value(int64(apiBackupConfig.GetFrequencyMinutes())), - "retention_days": types.Int64Value(int64(apiBackupConfig.GetRetentionDays())), + "retention_days": types.Int64Value(int64(apiBackupConfig.GetRetentionDays())), } objectValue, diags := types.ObjectValue(backupConfigElementTypes, elements) return objectValue, diags @@ -1363,8 +1383,8 @@ func providerBackupConfigToClientBackupConfig(ctx context.Context, providerBacku } backupUpdateRequest := &client.BackupConfiguration{ - Enabled: planBackupConfig.Enabled.ValueBool(), - RetentionDays: int32(planBackupConfig.RetentionDays.ValueInt64()), + Enabled: planBackupConfig.Enabled.ValueBool(), + RetentionDays: int32(planBackupConfig.RetentionDays.ValueInt64()), FrequencyMinutes: int32(planBackupConfig.FrequencyMinutes.ValueInt64()), } return backupUpdateRequest, diags diff --git a/internal/provider/models.go b/internal/provider/models.go index 0b8807fb..d5e98235 100644 --- a/internal/provider/models.go +++ b/internal/provider/models.go @@ -41,13 +41,14 @@ type Region struct { } type DedicatedClusterConfig struct { - MachineType types.String `tfsdk:"machine_type"` - NumVirtualCpus types.Int64 `tfsdk:"num_virtual_cpus"` - StorageGib types.Int64 `tfsdk:"storage_gib"` - MemoryGib types.Float64 `tfsdk:"memory_gib"` - DiskIops types.Int64 `tfsdk:"disk_iops"` - PrivateNetworkVisibility types.Bool `tfsdk:"private_network_visibility"` - CidrRange types.String `tfsdk:"cidr_range"` + MachineType types.String `tfsdk:"machine_type"` + NumVirtualCpus types.Int64 `tfsdk:"num_virtual_cpus"` + StorageGib types.Int64 `tfsdk:"storage_gib"` + MemoryGib types.Float64 `tfsdk:"memory_gib"` + DiskIops types.Int64 `tfsdk:"disk_iops"` + PrivateNetworkVisibility types.Bool `tfsdk:"private_network_visibility"` + CidrRange types.String `tfsdk:"cidr_range"` + SupportPhysicalClusterReplication types.Bool `tfsdk:"support_physical_cluster_replication"` } type ServerlessClusterConfig struct {