diff --git a/azurerm/helpers/azure/mssql.go b/azurerm/helpers/azure/mssql.go index bc7e0e90b72e..cd7e169f818a 100644 --- a/azurerm/helpers/azure/mssql.go +++ b/azurerm/helpers/azure/mssql.go @@ -42,3 +42,10 @@ func ValidateMsSqlElasticPoolName(i interface{}, k string) (_ []string, errors [ return nil, nil } + +func ValidateLongTermRetentionPoliciesIsoFormat(i interface{}, k string) (_ []string, errors []error) { + if m, regexErrs := validate.RegExHelper(i, k, `^P[0-9]*[YMWD]`); !m { + return nil, append(regexErrs, fmt.Errorf(`%q has to be a valid Duration format, starting with "P" and ending with either of the letters "YMWD"`, k)) + } + return nil, nil +} diff --git a/azurerm/internal/services/mssql/client/client.go b/azurerm/internal/services/mssql/client/client.go index fbf5aad94bf0..eaa11c24737f 100644 --- a/azurerm/internal/services/mssql/client/client.go +++ b/azurerm/internal/services/mssql/client/client.go @@ -7,6 +7,8 @@ import ( ) type Client struct { + BackupLongTermRetentionPoliciesClient *sql.BackupLongTermRetentionPoliciesClient + BackupShortTermRetentionPoliciesClient *sql.BackupShortTermRetentionPoliciesClient DatabasesClient *sql.DatabasesClient DatabaseExtendedBlobAuditingPoliciesClient *sql.ExtendedDatabaseBlobAuditingPoliciesClient DatabaseThreatDetectionPoliciesClient *sql.DatabaseThreatDetectionPoliciesClient @@ -23,6 +25,12 @@ type Client struct { } func NewClient(o *common.ClientOptions) *Client { + BackupLongTermRetentionPoliciesClient := sql.NewBackupLongTermRetentionPoliciesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&BackupLongTermRetentionPoliciesClient.Client, o.ResourceManagerAuthorizer) + + BackupShortTermRetentionPoliciesClient := sql.NewBackupShortTermRetentionPoliciesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&BackupShortTermRetentionPoliciesClient.Client, o.ResourceManagerAuthorizer) + databasesClient := sql.NewDatabasesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&databasesClient.Client, o.ResourceManagerAuthorizer) @@ -63,7 +71,9 @@ func NewClient(o *common.ClientOptions) *Client { o.ConfigureClient(&sqlVirtualMachinesClient.Client, o.ResourceManagerAuthorizer) return &Client{ - DatabasesClient: &databasesClient, + BackupLongTermRetentionPoliciesClient: &BackupLongTermRetentionPoliciesClient, + BackupShortTermRetentionPoliciesClient: &BackupShortTermRetentionPoliciesClient, + DatabasesClient: &databasesClient, DatabaseExtendedBlobAuditingPoliciesClient: &databaseExtendedBlobAuditingPoliciesClient, DatabaseThreatDetectionPoliciesClient: &databaseThreatDetectionPoliciesClient, DatabaseVulnerabilityAssessmentRuleBaselinesClient: &databaseVulnerabilityAssessmentRuleBaselinesClient, diff --git a/azurerm/internal/services/mssql/helper/sql_retention_policies.go b/azurerm/internal/services/mssql/helper/sql_retention_policies.go new file mode 100644 index 000000000000..c2979a7119b4 --- /dev/null +++ b/azurerm/internal/services/mssql/helper/sql_retention_policies.go @@ -0,0 +1,171 @@ +package helper + +import ( + "github.com/Azure/azure-sdk-for-go/services/preview/sql/mgmt/v3.0/sql" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func LongTermRetentionPolicySchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + // WeeklyRetention - The weekly retention policy for an LTR backup in an ISO 8601 format. + "weekly_retention": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: azure.ValidateLongTermRetentionPoliciesIsoFormat, + }, + // MonthlyRetention - The monthly retention policy for an LTR backup in an ISO 8601 format. + "monthly_retention": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: azure.ValidateLongTermRetentionPoliciesIsoFormat, + }, + // YearlyRetention - The yearly retention policy for an LTR backup in an ISO 8601 format. + "yearly_retention": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: azure.ValidateLongTermRetentionPoliciesIsoFormat, + }, + // WeekOfYear - The week of year to take the yearly backup in an ISO 8601 format. + "week_of_year": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ValidateFunc: validation.IntBetween(1, 52), + }, + }, + }, + } +} + +func ShortTermRetentionPolicySchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "retention_days": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(7, 35), + }, + }, + }, + } +} + +func ExpandLongTermRetentionPolicy(input []interface{}) *sql.LongTermRetentionPolicyProperties { + if len(input) == 0 || input[0] == nil { + return nil + } + + longTermRetentionPolicy := input[0].(map[string]interface{}) + + longTermPolicyProperties := sql.LongTermRetentionPolicyProperties{ + WeeklyRetention: utils.String("PT0S"), + MonthlyRetention: utils.String("PT0S"), + YearlyRetention: utils.String("PT0S"), + WeekOfYear: utils.Int32(0), + } + + if v, ok := longTermRetentionPolicy["weekly_retention"]; ok { + longTermPolicyProperties.WeeklyRetention = utils.String(v.(string)) + } + + if v, ok := longTermRetentionPolicy["monthly_retention"]; ok { + longTermPolicyProperties.MonthlyRetention = utils.String(v.(string)) + } + + if v, ok := longTermRetentionPolicy["yearly_retention"]; ok { + longTermPolicyProperties.YearlyRetention = utils.String(v.(string)) + } + + if v, ok := longTermRetentionPolicy["week_of_year"]; ok { + longTermPolicyProperties.WeekOfYear = utils.Int32(int32(v.(int))) + } + + return &longTermPolicyProperties +} + +func FlattenLongTermRetentionPolicy(longTermRetentionPolicy *sql.BackupLongTermRetentionPolicy, d *schema.ResourceData) []interface{} { + if longTermRetentionPolicy == nil { + return []interface{}{} + } + + monthlyRetention := "PT0S" + if longTermRetentionPolicy.MonthlyRetention != nil { + monthlyRetention = *longTermRetentionPolicy.MonthlyRetention + } + + weeklyRetention := "PT0S" + if longTermRetentionPolicy.WeeklyRetention != nil { + weeklyRetention = *longTermRetentionPolicy.WeeklyRetention + } + + weekOfYear := int32(0) + if longTermRetentionPolicy.WeekOfYear != nil { + weekOfYear = *longTermRetentionPolicy.WeekOfYear + } + + yearlyRetention := "PT0S" + if longTermRetentionPolicy.YearlyRetention != nil { + yearlyRetention = *longTermRetentionPolicy.YearlyRetention + } + + return []interface{}{ + map[string]interface{}{ + "monthly_retention": monthlyRetention, + "weekly_retention": weeklyRetention, + "week_of_year": weekOfYear, + "yearly_retention": yearlyRetention, + }, + } +} + +func ExpandShortTermRetentionPolicy(input []interface{}) *sql.BackupShortTermRetentionPolicyProperties { + if len(input) == 0 || input[0] == nil { + return nil + } + + shortTermRetentionPolicy := input[0].(map[string]interface{}) + + shortTermPolicyProperties := sql.BackupShortTermRetentionPolicyProperties{ + RetentionDays: utils.Int32(7), + } + + if v, ok := shortTermRetentionPolicy["retention_days"]; ok { + shortTermPolicyProperties.RetentionDays = utils.Int32(int32(v.(int))) + } + + return &shortTermPolicyProperties +} + +func FlattenShortTermRetentionPolicy(shortTermRetentionPolicy *sql.BackupShortTermRetentionPolicy, d *schema.ResourceData) []interface{} { + if shortTermRetentionPolicy == nil { + return []interface{}{} + } + + retentionDays := int32(7) + if shortTermRetentionPolicy.RetentionDays != nil { + retentionDays = *shortTermRetentionPolicy.RetentionDays + } + + return []interface{}{ + map[string]interface{}{ + "retention_days": retentionDays, + }, + } +} diff --git a/azurerm/internal/services/mssql/mssql_database_resource.go b/azurerm/internal/services/mssql/mssql_database_resource.go index e2298d7e4cd2..0a080288c717 100644 --- a/azurerm/internal/services/mssql/mssql_database_resource.go +++ b/azurerm/internal/services/mssql/mssql_database_resource.go @@ -111,6 +111,10 @@ func resourceArmMsSqlDatabase() *schema.Resource { }, false), }, + "long_term_retention_policy": helper.LongTermRetentionPolicySchema(), + + "short_term_retention_policy": helper.ShortTermRetentionPolicySchema(), + "max_size_gb": { Type: schema.TypeInt, Optional: true, @@ -292,6 +296,8 @@ func resourceArmMsSqlDatabaseCreateUpdate(d *schema.ResourceData, meta interface auditingClient := meta.(*clients.Client).MSSQL.DatabaseExtendedBlobAuditingPoliciesClient serverClient := meta.(*clients.Client).MSSQL.ServersClient threatClient := meta.(*clients.Client).MSSQL.DatabaseThreatDetectionPoliciesClient + longTermRetentionClient := meta.(*clients.Client).MSSQL.BackupLongTermRetentionPoliciesClient + shortTermRetentionClient := meta.(*clients.Client).MSSQL.BackupShortTermRetentionPoliciesClient ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) defer cancel() @@ -385,9 +391,10 @@ func resourceArmMsSqlDatabaseCreateUpdate(d *schema.ResourceData, meta interface params.DatabaseProperties.RestorePointInTime = &date.Time{Time: restorePointInTime} } - if v, ok := d.GetOk("sku_name"); ok { + skuName, ok := d.GetOk("sku_name") + if ok { params.Sku = &sql.Sku{ - Name: utils.String(v.(string)), + Name: utils.String(skuName.(string)), } } @@ -436,6 +443,49 @@ func resourceArmMsSqlDatabaseCreateUpdate(d *schema.ResourceData, meta interface } } + // hyper-scale SKU's do not support LRP currently + if d.HasChange("long_term_retention_policy") { + v := d.Get("long_term_retention_policy") + longTermRetentionProps := helper.ExpandLongTermRetentionPolicy(v.([]interface{})) + if longTermRetentionProps != nil { + longTermRetentionPolicy := sql.BackupLongTermRetentionPolicy{} + + if !strings.HasPrefix(skuName.(string), "HS") { + longTermRetentionPolicy.LongTermRetentionPolicyProperties = longTermRetentionProps + } + + longTermRetentionfuture, err := longTermRetentionClient.CreateOrUpdate(ctx, serverId.ResourceGroup, serverId.Name, name, longTermRetentionPolicy) + if err != nil { + return fmt.Errorf("Error issuing create/update request for Sql Server %q (Database %q) Long Term Retention Policies (Resource Group %q): %+v", serverId.Name, name, serverId.ResourceGroup, err) + } + + if err = longTermRetentionfuture.WaitForCompletionRef(ctx, longTermRetentionClient.Client); err != nil { + return fmt.Errorf("Error waiting for completion of Create/Update for Sql Server %q (Database %q) Long Term Retention Policies (Resource Group %q): %+v", serverId.Name, name, serverId.ResourceGroup, err) + } + } + } + + if d.HasChange("short_term_retention_policy") { + v := d.Get("short_term_retention_policy") + backupShortTermPolicyProps := helper.ExpandShortTermRetentionPolicy(v.([]interface{})) + if backupShortTermPolicyProps != nil { + backupShortTermPolicy := sql.BackupShortTermRetentionPolicy{} + + if !strings.HasPrefix(skuName.(string), "HS") { + backupShortTermPolicy.BackupShortTermRetentionPolicyProperties = backupShortTermPolicyProps + } + + shortTermRetentionFuture, err := shortTermRetentionClient.CreateOrUpdate(ctx, serverId.ResourceGroup, serverId.Name, name, backupShortTermPolicy) + if err != nil { + return fmt.Errorf("Error issuing create/update request for Sql Server %q (Database %q) Short Term Retention Policies (Resource Group %q): %+v", serverId.Name, name, serverId.ResourceGroup, err) + } + + if err = shortTermRetentionFuture.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for completion of Create/Update for Sql Server %q (Database %q) Short Term Retention Policies (Resource Group %q): %+v", serverId.Name, name, serverId.ResourceGroup, err) + } + } + } + return resourceArmMsSqlDatabaseRead(d, meta) } @@ -443,6 +493,8 @@ func resourceArmMsSqlDatabaseRead(d *schema.ResourceData, meta interface{}) erro client := meta.(*clients.Client).MSSQL.DatabasesClient threatClient := meta.(*clients.Client).MSSQL.DatabaseThreatDetectionPoliciesClient auditingClient := meta.(*clients.Client).MSSQL.DatabaseExtendedBlobAuditingPoliciesClient + longTermRetentionClient := meta.(*clients.Client).MSSQL.BackupLongTermRetentionPoliciesClient + shortTermRetentionClient := meta.(*clients.Client).MSSQL.BackupShortTermRetentionPoliciesClient ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() @@ -470,6 +522,7 @@ func resourceArmMsSqlDatabaseRead(d *schema.ResourceData, meta interface{}) erro } d.Set("server_id", serverResp.ID) + skuName := "" if props := resp.DatabaseProperties; props != nil { d.Set("auto_pause_delay_in_minutes", props.AutoPauseDelay) d.Set("collation", props.Collation) @@ -485,6 +538,9 @@ func resourceArmMsSqlDatabaseRead(d *schema.ResourceData, meta interface{}) erro } else if props.ReadScale == sql.DatabaseReadScaleDisabled { d.Set("read_scale", false) } + if props.CurrentServiceObjectiveName != nil { + skuName = *props.CurrentServiceObjectiveName + } d.Set("sku_name", props.CurrentServiceObjectiveName) d.Set("zone_redundant", props.ZoneRedundant) } @@ -506,6 +562,33 @@ func resourceArmMsSqlDatabaseRead(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("failure in setting `extended_auditing_policy`: %+v", err) } + // Hyper Scale SKU's do not currently support LRP and do not honour normal SRP operations + if !strings.HasPrefix(skuName, "HS") { + longTermPolicy, err := longTermRetentionClient.Get(ctx, id.ResourceGroup, id.MsSqlServer, id.Name) + if err != nil { + return fmt.Errorf("Error retrieving Long Term Policies for Database %q (Sql Server %q ;Resource Group %q): %+v", id.Name, id.MsSqlServer, id.ResourceGroup, err) + } + flattenlongTermPolicy := helper.FlattenLongTermRetentionPolicy(&longTermPolicy, d) + if err := d.Set("long_term_retention_policy", flattenlongTermPolicy); err != nil { + return fmt.Errorf("failure in setting `long_term_retention_policy`: %+v", err) + } + + shortTermPolicy, err := shortTermRetentionClient.Get(ctx, id.ResourceGroup, id.MsSqlServer, id.Name) + if err != nil { + return fmt.Errorf("Error retrieving Short Term Policies for Database %q (Sql Server %q ;Resource Group %q): %+v", id.Name, id.MsSqlServer, id.ResourceGroup, err) + } + + flattenShortTermPolicy := helper.FlattenShortTermRetentionPolicy(&shortTermPolicy, d) + if err := d.Set("short_term_retention_policy", flattenShortTermPolicy); err != nil { + return fmt.Errorf("failure in setting `short_term_retention_policy`: %+v", err) + } + } else { + // HS SKUs need the retention policies zeroing for state consistency + zero := make([]interface{}, 0) + d.Set("long_term_retention_policy", zero) + d.Set("short_term_retention_policy", zero) + } + return tags.FlattenAndSet(d, resp.Tags) } 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 fe2d69b4b064..a03f3811dfa0 100644 --- a/azurerm/internal/services/mssql/tests/mssql_database_resource_test.go +++ b/azurerm/internal/services/mssql/tests/mssql_database_resource_test.go @@ -474,6 +474,71 @@ func TestAccAzureRMMsSqlDatabase_minCapacity0(t *testing.T) { }) } +func TestAccAzureRMMsSqlDatabase_withLongTermRetentionPolicy(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_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMsSqlDatabaseExists(data.ResourceName), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMMsSqlDatabase_withLongTermRetentionPolicy(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMsSqlDatabaseExists(data.ResourceName), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMMsSqlDatabase_withLongTermRetentionPolicyUpdated(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMsSqlDatabaseExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMMsSqlDatabase_withShortTermRetentionPolicy(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_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMsSqlDatabaseExists(data.ResourceName), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMMsSqlDatabase_withShortTermRetentionPolicy(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMsSqlDatabaseExists(data.ResourceName), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMMsSqlDatabase_withShortTermRetentionPolicyUpdated(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMMsSqlDatabaseExists(data.ResourceName), + ), + }, + }, + }) +} + func testCheckAzureRMMsSqlDatabaseExists(resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { client := acceptance.AzureProvider.Meta().(*clients.Client).MSSQL.DatabasesClient @@ -1116,3 +1181,132 @@ resource "azurerm_mssql_database" "test" { } `, template, data.RandomInteger) } + +func testAccAzureRMMsSqlDatabase_withLongTermRetentionPolicy(data acceptance.TestData) string { + template := testAccAzureRMMsSqlDatabase_template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_storage_account" "test" { + name = "acctest%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_storage_account" "test2" { + name = "acctest2%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_mssql_database" "test" { + name = "acctest-db-%[3]d" + server_id = azurerm_sql_server.test.id + long_term_retention_policy { + weekly_retention = "P1W" + monthly_retention = "P1M" + yearly_retention = "P1Y" + week_of_year = 1 + } +} +`, template, data.RandomIntOfLength(15), data.RandomInteger) +} + +func testAccAzureRMMsSqlDatabase_withLongTermRetentionPolicyUpdated(data acceptance.TestData) string { + template := testAccAzureRMMsSqlDatabase_template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_storage_account" "test" { + name = "acctest%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_storage_account" "test2" { + name = "acctest2%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_mssql_database" "test" { + name = "acctest-db-%[3]d" + server_id = azurerm_sql_server.test.id + long_term_retention_policy { + weekly_retention = "P1W" + yearly_retention = "P1Y" + week_of_year = 2 + } +} +`, template, data.RandomIntOfLength(15), data.RandomInteger) +} + +func testAccAzureRMMsSqlDatabase_withShortTermRetentionPolicy(data acceptance.TestData) string { + template := testAccAzureRMMsSqlDatabase_template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_storage_account" "test" { + name = "acctest%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_storage_account" "test2" { + name = "acctest2%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_mssql_database" "test" { + name = "acctest-db-%[3]d" + server_id = azurerm_sql_server.test.id + short_term_retention_policy { + retention_days = 8 + } +} +`, template, data.RandomIntOfLength(15), data.RandomInteger) +} + +func testAccAzureRMMsSqlDatabase_withShortTermRetentionPolicyUpdated(data acceptance.TestData) string { + template := testAccAzureRMMsSqlDatabase_template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_storage_account" "test" { + name = "acctest%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_storage_account" "test2" { + name = "acctest2%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_mssql_database" "test" { + name = "acctest-db-%[3]d" + server_id = azurerm_sql_server.test.id + short_term_retention_policy { + retention_days = 10 + } +} +`, template, data.RandomIntOfLength(15), data.RandomInteger) +} diff --git a/website/azurerm.erb b/website/azurerm.erb index c982087cad5a..2a2ac01ffe2b 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -1411,7 +1411,7 @@