diff --git a/internal/api/cluster/cluster.go b/internal/api/cluster/cluster.go index 7ac3dba0..2a274a4e 100644 --- a/internal/api/cluster/cluster.go +++ b/internal/api/cluster/cluster.go @@ -15,6 +15,9 @@ type Availability struct { // AvailabilityType is availability zone type, either 'single' or 'multi'. type AvailabilityType string +// ConfigurationType defines model for ConfigurationType, either 'multiNode' or 'singleNode'. +type ConfigurationType string + // CreateClusterRequest is the request payload sent to the Capella V4 Public API in order to create a new cluster. // A Couchbase cluster consists of one or more instances of Couchbase Capella, each running on an independent node. // Data and services are shared across the cluster. @@ -50,6 +53,9 @@ type CreateClusterRequest struct { // Enum: "single" "multi" Availability Availability `json:"availability"` + // ConfigurationType defines model for ConfigurationType, either 'multiNode' or 'singleNode' + ConfigurationType ConfigurationType `json:"configurationType"` + // Name is the name of the cluster (up to 256 characters). Name string `json:"name"` @@ -101,6 +107,9 @@ type GetClusterResponse struct { // CurrentState tells the status of the cluster - if it's healthy or degraded. CurrentState State `json:"currentState"` + // ConfigurationType defines model for ConfigurationType, either 'multiNode' or 'singleNode' + ConfigurationType ConfigurationType `json:"configurationType"` + // Availability zone type, either 'single' or 'multi'. // Enum: "single" "multi" Availability Availability `json:"availability"` diff --git a/internal/resources/acceptance_tests/cluster_acceptance_test.go b/internal/resources/acceptance_tests/cluster_acceptance_test.go index 168c4c6d..e6bb2ea6 100644 --- a/internal/resources/acceptance_tests/cluster_acceptance_test.go +++ b/internal/resources/acceptance_tests/cluster_acceptance_test.go @@ -60,6 +60,7 @@ func TestAccClusterResourceWithOnlyReqFieldAWS(t *testing.T) { resource.TestCheckResourceAttr(resourceReference, "cloud_provider.type", "aws"), resource.TestCheckResourceAttr(resourceReference, "cloud_provider.region", "us-east-1"), resource.TestCheckResourceAttr(resourceReference, "cloud_provider.cidr", cidr), + resource.TestCheckResourceAttr(resourceReference, "configuration_type", "multiNode"), resource.TestCheckResourceAttr(resourceReference, "service_groups.0.node.compute.cpu", "4"), resource.TestCheckResourceAttr(resourceReference, "service_groups.0.node.compute.ram", "16"), resource.TestCheckResourceAttr(resourceReference, "service_groups.0.node.disk.storage", "50"), @@ -98,6 +99,7 @@ func TestAccClusterResourceWithOnlyReqFieldAWS(t *testing.T) { resource.TestCheckResourceAttr(resourceReference, "cloud_provider.type", "aws"), resource.TestCheckResourceAttr(resourceReference, "cloud_provider.region", "us-east-1"), resource.TestCheckResourceAttr(resourceReference, "cloud_provider.cidr", cidr), + resource.TestCheckResourceAttr(resourceReference, "configuration_type", "multiNode"), resource.TestCheckResourceAttr(resourceReference, "service_groups.1.node.compute.cpu", "8"), resource.TestCheckResourceAttr(resourceReference, "service_groups.1.node.compute.ram", "32"), resource.TestCheckResourceAttr(resourceReference, "service_groups.1.node.disk.storage", "51"), @@ -129,6 +131,7 @@ func TestAccClusterResourceWithOnlyReqFieldAWS(t *testing.T) { resource.TestCheckResourceAttr(resourceReference, "cloud_provider.type", "aws"), resource.TestCheckResourceAttr(resourceReference, "cloud_provider.region", "us-east-1"), resource.TestCheckResourceAttr(resourceReference, "cloud_provider.cidr", cidr), + resource.TestCheckResourceAttr(resourceReference, "configuration_type", "multiNode"), resource.TestCheckResourceAttr(resourceReference, "service_groups.1.node.compute.cpu", "8"), resource.TestCheckResourceAttr(resourceReference, "service_groups.1.node.compute.ram", "32"), resource.TestCheckResourceAttr(resourceReference, "service_groups.1.node.disk.storage", "51"), @@ -598,6 +601,59 @@ func TestAccClusterResourceForGCPWithIOPSFieldPopulatedInvalidScenario(t *testin }) } +// TestAccClusterResourceWithConfigurationTypeFieldAdded is a Terraform acceptance test that validates +// the creation of a cluster resource with the addition of the "configuration_type" field set to "singleNode" +// for an AWS (Amazon Web Services) cloud provider. +// +// This test ensures that a cluster resource can be successfully created with the specified configuration type. +func TestAccClusterResourceWithConfigurationTypeFieldAdded(t *testing.T) { + resourceName := "acc_cluster_" + acctest.GenerateRandomResourceName() + resourceReference := "couchbase-capella_cluster." + resourceName + projectResourceName := "acc_project_" + acctest.GenerateRandomResourceName() + projectResourceReference := "couchbase-capella_project." + projectResourceName + cidr := "10.249.250.0/23" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + PreConfig: testAccProjecCreationWaitTime(), + Config: testAccClusterResourceConfigWithConfigurationTypeFieldAdded(acctest.Cfg, resourceName, projectResourceName, projectResourceReference, cidr), + Check: resource.ComposeAggregateTestCheckFunc( + testAccExistsClusterResource(resourceReference), + resource.TestCheckResourceAttr(resourceReference, "name", resourceName), + resource.TestCheckResourceAttr(resourceReference, "description", ""), + resource.TestCheckResourceAttr(resourceReference, "cloud_provider.type", "aws"), + resource.TestCheckResourceAttr(resourceReference, "cloud_provider.region", "us-east-1"), + resource.TestCheckResourceAttr(resourceReference, "cloud_provider.cidr", cidr), + resource.TestCheckResourceAttr(resourceReference, "configuration_type", "singleNode"), + resource.TestCheckResourceAttr(resourceReference, "service_groups.0.node.compute.cpu", "2"), + resource.TestCheckResourceAttr(resourceReference, "service_groups.0.node.compute.ram", "8"), + resource.TestCheckResourceAttr(resourceReference, "service_groups.0.node.disk.storage", "50"), + resource.TestCheckResourceAttr(resourceReference, "service_groups.0.node.disk.type", "gp3"), + resource.TestCheckResourceAttr(resourceReference, "service_groups.0.node.disk.iops", "3000"), + resource.TestCheckResourceAttr(resourceReference, "service_groups.0.num_of_nodes", "1"), + resource.TestCheckResourceAttr(resourceReference, "service_groups.0.services.#", "3"), + resource.TestCheckResourceAttr(resourceReference, "service_groups.0.services.0", "data"), + resource.TestCheckResourceAttr(resourceReference, "service_groups.0.services.1", "index"), + resource.TestCheckResourceAttr(resourceReference, "service_groups.0.services.2", "query"), + resource.TestCheckResourceAttr(resourceReference, "availability.type", "single"), + resource.TestCheckResourceAttr(resourceReference, "support.plan", "developer pro"), + resource.TestCheckResourceAttr(resourceReference, "support.timezone", "PT"), + ), + }, + //// ImportState testing + { + ResourceName: resourceReference, + ImportStateIdFunc: generateClusterImportIdForResource(resourceReference), + ImportState: true, + ImportStateVerify: false, + }, + }, + }) +} + // TestAccClusterResourceNotFound is a Terraform acceptance test that simulates the scenario where a cluster is created // from Terraform, but it is deleted by a REST API call and the deletion is successful. Then, updating the cluster via Terraform // should not cause any issues and should create a new cluster with the updated configuration. @@ -965,6 +1021,54 @@ resource "couchbase-capella_cluster" "%[2]s" { `, cfg, resourceName, projectResourceName, projectResourceReference, cidr) } +// testAccClusterResourceConfigWithConfigurationTypeFieldAdded generates a Terraform configuration string for testing an +// acceptance test scenario where a cluster is created with the "configuration_type" field set to "singleNode". +func testAccClusterResourceConfigWithConfigurationTypeFieldAdded(cfg, resourceName, projectResourceName, projectResourceReference, cidr string) string { + return fmt.Sprintf(` +%[1]s +resource "couchbase-capella_project" "%[3]s" { + organization_id = var.organization_id + name = "acc_test_project_name" + description = "description" +} +resource "couchbase-capella_cluster" "%[2]s" { + organization_id = var.organization_id + project_id = %[4]s.id + name = "%[2]s" + cloud_provider = { + type = "aws" + region = "us-east-1" + cidr = "%[5]s" + } + configuration_type = "singleNode" + service_groups = [ + { + node = { + compute = { + cpu = 2 + ram = 8 + } + disk = { + storage = 50 + type = "gp3" + iops = 3000 + } + } + num_of_nodes = 1 + services = ["data", "index", "query"] + } + ] + availability = { + "type" : "single" + } + support = { + plan = "developer pro" + timezone = "PT" + } +} +`, cfg, resourceName, projectResourceName, projectResourceReference, cidr) +} + // testAccClusterResourceConfigAzureUpdateToDiskTypeP6 generates a Terraform configuration string for testing an acceptance test scenario // where a cluster resource is updated to change the disk type to "P6". func testAccClusterResourceConfigAzureUpdateToDiskTypeP6(cfg, resourceName, projectResourceName, projectResourceReference, cidr string) string { diff --git a/internal/resources/attributes.go b/internal/resources/attributes.go index 369d4cfa..2f0586d7 100644 --- a/internal/resources/attributes.go +++ b/internal/resources/attributes.go @@ -24,6 +24,8 @@ const ( sensitive = "sensitive" requiresReplace = "requiresReplace" useStateForUnknown = "useStateForUnknown" + deprecated = "deprecated" + deprecationMessage = "Remove this attribute's configuration as it no longer in use and the attribute will be removed in the next major version of the provider." ) // stringAttribute is a variadic function which sets the requested fields @@ -54,6 +56,8 @@ func stringAttribute(fields []string, validators ...validator.String) *schema.St stringplanmodifier.UseStateForUnknown(), } attribute.PlanModifiers = append(attribute.PlanModifiers, planModifiers...) + case deprecated: + attribute.DeprecationMessage = deprecationMessage } } return &attribute diff --git a/internal/resources/cluster.go b/internal/resources/cluster.go index 2969eccc..4f009552 100644 --- a/internal/resources/cluster.go +++ b/internal/resources/cluster.go @@ -118,6 +118,10 @@ func (c *Cluster) Create(ctx context.Context, req resource.CreateRequest, resp * } } + if !plan.ConfigurationType.IsNull() && !plan.ConfigurationType.IsUnknown() { + clusterRequest.ConfigurationType = clusterapi.ConfigurationType(plan.ConfigurationType.ValueString()) + } + serviceGroups, err := c.morphToApiServiceGroups(plan) if err != nil { resp.Diagnostics.AddError( @@ -798,7 +802,9 @@ func initializePendingClusterWithPlanAndId(plan providerschema.Cluster, id strin if plan.Description.IsNull() || plan.Description.IsUnknown() { plan.Description = types.StringNull() } - + if plan.ConfigurationType.IsNull() || plan.ConfigurationType.IsUnknown() { + plan.ConfigurationType = types.StringNull() + } if plan.CouchbaseServer.IsNull() || plan.CouchbaseServer.IsUnknown() { plan.CouchbaseServer = types.ObjectNull(providerschema.CouchbaseServer{}.AttributeTypes()) } diff --git a/internal/resources/cluster_schema.go b/internal/resources/cluster_schema.go index 8e858808..b0888862 100644 --- a/internal/resources/cluster_schema.go +++ b/internal/resources/cluster_schema.go @@ -31,6 +31,7 @@ func ClusterSchema() schema.Schema { objectplanmodifier.RequiresReplace(), }, }, + "configuration_type": stringAttribute([]string{optional, computed, requiresReplace, useStateForUnknown, deprecated}), "couchbase_server": schema.SingleNestedAttribute{ Optional: true, Computed: true, diff --git a/internal/schema/cluster.go b/internal/schema/cluster.go index a4f7209b..c377c517 100644 --- a/internal/schema/cluster.go +++ b/internal/schema/cluster.go @@ -132,6 +132,9 @@ type Cluster struct { OrganizationId types.String `tfsdk:"organization_id"` Audit types.Object `tfsdk:"audit"` + // ConfigurationType represents whether a cluster is configured as a single-node or multi-node cluster. + ConfigurationType types.String `tfsdk:"configuration_type"` + // CouchbaseServer is the version of the Couchbase Server to be installed in the cluster. CouchbaseServer types.Object `tfsdk:"couchbase_server"` @@ -194,6 +197,7 @@ func NewCluster(ctx context.Context, cluster *clusterapi.GetClusterResponse, org Region: types.StringValue(cluster.CloudProvider.Region), Type: types.StringValue(string(cluster.CloudProvider.Type)), }, + ConfigurationType: types.StringValue(string(cluster.ConfigurationType)), Support: &Support{ Plan: types.StringValue(string(cluster.Support.Plan)), Timezone: types.StringValue(string(cluster.Support.Timezone)),