diff --git a/azurerm/internal/services/cosmos/registration.go b/azurerm/internal/services/cosmos/registration.go index ee333431573f..f6579c305d90 100644 --- a/azurerm/internal/services/cosmos/registration.go +++ b/azurerm/internal/services/cosmos/registration.go @@ -23,6 +23,7 @@ func (r Registration) SupportedResources() map[string]*schema.Resource { return map[string]*schema.Resource{ "azurerm_cosmosdb_account": resourceArmCosmosDbAccount(), "azurerm_cosmosdb_cassandra_keyspace": resourceArmCosmosDbCassandraKeyspace(), + "azurerm_cosmosdb_gremlin_database": resourceArmCosmosGremlinDatabase(), "azurerm_cosmosdb_mongo_collection": resourceArmCosmosDbMongoCollection(), "azurerm_cosmosdb_mongo_database": resourceArmCosmosDbMongoDatabase(), "azurerm_cosmosdb_sql_container": resourceArmCosmosDbSQLContainer(), diff --git a/azurerm/internal/services/cosmos/resource_arm_cosmosdb_gremlin_database.go b/azurerm/internal/services/cosmos/resource_arm_cosmosdb_gremlin_database.go new file mode 100644 index 000000000000..bdabcaf2bb14 --- /dev/null +++ b/azurerm/internal/services/cosmos/resource_arm_cosmosdb_gremlin_database.go @@ -0,0 +1,249 @@ +package cosmos + +import ( + "fmt" + "log" + "strconv" + "time" + + "github.com/Azure/azure-sdk-for-go/services/cosmos-db/mgmt/2015-04-08/documentdb" + "github.com/hashicorp/go-azure-helpers/response" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmCosmosGremlinDatabase() *schema.Resource { + return &schema.Resource{ + Create: resourceArmCosmosGremlinDatabaseCreate, + Update: resourceArmCosmosGremlinDatabaseUpdate, + Read: resourceArmCosmosGremlinDatabaseRead, + Delete: resourceArmCosmosGremlinDatabaseDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.CosmosEntityName, + }, + + "resource_group_name": azure.SchemaResourceGroupName(), + + "account_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.CosmosAccountName, + }, + + "throughput": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ValidateFunc: validate.CosmosThroughput, + }, + }, + } +} + +func resourceArmCosmosGremlinDatabaseCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Cosmos.DatabaseClient + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) + defer cancel() + + name := d.Get("name").(string) + resourceGroup := d.Get("resource_group_name").(string) + account := d.Get("account_name").(string) + + if features.ShouldResourcesBeImported() { + existing, err := client.GetGremlinDatabase(ctx, resourceGroup, account, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("Error checking for presence of creating Gremlin Database %s (Account %s): %+v", name, account, err) + } + } else { + id, err := azure.CosmosGetIDFromResponse(existing.Response) + if err != nil { + return fmt.Errorf("Error generating import ID for Cosmos Gremlin Database '%s' (Account %s)", name, account) + } + + return tf.ImportAsExistsError("azurerm_cosmosdb_gremlin_database", id) + } + } + + db := documentdb.GremlinDatabaseCreateUpdateParameters{ + GremlinDatabaseCreateUpdateProperties: &documentdb.GremlinDatabaseCreateUpdateProperties{ + Resource: &documentdb.GremlinDatabaseResource{ + ID: &name, + }, + Options: map[string]*string{}, + }, + } + + if throughput, hasThroughput := d.GetOk("throughput"); hasThroughput { + db.GremlinDatabaseCreateUpdateProperties.Options = map[string]*string{ + "throughput": utils.String(strconv.Itoa(throughput.(int))), + } + } + + future, err := client.CreateUpdateGremlinDatabase(ctx, resourceGroup, account, name, db) + if err != nil { + return fmt.Errorf("Error issuing create/update request for Gremlin Database %s (Account %s): %+v", name, account, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting on create/update future for Cosmos Gremlin Database %s (Account %s): %+v", name, account, err) + } + + resp, err := client.GetGremlinDatabase(ctx, resourceGroup, account, name) + if err != nil { + return fmt.Errorf("Error making get request for Cosmos Gremlin Database %s (Account %s) ID: %v", name, account, err) + } + + id, err := azure.CosmosGetIDFromResponse(resp.Response) + if err != nil { + return fmt.Errorf("Error retrieving the ID for Cosmos Gremlin Database '%s' (Account %s) ID: %v", name, account, err) + } + d.SetId(id) + + return resourceArmCosmosGremlinDatabaseRead(d, meta) +} + +func resourceArmCosmosGremlinDatabaseUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Cosmos.DatabaseClient + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := azure.ParseCosmosDatabaseID(d.Id()) + if err != nil { + return err + } + + db := documentdb.GremlinDatabaseCreateUpdateParameters{ + GremlinDatabaseCreateUpdateProperties: &documentdb.GremlinDatabaseCreateUpdateProperties{ + Resource: &documentdb.GremlinDatabaseResource{ + ID: &id.Database, + }, + Options: map[string]*string{}, + }, + } + + future, err := client.CreateUpdateGremlinDatabase(ctx, id.ResourceGroup, id.Account, id.Database, db) + if err != nil { + return fmt.Errorf("Error issuing create/update request for Cosmos Gremlin Database %s (Account %s): %+v", id.Database, id.Account, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting on create/update future for Cosmos Gremlin Database %s (Account %s): %+v", id.Database, id.Account, err) + } + + if d.HasChange("throughput") { + throughputParameters := documentdb.ThroughputUpdateParameters{ + ThroughputUpdateProperties: &documentdb.ThroughputUpdateProperties{ + Resource: &documentdb.ThroughputResource{ + Throughput: utils.Int32(int32(d.Get("throughput").(int))), + }, + }, + } + + throughputFuture, err := client.UpdateGremlinDatabaseThroughput(ctx, id.ResourceGroup, id.Account, id.Database, throughputParameters) + if err != nil { + if response.WasNotFound(throughputFuture.Response()) { + return fmt.Errorf("Error setting Throughput for Cosmos Gremlin Database %s (Account %s): %+v - "+ + "If the collection has not been created with and initial throughput, you cannot configure it later.", id.Database, id.Account, err) + } + } + + if err = throughputFuture.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting on ThroughputUpdate future for Cosmos Gremlin Database %s (Account %s, Database %s): %+v", id.Database, id.Account, id.Database, err) + } + } + + if _, err = client.GetGremlinDatabase(ctx, id.ResourceGroup, id.Account, id.Database); err != nil { + return fmt.Errorf("Error making get request for Cosmos Gremlin Database %s (Account %s): %+v", id.Database, id.Account, err) + } + + return resourceArmCosmosGremlinDatabaseRead(d, meta) +} + +func resourceArmCosmosGremlinDatabaseRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Cosmos.DatabaseClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := azure.ParseCosmosDatabaseID(d.Id()) + if err != nil { + return err + } + + resp, err := client.GetGremlinDatabase(ctx, id.ResourceGroup, id.Account, id.Database) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[INFO] Error reading Cosmos Gremlin Database %s (Account %s) - removing from state", id.Database, id.Account) + d.SetId("") + return nil + } + + return fmt.Errorf("Error reading Cosmos Gremlin Database %s (Account %s): %+v", id.Database, id.Account, err) + } + + d.Set("resource_group_name", id.ResourceGroup) + d.Set("account_name", id.Account) + if props := resp.GremlinDatabaseProperties; props != nil { + d.Set("name", props.ID) + } + + throughputResp, err := client.GetGremlinDatabaseThroughput(ctx, id.ResourceGroup, id.Account, id.Database) + if err != nil { + if !utils.ResponseWasNotFound(throughputResp.Response) { + return fmt.Errorf("Error reading Throughput on Cosmos Gremlin Database %s (Account %s): %+v", id.Database, id.Account, err) + } else { + d.Set("throughput", nil) + } + } else { + d.Set("throughput", throughputResp.Throughput) + } + + return nil +} + +func resourceArmCosmosGremlinDatabaseDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Cosmos.DatabaseClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := azure.ParseCosmosDatabaseID(d.Id()) + if err != nil { + return err + } + + future, err := client.DeleteGremlinDatabase(ctx, id.ResourceGroup, id.Account, id.Database) + if err != nil { + if !response.WasNotFound(future.Response()) { + return fmt.Errorf("Error deleting Cosmos Gremlin Database %s (Account %s): %+v", id.Database, id.Account, err) + } + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting on delete future for Cosmos Gremlin Database %s (Account %s): %+v", id.Database, id.Account, err) + } + + return nil +} diff --git a/azurerm/internal/services/cosmos/tests/resource_arm_cosmosdb_gremlin_database_test.go b/azurerm/internal/services/cosmos/tests/resource_arm_cosmosdb_gremlin_database_test.go new file mode 100644 index 000000000000..65fd940b64af --- /dev/null +++ b/azurerm/internal/services/cosmos/tests/resource_arm_cosmosdb_gremlin_database_test.go @@ -0,0 +1,181 @@ +package tests + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMCosmosGremlinDatabase_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_cosmosdb_gremlin_database", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMCosmosGremlinDatabaseDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMCosmosGremlinDatabase_basic(data), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMCosmosGremlinDatabaseExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMCosmosGremlinDatabase_requiresImport(t *testing.T) { + if !features.ShouldResourcesBeImported() { + t.Skip("Skipping since resources aren't required to be imported") + return + } + + data := acceptance.BuildTestData(t, "azurerm_cosmosdb_gremlin_database", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMCosmosGremlinDatabaseDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMCosmosGremlinDatabase_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMCosmosGremlinDatabaseExists(data.ResourceName), + ), + }, + { + Config: testAccAzureRMCosmosDatabase_requiresImport(data), + ExpectError: acceptance.RequiresImportError("azurerm_cosmosdb_gremlin_database"), + }, + }, + }) +} + +func TestAccAzureRMCosmosGremlinDatabase_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_cosmosdb_gremlin_database", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMCosmosGremlinDatabaseDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMCosmosGremlinDatabase_complete(data, 700), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMCosmosGremlinDatabaseExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "throughput", "700"), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMCosmosGremlinDatabase_complete(data, 1700), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMCosmosGremlinDatabaseExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "throughput", "1700"), + ), + }, + data.ImportStep(), + }, + }) +} + +func testCheckAzureRMCosmosGremlinDatabaseDestroy(s *terraform.State) error { + client := acceptance.AzureProvider.Meta().(*clients.Client).Cosmos.DatabaseClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_cosmosdb_gremlin_database" { + continue + } + + name := rs.Primary.Attributes["name"] + account := rs.Primary.Attributes["account_name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + resp, err := client.GetGremlinDatabase(ctx, resourceGroup, account, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: Error checking destroy for Cosmos Gremlin Database %s (Account %s) still exists:\n%v", name, account, err) + } + } + + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Cosmos Gremlin Database %s (Account %s): still exist:\n%#v", name, account, resp) + } + } + + return nil +} + +func testCheckAzureRMCosmosGremlinDatabaseExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := acceptance.AzureProvider.Meta().(*clients.Client).Cosmos.DatabaseClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + name := rs.Primary.Attributes["name"] + account := rs.Primary.Attributes["account_name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + resp, err := client.GetGremlinDatabase(ctx, resourceGroup, account, name) + if err != nil { + return fmt.Errorf("Bad: Get on cosmosAccountsClient: %+v", err) + } + + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("Bad: Cosmos database '%s' (Account: '%s') does not exist", name, account) + } + + return nil + } +} + +func testAccAzureRMCosmosGremlinDatabase_basic(data acceptance.TestData) string { + return fmt.Sprintf(` + %[1]s + + resource "azurerm_cosmosdb_gremlin_database" "test" { + name = "acctest-%[2]d" + resource_group_name = "${azurerm_cosmosdb_account.test.resource_group_name}" + account_name = "${azurerm_cosmosdb_account.test.name}" + } + `, testAccAzureRMCosmosDBAccount_capabilityGremlin(data), data.RandomInteger) +} + +func testAccAzureRMCosmosDatabase_requiresImport(data acceptance.TestData) string { + return fmt.Sprintf(` + %s + + resource "azurerm_cosmosdb_gremlin_database" "import" { + name = "${azurerm_cosmosdb_database.test.name}" + resource_group_name = "${azurerm_cosmosdb_database.test.name" + account_name = "${azurerm_cosmosdb_database.test.name}" + } + `, testAccAzureRMCosmosGremlinDatabase_basic(data)) +} + +func testAccAzureRMCosmosGremlinDatabase_complete(data acceptance.TestData, throughput int) string { + return fmt.Sprintf(` + %[1]s + + resource "azurerm_cosmosdb_gremlin_database" "test" { + name = "acctest-%[2]d" + resource_group_name = "${azurerm_cosmosdb_account.test.resource_group_name}" + account_name = "${azurerm_cosmosdb_account.test.name}" + throughput = %[3]d + } + `, testAccAzureRMCosmosDBAccount_capabilityGremlin(data), data.RandomInteger, throughput) +} diff --git a/website/docs/r/cosmosdb_gremlin_database.html.markdown b/website/docs/r/cosmosdb_gremlin_database.html.markdown new file mode 100644 index 000000000000..8141c720b3b1 --- /dev/null +++ b/website/docs/r/cosmosdb_gremlin_database.html.markdown @@ -0,0 +1,55 @@ +--- +subcategory: "CosmosDB (DocumentDB)" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_cosmosdb_gremlin_database" +sidebar_current: "docs-azurerm-resource-cosmosdb-gremlin-database" +description: |- + Manages a Gremlin Database within a Cosmos DB Account. +--- + +# azurerm_cosmosdb_gremlin_database + +Manages a Gremlin Database within a Cosmos DB Account. + +## Example Usage + +```hcl +data "azurerm_cosmosdb_account" "example" { + name = "tfex-cosmosdb-account" + resource_group_name = "tfex-cosmosdb-account-rg" +} + +resource "azurerm_cosmosdb_gremlin_database" "example" { + name = "tfex-cosmos-gremlin-db" + resource_group_name = "${data.azurerm_cosmosdb_account.example.resource_group_name}" + account_name = "${data.azurerm_cosmosdb_account.example.name}" + throughput = 400 +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) Specifies the name of the Cosmos DB Gremlin Database. Changing this forces a new resource to be created. + +* `resource_group_name` - (Required) The name of the resource group in which the Cosmos DB Gremlin Database is created. Changing this forces a new resource to be created. + +* `account_name` - (Required) The name of the CosmosDB Account to create the Gremlin Database within. Changing this forces a new resource to be created. + +* `throughput` - (Optional) The throughput of the Gremlin database (RU/s). Must be set in increments of `100`. The minimum value is `400`. This must be set upon database creation otherwise it cannot be updated without a manual terraform destroy-apply. + + +## Attributes Reference + +The following attributes are exported: + +* `id` - the Cosmos DB Gremlin Database ID. + +## Import + +Cosmos Mongo Database can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_cosmosdb_gremlin_database.db1 /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg1/providers/Microsoft.DocumentDB/databaseAccounts/account1/apis/gremlin/databases/db1 +```