Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

azurerm_mssql_virtual_machine: support auto_backup #10460

Merged
merged 5 commits into from
Feb 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
318 changes: 318 additions & 0 deletions azurerm/internal/services/mssql/mssql_virtual_machine_resource.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package mssql

import (
"context"
"fmt"
"log"
"strings"
"time"

"github.com/Azure/azure-sdk-for-go/services/preview/sqlvirtualmachine/mgmt/2017-03-01-preview/sqlvirtualmachine"
"github.com/hashicorp/go-azure-helpers/response"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"

Expand All @@ -20,6 +22,7 @@ import (
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/mssql/validate"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags"
azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/suppress"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)
Expand All @@ -31,6 +34,8 @@ func resourceMsSqlVirtualMachine() *schema.Resource {
Update: resourceMsSqlVirtualMachineCreateUpdate,
Delete: resourceMsSqlVirtualMachineDelete,

CustomizeDiff: resourceMsSqlVirtualMachineCustomDiff,

Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error {
_, err := parse.SqlVirtualMachineID(id)
return err
Expand Down Expand Up @@ -61,6 +66,88 @@ func resourceMsSqlVirtualMachine() *schema.Resource {
}, false),
},

"auto_backup": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"encryption_enabled": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},

"encryption_password": {
Type: schema.TypeString,
Optional: true,
Sensitive: true,
ValidateFunc: validation.StringIsNotEmpty,
},

"manual_schedule": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"full_backup_frequency": {
Type: schema.TypeString,
Required: true,
DiffSuppressFunc: suppress.CaseDifference,
ValidateFunc: validation.StringInSlice([]string{
string(sqlvirtualmachine.Daily),
string(sqlvirtualmachine.Weekly),
}, false),
},

"full_backup_start_hour": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntBetween(0, 23),
},

"full_backup_window_in_hours": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntBetween(1, 23),
},

"log_backup_frequency_in_minutes": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntBetween(5, 60),
},
},
},
},

"retention_period_in_days": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntBetween(1, 30),
},

"storage_blob_endpoint": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.IsURLWithHTTPS,
},

"storage_account_access_key": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringIsNotEmpty,
},

"system_databases_backup_enabled": {
Type: schema.TypeBool,
Optional: true,
},
},
},
},

"auto_patching": {
Type: schema.TypeList,
Optional: true,
Expand Down Expand Up @@ -210,6 +297,28 @@ func resourceMsSqlVirtualMachine() *schema.Resource {
}
}

func resourceMsSqlVirtualMachineCustomDiff(d *schema.ResourceDiff, _ interface{}) error {
// ForceNew when removing the auto_backup block.
// See https://github.com/Azure/azure-rest-api-specs/issues/12818#issuecomment-773727756
old, new := d.GetChange("auto_backup")
if len(old.([]interface{})) == 1 && len(new.([]interface{})) == 0 {
return d.ForceNew("auto_backup")
}

encryptionEnabled := d.Get("auto_backup.0.encryption_enabled")
v, ok := d.GetOk("auto_backup.0.encryption_password")

if encryptionEnabled.(bool) && (!ok || v.(string) == "") {
return fmt.Errorf("auto_backup: `encryption_password` is required when `encryption_enabled` is true")
}

if !encryptionEnabled.(bool) && ok && v.(string) != "" {
return fmt.Errorf("auto_backup: `encryption_enabled` must be true when `encryption_password` is set")
}

return nil
}

func resourceMsSqlVirtualMachineCreateUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).MSSQL.VirtualMachinesClient
vmclient := meta.(*clients.Client).Compute.VMClient
Expand Down Expand Up @@ -250,6 +359,7 @@ func resourceMsSqlVirtualMachineCreateUpdate(d *schema.ResourceData, meta interf
VirtualMachineResourceID: utils.String(d.Get("virtual_machine_id").(string)),
SQLServerLicenseType: sqlvirtualmachine.SQLServerLicenseType(d.Get("sql_license_type").(string)),
SQLManagement: sqlvirtualmachine.Full,
AutoBackupSettings: expandSqlVirtualMachineAutoBackupSettings(d.Get("auto_backup").([]interface{})),
AutoPatchingSettings: expandSqlVirtualMachineAutoPatchingSettings(d.Get("auto_patching").([]interface{})),
KeyVaultCredentialSettings: expandSqlVirtualMachineKeyVaultCredential(d.Get("key_vault_credential").([]interface{})),
ServerConfigurationsManagementSettings: &sqlvirtualmachine.ServerConfigurationsManagementSettings{
Expand Down Expand Up @@ -283,8 +393,32 @@ func resourceMsSqlVirtualMachineCreateUpdate(d *schema.ResourceData, meta interf
if resp.ID == nil {
return fmt.Errorf("Cannot read Sql Virtual Machine (Sql Virtual Machine Name %q / Resource Group %q) ID", id.Name, id.ResourceGroup)
}

d.SetId(*resp.ID)

// Wait for the auto backup settings to take effect
// See: https://github.com/Azure/azure-rest-api-specs/issues/12818
if autoBackup := d.Get("auto_backup"); (d.IsNewResource() && len(autoBackup.([]interface{})) > 0) || (!d.IsNewResource() && d.HasChange("auto_backup")) {
log.Printf("[DEBUG] Waiting for SQL Virtual Machine %q AutoBackupSettings to take effect", d.Id())
stateConf := &resource.StateChangeConf{
Pending: []string{"Retry", "Pending"},
Target: []string{"Updated"},
Refresh: resourceMsSqlVirtualMachineAutoBackupSettingsRefreshFunc(ctx, client, d),
MinTimeout: 1 * time.Minute,
ContinuousTargetOccurence: 2,
}

if d.IsNewResource() {
stateConf.Timeout = d.Timeout(schema.TimeoutCreate)
} else {
stateConf.Timeout = d.Timeout(schema.TimeoutUpdate)
}

if _, err := stateConf.WaitForState(); err != nil {
return fmt.Errorf("waiting for SQL Virtual Machine %q AutoBackupSettings to take effect: %+v", d.Id(), err)
}
}

return resourceMsSqlVirtualMachineRead(d, meta)
}

Expand All @@ -311,6 +445,10 @@ func resourceMsSqlVirtualMachineRead(d *schema.ResourceData, meta interface{}) e
if props := resp.Properties; props != nil {
d.Set("virtual_machine_id", props.VirtualMachineResourceID)
d.Set("sql_license_type", string(props.SQLServerLicenseType))
if err := d.Set("auto_backup", flattenSqlVirtualMachineAutoBackup(props.AutoBackupSettings, d)); err != nil {
return fmt.Errorf("setting `auto_backup`: %+v", err)
}

if err := d.Set("auto_patching", flattenSqlVirtualMachineAutoPatching(props.AutoPatchingSettings)); err != nil {
return fmt.Errorf("setting `auto_patching`: %+v", err)
}
Expand Down Expand Up @@ -367,6 +505,186 @@ func resourceMsSqlVirtualMachineDelete(d *schema.ResourceData, meta interface{})
return nil
}

func resourceMsSqlVirtualMachineAutoBackupSettingsRefreshFunc(ctx context.Context, client *sqlvirtualmachine.SQLVirtualMachinesClient, d *schema.ResourceData) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
id, err := parse.SqlVirtualMachineID(d.Id())
if err != nil {
return nil, "Error", err
}

resp, err := client.Get(ctx, id.ResourceGroup, id.Name, "*")
if err != nil {
return nil, "Retry", err
}

if props := resp.Properties; props != nil {
autoBackupSettings := flattenSqlVirtualMachineAutoBackup(props.AutoBackupSettings, d)

if len(autoBackupSettings) == 0 {
// auto backup was nil or disabled in the response
if v, ok := d.GetOk("auto_backup"); !ok || len(v.([]interface{})) == 0 {
// also disabled in the config
return resp, "Updated", nil
}
return resp, "Pending", nil
}

if v, ok := d.GetOk("auto_backup"); !ok || len(v.([]interface{})) == 0 {
// still waiting for it to be disabled
return resp, "Pending", nil
}

// check each property in the auto_backup block for drift
for prop, val := range autoBackupSettings[0].(map[string]interface{}) {
v := d.Get(fmt.Sprintf("auto_backup.0.%s", prop))
switch prop {
case "manual_schedule":
if m := val.([]interface{}); len(m) > 0 {
if b, ok := d.GetOk("auto_backup.0.manual_schedule"); !ok || len(b.([]interface{})) == 0 {
// manual schedule disabled in config but still showing in response
return resp, "Pending", nil
}
// check each property in the manual_schedule block for drift
for prop2, val2 := range m[0].(map[string]interface{}) {
v2 := d.Get(fmt.Sprintf("auto_backup.0.manual_schedule.0.%s", prop2))
switch prop2 {
case "full_backup_frequency":
if !strings.EqualFold(v2.(string), val2.(string)) {
return resp, "Pending", nil
}
default:
if v2 != val2 {
return resp, "Pending", nil
}
}
}
} else if b, ok := d.GetOk("auto_backup.0.manual_schedule"); ok || len(b.([]interface{})) > 0 {
// manual schedule set in config but not reflecting in response
return resp, "Pending", nil
}
default:
if v != val {
return resp, "Pending", nil
}
}
}

return resp, "Updated", nil
}

return resp, "Retry", nil
}
}

func expandSqlVirtualMachineAutoBackupSettings(input []interface{}) *sqlvirtualmachine.AutoBackupSettings {
ret := sqlvirtualmachine.AutoBackupSettings{
Enable: utils.Bool(false),
}

if len(input) > 0 {
config := input[0].(map[string]interface{})
ret.Enable = utils.Bool(true)

if v, ok := config["retention_period_in_days"]; ok {
ret.RetentionPeriod = utils.Int32(int32(v.(int)))
}
if v, ok := config["storage_blob_endpoint"]; ok {
ret.StorageAccountURL = utils.String(v.(string))
}
if v, ok := config["storage_account_access_key"]; ok {
ret.StorageAccessKey = utils.String(v.(string))
}

v, ok := config["encryption_enabled"]
enableEncryption := ok && v.(bool)
ret.EnableEncryption = utils.Bool(enableEncryption)
if v, ok := config["encryption_password"]; enableEncryption && ok {
ret.Password = utils.String(v.(string))
}

if v, ok := config["system_databases_backup_enabled"]; ok {
ret.BackupSystemDbs = utils.Bool(v.(bool))
}

ret.BackupScheduleType = sqlvirtualmachine.Automated
if v, ok := config["manual_schedule"]; ok && len(v.([]interface{})) > 0 {
manualSchedule := v.([]interface{})[0].(map[string]interface{})
ret.BackupScheduleType = sqlvirtualmachine.Manual

if v, ok := manualSchedule["full_backup_frequency"]; ok {
ret.FullBackupFrequency = sqlvirtualmachine.FullBackupFrequencyType(v.(string))
}
if v, ok := manualSchedule["full_backup_start_hour"]; ok {
ret.FullBackupStartTime = utils.Int32(int32(v.(int)))
}
if v, ok := manualSchedule["full_backup_window_in_hours"]; ok {
ret.FullBackupWindowHours = utils.Int32(int32(v.(int)))
}
if v, ok := manualSchedule["log_backup_frequency_in_minutes"]; ok {
ret.LogBackupFrequency = utils.Int32(int32(v.(int)))
}
}
}

return &ret
}

func flattenSqlVirtualMachineAutoBackup(autoBackup *sqlvirtualmachine.AutoBackupSettings, d *schema.ResourceData) []interface{} {
if autoBackup == nil || autoBackup.Enable == nil || !*autoBackup.Enable {
return []interface{}{}
}

manualSchedule := make([]interface{}, 0)
if strings.EqualFold(string(autoBackup.BackupScheduleType), string(sqlvirtualmachine.Manual)) {
var fullBackupStartHour int
if autoBackup.FullBackupStartTime != nil {
fullBackupStartHour = int(*autoBackup.FullBackupStartTime)
}

var fullBackupWindowHours int
if autoBackup.FullBackupWindowHours != nil {
fullBackupWindowHours = int(*autoBackup.FullBackupWindowHours)
}

var logBackupFrequency int
if autoBackup.LogBackupFrequency != nil {
logBackupFrequency = int(*autoBackup.LogBackupFrequency)
}

manualSchedule = []interface{}{
map[string]interface{}{
"full_backup_frequency": string(autoBackup.FullBackupFrequency),
"full_backup_start_hour": fullBackupStartHour,
"full_backup_window_in_hours": fullBackupWindowHours,
"log_backup_frequency_in_minutes": logBackupFrequency,
},
}
}

var retentionPeriod int
if autoBackup.RetentionPeriod != nil {
retentionPeriod = int(*autoBackup.RetentionPeriod)
}

// Password, StorageAccessKey, StorageAccountURL are not returned, so we try to copy them
// from existing config as a best effort.
encryptionPassword := d.Get("auto_backup.0.encryption_password").(string)
storageKey := d.Get("auto_backup.0.storage_account_access_key").(string)
blobEndpoint := d.Get("auto_backup.0.storage_blob_endpoint").(string)

return []interface{}{
map[string]interface{}{
"encryption_enabled": autoBackup.EnableEncryption != nil && *autoBackup.EnableEncryption,
"encryption_password": encryptionPassword,
"manual_schedule": manualSchedule,
"retention_period_in_days": retentionPeriod,
"storage_account_access_key": storageKey,
"storage_blob_endpoint": blobEndpoint,
"system_databases_backup_enabled": autoBackup.BackupSystemDbs != nil && *autoBackup.BackupSystemDbs,
},
}
}

func expandSqlVirtualMachineAutoPatchingSettings(input []interface{}) *sqlvirtualmachine.AutoPatchingSettings {
if len(input) == 0 {
return nil
Expand Down
Loading