Skip to content
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

WIP: add delete_protection to the Cluster resource and data source (#205) #228

Closed
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
2 changes: 0 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
name: Cockroach Terraform Provider CI
on:
pull_request:
branches:
- 'main'

jobs:

Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## Added

- Added `delete_protection` to the Cluster resource and data source. When set
to true, attempts to delete the cluster will fail. Set to false to disable
delete protection.

## [1.5.0] - 2024-05-26

- No changes.

## [1.4.1] - 2024-04-04

## Added
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ HOSTNAME=registry.terraform.io
NAMESPACE=cockroachdb
NAME=cockroach
BINARY=terraform-provider-${NAME}
VERSION=0.4.3
VERSION=1.6.0
OS_ARCH=darwin_amd64

default: install
Expand Down
1 change: 1 addition & 0 deletions docs/data-sources/cluster.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ data "cockroach_cluster" "cockroach" {
- `cockroach_version` (String) Full version of CockroachDB running on the cluster.
- `creator_id` (String) ID of the user who created the cluster.
- `dedicated` (Attributes) (see [below for nested schema](#nestedatt--dedicated))
- `delete_protection` (Boolean) Set to true to enable delete protection on the cluster.
- `id` (String) The ID of this resource.
- `name` (String) Name of the cluster.
- `operation_status` (String) Describes the current long-running operation, if any.
Expand Down
1 change: 1 addition & 0 deletions docs/resources/cluster.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ resource "cockroach_cluster" "serverless" {

- `cockroach_version` (String) Major version of CockroachDB running on the cluster.
- `dedicated` (Attributes) (see [below for nested schema](#nestedatt--dedicated))
- `delete_protection` (Boolean) Set to true to enable delete protection on the cluster.
- `parent_id` (String) The ID of the cluster's parent folder. 'root' is used for a cluster at the root level.
- `serverless` (Attributes) (see [below for nested schema](#nestedatt--serverless))

Expand Down
2 changes: 2 additions & 0 deletions examples/resources/cockroach_cluster/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ resource "cockroach_cluster" "dedicated" {
node_count = 1
}
]
delete_protection = true
}

resource "cockroach_cluster" "serverless" {
Expand All @@ -24,4 +25,5 @@ resource "cockroach_cluster" "serverless" {
name = "us-east1"
}
]
delete_protection = false
}
7 changes: 6 additions & 1 deletion internal/provider/cluster_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ package provider
import (
"context"
"fmt"
"net/http"

"github.com/cockroachdb/cockroach-cloud-sdk-go/pkg/client"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"net/http"
)

type clusterDataSource struct {
Expand Down Expand Up @@ -165,6 +166,10 @@ func (d *clusterDataSource) Schema(
Computed: true,
MarkdownDescription: "The ID of the cluster's parent folder. 'root' is used for a cluster at the root level.",
},
"delete_protection": schema.BoolAttribute{
Computed: true,
Description: "Set to true to enable delete protection on the cluster.",
},
},
}
}
Expand Down
90 changes: 67 additions & 23 deletions internal/provider/cluster_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,11 @@ func (r *clusterResource) Schema(
validators.FolderParentID(),
},
},
"delete_protection": schema.BoolAttribute{
Computed: true,
Optional: true,
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.",
},
},
}
}
Expand Down Expand Up @@ -404,6 +409,12 @@ func (r *clusterResource) Create(
clusterSpec.SetParentId(parentID)
}

deleteProtection := client.DELETEPROTECTIONSTATETYPE_DISABLED
if plan.DeleteProtection.ValueBool() {
deleteProtection = client.DELETEPROTECTIONSTATETYPE_ENABLED
}
clusterSpec.SetDeleteProtection(deleteProtection)

clusterReq := client.NewCreateClusterRequest(plan.Name.ValueString(), client.CloudProviderType(plan.CloudProvider.ValueString()), *clusterSpec)
clusterObj, _, err := r.provider.service.CreateCluster(ctx, clusterReq)
if err != nil {
Expand Down Expand Up @@ -513,33 +524,48 @@ func (r *clusterResource) ModifyPlan(
var plan *CockroachCluster
diags = req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() || plan == nil {
if resp.Diagnostics.HasError() {
return
}

if plan.Name != state.Name {
resp.Diagnostics.AddError("Cannot update cluster name",
"To prevent accidental deletion of data, renaming clusters isn't allowed. "+
"Please explicitly destroy this cluster before changing its name.")
}
if plan.CloudProvider != state.CloudProvider {
resp.Diagnostics.AddError("Cannot update cluster cloud provider",
"To prevent accidental deletion of data, changing a cluster's cloud provider "+
"isn't allowed. Please explicitly destroy this cluster before changing its cloud provider.")
}
if ((plan.DedicatedConfig == nil) != (state.DedicatedConfig == nil)) ||
((plan.ServerlessConfig == nil) != (state.ServerlessConfig == nil)) {
resp.Diagnostics.AddError("Cannot update cluster plan type",
"To prevent accidental deletion of data, changing a cluster's plan type "+
"isn't allowed. Please explicitly destroy this cluster before changing between "+
"dedicated and serverless plans.")
return
if plan != nil {
if plan.Name != state.Name {
resp.Diagnostics.AddError("Cannot update cluster name",
"To prevent accidental deletion of data, renaming clusters isn't allowed. "+
"Please explicitly destroy this cluster before changing its name.")
}
if plan.CloudProvider != state.CloudProvider {
resp.Diagnostics.AddError("Cannot update cluster cloud provider",
"To prevent accidental deletion of data, changing a cluster's cloud provider "+
"isn't allowed. Please explicitly destroy this cluster before changing its cloud provider.")
}
if ((plan.DedicatedConfig == nil) != (state.DedicatedConfig == nil)) ||
((plan.ServerlessConfig == nil) != (state.ServerlessConfig == nil)) {
resp.Diagnostics.AddError("Cannot update cluster plan type",
"To prevent accidental deletion of data, changing a cluster's plan type "+
"isn't allowed. Please explicitly destroy this cluster before changing between "+
"dedicated and serverless plans.")
return
}
if dedicated := plan.DedicatedConfig; dedicated != nil && dedicated.PrivateNetworkVisibility != state.DedicatedConfig.PrivateNetworkVisibility {
resp.Diagnostics.AddError("Cannot update network visibility",
"To prevent accidental deletion of data, changing a cluster's network "+
"visibility isn't allowed. Please explicitly destroy this cluster before changing "+
"network visibility.")
}
}
if dedicated := plan.DedicatedConfig; dedicated != nil && dedicated.PrivateNetworkVisibility != state.DedicatedConfig.PrivateNetworkVisibility {
resp.Diagnostics.AddError("Cannot update network visibility",
"To prevent accidental deletion of data, changing a cluster's network "+
"visibility isn't allowed. Please explicitly destroy this cluster before changing "+
"network visibility.")

if req.Plan.Raw.IsNull() {
// This is a plan to destroy the cluster. We'll check if this cluster
// has delete protection enabled and throw an error here if it does.
// This causes the apply to fail _before_ taking any action, which
// prevents _other_ resources peripheral to the cluster from being
// destroyed as well.
if state.DeleteProtection.ValueBool() {
resp.Diagnostics.AddError("Cannot destroy cluster with delete protection enabled",
"To prevent accidental deletion of data, destroying a cluster with delete protection "+
"enabled isn't allowed. Please disable delete protection before destroying this cluster.")
}
}
}

Expand Down Expand Up @@ -736,6 +762,17 @@ func (r *clusterResource) Update(
clusterReq.SetParentId(parentID)
}

if !(plan.DeleteProtection.IsNull() || plan.DeleteProtection.IsUnknown()) &&
plan.DeleteProtection.ValueBool() != state.DeleteProtection.ValueBool() {
var deleteProtection client.DeleteProtectionStateType
if plan.DeleteProtection.ValueBool() {
deleteProtection = client.DELETEPROTECTIONSTATETYPE_ENABLED
} else {
deleteProtection = client.DELETEPROTECTIONSTATETYPE_DISABLED
}
clusterReq.SetDeleteProtection(deleteProtection)
}

clusterObj, _, err := r.provider.service.UpdateCluster(ctx, state.ID.ValueString(), clusterReq)
if err != nil {
resp.Diagnostics.AddError(
Expand Down Expand Up @@ -882,6 +919,13 @@ func loadClusterToTerraformState(
state.ParentId = types.StringValue(*clusterObj.ParentId)
}

if clusterObj.DeleteProtection != nil &&
*clusterObj.DeleteProtection == client.DELETEPROTECTIONSTATETYPE_ENABLED {
state.DeleteProtection = types.BoolValue(true)
} else {
state.DeleteProtection = types.BoolValue(false)
}

if clusterObj.Config.Serverless != nil {
serverlessConfig := &ServerlessClusterConfig{
RoutingId: types.StringValue(clusterObj.Config.Serverless.RoutingId),
Expand Down
Loading
Loading