diff --git a/azurerm/internal/services/mssql/client/client.go b/azurerm/internal/services/mssql/client/client.go index 9c3f834b3823..fbf5aad94bf0 100644 --- a/azurerm/internal/services/mssql/client/client.go +++ b/azurerm/internal/services/mssql/client/client.go @@ -12,6 +12,7 @@ type Client struct { DatabaseThreatDetectionPoliciesClient *sql.DatabaseThreatDetectionPoliciesClient ElasticPoolsClient *sql.ElasticPoolsClient DatabaseVulnerabilityAssessmentRuleBaselinesClient *sql.DatabaseVulnerabilityAssessmentRuleBaselinesClient + RestorableDroppedDatabasesClient *sql.RestorableDroppedDatabasesClient ServerAzureADAdministratorsClient *sql.ServerAzureADAdministratorsClient ServersClient *sql.ServersClient ServerExtendedBlobAuditingPoliciesClient *sql.ExtendedServerBlobAuditingPoliciesClient @@ -37,6 +38,9 @@ func NewClient(o *common.ClientOptions) *Client { elasticPoolsClient := sql.NewElasticPoolsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&elasticPoolsClient.Client, o.ResourceManagerAuthorizer) + restorableDroppedDatabasesClient := sql.NewRestorableDroppedDatabasesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&restorableDroppedDatabasesClient.Client, o.ResourceManagerAuthorizer) + serverSecurityAlertPoliciesClient := sql.NewServerSecurityAlertPoliciesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&serverSecurityAlertPoliciesClient.Client, o.ResourceManagerAuthorizer) @@ -64,6 +68,7 @@ func NewClient(o *common.ClientOptions) *Client { DatabaseThreatDetectionPoliciesClient: &databaseThreatDetectionPoliciesClient, DatabaseVulnerabilityAssessmentRuleBaselinesClient: &databaseVulnerabilityAssessmentRuleBaselinesClient, ElasticPoolsClient: &elasticPoolsClient, + RestorableDroppedDatabasesClient: &restorableDroppedDatabasesClient, ServerAzureADAdministratorsClient: &serverAzureADAdministratorsClient, ServersClient: &serversClient, ServerExtendedBlobAuditingPoliciesClient: &serverExtendedBlobAuditingPoliciesClient, diff --git a/azurerm/internal/services/mssql/mssql_database_resource.go b/azurerm/internal/services/mssql/mssql_database_resource.go index b23e0d21d0be..d4980fdbf9c4 100644 --- a/azurerm/internal/services/mssql/mssql_database_resource.go +++ b/azurerm/internal/services/mssql/mssql_database_resource.go @@ -66,7 +66,6 @@ func resourceArmMsSqlDatabase() *schema.Resource { ValidateFunc: validate.MsSqlDatabaseAutoPauseDelay, }, - // recovery is not support in version 2017-10-01-preview "create_mode": { Type: schema.TypeString, Optional: true, @@ -78,6 +77,7 @@ func resourceArmMsSqlDatabase() *schema.Resource { string(sql.CreateModeOnlineSecondary), string(sql.CreateModePointInTimeRestore), string(sql.CreateModeRestore), + string(sql.CreateModeRecovery), string(sql.CreateModeRestoreExternalBackup), string(sql.CreateModeRestoreExternalBackupSecondary), string(sql.CreateModeRestoreLongTermRetentionBackup), @@ -133,6 +133,18 @@ func resourceArmMsSqlDatabase() *schema.Resource { ValidateFunc: validation.IsRFC3339Time, }, + "recover_database_id": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validate.MsSqlRecoverableDatabaseID, + }, + + "restore_dropped_database_id": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validate.MsSqlRestorableDatabaseID, + }, + "read_replica_count": { Type: schema.TypeInt, Optional: true, @@ -338,9 +350,16 @@ func resourceArmMsSqlDatabaseCreateUpdate(d *schema.ResourceData, meta interface } createMode, ok := d.GetOk("create_mode") - if _, dbok := d.GetOk("creation_source_database_id"); ok && (createMode.(string) == string(sql.CreateModeCopy) || createMode.(string) == string(sql.CreateModePointInTimeRestore) || createMode.(string) == string(sql.CreateModeRestore) || createMode.(string) == string(sql.CreateModeSecondary)) && !dbok { + if _, dbok := d.GetOk("creation_source_database_id"); ok && (createMode.(string) == string(sql.CreateModeCopy) || createMode.(string) == string(sql.CreateModePointInTimeRestore) || createMode.(string) == string(sql.CreateModeSecondary)) && !dbok { return fmt.Errorf("'creation_source_database_id' is required for create_mode %s", createMode.(string)) } + if _, dbok := d.GetOk("recover_database_id"); ok && createMode.(string) == string(sql.CreateModeRecovery) && !dbok { + return fmt.Errorf("'recover_database_id' is required for create_mode %s", createMode.(string)) + } + if _, dbok := d.GetOk("restore_dropped_database_id"); ok && createMode.(string) == string(sql.CreateModeRestore) && !dbok { + return fmt.Errorf("'restore_dropped_database_id' is required for create_mode %s", createMode.(string)) + } + params.DatabaseProperties.CreateMode = sql.CreateMode(createMode.(string)) auditingPolicies := d.Get("extended_auditing_policy").([]interface{}) @@ -376,6 +395,14 @@ func resourceArmMsSqlDatabaseCreateUpdate(d *schema.ResourceData, meta interface params.DatabaseProperties.SourceDatabaseID = utils.String(v.(string)) } + if v, ok := d.GetOk("recover_database_id"); ok { + params.DatabaseProperties.RecoverableDatabaseID = utils.String(v.(string)) + } + + if v, ok := d.GetOk("restore_dropped_database_id"); ok { + params.DatabaseProperties.RestorableDroppedDatabaseID = utils.String(v.(string)) + } + future, err := client.CreateOrUpdate(ctx, serverId.ResourceGroup, serverId.Name, name, params) if err != nil { return fmt.Errorf("creating MsSql Database %q (Sql Server %q / Resource Group %q): %+v", name, serverId.Name, serverId.ResourceGroup, err) diff --git a/azurerm/internal/services/mssql/mssql_server_data_source.go b/azurerm/internal/services/mssql/mssql_server_data_source.go new file mode 100644 index 000000000000..46678e0273ab --- /dev/null +++ b/azurerm/internal/services/mssql/mssql_server_data_source.go @@ -0,0 +1,126 @@ +package mssql + +import ( + "fmt" + "time" + + "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/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/location" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func dataSourceMsSqlServer() *schema.Resource { + return &schema.Resource{ + Read: dataSourceArmMsSqlServerRead, + + Timeouts: &schema.ResourceTimeout{ + Read: schema.DefaultTimeout(5 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + + "resource_group_name": azure.SchemaResourceGroupNameForDataSource(), + + "location": azure.SchemaLocationForDataSource(), + + "version": { + Type: schema.TypeString, + Computed: true, + }, + + "administrator_login": { + Type: schema.TypeString, + Computed: true, + }, + + "fully_qualified_domain_name": { + Type: schema.TypeString, + Computed: true, + }, + + "identity": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Computed: true, + }, + "principal_id": { + Type: schema.TypeString, + Computed: true, + }, + "tenant_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + "restorable_dropped_database_ids": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + + "tags": tags.SchemaDataSource(), + }, + } +} + +func dataSourceArmMsSqlServerRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).MSSQL.ServersClient + restorableDroppedDatabasesClient := meta.(*clients.Client).MSSQL.RestorableDroppedDatabasesClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + name := d.Get("name").(string) + resourceGroup := d.Get("resource_group_name").(string) + + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("sql Server %q was not found in Resource Group %q", name, resourceGroup) + } + + return fmt.Errorf("retrieving Sql Server %q (Resource Group %q): %s", name, resourceGroup, err) + } + + if resp.ID == nil || *resp.ID == "" { + return fmt.Errorf("reading Ms Sql Server %q (Resource Group %q) ID is empty or nil", name, resourceGroup) + } + d.SetId(*resp.ID) + d.Set("location", location.NormalizeNilable(resp.Location)) + + if props := resp.ServerProperties; props != nil { + d.Set("version", props.Version) + d.Set("administrator_login", props.AdministratorLogin) + d.Set("fully_qualified_domain_name", props.FullyQualifiedDomainName) + } + + if err := d.Set("identity", flattenAzureRmSqlServerIdentity(resp.Identity)); err != nil { + return fmt.Errorf("setting `identity`: %+v", err) + } + + restorableResp, err := restorableDroppedDatabasesClient.ListByServer(ctx, resourceGroup, name) + if err != nil { + return fmt.Errorf("listing SQL Server %s Restorable Dropped Databases: %v", name, err) + } + if err := d.Set("restorable_dropped_database_ids", flattenAzureRmSqlServerRestorableDatabases(restorableResp)); err != nil { + return fmt.Errorf("setting `restorable_dropped_database_ids`: %+v", err) + } + + return tags.FlattenAndSet(d, resp.Tags) +} diff --git a/azurerm/internal/services/mssql/mssql_server_resource.go b/azurerm/internal/services/mssql/mssql_server_resource.go index 7da1537807f1..fad22b4093bb 100644 --- a/azurerm/internal/services/mssql/mssql_server_resource.go +++ b/azurerm/internal/services/mssql/mssql_server_resource.go @@ -149,6 +149,14 @@ func resourceArmMsSqlServer() *schema.Resource { Computed: true, }, + "restorable_dropped_database_ids": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "tags": tags.Schema(), }, } @@ -280,6 +288,7 @@ func resourceArmMsSqlServerRead(d *schema.ResourceData, meta interface{}) error auditingClient := meta.(*clients.Client).MSSQL.ServerExtendedBlobAuditingPoliciesClient connectionClient := meta.(*clients.Client).MSSQL.ServerConnectionPoliciesClient adminClient := meta.(*clients.Client).MSSQL.ServerAzureADAdministratorsClient + restorableDroppedDatabasesClient := meta.(*clients.Client).MSSQL.RestorableDroppedDatabasesClient ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() @@ -348,6 +357,14 @@ func resourceArmMsSqlServerRead(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("Error setting `extended_auditing_policy`: %+v", err) } + restorableResp, err := restorableDroppedDatabasesClient.ListByServer(ctx, resGroup, name) + if err != nil { + return fmt.Errorf("listing SQL Server %s Restorable Dropped Databases: %v", name, err) + } + if err := d.Set("restorable_dropped_database_ids", flattenAzureRmSqlServerRestorableDatabases(restorableResp)); err != nil { + return fmt.Errorf("setting `restorable_dropped_database_ids`: %+v", err) + } + return tags.FlattenAndSet(d, resp.Tags) } @@ -445,3 +462,18 @@ func flatternAzureRmMsSqlServerAdministrator(admin sql.ServerAzureADAdministrato }, } } + +func flattenAzureRmSqlServerRestorableDatabases(resp sql.RestorableDroppedDatabaseListResult) []string { + if resp.Value == nil || len(*resp.Value) == 0 { + return []string{} + } + res := make([]string, 0) + for _, r := range *resp.Value { + var id string + if r.ID != nil { + id = *r.ID + } + res = append(res, id) + } + return res +} diff --git a/azurerm/internal/services/mssql/parse/mssql.go b/azurerm/internal/services/mssql/parse/mssql.go index 408a07c04f8d..fe715f64a2ae 100644 --- a/azurerm/internal/services/mssql/parse/mssql.go +++ b/azurerm/internal/services/mssql/parse/mssql.go @@ -2,6 +2,7 @@ package parse import ( "fmt" + "strings" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" ) @@ -34,6 +35,19 @@ type MsSqlServerExtendedAuditingPolicyId struct { ResourceGroup string } +type MsSqlRestorableDBId struct { + Name string + MsSqlServer string + ResourceGroup string + RestoreName string +} + +type MsSqlRecoverableDBId struct { + Name string + MsSqlServer string + ResourceGroup string +} + func NewMsSqlDatabaseID(resourceGroup, msSqlServer, name string) MsSqlDatabaseId { return MsSqlDatabaseId{ ResourceGroup: resourceGroup, @@ -196,3 +210,61 @@ func MssqlServerExtendedAuditingPolicyID(input string) (*MsSqlServerExtendedAudi return &sqlServerExtendedAuditingPolicyId, nil } + +func MssqlRestorableDBID(input string) (*MsSqlRestorableDBId, error) { + inputList := strings.Split(input, ",") + + if len(inputList) != 2 { + return nil, fmt.Errorf("[ERROR] Unable to parse Microsoft Sql Restorable DB ID %q, please refer to '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.Sql/servers/sqlServer1/restorableDroppedDatabases/sqlDB1,000000000000000000'", input) + } + + restorableDBId := MsSqlRestorableDBId{ + RestoreName: inputList[1], + } + + id, err := azure.ParseAzureResourceID(inputList[0]) + if err != nil { + return nil, fmt.Errorf("[ERROR] Unable to parse Microsoft Sql Restorable DB ID %q: %+v", input, err) + } + + restorableDBId.ResourceGroup = id.ResourceGroup + + if restorableDBId.MsSqlServer, err = id.PopSegment("servers"); err != nil { + return nil, err + } + + if restorableDBId.Name, err = id.PopSegment("restorableDroppedDatabases"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(inputList[0]); err != nil { + return nil, err + } + + return &restorableDBId, nil +} + +func MssqlRecoverableDBID(input string) (*MsSqlRecoverableDBId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, fmt.Errorf("[ERROR] Unable to parse Microsoft Sql Recoverable DB ID %q: %+v", input, err) + } + + recoverableDBId := MsSqlRecoverableDBId{ + ResourceGroup: id.ResourceGroup, + } + + if recoverableDBId.MsSqlServer, err = id.PopSegment("servers"); err != nil { + return nil, err + } + + if recoverableDBId.Name, err = id.PopSegment("recoverabledatabases"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &recoverableDBId, nil +} diff --git a/azurerm/internal/services/mssql/parse/mssql_test.go b/azurerm/internal/services/mssql/parse/mssql_test.go index 5db188c5969a..09b6b45a950f 100644 --- a/azurerm/internal/services/mssql/parse/mssql_test.go +++ b/azurerm/internal/services/mssql/parse/mssql_test.go @@ -397,3 +397,179 @@ func TestMssqlServerExtendedAuditingPolicy(t *testing.T) { } } } + +func TestMsSqlRestoreDBID(t *testing.T) { + testData := []struct { + Name string + Input string + Expected *MsSqlRestorableDBId + }{ + { + Name: "Empty", + Input: "", + Expected: nil, + }, + { + Name: "No Restore Name", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000", + Expected: nil, + }, + { + Name: "No Resource Groups Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000,000000000000000000", + Expected: nil, + }, + { + Name: "No Resource Groups Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/,000000000000000000", + Expected: nil, + }, + { + Name: "Resource Group ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/,000000000000000000", + Expected: nil, + }, + { + Name: "Missing Sql Server Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.Sql/servers/,000000000000000000", + Expected: nil, + }, + { + Name: "Missing Sql Restorable Database", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.Sql/servers/sqlServer1,000000000000000000", + Expected: nil, + }, + { + Name: "Missing Sql Restorable Database Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.Sql/servers/sqlServer1/restorableDroppedDatabases,000000000000000000", + Expected: nil, + }, + { + Name: "Sql Restorable Database ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.Sql/servers/sqlServer1/restorableDroppedDatabases/sqlDB1,000000000000000000", + Expected: &MsSqlRestorableDBId{ + Name: "sqlDB1", + MsSqlServer: "sqlServer1", + ResourceGroup: "resGroup1", + RestoreName: "000000000000000000", + }, + }, + { + Name: "Wrong Casing", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.Sql/servers/sqlServer1/RestorableDroppedDatabases/sqlDB1,000000000000000000", + Expected: nil, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Name) + + actual, err := MssqlRestorableDBID(v.Input) + if err != nil { + if v.Expected == nil { + continue + } + + t.Fatalf("Expected a value but got an error: %s", err) + } + + if actual.RestoreName != v.Expected.RestoreName { + t.Fatalf("Expected %q but got %q for Restore Name", v.Expected.Name, actual.Name) + } + + if actual.Name != v.Expected.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expected.Name, actual.Name) + } + + if actual.MsSqlServer != v.Expected.MsSqlServer { + t.Fatalf("Expected %q but got %q for Sql Server", v.Expected.Name, actual.Name) + } + + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for Resource Group", v.Expected.ResourceGroup, actual.ResourceGroup) + } + } +} + +func TestMssqlRecoverableDBID(t *testing.T) { + testData := []struct { + Name string + Input string + Expected *MsSqlRecoverableDBId + }{ + { + Name: "Empty", + Input: "", + Expected: nil, + }, + { + Name: "No Resource Groups Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000", + Expected: nil, + }, + { + Name: "No Resource Groups Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", + Expected: nil, + }, + { + Name: "Resource Group ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/", + Expected: nil, + }, + { + Name: "Missing Sql Server Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.Sql/servers/", + Expected: nil, + }, + { + Name: "Missing Sql Recoverable Database", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.Sql/servers/sqlServer1", + Expected: nil, + }, + { + Name: "Missing Sql Recoverable Database Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.Sql/servers/sqlServer1/recoverabledatabases", + Expected: nil, + }, + { + Name: "Sql Database ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.Sql/servers/sqlServer1/recoverabledatabases/sqlDB1", + Expected: &MsSqlRecoverableDBId{ + Name: "sqlDB1", + MsSqlServer: "sqlServer1", + ResourceGroup: "resGroup1", + }, + }, + { + Name: "Wrong Casing", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.Sql/servers/sqlServer1/Recoverabledatabases/sqlDB1", + Expected: nil, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Name) + + actual, err := MssqlRecoverableDBID(v.Input) + if err != nil { + if v.Expected == nil { + continue + } + + t.Fatalf("Expected a value but got an error: %s", err) + } + + if actual.Name != v.Expected.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expected.Name, actual.Name) + } + + if actual.MsSqlServer != v.Expected.MsSqlServer { + t.Fatalf("Expected %q but got %q for Sql Server", v.Expected.Name, actual.Name) + } + + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for Resource Group", v.Expected.ResourceGroup, actual.ResourceGroup) + } + } +} diff --git a/azurerm/internal/services/mssql/registration.go b/azurerm/internal/services/mssql/registration.go index c3656a06ebd8..f7baed0e0e4b 100644 --- a/azurerm/internal/services/mssql/registration.go +++ b/azurerm/internal/services/mssql/registration.go @@ -23,6 +23,7 @@ func (r Registration) SupportedDataSources() map[string]*schema.Resource { return map[string]*schema.Resource{ "azurerm_mssql_database": dataSourceArmMsSqlDatabase(), "azurerm_mssql_elasticpool": dataSourceArmMsSqlElasticpool(), + "azurerm_mssql_server": dataSourceMsSqlServer(), } } diff --git a/azurerm/internal/services/mssql/tests/mssql_database_resource_test.go b/azurerm/internal/services/mssql/tests/mssql_database_resource_test.go index 642b1d842409..dc6667d6efb0 100644 --- a/azurerm/internal/services/mssql/tests/mssql_database_resource_test.go +++ b/azurerm/internal/services/mssql/tests/mssql_database_resource_test.go @@ -308,6 +308,46 @@ func TestAccAzureRMMsSqlDatabase_createSecondaryMode(t *testing.T) { }) } +func TestAccAzureRMMsSqlDatabase_createRestoreMode(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_mssql_database", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMMsSqlDatabaseDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMMsSqlDatabase_createRestoreMode(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMsSqlDatabaseExists(data.ResourceName), + ), + }, + data.ImportStep("create_mode", "creation_source_database_id"), + + { + PreConfig: func() { time.Sleep(8 * time.Minute) }, + Config: testAccAzureRMMsSqlDatabase_createRestoreModeDBDeleted(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMsSqlDatabaseExists(data.ResourceName), + ), + }, + + data.ImportStep(), + + { + PreConfig: func() { time.Sleep(8 * time.Minute) }, + Config: testAccAzureRMMsSqlDatabase_createRestoreModeDBRestored(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMsSqlDatabaseExists(data.ResourceName), + testCheckAzureRMMsSqlDatabaseExists("azurerm_mssql_database.restore"), + ), + }, + + data.ImportStep("create_mode", "restore_dropped_database_id"), + }, + }) +} + func TestAccAzureRMMsSqlDatabase_threatDetectionPolicy(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_mssql_database", "test") @@ -781,6 +821,106 @@ resource "azurerm_mssql_database" "secondary" { `, template, data.RandomInteger, data.Locations.Secondary) } +func testAccAzureRMMsSqlDatabase_createRestoreMode(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-mssql-%[1]d" + location = "%[2]s" +} + +resource "azurerm_mssql_server" "test" { + name = "acctest-sqlserver-%[1]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + version = "12.0" + administrator_login = "mradministrator" + administrator_login_password = "thisIsDog11" +} + + +resource "azurerm_mssql_database" "test" { + name = "acctest-db-%[1]d" + server_id = azurerm_mssql_server.test.id +} + +resource "azurerm_mssql_database" "copy" { + name = "acctest-dbc-%[1]d" + server_id = azurerm_mssql_server.test.id + create_mode = "Copy" + creation_source_database_id = azurerm_mssql_database.test.id +} +`, data.RandomInteger, data.Locations.Primary) +} + +func testAccAzureRMMsSqlDatabase_createRestoreModeDBDeleted(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-mssql-%[1]d" + location = "%[2]s" +} + +resource "azurerm_mssql_server" "test" { + name = "acctest-sqlserver-%[1]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + version = "12.0" + administrator_login = "mradministrator" + administrator_login_password = "thisIsDog11" +} + + +resource "azurerm_mssql_database" "test" { + name = "acctest-db-%[1]d" + server_id = azurerm_mssql_server.test.id +} + +`, data.RandomInteger, data.Locations.Primary) +} + +func testAccAzureRMMsSqlDatabase_createRestoreModeDBRestored(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-mssql-%[1]d" + location = "%[2]s" +} + +resource "azurerm_mssql_server" "test" { + name = "acctest-sqlserver-%[1]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + version = "12.0" + administrator_login = "mradministrator" + administrator_login_password = "thisIsDog11" +} + + +resource "azurerm_mssql_database" "test" { + name = "acctest-db-%[1]d" + server_id = azurerm_mssql_server.test.id +} + +resource "azurerm_mssql_database" "restore" { + name = "acctest-dbr-%[1]d" + server_id = azurerm_mssql_server.test.id + create_mode = "Restore" + restore_dropped_database_id = azurerm_mssql_server.test.restorable_dropped_database_ids[0] +} + +`, data.RandomInteger, data.Locations.Primary) +} + func testAccAzureRMMsSqlDatabase_threatDetectionPolicy(data acceptance.TestData, state string) string { template := testAccAzureRMMsSqlDatabase_template(data) return fmt.Sprintf(` diff --git a/azurerm/internal/services/mssql/tests/mssql_server_data_source_test.go b/azurerm/internal/services/mssql/tests/mssql_server_data_source_test.go new file mode 100644 index 000000000000..90a983ef4a23 --- /dev/null +++ b/azurerm/internal/services/mssql/tests/mssql_server_data_source_test.go @@ -0,0 +1,75 @@ +package tests + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" +) + +func TestAccDataSourceAzureRMMsSqlServer_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azurerm_mssql_server", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMMsSqlServerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAzureRMMsSqlServer_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMsSqlServerExists(data.ResourceName), + ), + }, + }, + }) +} + +func TestAccDataSourceAzureRMMsSqlServer_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azurerm_mssql_server", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMMsSqlServerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAzureRMMsSqlServer_complete(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMsSqlServerExists(data.ResourceName), + resource.TestCheckResourceAttrSet(data.ResourceName, "version"), + resource.TestCheckResourceAttrSet(data.ResourceName, "administrator_login"), + resource.TestCheckResourceAttrSet(data.ResourceName, "fully_qualified_domain_name"), + resource.TestCheckResourceAttrSet(data.ResourceName, "tags.%"), + ), + }, + }, + }) +} + +func testAccDataSourceAzureRMMsSqlServer_basic(data acceptance.TestData) string { + template := testAccAzureRMMsSqlServer_basic(data) + return fmt.Sprintf(` +%s + +data "azurerm_mssql_server" "test" { + name = azurerm_mssql_server.test.name + resource_group_name = azurerm_resource_group.test.name +} + +`, template) +} + +func testAccDataSourceAzureRMMsSqlServer_complete(data acceptance.TestData) string { + template := testAccAzureRMMsSqlServer_complete(data) + return fmt.Sprintf(` +%s + +data "azurerm_mssql_server" "test" { + name = azurerm_mssql_server.test.name + resource_group_name = azurerm_resource_group.test.name +} + +`, template) +} diff --git a/azurerm/internal/services/mssql/validate/mssql_database.go b/azurerm/internal/services/mssql/validate/mssql_database.go index bf643b9bf6bb..cd23a035a9c2 100644 --- a/azurerm/internal/services/mssql/validate/mssql_database.go +++ b/azurerm/internal/services/mssql/validate/mssql_database.go @@ -54,3 +54,31 @@ func MsSqlDBCollation() schema.SchemaValidateFunc { `This is not a valid collation.`, ) } + +func MsSqlRestorableDatabaseID(i interface{}, k string) (warnings []string, errors []error) { + v, ok := i.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %q to be string", k)) + return warnings, errors + } + + if _, err := parse.MssqlRestorableDBID(v); err != nil { + errors = append(errors, fmt.Errorf("Can not parse %q as a MsSql Restorable Database resource id: %v", k, err)) + } + + return warnings, errors +} + +func MsSqlRecoverableDatabaseID(i interface{}, k string) (warnings []string, errors []error) { + v, ok := i.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %q to be string", k)) + return warnings, errors + } + + if _, err := parse.MssqlRecoverableDBID(v); err != nil { + errors = append(errors, fmt.Errorf("Can not parse %q as a MsSql Recoverable Database resource id: %v", k, err)) + } + + return warnings, errors +} diff --git a/website/azurerm.erb b/website/azurerm.erb index fe6bad1ddc7c..e3634bda48fb 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -424,6 +424,10 @@
  • azurerm_mssql_elasticpool
  • + +
  • + azurerm_mssql_server +
  • azurerm_nat_gateway diff --git a/website/docs/d/mssql_server.html.markdown b/website/docs/d/mssql_server.html.markdown new file mode 100644 index 000000000000..bcab06b0c49b --- /dev/null +++ b/website/docs/d/mssql_server.html.markdown @@ -0,0 +1,68 @@ +--- +subcategory: "Database" +layout: "azurerm" +page_title: "Azure Resource Manager: Data Source: azurerm_mssql_server" +description: |- + Gets information about an existing Microsoft SQL Server. +--- + +# Data Source: azurerm_mssql_server + +Use this data source to access information about an existing Microsoft SQL Server. + +## Example Usage + +```hcl +data "azurerm_mssql_server" "example" { + name = "existingMsSqlServer" + resource_group_name = "existingResGroup" +} + +output "id" { + value = data.azurerm_mssql_server.example.id +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `name` - (Required) The name of this Microsoft SQL Server. + +* `resource_group_name` - (Required) The name of the Resource Group where the Microsoft SQL Server exists. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the Microsoft SQL Server. + +* `administrator_login` - The server's administrator login name. + +* `fully_qualified_domain_name` - The fully qualified domain name of the Azure SQL Server. + +* `identity` - A `identity` block as defined below. + +* `location` - The Azure Region where the Microsoft SQL Server exists. + +* `restorable_dropped_database_ids` - A list of dropped restorable database IDs on the server. + +* `tags` - A mapping of tags assigned to this Microsoft SQL Server. + +* `version` - This servers MS SQL version. + +--- + +A `identity` block exports the following: + +* `principal_id` - The Principal ID for the Service Principal associated with the Identity of this SQL Server. + +* `tenant_id` - The Tenant ID for the Service Principal associated with the Identity of this SQL Server. + +* `type` - The identity type of the Microsoft SQL Server. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `read` - (Defaults to 5 minutes) Used when retrieving the Microsoft SQL Server. diff --git a/website/docs/r/mssql_database.html.markdown b/website/docs/r/mssql_database.html.markdown index e6caa16a0d3d..0de44d944121 100644 --- a/website/docs/r/mssql_database.html.markdown +++ b/website/docs/r/mssql_database.html.markdown @@ -78,7 +78,7 @@ The following arguments are supported: * `auto_pause_delay_in_minutes` - (Optional) Time in minutes after which database is automatically paused. A value of `-1` means that automatic pause is disabled. This property is only settable for General Purpose Serverless databases. -* `create_mode` - (Optional) The create mode of the database. Possible values are `Copy`, `Default`, `OnlineSecondary`, `PointInTimeRestore`, `Restore`, `RestoreExternalBackup`, `RestoreExternalBackupSecondary`, `RestoreLongTermRetentionBackup` and `Secondary`. +* `create_mode` - (Optional) The create mode of the database. Possible values are `Copy`, `Default`, `OnlineSecondary`, `PointInTimeRestore`, `Recovery`, `Restore`, `RestoreExternalBackup`, `RestoreExternalBackupSecondary`, `RestoreLongTermRetentionBackup` and `Secondary`. * `creation_source_database_id` - (Optional) The id of the source database to be referred to create the new database. This should only be used for databases with `create_mode` values that use another database as reference. Changing this forces a new resource to be created. @@ -96,6 +96,10 @@ The following arguments are supported: * `restore_point_in_time` - (Required) Specifies the point in time (ISO8601 format) of the source database that will be restored to create the new database. This property is only settable for `create_mode`= `PointInTimeRestore` databases. +* `recover_database_id` - (Optional) The ID of the database to be recovered. This property is only applicable when the `create_mode` is `Recovery`. + +* `restore_dropped_database_id` - (Optional) The ID of the database to be restored. This property is only applicable when the `create_mode` is `Restore`. + * `read_replica_count` - (Optional) The number of readonly secondary replicas associated with the database to which readonly application intent connections may be routed. This property is only settable for Hyperscale edition databases. * `read_scale` - (Optional) If enabled, connections that have application intent set to readonly in their connection string may be routed to a readonly secondary replica. This property is only settable for Premium and Business Critical databases. diff --git a/website/docs/r/mssql_server.html.markdown b/website/docs/r/mssql_server.html.markdown index 8480c70b996f..2bafa7b8a038 100644 --- a/website/docs/r/mssql_server.html.markdown +++ b/website/docs/r/mssql_server.html.markdown @@ -1,7 +1,7 @@ --- subcategory: "Database" layout: "azurerm" -page_title: "Azure Resource Manager: azurerm_sql_server" +page_title: "Azure Resource Manager: azurerm_mssql_server" description: |- Manages a Microsoft SQL Azure Database Server. @@ -65,9 +65,9 @@ The following arguments are supported: * `location` - (Required) Specifies the supported Azure location where the resource exists. Changing this forces a new resource to be created. -* `version` - (Required) The version for the new server. Valid values are: 2.0 (for v11 server) and 12.0 (for v12 server). +* `version` - (Required) This servers MS SQL version. Valid values are: 2.0 (for v11 server) and 12.0 (for v12 server). -* `administrator_login` - (Required) The administrator login name for the new server. Changing this forces a new resource to be created. +* `administrator_login` - (Required) The administrator's login name for the new server. Changing this forces a new resource to be created. * `administrator_login_password` - (Required) The password associated with the `administrator_login` user. Needs to comply with Azure's [Password Policy](https://msdn.microsoft.com/library/ms161959.aspx) @@ -99,6 +99,8 @@ The following attributes are exported: * `fully_qualified_domain_name` - The fully qualified domain name of the Azure SQL Server (e.g. myServerName.database.windows.net) +* `restorable_dropped_database_ids` - A list of dropped restorable database IDs on the server. + --- `identity` exports the following: