Skip to content

Commit

Permalink
New Resource: azurerm_cosmosdb_mongo_collection (#3459)
Browse files Browse the repository at this point in the history
fixes #548
  • Loading branch information
katbyte authored May 16, 2019
1 parent 2eb9c71 commit b758976
Show file tree
Hide file tree
Showing 13 changed files with 666 additions and 38 deletions.
23 changes: 15 additions & 8 deletions azurerm/helpers/azure/cosmos.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,21 @@ type CosmosDatabaseCollectionID struct {
Collection string
}

type CosmosDatabaseContainerID struct {
CosmosDatabaseID
Container string
func ParseCosmosDatabaseCollectionID(id string) (*CosmosDatabaseCollectionID, error) {
subid, err := ParseCosmosDatabaseID(id)
if err != nil {
return nil, err
}

collection, ok := subid.Path["collections"]
if !ok {
return nil, fmt.Errorf("Error: Unable to parse Cosmos Database Resource ID: collections is missing from: %s", id)
}

return &CosmosDatabaseCollectionID{
CosmosDatabaseID: *subid,
Collection: collection,
}, nil
}

type CosmosKeyspaceID struct {
Expand All @@ -100,11 +112,6 @@ func ParseCosmosKeyspaceID(id string) (*CosmosKeyspaceID, error) {
}, nil
}

type CosmosKeyspaceTableID struct {
CosmosKeyspaceID
Table string
}

type CosmosTableID struct {
CosmosAccountID
Table string
Expand Down
1 change: 1 addition & 0 deletions azurerm/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ func Provider() terraform.ResourceProvider {
"azurerm_container_service": resourceArmContainerService(),
"azurerm_cosmosdb_account": resourceArmCosmosDbAccount(),
"azurerm_cosmosdb_cassandra_keyspace": resourceArmCosmosDbCassandraKeyspace(),
"azurerm_cosmosdb_mongo_collection": resourceArmCosmosDbMongoCollection(),
"azurerm_cosmosdb_mongo_database": resourceArmCosmosDbMongoDatabase(),
"azurerm_cosmosdb_sql_database": resourceArmCosmosDbSQLDatabase(),
"azurerm_cosmosdb_table": resourceArmCosmosDbTable(),
Expand Down
14 changes: 7 additions & 7 deletions azurerm/resource_arm_cosmosdb_cassandra_keyspace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@ import (
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)

func TestAccAzureRMCosmosCassandraKeyspace_basic(t *testing.T) {
func TestAccAzureRMCosmosDbCassandraKeyspace_basic(t *testing.T) {
ri := tf.AccRandTimeInt()
resourceName := "azurerm_cosmosdb_cassandra_keyspace.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testCheckAzureRMCosmosCassandraKeyspaceDestroy,
CheckDestroy: testCheckAzureRMCosmosDbCassandraKeyspaceDestroy,
Steps: []resource.TestStep{
{
Config: testAccAzureRMCosmosCassandraKeyspace_basic(ri, testLocation()),
Config: testAccAzureRMCosmosDbCassandraKeyspace_basic(ri, testLocation()),
Check: resource.ComposeAggregateTestCheckFunc(
testCheckAzureRMCosmosCassandraKeyspaceExists(resourceName),
testCheckAzureRMCosmosDbCassandraKeyspaceExists(resourceName),
),
},
{
Expand All @@ -35,7 +35,7 @@ func TestAccAzureRMCosmosCassandraKeyspace_basic(t *testing.T) {
})
}

func testCheckAzureRMCosmosCassandraKeyspaceDestroy(s *terraform.State) error {
func testCheckAzureRMCosmosDbCassandraKeyspaceDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*ArmClient).cosmosAccountsClient
ctx := testAccProvider.Meta().(*ArmClient).StopContext

Expand Down Expand Up @@ -63,7 +63,7 @@ func testCheckAzureRMCosmosCassandraKeyspaceDestroy(s *terraform.State) error {
return nil
}

func testCheckAzureRMCosmosCassandraKeyspaceExists(resourceName string) resource.TestCheckFunc {
func testCheckAzureRMCosmosDbCassandraKeyspaceExists(resourceName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
client := testAccProvider.Meta().(*ArmClient).cosmosAccountsClient
ctx := testAccProvider.Meta().(*ArmClient).StopContext
Expand Down Expand Up @@ -91,7 +91,7 @@ func testCheckAzureRMCosmosCassandraKeyspaceExists(resourceName string) resource
}
}

func testAccAzureRMCosmosCassandraKeyspace_basic(rInt int, location string) string {
func testAccAzureRMCosmosDbCassandraKeyspace_basic(rInt int, location string) string {
return fmt.Sprintf(`
%[1]s
Expand Down
301 changes: 301 additions & 0 deletions azurerm/resource_arm_cosmosdb_mongo_collection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
package azurerm

import (
"fmt"
"log"
"strings"

"github.com/Azure/azure-sdk-for-go/services/cosmos-db/mgmt/2015-04-08/documentdb"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response"
"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/utils"
)

func resourceArmCosmosDbMongoCollection() *schema.Resource {
return &schema.Resource{
Create: resourceArmCosmosDbMongoCollectionCreateUpdate,
Read: resourceArmCosmosDbMongoCollectionRead,
Update: resourceArmCosmosDbMongoCollectionCreateUpdate,
Delete: resourceArmCosmosDbMongoCollectionDelete,

Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validate.CosmosEntityName,
},

"resource_group_name": resourceGroupNameSchema(),

"account_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validate.CosmosAccountName,
},

"database_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validate.CosmosEntityName,
},

// SDK/api accepts an array.. but only one is allowed
"shard_key": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validate.NoEmptyStrings,
},

// default TTL is simply an index on _ts with expireAfterOption, given we can't seem to set TTLs on a given index lets expose this to match the portal
"default_ttl_seconds": {
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntAtLeast(-1),
},

"indexes": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"key": {
Type: schema.TypeString, // this is a list in the SDK/API, however any more then a single value causes a 404
Required: true,
ValidateFunc: validate.NoEmptyStrings,
},

"unique": {
Type: schema.TypeBool,
Optional: true,
Default: true, // portal defaults to true
},

// expire_after_seconds is only allowed on `_ts`:
// Unable to parse request payload due to the following reason: 'The 'expireAfterSeconds' option is supported on '_ts' field only.
},
},
},
},
}
}

func resourceArmCosmosDbMongoCollectionCreateUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).cosmosAccountsClient
ctx := meta.(*ArmClient).StopContext

name := d.Get("name").(string)
resourceGroup := d.Get("resource_group_name").(string)
account := d.Get("account_name").(string)
database := d.Get("database_name").(string)

if requireResourcesToBeImported && d.IsNewResource() {
existing, err := client.GetMongoDBCollection(ctx, resourceGroup, account, database, name)
if err != nil {
if !utils.ResponseWasNotFound(existing.Response) {
return fmt.Errorf("Error checking for presence of creating Cosmos Mongo Collection %s (Account %s, Database %s): %+v", name, account, database, err)
}
} else {
id, err := azure.CosmosGetIDFromResponse(existing.Response)
if err != nil {
return fmt.Errorf("Error generating import ID for Cosmos Mongo Collection %s (Account %s, Database %s)", name, account, database)
}

return tf.ImportAsExistsError("azurerm_cosmosdb_mongo_collection", id)
}
}

var ttl *int
if v, ok := d.GetOkExists("default_ttl_seconds"); ok {
ttl = utils.Int(v.(int))
}

db := documentdb.MongoDBCollectionCreateUpdateParameters{
MongoDBCollectionCreateUpdateProperties: &documentdb.MongoDBCollectionCreateUpdateProperties{
Resource: &documentdb.MongoDBCollectionResource{
ID: &name,
Indexes: expandCosmosMongoCollectionIndexes(d.Get("indexes"), ttl),
},
Options: map[string]*string{},
},
}

if v, ok := d.GetOkExists("shard_key"); ok {
db.MongoDBCollectionCreateUpdateProperties.Resource.ShardKey = map[string]*string{
v.(string): utils.String("Hash"), // looks like only hash is supported for now
}
}

future, err := client.CreateUpdateMongoDBCollection(ctx, resourceGroup, account, database, name, db)
if err != nil {
return fmt.Errorf("Error issuing create/update request for Cosmos Mongo Collection %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 Mongo Collection %s (Account %s, Database %s): %+v", name, account, database, err)
}

resp, err := client.GetMongoDBCollection(ctx, resourceGroup, account, database, name)
if err != nil {
return fmt.Errorf("Error making get request for Cosmos Mongo Collection %s (Account %s, Database %s): %+v", name, account, database, err)
}

id, err := azure.CosmosGetIDFromResponse(resp.Response)
if err != nil {
return fmt.Errorf("Error getting ID for Cosmos Mongo Collection %s (Account %s, Database %s) ID: %v", name, account, database, err)
}
d.SetId(id)

return resourceArmCosmosDbMongoCollectionRead(d, meta)
}

func resourceArmCosmosDbMongoCollectionRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).cosmosAccountsClient
ctx := meta.(*ArmClient).StopContext

id, err := azure.ParseCosmosDatabaseCollectionID(d.Id())
if err != nil {
return err
}

resp, err := client.GetMongoDBCollection(ctx, id.ResourceGroup, id.Account, id.Database, id.Collection)
if err != nil {
if utils.ResponseWasNotFound(resp.Response) {
log.Printf("[INFO] Error reading Cosmos Mongo Collection %s (Account %s, Database %s)", id.Collection, id.Account, id.Database)
d.SetId("")
return nil
}

return fmt.Errorf("Error reading Cosmos Mongo Collection %s (Account %s, Database %s): %+v", id.Collection, id.Account, id.Database, err)
}

d.Set("resource_group_name", id.ResourceGroup)
d.Set("account_name", id.Account)
d.Set("database_name", id.Database)
if props := resp.MongoDBCollectionProperties; props != nil {
d.Set("name", props.ID)

// you can only have one
if len(props.ShardKey) > 2 {
return fmt.Errorf("unexpected number of shard keys: %d", len(props.ShardKey))
}

for k := range props.ShardKey {
d.Set("shard_key", k)
}

indexes, ttl := flattenCosmosMongoCollectionIndexes(props.Indexes)
d.Set("default_ttl_seconds", ttl)
if err := d.Set("indexes", indexes); err != nil {
return fmt.Errorf("Error setting `indexes`: %+v", err)
}

}

return nil
}

func resourceArmCosmosDbMongoCollectionDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).cosmosAccountsClient
ctx := meta.(*ArmClient).StopContext

id, err := azure.ParseCosmosDatabaseCollectionID(d.Id())
if err != nil {
return err
}

future, err := client.DeleteMongoDBCollection(ctx, id.ResourceGroup, id.Account, id.Database, id.Collection)
if err != nil {
if !response.WasNotFound(future.Response()) {
return fmt.Errorf("Error deleting Cosmos Mongo Collection %s (Account %s, Database %s): %+v", id.Collection, id.Account, id.Database, err)
}
}

err = future.WaitForCompletionRef(ctx, client.Client)
if err != nil {
return fmt.Errorf("Error waiting on delete future for Cosmos Mongo Collection %s (Account %s, Database %s): %+v", id.Collection, id.Account, id.Database, err)
}

return nil
}

func expandCosmosMongoCollectionIndexes(input interface{}, defaultTtl *int) *[]documentdb.MongoIndex {
outputs := make([]documentdb.MongoIndex, 0)

for _, i := range input.(*schema.Set).List() {
b := i.(map[string]interface{})
outputs = append(outputs, documentdb.MongoIndex{
Key: &documentdb.MongoIndexKeys{
Keys: &[]string{b["key"].(string)},
},
Options: &documentdb.MongoIndexOptions{
Unique: utils.Bool(b["unique"].(bool)),
},
})
}

if defaultTtl != nil {
outputs = append(outputs, documentdb.MongoIndex{
Key: &documentdb.MongoIndexKeys{
Keys: &[]string{"_ts"},
},
Options: &documentdb.MongoIndexOptions{
ExpireAfterSeconds: utils.Int32(int32(*defaultTtl)),
},
})
}

return &outputs
}

func flattenCosmosMongoCollectionIndexes(indexes *[]documentdb.MongoIndex) (*[]map[string]interface{}, *int) {
slice := make([]map[string]interface{}, 0)

var ttl int
for _, i := range *indexes {
if key := i.Key; key != nil {
m := map[string]interface{}{}
var ttlInner int32

if options := i.Options; options != nil {
if v := options.Unique; v != nil {
m["unique"] = *v
} else {
m["unique"] = false // todo required? API sends back nothing for false
}

if v := options.ExpireAfterSeconds; v != nil {
ttlInner = *v
}
}

if keys := key.Keys; keys != nil && len(*keys) > 0 {
k := (*keys)[0]

if !strings.HasPrefix(k, "_") && k != "DocumentDBDefaultIndex" { // lets ignore system properties?
m["key"] = k

// only append indexes with a non system key
slice = append(slice, m)
}

if k == "_ts" {
ttl = int(ttlInner)
}
}
}
}

return &slice, &ttl
}
Loading

0 comments on commit b758976

Please sign in to comment.