#!powershell # -*- coding: utf-8 -*- # (c) 2022, John McCall (@lowlydba) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) #AnsibleRequires -CSharpUtil Ansible.Basic #AnsibleRequires -PowerShell ansible_collections.lowlydba.sqlserver.plugins.module_utils._SqlServerUtils #Requires -Modules @{ ModuleName="dbatools"; ModuleVersion="2.0.0" } $ErrorActionPreference = "Stop" $spec = @{ supports_check_mode = $true options = @{ sql_instance_secondary = @{type = "str"; required = $false } sql_username_secondary = @{type = 'str'; required = $false } sql_password_secondary = @{type = 'str'; required = $false; no_log = $true } database = @{type = "str"; required = $false; aliases = @('database_name') } ag_name = @{type = "str"; required = $true } all_ags = @{type = "bool"; required = $false; } shared_path = @{type = "str"; required = $false; default = $null } dtc_support_enabled = @{type = "bool"; required = $false; } basic_availability_group = @{type = "bool"; required = $false; } database_health_trigger = @{type = "bool"; required = $false; } is_distributed_ag = @{type = "bool"; required = $false; } use_last_backup = @{type = "bool"; required = $false; } healthcheck_timeout = @{type = "int"; required = $false; } availability_mode = @{ type = "str" required = $false default = "SynchronousCommit" choices = @("SynchronousCommit", "AsynchronousCommit") } failure_condition_level = @{ type = "str" required = $false choices = @( "OnAnyQualifiedFailureCondition", "OnCriticalServerErrors", "OnModerateServerErrors", "OnServerDown", "OnServerUnresponsive" ) } failover_mode = @{ type = "str" required = $false default = "Automatic" choices = @("Manual", "Automatic") } seeding_mode = @{ type = "str" required = $false default = "Manual" choices = @("Manual", "Automatic") } automated_backup_preference = @{ type = "str" required = $false default = "Secondary" choices = @("None", "Primary", "Secondary", "SecondaryOnly") } cluster_type = @{ type = "str" required = $false default = "Wsfc" choices = @("Wsfc", "External", "None") } allow_null_backup = @{type = "bool"; required = $false } force = @{type = "bool"; required = $false } state = @{type = "str"; required = $false; default = "present"; choices = @("present", "absent") } } required_together = @( , @('sql_username_secondary', 'sql_password_secondary') ) } # Setup var $module = [Ansible.Basic.AnsibleModule]::Create($args, $spec, @(Get-LowlyDbaSqlServerAuthSpec)) $ProgressPreference = "SilentlyContinue" # Var $sqlInstance, $sqlCredential = Get-SqlCredential -Module $module $secondary = $module.Params.sql_instance_secondary if ($null -ne $module.Params.sql_username_secondary) { [securestring]$secondarySecPassword = ConvertTo-SecureString $Module.Params.sql_password_secondary -AsPlainText -Force [pscredential]$secondarySqlCredential = New-Object System.Management.Automation.PSCredential ($Module.Params.sql_username_secondary, $secondarySecPassword) } $agName = $module.Params.ag_name $database = $module.Params.database $seedingMode = $module.Params.seeding_mode $sharedPath = $module.Params.shared_path $healthCheckTimeout = $module.Params.healthcheck_timeout $availabilityMode = $module.Params.availability_mode $failureConditionLevel = $module.Params.failure_condition_level $failoverMode = $module.Params.failover_mode $automatedBackupPreference = $module.Params.automated_backup_preference $clusterType = $module.Params.cluster_type $state = $module.Params.state [nullable[bool]]$all_ags = $module.Params.all_ags [nullable[bool]]$useLastBackup = $module.Params.use_last_backup [nullable[bool]]$dtcSupportEnabled = $module.Params.dtc_support_enabled [nullable[bool]]$basicAvailabilityGroup = $module.Params.basic_availability_group [nullable[bool]]$databaseHealthTrigger = $module.Params.database_health_trigger [nullable[bool]]$isDistributedAg = $module.Params.is_distributed_ag [nullable[bool]]$force = $module.Params.force [nullable[bool]]$allowNullBackup = $module.Params.allow_null_backup $checkMode = $module.CheckMode $module.Result.changed = $false $PSDefaultParameterValues = @{ "*:EnableException" = $true; "*:Confirm" = $false; "*:WhatIf" = $checkMode } try { $existingAG = Get-DbaAvailabilityGroup -SqlInstance $sqlInstance -SqlCredential $sqlCredential -AvailabilityGroup $agName if ($state -eq "present") { $agSplat = @{ Primary = $sqlInstance PrimarySqlCredential = $sqlCredential Name = $agName SeedingMode = $seedingMode FailoverMode = $failoverMode AvailabilityMode = $availabilityMode AutomatedBackupPreference = $automatedBackupPreference ClusterType = $clusterType } if ($null -ne $sharedPath -and $seedingMode -eq "Manual") { $agSplat.Add("SharedPath", $sharedPath) } if ($useLastBackup -eq $true) { $agSplat.Add("UseLastBackup", $useLastBackup) } if ($dtcSupportEnabled -eq $true) { $agSplat.Add("DtcSupport", $dtcSupportEnabled) } if ($basicAvailabilityGroup -eq $true) { $agSplat.Add("Basic", $basicAvailabilityGroup) } if ($databaseHealthTrigger -eq $true) { $agSplat.Add("DatabaseHealthTrigger", $databaseHealthTrigger) } if ($null -ne $healthCheckTimeout) { $agSplat.Add("HealthCheckTimeout", $healthCheckTimeout) } if ($null -ne $failureConditionLevel) { $agSplat.Add("FailureConditionLevel", $failureConditionLevel) } if ($null -ne $database) { $agSplat.Add("Database", $database) } if ($null -ne $secondary) { $agSplat.Add("Secondary", $secondary) } if ($null -ne $secondarySqlCredential) { $agSplat.Add("SecondarySqlCredential", $secondarySqlCredential) } if ($force -eq $true) { $agSplat.Add("Force", $force) } # Create the AG with initial replica(s) if ($null -eq $existingAG) { # Full backup requirement for new AG via automatic seeding if ($seedingMode -eq "automatic" -and $null -ne $database) { $dbBackup = Get-DbaLastBackup -SqlInstance $sqlInstance -SqlCredential $sqlCredential -Database $database if ($null -eq $dbBackup.LastFullBackup -and $allowNullBackup -eq $true) { $backupSplat = @{ SqlInstance = $sqlInstance SqlCredential = $sqlCredential Database = $database FilePath = "NUL" Type = "Full" } $null = Backup-DbaDatabase @backupSplat } } $output = New-DbaAvailabilityGroup @agSplat $module.Result.changed = $true } # Configure existing AG else { $setAgSplat = @{ AutomatedBackupPreference = $automatedBackupPreference ClusterType = $clusterType } if ($all_ags -eq $true) { $setAgSplat.Add("AllAvailabilityGroups", $all_ags) } if ($dtcSupportEnabled -eq $true) { $setAgSplat.Add("DtcSupportEnabled", $dtcSupportEnabled) } if ($basicAvailabilityGroup -eq $true) { $setAgSplat.Add("BasicAvailabilityGroup", $basicAvailabilityGroup) } if ($databaseHealthTrigger -eq $true) { $setAgSplat.Add("DatabaseHealthTrigger", $databaseHealthTrigger) } if ($null -ne $failureConditionLevel) { $setAgSplat.Add("FailureConditionLevel", $failureConditionLevel) } if ($null -ne $healthCheckTimeout) { $setAgSplat.Add("HealthCheckTimeout", $healthCheckTimeout) } if ($isDistributedAg -eq $true) { $setAgSplat.Add("IsDistributedAvailabilityGroup", $isDistributedAg) } $compareProperty = ($existingAG.Properties | Where-Object Name -in $setAgSplat.Keys).Name $agDiff = Compare-Object -ReferenceObject $existingAG -DifferenceObject $setAgSplat -Property $compareProperty if ($null -ne $agDiff) { $output = $existingAG | Set-DbaAvailabilityGroup @setAgSplat $module.Result.changed = $true } } } elseif ($state -eq "absent") { if ($null -ne $existingAG) { if ($all_ags -eq $true) { $existingAG | Remove-DbaAvailabilityGroup -AllAvailabilityGroups } else { $existingAG | Remove-DbaAvailabilityGroup } $module.Result.changed = $true } } if ($output) { $resultData = ConvertTo-SerializableObject -InputObject $output $module.Result.data = $resultData } $module.ExitJson() } catch { $module.FailJson("Configuring Availability Group failed: $($_.Exception.Message)", $_) }