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

azurerm_mongo_cluster - Supports connection_strings #28880

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 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
147 changes: 111 additions & 36 deletions internal/services/mongocluster/mongo_cluster_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package mongocluster
import (
"context"
"fmt"
"net/url"
"regexp"
"time"

Expand All @@ -14,7 +15,6 @@ import (
"github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema"
"github.com/hashicorp/go-azure-helpers/resourcemanager/location"
"github.com/hashicorp/go-azure-sdk/resource-manager/mongocluster/2024-07-01/mongoclusters"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-provider-azurerm/internal/sdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation"
Expand All @@ -27,22 +27,29 @@ var _ sdk.ResourceWithUpdate = MongoClusterResource{}
var _ sdk.ResourceWithCustomizeDiff = MongoClusterResource{}

type MongoClusterResourceModel struct {
Name string `tfschema:"name"`
ResourceGroupName string `tfschema:"resource_group_name"`
Location string `tfschema:"location"`
AdministratorUserName string `tfschema:"administrator_username"`
AdministratorPassword string `tfschema:"administrator_password"`
CreateMode string `tfschema:"create_mode"`
ShardCount int64 `tfschema:"shard_count"`
SourceLocation string `tfschema:"source_location"`
SourceServerId string `tfschema:"source_server_id"`
ComputeTier string `tfschema:"compute_tier"`
HighAvailabilityMode string `tfschema:"high_availability_mode"`
PublicNetworkAccess string `tfschema:"public_network_access"`
PreviewFeatures []string `tfschema:"preview_features"`
StorageSizeInGb int64 `tfschema:"storage_size_in_gb"`
Tags map[string]string `tfschema:"tags"`
Version string `tfschema:"version"`
Name string `tfschema:"name"`
ResourceGroupName string `tfschema:"resource_group_name"`
Location string `tfschema:"location"`
AdministratorUserName string `tfschema:"administrator_username"`
AdministratorPassword string `tfschema:"administrator_password"`
CreateMode string `tfschema:"create_mode"`
ShardCount int64 `tfschema:"shard_count"`
SourceLocation string `tfschema:"source_location"`
SourceServerId string `tfschema:"source_server_id"`
ComputeTier string `tfschema:"compute_tier"`
HighAvailabilityMode string `tfschema:"high_availability_mode"`
PublicNetworkAccess string `tfschema:"public_network_access"`
PreviewFeatures []string `tfschema:"preview_features"`
StorageSizeInGb int64 `tfschema:"storage_size_in_gb"`
ConnectionStrings []MongoClusterConnectionString `tfschema:"connection_strings"`
Tags map[string]string `tfschema:"tags"`
Version string `tfschema:"version"`
}

type MongoClusterConnectionString struct {
ConnectionString string `tfschema:"connection_string"`
Description string `tfschema:"description"`
Name string `tfschema:"name"`
}

func (r MongoClusterResource) ModelObject() interface{} {
Expand All @@ -57,12 +64,12 @@ func (r MongoClusterResource) ResourceType() string {
return "azurerm_mongo_cluster"
}

func (r MongoClusterResource) Arguments() map[string]*schema.Schema {
return map[string]*schema.Schema{
func (r MongoClusterResource) Arguments() map[string]*pluginsdk.Schema {
return map[string]*pluginsdk.Schema{
"name": {
ForceNew: true,
Required: true,
Type: schema.TypeString,
Type: pluginsdk.TypeString,
ValidateFunc: validation.StringMatch(
regexp.MustCompile(`^[a-z\d]([-a-z\d]{1,38}[a-z\d])$`),
"`name` must be between 3 and 40 characters. It can contain only lowercase letters, numbers, and hyphens (-). It must start and end with a lowercase letter or number.",
Expand All @@ -74,15 +81,15 @@ func (r MongoClusterResource) Arguments() map[string]*schema.Schema {
"location": commonschema.Location(),

"administrator_username": {
Type: schema.TypeString,
Type: pluginsdk.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validation.StringIsNotEmpty,
RequiredWith: []string{"administrator_password"},
},

"create_mode": {
Type: schema.TypeString,
Type: pluginsdk.TypeString,
Optional: true,
ForceNew: true,
Default: string(mongoclusters.CreateModeDefault),
Expand All @@ -94,24 +101,24 @@ func (r MongoClusterResource) Arguments() map[string]*schema.Schema {
},

"preview_features": {
Type: schema.TypeList,
Type: pluginsdk.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Schema{
Type: schema.TypeString,
Elem: &pluginsdk.Schema{
Type: pluginsdk.TypeString,
ValidateFunc: validation.StringInSlice(mongoclusters.PossibleValuesForPreviewFeature(), false),
},
},

"shard_count": {
Type: schema.TypeInt,
Type: pluginsdk.TypeInt,
Optional: true,
ValidateFunc: validation.IntAtLeast(1),
ForceNew: true,
},

"source_location": {
Type: schema.TypeString,
Type: pluginsdk.TypeString,
Optional: true,
ForceNew: true,
StateFunc: location.StateFunc,
Expand All @@ -121,22 +128,22 @@ func (r MongoClusterResource) Arguments() map[string]*schema.Schema {
},

"source_server_id": {
Type: schema.TypeString,
Type: pluginsdk.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: mongoclusters.ValidateMongoClusterID,
},

"administrator_password": {
Type: schema.TypeString,
Type: pluginsdk.TypeString,
Optional: true,
Sensitive: true,
ValidateFunc: validation.StringIsNotEmpty,
RequiredWith: []string{"administrator_username"},
},

"compute_tier": {
Type: schema.TypeString,
Type: pluginsdk.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{
"Free",
Expand All @@ -150,7 +157,7 @@ func (r MongoClusterResource) Arguments() map[string]*schema.Schema {
},

"high_availability_mode": {
Type: schema.TypeString,
Type: pluginsdk.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{
// Confirmed with service team the `SameZone` is currently not supported.
Expand All @@ -160,22 +167,22 @@ func (r MongoClusterResource) Arguments() map[string]*schema.Schema {
},

"public_network_access": {
Type: schema.TypeString,
Type: pluginsdk.TypeString,
Optional: true,
Default: string(mongoclusters.PublicNetworkAccessEnabled),
ValidateFunc: validation.StringInSlice(mongoclusters.PossibleValuesForPublicNetworkAccess(), false),
},

"storage_size_in_gb": {
Type: schema.TypeInt,
Type: pluginsdk.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(32, 16384),
},

"tags": commonschema.Tags(),

"version": {
Type: schema.TypeString,
Type: pluginsdk.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{
"5.0",
Expand All @@ -186,8 +193,30 @@ func (r MongoClusterResource) Arguments() map[string]*schema.Schema {
}
}

func (r MongoClusterResource) Attributes() map[string]*schema.Schema {
return map[string]*schema.Schema{}
func (r MongoClusterResource) Attributes() map[string]*pluginsdk.Schema {
return map[string]*pluginsdk.Schema{
"connection_strings": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should change this to connection_string because otherwise it'll look like the following in state as

  connection_strings {
    //connection string 1 info
  }
 
  connection_strings {
    //connection string 2 info
  }

A plural connection_strings signifies multiple connection strings in a single block rather than one connection string per block.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mbfrahry This one is a read only attribute, I assume we shall keep it plural as the user can only reference them like: azurerm_mongo_cluster.example.connection_strings.x?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhh good call! That makes sense to me! Thank you for walking me through that

Type: pluginsdk.TypeList,
Sensitive: true,
Computed: true,
Elem: &pluginsdk.Resource{
Schema: map[string]*pluginsdk.Schema{
"name": {
Type: pluginsdk.TypeString,
Computed: true,
},
"description": {
Type: pluginsdk.TypeString,
Computed: true,
},
"connection_string": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid confusion around connection_string.x.connection_string we should change this to value.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Type: pluginsdk.TypeString,
Computed: true,
},
},
},
},
}
}

func (r MongoClusterResource) Create() sdk.ResourceFunc {
Expand Down Expand Up @@ -452,6 +481,14 @@ func (r MongoClusterResource) Read() sdk.ResourceFunc {
state.Tags = pointer.From(model.Tags)
}

csResp, err := client.ListConnectionStrings(ctx, *id)
if err != nil {
return fmt.Errorf("listing connection string for %s: %+v", *id, err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("listing connection string for %s: %+v", *id, err)
return fmt.Errorf("listing connection strings for %s: %+v", *id, err)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}
if model := csResp.Model; model != nil {
state.ConnectionStrings = flattenMongoClusterConnectionString(model.ConnectionStrings, state.AdministratorUserName, state.AdministratorPassword)
}

return metadata.Encode(&state)
},
}
Expand Down Expand Up @@ -570,3 +607,41 @@ func flattenMongoClusterPreviewFeatures(input *[]mongoclusters.PreviewFeature) [

return results
}

func flattenMongoClusterConnectionString(input *[]mongoclusters.ConnectionString, userName, userPassword string) []MongoClusterConnectionString {
if input == nil {
return nil
}

results := make([]MongoClusterConnectionString, 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if input == nil {
return nil
}
results := make([]MongoClusterConnectionString, 0)
results := make([]MongoClusterConnectionString, 0)
if input == nil {
return results
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

for _, cs := range *input {
var name string
if cs.Name != nil {
name = *cs.Name
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var name string
if cs.Name != nil {
name = *cs.Name
}
name := pointer.From(cs.Name)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


var description string
if cs.Description != nil {
description = *cs.Description
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var description string
if cs.Description != nil {
description = *cs.Description
}
description := pointer.From(cs.Description)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


var connectionString string
if cs.ConnectionString != nil {
connectionString = *cs.ConnectionString
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var connectionString string
if cs.ConnectionString != nil {
connectionString = *cs.ConnectionString
}
connectionString := pointer.From(cs.ConnectionString)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


// Password can be empty if it isn't available in the state file (e.g. during import).
// In this case, we simply leave the placeholder unchanged.
if userPassword != "" {
connectionString = regexp.MustCompile(`<user>:<password>`).ReplaceAllString(connectionString, url.UserPassword(userName, userPassword).String())
}

results = append(results, MongoClusterConnectionString{
Name: name,
Description: description,
ConnectionString: connectionString,
})
}

return results
}
14 changes: 5 additions & 9 deletions internal/services/mongocluster/mongo_cluster_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,12 @@ func testAccMongoCluster_basic(t *testing.T) {
Config: r.basic(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("connection_strings.0.connection_string").HasValue(
fmt.Sprintf(`mongodb+srv://adminTerraform:QAZwsx123basic@acctest-mc%d.mongocluster.cosmos.azure.com/?tls=true&authMechanism=SCRAM-SHA-256&retrywrites=false&maxIdleTimeMS=120000`,
data.RandomInteger)),
),
},
data.ImportStep("administrator_password", "create_mode"),
data.ImportStep("administrator_password", "create_mode", "connection_strings.0.connection_string"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need to add "connection_strings.0.connection_string" to all data.ImportSteps in this test file

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

})
}

Expand Down Expand Up @@ -134,19 +137,12 @@ func (r MongoClusterResource) basic(data acceptance.TestData) string {
return fmt.Sprintf(`
%s

resource "random_password" "test" {
length = 8
numeric = true
upper = true
lower = true
}

resource "azurerm_mongo_cluster" "test" {
name = "acctest-mc%d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
administrator_username = "adminTerraform"
administrator_password = random_password.test.result
administrator_password = "QAZwsx123basic"
shard_count = "1"
compute_tier = "Free"
high_availability_mode = "Disabled"
Expand Down
12 changes: 12 additions & 0 deletions website/docs/r/mongo_cluster.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,18 @@ In addition to the Arguments listed above - the following Attributes are exporte

* `id` - The ID of the MongoDB Cluster.

* `connection_strings` - The list of `connection_strings` blocks as defined below.

---

A `connection_strings` exports the following:

* `name` - The name of the connection string.

* `description` - The description of the connection string.

* `connection_string` - The Mongo Cluster connection string. The `<user>:<password>` place holder returned from API will be replaced by the real `administrator_username` and `administrator_password` if available in the state.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* `connection_string` - The Mongo Cluster connection string. The `<user>:<password>` place holder returned from API will be replaced by the real `administrator_username` and `administrator_password` if available in the state.
* `connection_string` - The Mongo Cluster connection string. The `<user>:<password>` placeholder returned from API will be replaced by the real `administrator_username` and `administrator_password` if available in the state.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


## Timeouts

The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions:
Expand Down
Loading