diff --git a/builtin/providers/azurerm/config.go b/builtin/providers/azurerm/config.go index 8535b67c7..55b7f0949 100644 --- a/builtin/providers/azurerm/config.go +++ b/builtin/providers/azurerm/config.go @@ -19,6 +19,7 @@ import ( "github.com/Azure/azure-sdk-for-go/arm/resources/resources" "github.com/Azure/azure-sdk-for-go/arm/scheduler" "github.com/Azure/azure-sdk-for-go/arm/servicebus" + "github.com/Azure/azure-sdk-for-go/arm/sql" "github.com/Azure/azure-sdk-for-go/arm/storage" "github.com/Azure/azure-sdk-for-go/arm/trafficmanager" mainStorage "github.com/Azure/azure-sdk-for-go/storage" @@ -99,6 +100,11 @@ type ArmClient struct { serviceBusSubscriptionsClient servicebus.SubscriptionsClient keyVaultClient keyvault.VaultsClient + + sqlDatabasesClient sql.DatabasesClient + sqlElasticPoolsClient sql.ElasticPoolsClient + sqlRecommendedElasticPoolsClient sql.RecommendedElasticPoolsClient + sqlServersClient sql.ServersClient } func withRequestLogging() autorest.SendDecorator { @@ -458,6 +464,30 @@ func (c *Config) getArmClient() (*ArmClient, error) { kvc.Sender = autorest.CreateSender(withRequestLogging()) client.keyVaultClient = kvc + sqldc := sql.NewDatabasesClientWithBaseURI(endpoint, c.SubscriptionID) + setUserAgent(&sqldc.Client) + sqldc.Authorizer = spt + sqldc.Sender = autorest.CreateSender(withRequestLogging()) + client.sqlDatabasesClient = sqldc + + sqlepc := sql.NewElasticPoolsClientWithBaseURI(endpoint, c.SubscriptionID) + setUserAgent(&sqlepc.Client) + sqlepc.Authorizer = spt + sqlepc.Sender = autorest.CreateSender(withRequestLogging()) + client.sqlElasticPoolsClient = sqlepc + + sqlrepc := sql.NewRecommendedElasticPoolsClientWithBaseURI(endpoint, c.SubscriptionID) + setUserAgent(&sqlrepc.Client) + sqlrepc.Authorizer = spt + sqlrepc.Sender = autorest.CreateSender(withRequestLogging()) + client.sqlRecommendedElasticPoolsClient = sqlrepc + + sqlsc := sql.NewServersClientWithBaseURI(endpoint, c.SubscriptionID) + setUserAgent(&sqlsc.Client) + sqlsc.Authorizer = spt + sqlsc.Sender = autorest.CreateSender(withRequestLogging()) + client.sqlServersClient = sqlsc + return &client, nil } diff --git a/builtin/providers/azurerm/import_arm_sql_elasticpool_test.go b/builtin/providers/azurerm/import_arm_sql_elasticpool_test.go new file mode 100644 index 000000000..1657f5c15 --- /dev/null +++ b/builtin/providers/azurerm/import_arm_sql_elasticpool_test.go @@ -0,0 +1,32 @@ +package azurerm + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "testing" +) + +func TestAccAzureRMSqlElasticPool_importBasic(t *testing.T) { + resourceName := "azurerm_sql_elasticpool.test" + + ri := acctest.RandInt() + config := fmt.Sprintf(testAccAzureRMSqlElasticPool_basic, ri) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSqlElasticPoolDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: config, + }, + + resource.TestStep{ + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/builtin/providers/azurerm/provider.go b/builtin/providers/azurerm/provider.go index 3ff68a83c..e7875650e 100644 --- a/builtin/providers/azurerm/provider.go +++ b/builtin/providers/azurerm/provider.go @@ -99,6 +99,7 @@ func Provider() terraform.ResourceProvider { "azurerm_servicebus_namespace": resourceArmServiceBusNamespace(), "azurerm_servicebus_subscription": resourceArmServiceBusSubscription(), "azurerm_servicebus_topic": resourceArmServiceBusTopic(), + "azurerm_sql_elasticpool": resourceArmSqlElasticPool(), "azurerm_storage_account": resourceArmStorageAccount(), "azurerm_storage_blob": resourceArmStorageBlob(), "azurerm_storage_container": resourceArmStorageContainer(), diff --git a/builtin/providers/azurerm/resource_arm_sql_elasticpool.go b/builtin/providers/azurerm/resource_arm_sql_elasticpool.go new file mode 100644 index 000000000..3e1cf0412 --- /dev/null +++ b/builtin/providers/azurerm/resource_arm_sql_elasticpool.go @@ -0,0 +1,217 @@ +package azurerm + +import ( + "fmt" + "github.com/Azure/azure-sdk-for-go/arm/sql" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "log" + "net/http" + "time" +) + +func resourceArmSqlElasticPool() *schema.Resource { + return &schema.Resource{ + Create: resourceArmSqlElasticPoolCreate, + Read: resourceArmSqlElasticPoolRead, + Update: resourceArmSqlElasticPoolCreate, + Delete: resourceArmSqlElasticPoolDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "location": locationSchema(), + + "resource_group_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "server_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "edition": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateSqlElasticPoolEdition(), + }, + + "dtu": { + Type: schema.TypeInt, + Required: true, + }, + + "db_dtu_min": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + + "db_dtu_max": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + + "pool_size": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + + "creation_date": { + Type: schema.TypeString, + Computed: true, + }, + + "tags": tagsSchema(), + }, + } +} + +func resourceArmSqlElasticPoolCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient) + elasticPoolsClient := client.sqlElasticPoolsClient + + log.Printf("[INFO] preparing arguments for Azure ARM SQL ElasticPool creation.") + + name := d.Get("name").(string) + serverName := d.Get("server_name").(string) + location := d.Get("location").(string) + resGroup := d.Get("resource_group_name").(string) + tags := d.Get("tags").(map[string]interface{}) + + elasticPool := sql.ElasticPool{ + Name: &name, + Location: &location, + ElasticPoolProperties: getArmSqlElasticPoolProperties(d), + Tags: expandTags(tags), + } + + _, err := elasticPoolsClient.CreateOrUpdate(resGroup, serverName, name, elasticPool, make(chan struct{})) + if err != nil { + return err + } + + read, err := elasticPoolsClient.Get(resGroup, serverName, name) + if err != nil { + return err + } + if read.ID == nil { + return fmt.Errorf("Cannot read SQL ElasticPool %s (resource group %s) ID", name, resGroup) + } + + d.SetId(*read.ID) + + return resourceArmSqlElasticPoolRead(d, meta) +} + +func resourceArmSqlElasticPoolRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient) + elasticPoolsClient := client.sqlElasticPoolsClient + + resGroup, serverName, name, err := parseArmSqlElasticPoolId(d.Id()) + if err != nil { + return err + } + + resp, err := elasticPoolsClient.Get(resGroup, serverName, name) + if err != nil { + if resp.StatusCode == http.StatusNotFound { + d.SetId("") + return nil + } + return fmt.Errorf("Error making Read request on Sql Elastic Pool %s: %s", name, err) + } + + elasticPool := *resp.ElasticPoolProperties + + d.Set("name", resp.Name) + d.Set("resource_group_name", resGroup) + d.Set("location", azureRMNormalizeLocation(*resp.Location)) + d.Set("server_name", serverName) + d.Set("edition", string(elasticPool.Edition)) + d.Set("dtu", int(*elasticPool.Dtu)) + d.Set("db_dtu_min", int(*elasticPool.DatabaseDtuMin)) + d.Set("db_dtu_max", int(*elasticPool.DatabaseDtuMax)) + d.Set("pool_size", int(*elasticPool.StorageMB)) + + if elasticPool.CreationDate != nil { + d.Set("creation_date", elasticPool.CreationDate.Format(time.RFC3339)) + } + + flattenAndSetTags(d, resp.Tags) + + return nil +} + +func resourceArmSqlElasticPoolDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient) + elasticPoolsClient := client.sqlElasticPoolsClient + + resGroup, serverName, name, err := parseArmSqlElasticPoolId(d.Id()) + if err != nil { + return err + } + + _, err = elasticPoolsClient.Delete(resGroup, serverName, name) + + return err +} + +func getArmSqlElasticPoolProperties(d *schema.ResourceData) *sql.ElasticPoolProperties { + edition := sql.ElasticPoolEditions(d.Get("edition").(string)) + dtu := int32(d.Get("dtu").(int)) + + props := &sql.ElasticPoolProperties{ + Edition: edition, + Dtu: &dtu, + } + + if databaseDtuMin, ok := d.GetOk("db_dtu_min"); ok { + databaseDtuMin := int32(databaseDtuMin.(int)) + props.DatabaseDtuMin = &databaseDtuMin + } + + if databaseDtuMax, ok := d.GetOk("db_dtu_max"); ok { + databaseDtuMax := int32(databaseDtuMax.(int)) + props.DatabaseDtuMax = &databaseDtuMax + } + + if poolSize, ok := d.GetOk("pool_size"); ok { + poolSize := int32(poolSize.(int)) + props.StorageMB = &poolSize + } + + return props +} + +func parseArmSqlElasticPoolId(sqlElasticPoolId string) (string, string, string, error) { + id, err := parseAzureResourceID(sqlElasticPoolId) + if err != nil { + return "", "", "", fmt.Errorf("[ERROR] Unable to parse SQL ElasticPool ID '%s': %+v", sqlElasticPoolId, err) + } + + return id.ResourceGroup, id.Path["servers"], id.Path["elasticPools"], nil +} + +func validateSqlElasticPoolEdition() schema.SchemaValidateFunc { + return validation.StringInSlice([]string{ + string(sql.ElasticPoolEditionsBasic), + string(sql.ElasticPoolEditionsStandard), + string(sql.ElasticPoolEditionsPremium), + }, false) +} diff --git a/builtin/providers/azurerm/resource_arm_sql_elasticpool_test.go b/builtin/providers/azurerm/resource_arm_sql_elasticpool_test.go new file mode 100644 index 000000000..991eb691b --- /dev/null +++ b/builtin/providers/azurerm/resource_arm_sql_elasticpool_test.go @@ -0,0 +1,168 @@ +package azurerm + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "net/http" + "testing" +) + +func TestAccAzureRMSqlElasticPool_basic(t *testing.T) { + ri := acctest.RandInt() + config := fmt.Sprintf(testAccAzureRMSqlElasticPool_basic, ri) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSqlElasticPoolDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSqlElasticPoolExists("azurerm_sql_elasticpool.test"), + ), + }, + }, + }) +} + +func TestAccAzureRMSqlElasticPool_resizeDtu(t *testing.T) { + ri := acctest.RandInt() + preConfig := fmt.Sprintf(testAccAzureRMSqlElasticPool_basic, ri) + postConfig := fmt.Sprintf(testAccAzureRMSqlElasticPool_resizedDtu, ri) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSqlElasticPoolDestroy, + Steps: []resource.TestStep{ + { + Config: preConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSqlElasticPoolExists("azurerm_sql_elasticpool.test"), + resource.TestCheckResourceAttr( + "azurerm_sql_elasticpool.test", "dtu", "50"), + resource.TestCheckResourceAttr( + "azurerm_sql_elasticpool.test", "pool_size", "5000"), + ), + }, + { + Config: postConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSqlElasticPoolExists("azurerm_sql_elasticpool.test"), + resource.TestCheckResourceAttr( + "azurerm_sql_elasticpool.test", "dtu", "100"), + resource.TestCheckResourceAttr( + "azurerm_sql_elasticpool.test", "pool_size", "10000"), + ), + }, + }, + }) +} + +func testCheckAzureRMSqlElasticPoolExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + ressource, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + resourceGroup, serverName, name, err := parseArmSqlElasticPoolId(ressource.Primary.ID) + if err != nil { + return err + } + + conn := testAccProvider.Meta().(*ArmClient).sqlElasticPoolsClient + + resp, err := conn.Get(resourceGroup, serverName, name) + if err != nil { + return fmt.Errorf("Bad: Get on sqlElasticPoolsClient: %s", err) + } + + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("Bad: SQL Elastic Pool %q on server: %q (resource group: %q) does not exist", name, serverName, resourceGroup) + } + + return nil + } +} + +func testCheckAzureRMSqlElasticPoolDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*ArmClient).sqlElasticPoolsClient + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_sql_elasticpool" { + continue + } + + name := rs.Primary.Attributes["name"] + serverName := rs.Primary.Attributes["server_name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + resp, err := conn.Get(resourceGroup, serverName, name) + + if err != nil { + return nil + } + + if resp.StatusCode != http.StatusNotFound { + return fmt.Errorf("SQL Elastic Pool still exists:\n%#v", resp.ElasticPoolProperties) + } + } + + return nil +} + +var testAccAzureRMSqlElasticPool_basic = ` +resource "azurerm_resource_group" "test" { + name = "acctest-%[1]d" + location = "West US" +} + +resource "azurerm_sql_server" "test" { + name = "acctest%[1]d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "West US" + version = "12.0" + administrator_login = "4dm1n157r470r" + administrator_login_password = "4-v3ry-53cr37-p455w0rd" +} + +resource "azurerm_sql_elasticpool" "test" { + name = "acctest-pool-%[1]d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "West US" + server_name = "${azurerm_sql_server.test.name}" + edition = "Basic" + dtu = 50 + pool_size = 5000 +} +` + +var testAccAzureRMSqlElasticPool_resizedDtu = ` +resource "azurerm_resource_group" "test" { + name = "acctest-%[1]d" + location = "West US" +} + +resource "azurerm_sql_server" "test" { + name = "acctest%[1]d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "West US" + version = "12.0" + administrator_login = "4dm1n157r470r" + administrator_login_password = "4-v3ry-53cr37-p455w0rd" +} + +resource "azurerm_sql_elasticpool" "test" { + name = "acctest-pool-%[1]d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "West US" + server_name = "${azurerm_sql_server.test.name}" + edition = "Basic" + dtu = 100 + pool_size = 10000 +} +` diff --git a/website/source/docs/providers/azurerm/r/sql_elasticpool.html.markdown b/website/source/docs/providers/azurerm/r/sql_elasticpool.html.markdown new file mode 100644 index 000000000..6b98eeed0 --- /dev/null +++ b/website/source/docs/providers/azurerm/r/sql_elasticpool.html.markdown @@ -0,0 +1,75 @@ +--- +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_sql_elasticpool" +sidebar_current: "docs-azurerm-resource-sql-elasticpool" +description: |- + Create a SQL Elastic Pool. +--- + +# azurerm\_sql\_elasticpool + +Allows you to manage an Azure SQL Elastic Pool. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "test" { + name = "test" + location = "West US" +} + +resource "azurerm_sql_server" "test" { + name = "test" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "West US" + version = "12.0" + administrator_login = "4dm1n157r470r" + administrator_login_password = "4-v3ry-53cr37-p455w0rd" +} + +resource "azurerm_sql_elasticpool" "test" { + name = "test" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "West US" + server_name = "${azurerm_sql_server.test.name}" + edition = "Basic" + dtu = 100 + db_min_dtu = 0 + db_max_dtu = 5 + pool_size = 5000 +} +``` + +~> **NOTE on `azurerm_sql_elasticpool`:** - The values of `edition`, `dtu`, and `pool_size` must be consistent with the [Azure SQL Database Service Tiers](https://docs.microsoft.com/en-gb/azure/sql-database/sql-database-service-tiers#elastic-pool-service-tiers-and-performance-in-edtus). Any inconsistent argument configuration will be rejected. + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the elastic pool. + +* `resource_group_name` - (Required) The name of the resource group in which to create the elastic pool. This must be the same as the resource group of the underlying SQL server. + +* `location` - (Required) Specifies the supported Azure location where the resource exists. Changing this forces a new resource to be created. + +* `server_name` - (Required) The name of the SQL Server on which to create the elastic pool. Changing this forces a new resource to be created. + +* `edition` - (Required) The edition of the elastic pool to be created. Valid values are `Basic`, `Standard`, and `Premium`. Refer to [Azure SQL Database Service Tiers](https://docs.microsoft.com/en-gb/azure/sql-database/sql-database-service-tiers#elastic-pool-service-tiers-and-performance-in-edtus) for details. Changing this forces a new resource to be created. + +* `dtu` - (Required) The total shared DTU for the elastic pool. Valid values depend on the `edition` which has been defined. Refer to [Azure SQL Database Service Tiers](https://docs.microsoft.com/en-gb/azure/sql-database/sql-database-service-tiers#elastic-pool-service-tiers-and-performance-in-edtus) for valid combinations. + +* `db_dtu_min` - (Optional) The minimum DTU which will be guaranteed to all databases in the elastic pool to be created. + +* `db_dtu_max` - (Optional) The maximum DTU which will be guaranteed to all databases in the elastic pool to be created. + +* `pool_size` - (Optional) The maximum size in MB that all databases in the elastic pool can grow to. The maximum size must be consistent with combination of `edition` and `dtu` and the limits documented in [Azure SQL Database Service Tiers](https://docs.microsoft.com/en-gb/azure/sql-database/sql-database-service-tiers#elastic-pool-service-tiers-and-performance-in-edtus). If not defined when creating an elastic pool, the value is set to the size implied by `edition` and `dtu`. + +* `tags` - (Optional) A mapping of tags to assign to the resource. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The SQL Elastic Pool ID. + +* `creation_data` - The creation date of the SQL Elastic Pool. diff --git a/website/source/layouts/azurerm.erb b/website/source/layouts/azurerm.erb index 037498a04..80b38a275 100644 --- a/website/source/layouts/azurerm.erb +++ b/website/source/layouts/azurerm.erb @@ -264,6 +264,10 @@ azurerm_sql_database + > + azurerm_sql_elasticpool + + > azurerm_sql_firewall_rule