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_cosmosdb_cassandra_cluster - support new property tags #16743

Merged
merged 5 commits into from
May 13, 2022
Merged
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
71 changes: 69 additions & 2 deletions internal/services/cosmos/cosmosdb_cassandra_cluster_resource.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cosmos

import (
"context"
"fmt"
"log"
"time"
Expand All @@ -14,6 +15,7 @@ import (
"github.com/hashicorp/terraform-provider-azurerm/internal/clients"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/cosmos/parse"
networkValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/network/validate"
"github.com/hashicorp/terraform-provider-azurerm/internal/tags"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation"
"github.com/hashicorp/terraform-provider-azurerm/internal/timeouts"
Expand All @@ -24,6 +26,7 @@ func resourceCassandraCluster() *pluginsdk.Resource {
return &pluginsdk.Resource{
Create: resourceCassandraClusterCreate,
Read: resourceCassandraClusterRead,
Update: resourceCassandraClusterUpdate,
Delete: resourceCassandraClusterDelete,

Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error {
Expand All @@ -34,6 +37,7 @@ func resourceCassandraCluster() *pluginsdk.Resource {
Timeouts: &pluginsdk.ResourceTimeout{
Create: pluginsdk.DefaultTimeout(30 * time.Minute),
Read: pluginsdk.DefaultTimeout(5 * time.Minute),
Update: pluginsdk.DefaultTimeout(30 * time.Minute),
Delete: pluginsdk.DefaultTimeout(30 * time.Minute),
},

Expand Down Expand Up @@ -63,6 +67,8 @@ func resourceCassandraCluster() *pluginsdk.Resource {
Sensitive: true,
ValidateFunc: validation.StringIsNotEmpty,
},

"tags": tags.Schema(),
},
}
}
Expand Down Expand Up @@ -93,6 +99,7 @@ func resourceCassandraClusterCreate(d *pluginsdk.ResourceData, meta interface{})
DelegatedManagementSubnetID: utils.String(d.Get("delegated_management_subnet_id").(string)),
InitialCassandraAdminPassword: utils.String(d.Get("default_admin_password").(string)),
},
Tags: tags.Expand(d.Get("tags").(map[string]interface{})),
}

future, err := client.CreateUpdate(ctx, id.ResourceGroup, id.Name, body)
Expand All @@ -101,7 +108,7 @@ func resourceCassandraClusterCreate(d *pluginsdk.ResourceData, meta interface{})
}

if err = future.WaitForCompletionRef(ctx, client.Client); err != nil {
return fmt.Errorf("waiting on create/update for %q: %+v", id, err)
return fmt.Errorf("waiting on create for %q: %+v", id, err)
}

d.SetId(id.ID())
Expand Down Expand Up @@ -140,7 +147,52 @@ func resourceCassandraClusterRead(d *pluginsdk.ResourceData, meta interface{}) e
}
// The "default_admin_password" is not returned in GET response, hence setting it from config.
d.Set("default_admin_password", d.Get("default_admin_password").(string))
return nil
return tags.FlattenAndSet(d, resp.Tags)
}

func resourceCassandraClusterUpdate(d *pluginsdk.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Cosmos.CassandraClustersClient
subscriptionId := meta.(*clients.Client).Account.SubscriptionId
ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d)
defer cancel()

resourceGroupName := d.Get("resource_group_name").(string)
name := d.Get("name").(string)
id := parse.NewCassandraClusterID(subscriptionId, resourceGroupName, name)

body := documentdb.ClusterResource{
Location: utils.String(azure.NormalizeLocation(d.Get("location").(string))),
Properties: &documentdb.ClusterResourceProperties{
DelegatedManagementSubnetID: utils.String(d.Get("delegated_management_subnet_id").(string)),
InitialCassandraAdminPassword: utils.String(d.Get("default_admin_password").(string)),
},
Tags: tags.Expand(d.Get("tags").(map[string]interface{})),
}

// Though there is update method but Service API complains it isn't implemented
_, err := client.CreateUpdate(ctx, id.ResourceGroup, id.Name, body)
if err != nil {
return fmt.Errorf("updating %q: %+v", id, err)
}

// Issue: https://github.com/Azure/azure-rest-api-specs/issues/19021
// There is a long running issue on updating this resource.
// The API cannot update the property after WaitForCompletionRef is returned.
// It has to wait a while after that. Then the property can be updated successfully.
stateConf := &pluginsdk.StateChangeConf{
Delay: 1 * time.Minute,
Pending: []string{string(documentdb.ManagedCassandraProvisioningStateUpdating)},
Target: []string{string(documentdb.ManagedCassandraProvisioningStateSucceeded)},
Refresh: cosmosdbCassandraClusterStateRefreshFunc(ctx, client, id),
MinTimeout: 15 * time.Second,
Timeout: d.Timeout(pluginsdk.TimeoutUpdate),
}

if _, err := stateConf.WaitForStateContext(ctx); err != nil {
return fmt.Errorf("waiting for update of %s: %+v", id, err)
}

return resourceCassandraClusterRead(d, meta)
}

func resourceCassandraClusterDelete(d *pluginsdk.ResourceData, meta interface{}) error {
Expand All @@ -167,3 +219,18 @@ func resourceCassandraClusterDelete(d *pluginsdk.ResourceData, meta interface{})

return nil
}

func cosmosdbCassandraClusterStateRefreshFunc(ctx context.Context, client *documentdb.CassandraClustersClient, id parse.CassandraClusterId) pluginsdk.StateRefreshFunc {
return func() (interface{}, string, error) {
res, err := client.Get(ctx, id.ResourceGroup, id.Name)
if err != nil {
return nil, "", fmt.Errorf("polling for %s: %+v", id, err)
}

if res.Properties != nil && res.Properties.ProvisioningState != "" {
return res, string(res.Properties.ProvisioningState), nil
}
return nil, "", fmt.Errorf("unable to read provisioning state")
}

}
131 changes: 97 additions & 34 deletions internal/services/cosmos/cosmosdb_cassandra_cluster_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,22 @@ import (

type CassandraClusterResource struct{}

func TestAccCassandraCluster_basic(t *testing.T) {
func TestAccCassandraCluster(t *testing.T) {
// NOTE: this is a combined test rather than separate split out tests due to all below TCs sharing same sp
acceptance.RunTestsInSequence(t, map[string]map[string]func(t *testing.T){
"basic": {
"basic": testAccCassandraCluster_basic,
"requiresImport": testAccCassandraCluster_requiresImport,
"tags": testAccCassandraCluster_tags,
},
})
}

func testAccCassandraCluster_basic(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_cosmosdb_cassandra_cluster", "test")
r := CassandraClusterResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
data.ResourceSequentialTest(t, r, []acceptance.TestStep{
{
Config: r.basic(data),
Check: acceptance.ComposeAggregateTestCheckFunc(
Expand All @@ -30,11 +41,11 @@ func TestAccCassandraCluster_basic(t *testing.T) {
})
}

func TestAccCassandraCluster_requiresImport(t *testing.T) {
func testAccCassandraCluster_requiresImport(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_cosmosdb_cassandra_cluster", "test")
r := CassandraClusterResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
data.ResourceSequentialTest(t, r, []acceptance.TestStep{
{
Config: r.basic(data),
Check: acceptance.ComposeTestCheckFunc(
Expand All @@ -49,6 +60,28 @@ func TestAccCassandraCluster_requiresImport(t *testing.T) {
})
}

func testAccCassandraCluster_tags(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_cosmosdb_cassandra_cluster", "test")
r := CassandraClusterResource{}

data.ResourceSequentialTest(t, r, []acceptance.TestStep{
{
Config: r.tags(data, "Test"),
Check: acceptance.ComposeAggregateTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep("default_admin_password"),
{
Config: r.tags(data, "Test2"),
Check: acceptance.ComposeAggregateTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep("default_admin_password"),
})
}

func (t CassandraClusterResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) {
id, err := parse.CassandraClusterID(state.ID)
if err != nil {
Expand All @@ -63,59 +96,89 @@ func (t CassandraClusterResource) Exists(ctx context.Context, clients *clients.C
return utils.Bool(resp.ID != nil), nil
}

func (CassandraClusterResource) basic(data acceptance.TestData) string {
func (r CassandraClusterResource) basic(data acceptance.TestData) string {
return fmt.Sprintf(`
%s

resource "azurerm_cosmosdb_cassandra_cluster" "test" {
name = "acctca-mi-cluster-%d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
delegated_management_subnet_id = azurerm_subnet.test.id
default_admin_password = "Password1234"

depends_on = [azurerm_role_assignment.test]
}
`, r.template(data), data.RandomInteger)
}

func (r CassandraClusterResource) requiresImport(data acceptance.TestData) string {
return fmt.Sprintf(`
%s

resource "azurerm_cosmosdb_cassandra_cluster" "import" {
name = azurerm_cosmosdb_cassandra_cluster.test.name
resource_group_name = azurerm_cosmosdb_cassandra_cluster.test.resource_group_name
location = azurerm_cosmosdb_cassandra_cluster.test.location
delegated_management_subnet_id = azurerm_subnet.test.id
default_admin_password = "Password1234"
}
`, r.basic(data))
}

func (r CassandraClusterResource) tags(data acceptance.TestData, tag string) string {
return fmt.Sprintf(`
%s

resource "azurerm_cosmosdb_cassandra_cluster" "test" {
name = "acctca-mi-cluster-%d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
delegated_management_subnet_id = azurerm_subnet.test.id
default_admin_password = "Password1234"

tags = {
Env = "%s"
}

depends_on = [azurerm_role_assignment.test]
}
`, r.template(data), data.RandomInteger, tag)
}

func (r CassandraClusterResource) template(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}

resource "azurerm_resource_group" "test" {
name = "acctest-ca-%[1]d"
location = "%[2]s"
name = "acctest-ca-%d"
location = "%s"
}

resource "azurerm_virtual_network" "test" {
name = "acctvn-%[1]d"
name = "acctvn-%d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
address_space = ["10.0.0.0/16"]
}

resource "azurerm_subnet" "test" {
name = "acctsub-%[1]d"
name = "acctsub-%d"
resource_group_name = azurerm_resource_group.test.name
virtual_network_name = azurerm_virtual_network.test.name
address_prefixes = ["10.0.1.0/24"]
}

data "azuread_service_principal" "test" {
display_name = "Azure Cosmos DB"
}

resource "azurerm_role_assignment" "test" {
scope = azurerm_virtual_network.test.id
role_definition_name = "Network Contributor"
principal_id = "255f3c8e-0c3d-4f06-ba9d-2fb68af0faed"
}

resource "azurerm_cosmosdb_cassandra_cluster" "test" {
name = "acctca-mi-cluster-%[1]d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
delegated_management_subnet_id = azurerm_subnet.test.id
default_admin_password = "Password1234"
}
`, data.RandomInteger, data.Locations.Secondary)
}

func (CassandraClusterResource) requiresImport(data acceptance.TestData) string {
template := CassandraClusterResource{}.basic(data)
return fmt.Sprintf(`
%s

resource "azurerm_cosmosdb_cassandra_cluster" "import" {
name = azurerm_cosmosdb_cassandra_cluster.test.name
resource_group_name = azurerm_cosmosdb_cassandra_cluster.test.resource_group_name
location = azurerm_cosmosdb_cassandra_cluster.test.location
delegated_management_subnet_id = azurerm_subnet.test.id
default_admin_password = "Password1234"
principal_id = data.azuread_service_principal.test.object_id
}
`, template)
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger)
}
17 changes: 16 additions & 1 deletion website/docs/r/cosmosdb_cassandra_cluster.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ description: |-

Manages a Cassandra Cluster.

~> ** NOTE: ** In order for the `Azure Managed Instances for Apache Cassandra` to work properly the product requires the `Azure Cosmos DB` Application ID to be present and working in your tenant. If the `Azure Cosmos DB` Application ID is missing in your environment you will need to have an administrator of your tenant run the following command to add the `Azure Cosmos DB` Application ID to your tenant:

```powershell
New-AzADServicePrincipal -ApplicationId a232010e-820c-4083-83bb-3ace5fc29d0b
```

## Example Usage

```hcl
Expand All @@ -36,10 +42,14 @@ resource "azurerm_subnet" "example" {
address_prefixes = ["10.0.1.0/24"]
}

data "azuread_service_principal" "example" {
display_name = "Azure Cosmos DB"
}

resource "azurerm_role_assignment" "example" {
scope = azurerm_virtual_network.example.id
role_definition_name = "Network Contributor"
principal_id = "e5007d2c-4b13-4a74-9b6a-605d99f03501"
principal_id = data.azuread_service_principal.example.object_id
}

resource "azurerm_cosmosdb_cassandra_cluster" "example" {
Expand All @@ -48,6 +58,8 @@ resource "azurerm_cosmosdb_cassandra_cluster" "example" {
location = azurerm_resource_group.example.location
delegated_management_subnet_id = azurerm_subnet.example.id
default_admin_password = "Password1234"

depends_on = [azurerm_role_assignment.example]
}
```

Expand All @@ -65,6 +77,8 @@ The following arguments are supported:

* `default_admin_password` - (Required) The initial admin password for this Cassandra Cluster.

* `tags` - (Optional) A mapping of tags assigned to the resource.

## Attributes Reference

In addition to the Arguments listed above - the following Attributes are exported:
Expand All @@ -77,6 +91,7 @@ The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/d

* `create` - (Defaults to 30 minutes) Used when creating the Cassandra Cluster.
* `read` - (Defaults to 5 minutes) Used when retrieving the Cassandra Cluster.
* `update` - (Defaults to 30 minutes) Used when updating the Cassandra Cluster.
* `delete` - (Defaults to 30 minutes) Used when deleting the Cassandra Cluster.

## Import
Expand Down