diff --git a/azurerm/helpers/azure/cosmos.go b/azurerm/helpers/azure/cosmos.go index c3a9b09675a6..14c2e8c54cbf 100644 --- a/azurerm/helpers/azure/cosmos.go +++ b/azurerm/helpers/azure/cosmos.go @@ -155,3 +155,25 @@ func ParseCosmosTableID(id string) (*CosmosTableID, error) { Table: table, }, nil } + +type CosmosGremlinGraphID struct { + CosmosDatabaseID + Graph string +} + +func ParseCosmosGramlinGraphID(id string) (*CosmosGremlinGraphID, error) { + subid, err := ParseCosmosDatabaseID(id) + if err != nil { + return nil, err + } + + graph, ok := subid.Path["graphs"] + if !ok { + return nil, fmt.Errorf("Error: Unable to parse Cosmos Gremlin Graph Resource ID: Graph is missing from: %s", id) + } + + return &CosmosGremlinGraphID{ + CosmosDatabaseID: *subid, + Graph: graph, + }, nil +} diff --git a/azurerm/internal/services/cosmos/registration.go b/azurerm/internal/services/cosmos/registration.go index f6579c305d90..97aff3a4de0e 100644 --- a/azurerm/internal/services/cosmos/registration.go +++ b/azurerm/internal/services/cosmos/registration.go @@ -24,6 +24,7 @@ func (r Registration) SupportedResources() map[string]*schema.Resource { "azurerm_cosmosdb_account": resourceArmCosmosDbAccount(), "azurerm_cosmosdb_cassandra_keyspace": resourceArmCosmosDbCassandraKeyspace(), "azurerm_cosmosdb_gremlin_database": resourceArmCosmosGremlinDatabase(), + "azurerm_cosmosdb_gremlin_graph": resourceArmCosmosDbGremlinGraph(), "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_graph.go b/azurerm/internal/services/cosmos/resource_arm_cosmosdb_gremlin_graph.go new file mode 100644 index 000000000000..f68733e72ae5 --- /dev/null +++ b/azurerm/internal/services/cosmos/resource_arm_cosmosdb_gremlin_graph.go @@ -0,0 +1,594 @@ +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/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/terraform-providers/terraform-provider-azuread/azuread/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress" + "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 resourceArmCosmosDbGremlinGraph() *schema.Resource { + return &schema.Resource{ + Create: resourceArmCosmosDbGremlinGraphCreate, + Read: resourceArmCosmosDbGremlinGraphRead, + Update: resourceArmCosmosDbGremlinGraphUpdate, + Delete: resourceArmCosmosDbGremlinGraphDelete, + + 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, + }, + + "database_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.CosmosEntityName, + }, + + "throughput": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ValidateFunc: validate.CosmosThroughput, + }, + + "partition_key_path": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validate.NoEmptyStrings, + }, + + "index_policy": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "automatic": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + + "indexing_mode": { + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: suppress.CaseDifference, // Open issue https://github.com/Azure/azure-sdk-for-go/issues/6603 + ValidateFunc: validation.StringInSlice([]string{ + string(documentdb.Consistent), + string(documentdb.Lazy), + string(documentdb.None), + }, false), + }, + + "included_paths": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validate.NoEmptyStrings, + }, + Set: schema.HashString, + }, + + "excluded_paths": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validate.NoEmptyStrings, + }, + Set: schema.HashString, + }, + }, + }, + }, + + "conflict_resolution_policy": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "mode": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(documentdb.LastWriterWins), + string(documentdb.Custom), + }, false), + }, + + "conflict_resolution_path": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validate.NoEmptyStrings, + }, + + "conflict_resolution_procedure": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validate.NoEmptyStrings, + }, + }, + }, + }, + + "unique_key": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "paths": { + Type: schema.TypeSet, + Required: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validate.NoEmptyStrings, + }, + }, + }, + }, + }, + }, + } +} + +func resourceArmCosmosDbGremlinGraphCreate(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) + database := d.Get("database_name").(string) + account := d.Get("account_name").(string) + partitionkeypaths := d.Get("partition_key_path").(string) + + if features.ShouldResourcesBeImported() { + existing, err := client.GetGremlinGraph(ctx, resourceGroup, account, database, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("Error checking for presence of creating Cosmos Gremlin Graph %s (Account: %s, Database: %s): %+v", name, account, database, err) + } + } else { + id, err := azure.CosmosGetIDFromResponse(existing.Response) + if err != nil { + return fmt.Errorf("Error getting import ID for Cosmos Gremlin Graph '%s' (Account: %s, Database: %s)", name, account, database) + } + + return tf.ImportAsExistsError("azurerm_cosmosdb_gremlin_graph", id) + } + } + + db := documentdb.GremlinGraphCreateUpdateParameters{ + GremlinGraphCreateUpdateProperties: &documentdb.GremlinGraphCreateUpdateProperties{ + Resource: &documentdb.GremlinGraphResource{ + ID: &name, + IndexingPolicy: expandAzureRmCosmosDbGrelinGraphIndexingPolicy(d), + ConflictResolutionPolicy: expandAzureRmCosmosDbGremlinGraphConflicResolutionPolicy(d), + }, + Options: map[string]*string{}, + }, + } + + if partitionkeypaths != "" { + db.GremlinGraphCreateUpdateProperties.Resource.PartitionKey = &documentdb.ContainerPartitionKey{ + Paths: &[]string{partitionkeypaths}, + } + } + + if keys := expandAzureRmCosmosDbGremlinGraphUniqueKeys(d.Get("unique_key").(*schema.Set)); keys != nil { + db.GremlinGraphCreateUpdateProperties.Resource.UniqueKeyPolicy = &documentdb.UniqueKeyPolicy{ + UniqueKeys: keys, + } + } + + if throughput, hasThroughput := d.GetOk("throughput"); hasThroughput { + db.GremlinGraphCreateUpdateProperties.Options = map[string]*string{ + "throughput": utils.String(strconv.Itoa(throughput.(int))), + } + } + + future, err := client.CreateUpdateGremlinGraph(ctx, resourceGroup, account, database, name, db) + if err != nil { + return fmt.Errorf("Error issuing create/update request for Cosmos Gremlin Graph %s (Account: %s, Database: %s): %+v", name, account, database, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting on create/update future for Cosmos Gremlin Graph%s (Account: %s, Database:%s): %+v", name, account, database, err) + } + + resp, err := client.GetGremlinGraph(ctx, resourceGroup, account, database, name) + if err != nil { + return fmt.Errorf("Error making get request for Cosmos Gremlin Graph %s (Account: %s, Database:%s): %+v", name, account, database, err) + } + + id, err := azure.CosmosGetIDFromResponse(resp.Response) + if err != nil { + return fmt.Errorf("Error retrieving the ID for Cosmos Gramlin Graph '%s' (Account: %s, Database:%s) ID: %v", name, account, database, err) + } + d.SetId(id) + + return resourceArmCosmosDbGremlinGraphRead(d, meta) +} + +func resourceArmCosmosDbGremlinGraphUpdate(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.ParseCosmosGramlinGraphID(d.Id()) + if err != nil { + return err + } + + partitionkeypaths := d.Get("partition_key_path").(string) + + db := documentdb.GremlinGraphCreateUpdateParameters{ + GremlinGraphCreateUpdateProperties: &documentdb.GremlinGraphCreateUpdateProperties{ + Resource: &documentdb.GremlinGraphResource{ + ID: &id.Graph, + IndexingPolicy: expandAzureRmCosmosDbGrelinGraphIndexingPolicy(d), + ConflictResolutionPolicy: expandAzureRmCosmosDbGremlinGraphConflicResolutionPolicy(d), + }, + Options: map[string]*string{}, + }, + } + + if partitionkeypaths != "" { + db.GremlinGraphCreateUpdateProperties.Resource.PartitionKey = &documentdb.ContainerPartitionKey{ + Paths: &[]string{partitionkeypaths}, + } + } + + if keys := expandAzureRmCosmosDbGremlinGraphUniqueKeys(d.Get("unique_key").(*schema.Set)); keys != nil { + db.GremlinGraphCreateUpdateProperties.Resource.UniqueKeyPolicy = &documentdb.UniqueKeyPolicy{ + UniqueKeys: keys, + } + } + + future, err := client.CreateUpdateGremlinGraph(ctx, id.ResourceGroup, id.Account, id.Database, id.Graph, db) + if err != nil { + return fmt.Errorf("Error issuing create/update request for Cosmos Gremlin Graph %s (Account: %s, Database:%s): %+v", id.Graph, id.Account, id.Database, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting on create/update future for Cosmos Gremlin Graph %s (Account: %s, Database:%s): %+v", id.Graph, id.Account, id.Database, 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.UpdateGremlinGraphThroughput(ctx, id.ResourceGroup, id.Account, id.Database, id.Graph, throughputParameters) + if err != nil { + if response.WasNotFound(throughputFuture.Response()) { + return fmt.Errorf("Error setting Throughput for Cosmos Gremlin Graph %s (Account: %s, Database:%s): %+v - "+ + "If the graph has not been created with an initial throughput, you cannot configure it later.", id.Graph, id.Account, id.Database, err) + } + } + + if err = throughputFuture.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting on ThroughputUpdate future for Cosmos Gremlin Graph %s (Account: %s, Database:%s): %+v", id.Graph, id.Account, id.Database, err) + } + } + + return resourceArmCosmosDbGremlinGraphRead(d, meta) +} + +func resourceArmCosmosDbGremlinGraphRead(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.ParseCosmosGramlinGraphID(d.Id()) + if err != nil { + return err + } + + resp, err := client.GetGremlinGraph(ctx, id.ResourceGroup, id.Account, id.Database, id.Graph) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[INFO] Error reading Cosmos Gremlin Graph %s (Account %s) - removing from state", id.Graph, id.Account) + d.SetId("") + return nil + } + + return fmt.Errorf("Error reading Cosmos Gremlin Graph %s (Account %s): %+v", id.Graph, id.Account, err) + } + + d.Set("name", id.Graph) + d.Set("resource_group_name", id.ResourceGroup) + d.Set("account_name", id.Account) + d.Set("database_name", id.Database) + + if props := resp.GremlinGraphProperties; props != nil { + if pk := props.PartitionKey; pk != nil { + if paths := pk.Paths; paths != nil { + if len(*paths) > 1 { + return fmt.Errorf("Error reading PartitionKey Paths, more than 1 returned") + } else if len(*paths) == 1 { + d.Set("partition_key_path", (*paths)[0]) + } + } + } + + if ip := props.IndexingPolicy; ip != nil { + if err := d.Set("index_policy", flattenAzureRmCosmosDBGremlinGraphIndexingPolicy(props.IndexingPolicy)); err != nil { + return fmt.Errorf("Error setting `index_policy`: %+v", err) + } + } + + if crp := props.ConflictResolutionPolicy; crp != nil { + if err := d.Set("conflict_resolution_policy", flattenAzureRmCosmosDbGremlinGraphConflictResolutionPolicy(props.ConflictResolutionPolicy)); err != nil { + return fmt.Errorf("Error setting `conflict_resolution_policy`: %+v", err) + } + } + + if ukp := props.UniqueKeyPolicy; ukp != nil { + if err := d.Set("unique_key", flattenCosmosGremlinGraphUniqueKeys(ukp.UniqueKeys)); err != nil { + return fmt.Errorf("Error setting `unique_key`: %+v", err) + } + } + } + + throughputResp, err := client.GetGremlinGraphThroughput(ctx, id.ResourceGroup, id.Account, id.Database, id.Graph) + if err != nil { + if !utils.ResponseWasNotFound(throughputResp.Response) { + return fmt.Errorf("Error reading Throughput on Gremlin Graph '%s' (Account: %s, Database:%s) ID: %v", id.Graph, id.Account, id.Database, err) + } else { + d.Set("throughput", nil) + } + } else { + d.Set("throughput", throughputResp.Throughput) + } + + return nil +} + +func resourceArmCosmosDbGremlinGraphDelete(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.ParseCosmosGramlinGraphID(d.Id()) + if err != nil { + return err + } + + future, err := client.DeleteGremlinGraph(ctx, id.ResourceGroup, id.Account, id.Database, id.Graph) + if err != nil { + if !response.WasNotFound(future.Response()) { + return fmt.Errorf("Error deleting Cosmos Gremlin Graph %s (Account %s): %+v", id.Database, id.Graph, err) + } + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting on delete future for Comos Gremlin Graph %s (Account %s): %+v", id.Database, id.Account, err) + } + + return nil +} + +func expandAzureRmCosmosDbGrelinGraphIndexingPolicy(d *schema.ResourceData) *documentdb.IndexingPolicy { + i := d.Get("index_policy").([]interface{}) + if len(i) <= 0 || i[0] == nil { + return nil + } + + input := i[0].(map[string]interface{}) + indexingPolicy := input["indexing_mode"].(string) + policy := &documentdb.IndexingPolicy{ + IndexingMode: documentdb.IndexingMode(indexingPolicy), + IncludedPaths: expandAzureRmCosmosDbGrelimGraphIncludedPath(input), + ExcludedPaths: expandAzureRmCosmosDbGremlinGraphExcludedPath(input), + } + + if automatic, ok := input["automatic"].(bool); ok { + policy.Automatic = utils.Bool(automatic) + } + + return policy +} + +func expandAzureRmCosmosDbGremlinGraphConflicResolutionPolicy(d *schema.ResourceData) *documentdb.ConflictResolutionPolicy { + i := d.Get("conflict_resolution_policy").([]interface{}) + if len(i) <= 0 || i[0] == nil { + return nil + } + + input := i[0].(map[string]interface{}) + conflictResolutionMode := input["mode"].(string) + conflict := &documentdb.ConflictResolutionPolicy{ + Mode: documentdb.ConflictResolutionMode(conflictResolutionMode), + } + + if conflictResolutionPath, ok := input["conflict_resolution_path"].(string); ok { + conflict.ConflictResolutionPath = utils.String(conflictResolutionPath) + } + + if conflictResolutionProcedure, ok := input["conflict_resolution_procedure"].(string); ok { + conflict.ConflictResolutionProcedure = utils.String(conflictResolutionProcedure) + } + + return conflict +} + +func expandAzureRmCosmosDbGrelimGraphIncludedPath(input map[string]interface{}) *[]documentdb.IncludedPath { + includedPath := input["included_paths"].(*schema.Set).List() + paths := make([]documentdb.IncludedPath, len(includedPath)) + + for i, pathConfig := range includedPath { + attrs := pathConfig.(string) + path := documentdb.IncludedPath{ + Path: utils.String(attrs), + } + paths[i] = path + } + + return &paths +} + +func expandAzureRmCosmosDbGremlinGraphExcludedPath(input map[string]interface{}) *[]documentdb.ExcludedPath { + excludedPath := input["excluded_paths"].(*schema.Set).List() + paths := make([]documentdb.ExcludedPath, len(excludedPath)) + + for i, pathConfig := range excludedPath { + attrs := pathConfig.(string) + path := documentdb.ExcludedPath{ + Path: utils.String(attrs), + } + paths[i] = path + } + + return &paths +} + +func expandAzureRmCosmosDbGremlinGraphUniqueKeys(s *schema.Set) *[]documentdb.UniqueKey { + i := s.List() + if len(i) <= 0 || i[0] == nil { + return nil + } + + keys := make([]documentdb.UniqueKey, 0) + for _, k := range i { + key := k.(map[string]interface{}) + + paths := key["paths"].(*schema.Set).List() + if len(paths) == 0 { + continue + } + + keys = append(keys, documentdb.UniqueKey{ + Paths: utils.ExpandStringSlice(paths), + }) + } + + return &keys +} + +func flattenAzureRmCosmosDBGremlinGraphIndexingPolicy(input *documentdb.IndexingPolicy) []interface{} { + if input == nil { + return []interface{}{} + } + indexPolicy := make(map[string]interface{}) + + indexPolicy["automatic"] = input.Automatic + indexPolicy["indexing_mode"] = string(input.IndexingMode) + indexPolicy["included_paths"] = schema.NewSet(schema.HashString, flattenAzureRmCosmosDBGremlinGraphIncludedPaths(input.IncludedPaths)) + indexPolicy["excluded_paths"] = schema.NewSet(schema.HashString, flattenAzureRmCosmosDBGremlinGraphExcludedPaths(input.ExcludedPaths)) + + return []interface{}{indexPolicy} +} + +func flattenAzureRmCosmosDBGremlinGraphIncludedPaths(input *[]documentdb.IncludedPath) []interface{} { + if input == nil { + return []interface{}{} + } + + includedPaths := make([]interface{}, 0) + for _, includedPath := range *input { + if includedPath.Path == nil { + continue + } + + includedPaths = append(includedPaths, *includedPath.Path) + } + + return includedPaths +} + +func flattenAzureRmCosmosDBGremlinGraphExcludedPaths(input *[]documentdb.ExcludedPath) []interface{} { + if input == nil { + return []interface{}{} + } + + excludedPaths := make([]interface{}, 0) + for _, excludedPath := range *input { + if excludedPath.Path == nil { + continue + } + + excludedPaths = append(excludedPaths, *excludedPath.Path) + } + + return excludedPaths +} + +func flattenAzureRmCosmosDbGremlinGraphConflictResolutionPolicy(input *documentdb.ConflictResolutionPolicy) []interface{} { + if input == nil { + return []interface{}{} + } + conflictResolutionPolicy := make(map[string]interface{}) + + conflictResolutionPolicy["mode"] = string(input.Mode) + conflictResolutionPolicy["conflict_resolution_path"] = input.ConflictResolutionPath + conflictResolutionPolicy["conflict_resolution_procedure"] = input.ConflictResolutionProcedure + + return []interface{}{conflictResolutionPolicy} +} + +func flattenCosmosGremlinGraphUniqueKeys(keys *[]documentdb.UniqueKey) *[]map[string]interface{} { + if keys == nil { + return nil + } + + slice := make([]map[string]interface{}, 0) + for _, k := range *keys { + if k.Paths == nil { + continue + } + + slice = append(slice, map[string]interface{}{ + "paths": *k.Paths, + }) + } + + return &slice +} diff --git a/azurerm/internal/services/cosmos/tests/resource_arm_cosmosdb_gremlin_graph_test.go b/azurerm/internal/services/cosmos/tests/resource_arm_cosmosdb_gremlin_graph_test.go new file mode 100644 index 000000000000..00658df68a0f --- /dev/null +++ b/azurerm/internal/services/cosmos/tests/resource_arm_cosmosdb_gremlin_graph_test.go @@ -0,0 +1,305 @@ +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 TestAccAzureRMCosmosDbGremlinGraph_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_cosmosdb_gremlin_graph", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMCosmosDbGremlinGraphDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMCosmosDbGremlinGraph_basic(data), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRmCosmosDbGremlinGraphExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMCosmosDbGremlinGraph_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_graph", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMCosmosDbGremlinGraphDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMCosmosDbGremlinGraph_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRmCosmosDbGremlinGraphExists(data.ResourceName), + ), + }, + { + Config: testAccAzureRMCosmosDbGremlinGraph_requiresImport(data), + ExpectError: acceptance.RequiresImportError("azurerm_cosmosdb_gremlin_graph"), + }, + }, + }) +} + +func TestAccAzureRMCosmosDbGremlinGraph_customConflictResolutionPolicy(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_cosmosdb_gremlin_graph", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMCosmosDbGremlinGraphDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMCosmosDbGremlinGraph_customConflictResolutionPolicy(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRmCosmosDbGremlinGraphExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMCosmosDbGremlinGraph_indexPolicy(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_cosmosdb_gremlin_graph", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMCosmosDbGremlinGraphDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMCosmosDbGremlinGraph_indexPolicy(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRmCosmosDbGremlinGraphExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMCosmosDbGremlinGraph_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_cosmosdb_gremlin_graph", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMCosmosDbGremlinGraphDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMCosmosDbGremlinGraph_update(data, 700), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRmCosmosDbGremlinGraphExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "throughput", "700"), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMCosmosDbGremlinGraph_update(data, 1700), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckAzureRmCosmosDbGremlinGraphExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "throughput", "1700"), + ), + }, + data.ImportStep(), + }, + }) +} + +func testCheckAzureRMCosmosDbGremlinGraphDestroy(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_graph" { + continue + } + + name := rs.Primary.Attributes["name"] + account := rs.Primary.Attributes["account_name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + database := rs.Primary.Attributes["database_name"] + + resp, err := client.GetGremlinGraph(ctx, resourceGroup, account, database, name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: Error checking destroy for Cosmos Gremlin Graph %s (Account %s) still exists:\n%v", name, account, err) + } + } + + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Cosmos Gremlin Graph %s (Account %s) still exists:\n%#v", name, account, resp) + } + } + + return nil +} + +func testCheckAzureRmCosmosDbGremlinGraphExists(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 fount: %s", resourceName) + } + + name := rs.Primary.Attributes["name"] + account := rs.Primary.Attributes["account_name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + database := rs.Primary.Attributes["database_name"] + + resp, err := client.GetGremlinGraph(ctx, resourceGroup, database, account, name) + if err != nil { + return fmt.Errorf("Bad: Get on cosmosAccountsClient: %+v", err) + } + + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("Bad: Cosmos Graph '%s' (Account: '%s') does not exist", name, account) + } + return nil + } +} + +func testAccAzureRMCosmosDbGremlinGraph_basic(data acceptance.TestData) string { + return fmt.Sprintf(` + %[1]s + + resource "azurerm_cosmosdb_gremlin_graph" "test" { + name = "acctest-CGRPC-%[2]d" + resource_group_name = "${azurerm_cosmosdb_account.test.resource_group_name}" + account_name = "${azurerm_cosmosdb_account.test.name}" + database_name = "${azurerm_cosmosdb_gremlin_database.test.name}" + throughput = 400 + + index_policy { + automatic = true + indexing_mode = "Consistent" + included_paths = ["/*"] + excluded_paths = ["/\"_etag\"/?"] + } + + conflict_resolution_policy { + mode = "LastWriterWins" + conflict_resolution_path = "/_ts" + } + + } + `, testAccAzureRMCosmosGremlinDatabase_basic(data), data.RandomInteger) +} + +func testAccAzureRMCosmosDbGremlinGraph_requiresImport(data acceptance.TestData) string { + return fmt.Sprintf(` + %s + + resource "azurerm_cosmosdb_gremlin_graph" "import" { + name = "${azurerm_cosmosdb_gremlin_graph.test.name}" + resource_group_name = "${azurerm_cosmosdb_account.test.resource_group_name}" + account_name = "${azurerm_cosmosdb_account.test.name}" + database_name = "${azurerm_cosmosdb_gremlin_database.test.name}" + } + `, testAccAzureRMCosmosDbGremlinGraph_basic(data)) +} + +func testAccAzureRMCosmosDbGremlinGraph_customConflictResolutionPolicy(data acceptance.TestData) string { + return fmt.Sprintf(` + %[1]s + + resource "azurerm_cosmosdb_gremlin_graph" "test" { + name = "acctest-CGRPC-%[2]d" + resource_group_name = "${azurerm_cosmosdb_account.test.resource_group_name}" + account_name = "${azurerm_cosmosdb_account.test.name}" + database_name = "${azurerm_cosmosdb_gremlin_database.test.name}" + throughput = 400 + + index_policy { + automatic = true + indexing_mode = "Consistent" + included_paths = ["/*"] + excluded_paths = ["/\"_etag\"/?"] + } + + conflict_resolution_policy { + mode = "Custom" + conflict_resolution_procedure = "dbs/{0}/colls/{1}/sprocs/{2}" + } + } + `, testAccAzureRMCosmosGremlinDatabase_basic(data), data.RandomInteger) +} + +func testAccAzureRMCosmosDbGremlinGraph_indexPolicy(data acceptance.TestData) string { + return fmt.Sprintf(` + %[1]s + + resource "azurerm_cosmosdb_gremlin_graph" "test" { + name = "acctest-CGRPC-%[2]d" + resource_group_name = "${azurerm_cosmosdb_account.test.resource_group_name}" + account_name = "${azurerm_cosmosdb_account.test.name}" + database_name = "${azurerm_cosmosdb_gremlin_database.test.name}" + throughput = 400 + + index_policy { + automatic = false + indexing_mode = "None" + } + + conflict_resolution_policy { + mode = "LastWriterWins" + conflict_resolution_path = "/_ts" + } + } + `, testAccAzureRMCosmosGremlinDatabase_basic(data), data.RandomInteger) +} + +func testAccAzureRMCosmosDbGremlinGraph_update(data acceptance.TestData, throughput int) string { + return fmt.Sprintf(` + %[1]s + + resource "azurerm_cosmosdb_gremlin_graph" "test" { + name = "acctest-CGRPC-%[2]d" + resource_group_name = "${azurerm_cosmosdb_account.test.resource_group_name}" + account_name = "${azurerm_cosmosdb_account.test.name}" + database_name = "${azurerm_cosmosdb_gremlin_database.test.name}" + partition_key_path = "/test" + throughput = %[3]d + + index_policy { + automatic = true + indexing_mode = "Consistent" + included_paths = ["/*"] + excluded_paths = ["/\"_etag\"/?"] + } + + conflict_resolution_policy { + mode = "LastWriterWins" + conflict_resolution_path = "/_ts" + } + + unique_key { + paths = ["/definition/id1", "/definition/id2"] + } + } + `, testAccAzureRMCosmosGremlinDatabase_basic(data), data.RandomInteger, throughput) +} diff --git a/website/azurerm.erb b/website/azurerm.erb index 0caf52f598cc..5accc117f8d4 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -928,6 +928,12 @@
  • azurerm_cosmosdb_cassandra_keyspace
  • +
  • + azurerm_cosmosdb_gremlin_database +
  • +
  • + azurerm_cosmosdb_gremlin_graph +
  • azurerm_cosmosdb_mongo_collection
  • diff --git a/website/docs/r/cosmosdb_gremlin_graph.html.markdown b/website/docs/r/cosmosdb_gremlin_graph.html.markdown new file mode 100644 index 000000000000..14af3adcaa80 --- /dev/null +++ b/website/docs/r/cosmosdb_gremlin_graph.html.markdown @@ -0,0 +1,112 @@ +--- +subcategory: "CosmosDB (DocumentDB)" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_cosmosdb_gremlin_graph" +sidebar_current: "docs-azurerm-resource-cosmosdb-gremlin-graph" +description: |- + Manages a Gremlin Graph within a Cosmos DB Account. +--- + +# azurerm_cosmosdb_gremlin_graph + +Manages a Gremlin Graph 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}" +} + +resource "azurerm_cosmosdb_gremlin_graph" "example" { + name = "tfex-cosmos-gremlin-graph" + resource_group_name = "${azurerm_cosmosdb_account.example.resource_group_name}" + account_name = "${azurerm_cosmosdb_account.example.name}" + database_name = "${azurerm_cosmosdb_gremlin_database.example.name}" + partition_key_path = "/Example" + throughput = 400 + + index_policy { + automatic = true + indexing_mode = "Consistent" + included_paths = ["/*"] + excluded_paths = ["/\"_etag\"/?"] + } + + conflict_resolution_policy { + mode = "LastWriterWins" + conflict_resolution_path = "/_ts" + } + + unique_key { + paths = ["/definition/id1", "/definition/id2"] + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) Specifies the name of the Cosmos DB Gremlin Graph. 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 Graph is created. Changing this forces a new resource to be created. + +* `account_name` - (Required) The name of the CosmosDB Account to create the Gremlin Graph within. Changing this forces a new resource to be created. + +* `database_name` - (Required) The name of the Cosmos DB Graph Database in which the Cosmos DB Gremlin Graph is created. Changing this forces a new resource to be created. + +* `partition_key_path` - (Optional) Define a partition key. 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. + +* `index_policy` - (Required) The configuration of the indexing policy. One or more `index_policy` blocks as defined below. Changing this forces a new resource to be created. + +* `conflict_resolution_policy` - (Required) The conflict resolution policy for the graph. One or more `conflict_resolution_policy` blocks as defined below. Changing this forces a new resource to be created. + +* `unique_key` (Optional) One or more `unique_key` blocks as defined below. Changing this forces a new resource to be created. + +--- + +An `index_policy` block supports the following: + +* `automatic` - (Optional) Indicates if the indexing policy is automatic. Defaults to `true`. + +* `indexing_mode` - (Required) Indicates the indexing mode. Possible values include: `Consistent`, `Lazy`, `None`. + +* `included_paths` - (Optional) List of paths to include in the indexing. Required if `indexing_mode` is `Consistent` or `Lazy`. + +* `excluded_paths` - (Optional) List of paths to exclude from indexing. Required if `indexing_mode` is `Consistent` or `Lazy`. + +An `conflict_resolution_policy` block supports the following: + +* `mode` - (Required) Indicates the conflict resolution mode. Possible values include: `LastWriterWins`, `Custom`. + +* `conflict_resolution_path` - (Optional) The conflict resolution path in the case of LastWriterWins mode. + +* `conflict_resolution_procedure` - (Optional) The procedure to resolve conflicts in the case of custom mode. + +An `unique_key` block supports the following: + +* `paths` - (Required) A list of paths to use for this unique key. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The Cosmos DB Gremlin Graph ID. + +## Import + +Cosmos Gremlin Graph can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_cosmosdb_gremlin_graph.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg1/providers/Microsoft.DocumentDB/databaseAccounts/account1/apis/gremlin/databases/db1/graphs/graphs1 +```