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

feat: Adds mongodbatlas_flex_cluster resource and data sources #2816

Merged
merged 25 commits into from
Nov 26, 2024
Merged
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
dd472f5
use SDK dev-preview
oarbusi Oct 16, 2024
dd489fd
chore: Generate Flex Cluster file structure and resource schema (#2702)
oarbusi Oct 17, 2024
5d853e7
feat: Implements `mongodbatlas_flex_cluster` resource (#2716)
oarbusi Oct 21, 2024
37e07f2
chore: Enable mongodbatlas_flex_cluster test in CI (#2720)
cveticm Oct 22, 2024
11e7ba9
chore: Generate Flex Cluster data source file and schemas (#2725)
cveticm Oct 23, 2024
a8eb106
chore: Adds state transition logic to mongodbatlas_flex_cluster resou…
oarbusi Oct 23, 2024
727010e
chore: Update operation improvements for `mongodbatlas_flex_cluster` …
oarbusi Oct 28, 2024
6234ad6
test: Enable mongodbatlas_flex_cluster tests in QA environment (#2741)
oarbusi Oct 28, 2024
f037345
feat: Implements and tests `mongodbatlas_flex_cluster` data source (#…
cveticm Oct 29, 2024
5aa0d84
Adjusting default timeout of `flex_cluster` to 3 hours (#2757)
cveticm Oct 30, 2024
a762e89
chore: Rebase dev_branch onto master_branch (#2764)
cveticm Oct 31, 2024
4f39d7c
feat: Implements `mongodbatlas_flex_cluster` plural data source (#2767)
cveticm Nov 5, 2024
69e2df9
chore: Improvements to SDK version update (#2770)
cveticm Nov 6, 2024
01cd8f2
doc: Adds documentation and example for mongodbatlas_flex_cluster res…
oarbusi Nov 8, 2024
4529b81
deprecate: Deprecates Serverless functionality (#2742)
oarbusi Nov 11, 2024
f828c3e
doc: Adds migration guides to transition out of Serverless and Shared…
oarbusi Nov 21, 2024
052a4b1
chore: Removes mentions and examples of Serverless and Shared-tier in…
oarbusi Nov 22, 2024
2a1aec1
chore: Refactor tags attribute schema and conversion logic (#2788)
cveticm Nov 22, 2024
f2c44b3
Merge branch 'master' into CLOUDP-262066-flex-cluster-dev
oarbusi Nov 22, 2024
ed994ec
fix changelog entry
oarbusi Nov 22, 2024
390bb82
january or later for shared tier autoconversion
oarbusi Nov 22, 2024
48283ba
Merge branch 'master' into CLOUDP-262066-flex-cluster-dev
lantoli Nov 25, 2024
2e66575
auto-generate singular data source, temporarily with checks
lantoli Nov 25, 2024
61f7388
remove singular data source check
lantoli Nov 25, 2024
bb7f005
update Description in plural data source
lantoli Nov 25, 2024
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
Prev Previous commit
Next Next commit
chore: Update operation improvements for mongodbatlas_flex_cluster
…resource (#2729)

* schema refactors

* revert rename

* implement isUpdateAllowed

* failed update test case

* refactor equal checks

* make more exhaustive the failed update test

* remove UseStateForUnknown in state_name

* wip: use plan modifier for non updatable fields

* wip: use plan modifiers on all non updatable attributes

* use plan modifiers to fail on update of certain attributes

* rename planmodifier

* simplify planmodifier

* PR comments

* remove attribute parameter

* markdowndescription over description
oarbusi authored Oct 28, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
commit 727010e53204b150612c9d770dc6d6252971ca95
36 changes: 36 additions & 0 deletions internal/common/customplanmodifier/non_updatable.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package customplanmodifier

import (
"context"
"fmt"

planmodifier "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
)

func NonUpdatableStringAttributePlanModifier() planmodifier.String {
return &nonUpdatableStringAttributePlanModifier{}
}

type nonUpdatableStringAttributePlanModifier struct {
}

func (d *nonUpdatableStringAttributePlanModifier) Description(ctx context.Context) string {
return d.MarkdownDescription(ctx)
}

func (d *nonUpdatableStringAttributePlanModifier) MarkdownDescription(ctx context.Context) string {
return "Ensures that update operations fails when updating an attribute."
}

func (d *nonUpdatableStringAttributePlanModifier) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) {
planAttributeValue := req.PlanValue
stateAttributeValue := req.StateValue

if !stateAttributeValue.IsNull() && stateAttributeValue.ValueString() != planAttributeValue.ValueString() {
resp.Diagnostics.AddError(
fmt.Sprintf("%s cannot be updated", req.Path),
fmt.Sprintf("%s cannot be updated", req.Path),
)
return
}
}
13 changes: 7 additions & 6 deletions internal/service/flexcluster/resource.go
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@ import (
)

const resourceName = "flex_cluster"
const ErrorUpdateNotAllowed = "update not allowed"

var _ resource.ResourceWithConfigure = &rs{}
var _ resource.ResourceWithImportState = &rs{}
@@ -106,23 +107,23 @@ func (r *rs) Read(ctx context.Context, req resource.ReadRequest, resp *resource.
}

func (r *rs) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var tfModel TFModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &tfModel)...)
var plan TFModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}

flexClusterReq, diags := NewAtlasUpdateReq(ctx, &tfModel)
flexClusterReq, diags := NewAtlasUpdateReq(ctx, &plan)
if diags.HasError() {
resp.Diagnostics.Append(diags...)
return
}

projectID := tfModel.ProjectId.ValueString()
clusterName := tfModel.Name.ValueString()
projectID := plan.ProjectId.ValueString()
clusterName := plan.Name.ValueString()

connV2 := r.Client.AtlasV2
_, _, err := connV2.FlexClustersApi.UpdateFlexCluster(ctx, projectID, tfModel.Name.ValueString(), flexClusterReq).Execute()
_, _, err := connV2.FlexClustersApi.UpdateFlexCluster(ctx, projectID, plan.Name.ValueString(), flexClusterReq).Execute()
if err != nil {
resp.Diagnostics.AddError("error updating resource", err.Error())
return
73 changes: 59 additions & 14 deletions internal/service/flexcluster/resource_schema.go
Original file line number Diff line number Diff line change
@@ -5,37 +5,61 @@ import (

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/customplanmodifier"

"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/float64planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
)

func ResourceSchema(ctx context.Context) schema.Schema {
return schema.Schema{
Attributes: map[string]schema.Attribute{
"project_id": schema.StringAttribute{
Required: true,
Required: true,
PlanModifiers: []planmodifier.String{
customplanmodifier.NonUpdatableStringAttributePlanModifier(),
},
MarkdownDescription: "Unique 24-hexadecimal character string that identifies the project.",
},
"name": schema.StringAttribute{
Required: true,
Required: true,
PlanModifiers: []planmodifier.String{
customplanmodifier.NonUpdatableStringAttributePlanModifier(),
},
MarkdownDescription: "Human-readable label that identifies the instance.",
},
"provider_settings": schema.SingleNestedAttribute{
Attributes: map[string]schema.Attribute{
"backing_provider_name": schema.StringAttribute{
Required: true,
Required: true,
PlanModifiers: []planmodifier.String{
customplanmodifier.NonUpdatableStringAttributePlanModifier(),
},
MarkdownDescription: "Cloud service provider on which MongoDB Cloud provisioned the flex cluster.",
},
"disk_size_gb": schema.Float64Attribute{
Computed: true,
Computed: true,
PlanModifiers: []planmodifier.Float64{
float64planmodifier.UseStateForUnknown(),
},
MarkdownDescription: "Storage capacity available to the flex cluster expressed in gigabytes.",
},
"provider_name": schema.StringAttribute{
Computed: true,
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
MarkdownDescription: "Human-readable label that identifies the cloud service provider.",
},
"region_name": schema.StringAttribute{
Required: true,
Required: true,
PlanModifiers: []planmodifier.String{
customplanmodifier.NonUpdatableStringAttributePlanModifier(),
},
MarkdownDescription: "Human-readable label that identifies the geographic location of your MongoDB flex cluster. The region you choose can affect network latency for clients accessing your databases. For a complete list of region names, see [AWS](https://docs.atlas.mongodb.com/reference/amazon-aws/#std-label-amazon-aws), [GCP](https://docs.atlas.mongodb.com/reference/google-gcp/), and [Azure](https://docs.atlas.mongodb.com/reference/microsoft-azure/).",
},
},
@@ -54,11 +78,17 @@ func ResourceSchema(ctx context.Context) schema.Schema {
MarkdownDescription: "Flag that indicates whether backups are performed for this flex cluster. Backup uses [TODO](TODO) for flex clusters.",
},
},
Computed: true,
Computed: true,
PlanModifiers: []planmodifier.Object{
objectplanmodifier.UseStateForUnknown(),
},
MarkdownDescription: "Flex backup configuration",
},
"cluster_type": schema.StringAttribute{
Computed: true,
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
MarkdownDescription: "Flex cluster topology.",
},
"connection_strings": schema.SingleNestedAttribute{
@@ -72,15 +102,24 @@ func ResourceSchema(ctx context.Context) schema.Schema {
MarkdownDescription: "Public connection string that you can use to connect to this flex cluster. This connection string uses the `mongodb+srv://` protocol.",
},
},
Computed: true,
Computed: true,
PlanModifiers: []planmodifier.Object{
objectplanmodifier.UseStateForUnknown(),
},
MarkdownDescription: "Collection of Uniform Resource Locators that point to the MongoDB database.",
},
"create_date": schema.StringAttribute{
Computed: true,
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
MarkdownDescription: "Date and time when MongoDB Cloud created this instance. This parameter expresses its value in ISO 8601 format in UTC.",
},
"id": schema.StringAttribute{
Computed: true,
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
MarkdownDescription: "Unique 24-hexadecimal digit string that identifies the instance.",
},
"mongo_db_version": schema.StringAttribute{
@@ -92,12 +131,18 @@ func ResourceSchema(ctx context.Context) schema.Schema {
MarkdownDescription: "Human-readable label that indicates the current operating condition of this instance.",
},
"termination_protection_enabled": schema.BoolAttribute{
Optional: true,
Computed: true,
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.Bool{
boolplanmodifier.UseStateForUnknown(),
},
MarkdownDescription: "Flag that indicates whether termination protection is enabled on the cluster. If set to `true`, MongoDB Cloud won't delete the cluster. If set to `false`, MongoDB Cloud will delete the cluster.",
},
"version_release_system": schema.StringAttribute{
Computed: true,
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
MarkdownDescription: "Method by which the cluster maintains the MongoDB versions.",
},
},
65 changes: 57 additions & 8 deletions internal/service/flexcluster/resource_test.go
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"os"
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
@@ -21,27 +22,34 @@ func TestAccFlexClusterRS_basic(t *testing.T) {
resource.ParallelTest(t, *tc)
}

func TestAccFlexClusterRS_failedUpdate(t *testing.T) {
tc := failedUpdateTestCase(t)
resource.ParallelTest(t, *tc)
}

func basicTestCase(t *testing.T) *resource.TestCase {
t.Helper()
var (
projectID = os.Getenv("MONGODB_ATLAS_FLEX_PROJECT_ID")
clusterName = acc.RandomName()
provider = "AWS"
region = "US_EAST_1"
)
return &resource.TestCase{
PreCheck: func() { acc.PreCheckBasic(t) },
ProtoV6ProviderFactories: acc.TestAccProviderV6Factories,
CheckDestroy: checkDestroy,
Steps: []resource.TestStep{
{
Config: configBasic(projectID, clusterName, true),
Config: configBasic(projectID, clusterName, provider, region, true),
Check: checksFlexCluster(projectID, clusterName, true),
},
{
Config: configBasic(projectID, clusterName, false),
Config: configBasic(projectID, clusterName, provider, region, false),
Check: checksFlexCluster(projectID, clusterName, false),
},
{
Config: configBasic(projectID, clusterName, true),
Config: configBasic(projectID, clusterName, provider, region, true),
ResourceName: resourceName,
ImportStateIdFunc: importStateIDFunc(resourceName),
ImportState: true,
@@ -51,17 +59,58 @@ func basicTestCase(t *testing.T) *resource.TestCase {
}
}

func configBasic(projectID, clusterName string, terminationProtectionEnabled bool) string {
func failedUpdateTestCase(t *testing.T) *resource.TestCase {
t.Helper()
var (
projectID = os.Getenv("MONGODB_ATLAS_FLEX_PROJECT_ID")
projectIDUpdated = os.Getenv("MONGODB_ATLAS_FLEX_PROJECT_ID") + "-updated"
clusterName = acc.RandomName()
clusterNameUpdated = clusterName + "-updated"
provider = "AWS"
providerUpdated = "GCP"
region = "US_EAST_1"
regionUpdated = "US_EAST_2"
)
return &resource.TestCase{
PreCheck: func() { acc.PreCheckBasic(t) },
ProtoV6ProviderFactories: acc.TestAccProviderV6Factories,
CheckDestroy: checkDestroy,
Steps: []resource.TestStep{
{
Config: configBasic(projectID, clusterName, provider, region, false),
Check: checksFlexCluster(projectID, clusterName, false),
},
{
Config: configBasic(projectID, clusterNameUpdated, provider, region, false),
ExpectError: regexp.MustCompile("name cannot be updated"),
},
{
Config: configBasic(projectIDUpdated, clusterName, provider, region, false),
ExpectError: regexp.MustCompile("project_id cannot be updated"),
},
{
Config: configBasic(projectID, clusterName, providerUpdated, region, false),
ExpectError: regexp.MustCompile("provider_settings.backing_provider_name cannot be updated"),
},
{
Config: configBasic(projectID, clusterName, provider, regionUpdated, false),
ExpectError: regexp.MustCompile("provider_settings.region_name cannot be updated"),
},
},
}
}

func configBasic(projectID, clusterName, provider, region string, terminationProtectionEnabled bool) string {
return fmt.Sprintf(`
resource "mongodbatlas_flex_cluster" "flex_cluster" {
project_id = %[1]q
name = %[2]q
provider_settings = {
backing_provider_name = "AWS"
region_name = "US_EAST_1"
backing_provider_name = %[3]q
region_name = %[4]q
}
termination_protection_enabled = %[3]t
}`, projectID, clusterName, terminationProtectionEnabled)
termination_protection_enabled = %[5]t
}`, projectID, clusterName, provider, region, terminationProtectionEnabled)
}

func checksFlexCluster(projectID, clusterName string, terminationProtectionEnabled bool) resource.TestCheckFunc {