Add working provisioning and configuration for MS SQL data warehouse
This commit is contained in:
12
configuration/ansible_mssql/roles/common/defaults/main.yml
Normal file
12
configuration/ansible_mssql/roles/common/defaults/main.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
domain_name: "example.local"
|
||||
domain_join_user: "{{ domain_join_user }}"
|
||||
domain_join_password: "{{ domain_join_password }}"
|
||||
domain_ou_path: ""
|
||||
|
||||
data_disk_number: 1
|
||||
data_disk_size_gb: 80
|
||||
|
||||
sql_data_part_gb: 35
|
||||
sql_log_part_gb: 15
|
||||
sql_tempdb_part_gb: 10
|
||||
123
configuration/ansible_mssql/roles/common/tasks/main.yml
Normal file
123
configuration/ansible_mssql/roles/common/tasks/main.yml
Normal file
@@ -0,0 +1,123 @@
|
||||
---
|
||||
- name: Wait for system to be fully booted
|
||||
ansible.builtin.wait_for_connection:
|
||||
timeout: 300
|
||||
sleep: 10
|
||||
|
||||
- name: Ensure Windows Update service is running
|
||||
ansible.windows.win_service:
|
||||
name: wuauserv
|
||||
state: started
|
||||
start_mode: auto
|
||||
|
||||
- name: Ensure BITS service is running
|
||||
ansible.windows.win_service:
|
||||
name: BITS
|
||||
state: started
|
||||
start_mode: auto
|
||||
|
||||
- name: Install Windows updates (loop until no more pending)
|
||||
ansible.windows.win_updates:
|
||||
category_names:
|
||||
- SecurityUpdates
|
||||
- CriticalUpdates
|
||||
- UpdateRollups
|
||||
- Updates
|
||||
state: installed
|
||||
reboot: true
|
||||
reboot_timeout: 3600
|
||||
server_selection: windows_update
|
||||
register: win_updates_result
|
||||
until: win_updates_result.installed_update_count == 0
|
||||
retries: 5
|
||||
delay: 30
|
||||
|
||||
- name: Report Windows Update result
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
Windows Update complete.
|
||||
Last pass installed {{ win_updates_result.installed_update_count }} update(s).
|
||||
Reboot required: {{ win_updates_result.reboot_required }}.
|
||||
|
||||
- name: Join Active Directory domain
|
||||
microsoft.ad.membership:
|
||||
dns_domain_name: "{{ domain_name }}"
|
||||
hostname: "{{ inventory_hostname_short }}"
|
||||
domain_admin_user: "{{ domain_join_user }}"
|
||||
domain_admin_password: "{{ domain_join_password }}"
|
||||
domain_ou_path: "{{ domain_ou_path | default(omit) }}"
|
||||
state: domain
|
||||
reboot: true
|
||||
|
||||
- name: Initialize data disk as GPT
|
||||
community.windows.win_initialize_disk:
|
||||
disk_number: "{{ data_disk_number }}"
|
||||
style: gpt
|
||||
online: true
|
||||
|
||||
- name: Create SQL Data partition (F:)
|
||||
community.windows.win_partition:
|
||||
disk_number: "{{ data_disk_number }}"
|
||||
partition_size: "{{ sql_data_part_gb }} GiB"
|
||||
drive_letter: F
|
||||
state: present
|
||||
|
||||
- name: Format SQL Data partition (F:)
|
||||
community.windows.win_format:
|
||||
drive_letter: F
|
||||
file_system: NTFS
|
||||
new_label: SQLData
|
||||
allocation_unit_size: 65536
|
||||
|
||||
- name: Create SQL Log partition (G:)
|
||||
community.windows.win_partition:
|
||||
disk_number: "{{ data_disk_number }}"
|
||||
partition_size: "{{ sql_log_part_gb }} GiB"
|
||||
drive_letter: G
|
||||
state: present
|
||||
|
||||
- name: Format SQL Log partition (G:)
|
||||
community.windows.win_format:
|
||||
drive_letter: G
|
||||
file_system: NTFS
|
||||
new_label: SQLLog
|
||||
allocation_unit_size: 65536
|
||||
|
||||
- name: Create SQL TempDB partition (H:)
|
||||
community.windows.win_partition:
|
||||
disk_number: "{{ data_disk_number }}"
|
||||
partition_size: "{{ sql_tempdb_part_gb }} GiB"
|
||||
drive_letter: H
|
||||
state: present
|
||||
|
||||
- name: Format SQL TempDB partition (H:)
|
||||
community.windows.win_format:
|
||||
drive_letter: H
|
||||
file_system: NTFS
|
||||
new_label: SQLTempDB
|
||||
allocation_unit_size: 65536
|
||||
|
||||
- name: Create SQL Backup partition (I:)
|
||||
community.windows.win_partition:
|
||||
disk_number: "{{ data_disk_number }}"
|
||||
partition_size: "{{ (data_disk_size_gb | int) - (sql_data_part_gb | int) - (sql_log_part_gb | int) - (sql_tempdb_part_gb | int) - 1 }} GiB"
|
||||
drive_letter: I
|
||||
state: present
|
||||
|
||||
- name: Format SQL Backup partition (I:)
|
||||
community.windows.win_format:
|
||||
drive_letter: I
|
||||
file_system: NTFS
|
||||
new_label: SQLBackup
|
||||
allocation_unit_size: 65536
|
||||
|
||||
- name: Create SQL Server directories
|
||||
ansible.windows.win_file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
loop:
|
||||
- 'F:\Data'
|
||||
- 'G:\Log'
|
||||
- 'H:\TempDB\Data'
|
||||
- 'H:\TempDB\Log'
|
||||
- 'I:\Backup'
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
db_backup_dir: 'I:\Backup'
|
||||
db_data_dir: 'F:\Data'
|
||||
db_log_dir: 'G:\Log'
|
||||
|
||||
db_backups:
|
||||
- name: AdventureWorksDW2022
|
||||
filename: AdventureWorksDW2022.bak
|
||||
url: "https://github.com/Microsoft/sql-server-samples/releases/download/adventureworks/AdventureWorksDW2022.bak"
|
||||
|
||||
- name: WideWorldImportersDW
|
||||
filename: WideWorldImportersDW-Full.bak
|
||||
url: "https://github.com/Microsoft/sql-server-samples/releases/download/wide-world-importers-v1.0/WideWorldImportersDW-Full.bak"
|
||||
67
configuration/ansible_mssql/roles/db_restore/tasks/main.yml
Normal file
67
configuration/ansible_mssql/roles/db_restore/tasks/main.yml
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
- name: Find sqlcmd.exe
|
||||
ansible.windows.win_powershell:
|
||||
script: |
|
||||
$sqlcmd = Get-ChildItem 'C:\Program Files\Microsoft SQL Server' -Filter 'sqlcmd.exe' `
|
||||
-Recurse -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.FullName -notlike '*\x86\*' } |
|
||||
Sort-Object LastWriteTime -Descending |
|
||||
Select-Object -First 1 -ExpandProperty FullName
|
||||
if (-not $sqlcmd) { throw 'sqlcmd.exe not found' }
|
||||
$Ansible.Result = $sqlcmd
|
||||
$Ansible.Changed = $false
|
||||
register: sqlcmd_path
|
||||
|
||||
- name: Download database backups
|
||||
ansible.windows.win_get_url:
|
||||
url: "{{ item.url }}"
|
||||
dest: "{{ db_backup_dir }}\\{{ item.filename }}"
|
||||
loop: "{{ db_backups }}"
|
||||
|
||||
- name: Restore databases
|
||||
ansible.windows.win_powershell:
|
||||
script: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$sqlcmd = '{{ sqlcmd_path.result }}'
|
||||
$bakFile = '{{ db_backup_dir }}\{{ item.filename }}'
|
||||
$dbName = '{{ item.name }}'
|
||||
|
||||
# Idempotent: skip if database already exists
|
||||
$exists = (& $sqlcmd -S . -E -h -1 `
|
||||
-Q "SET NOCOUNT ON; SELECT name FROM sys.databases WHERE name = N'$dbName'" |
|
||||
Out-String).Trim()
|
||||
if ($exists -eq $dbName) {
|
||||
Write-Output "$dbName already exists — skipping restore."
|
||||
$Ansible.Changed = $false
|
||||
return
|
||||
}
|
||||
|
||||
# Get logical file list from the backup
|
||||
$raw = & $sqlcmd -S . -E -s "|" -W `
|
||||
-Q "RESTORE FILELISTONLY FROM DISK = N'$bakFile'"
|
||||
|
||||
$files = $raw | Select-Object -Skip 2 | Where-Object { $_ -match '\|' } | ForEach-Object {
|
||||
$cols = $_ -split '\|'
|
||||
[PSCustomObject]@{
|
||||
LogicalName = $cols[0].Trim()
|
||||
PhysicalName = $cols[1].Trim()
|
||||
FileType = $cols[2].Trim() # D = data, L = log
|
||||
}
|
||||
}
|
||||
|
||||
# Build MOVE clauses — keep original filename, redirect to correct partition
|
||||
$moves = $files | ForEach-Object {
|
||||
$dir = if ($_.FileType -eq 'L') { '{{ db_log_dir }}' } else { '{{ db_data_dir }}' }
|
||||
$file = [System.IO.Path]::GetFileName($_.PhysicalName)
|
||||
"MOVE N'$($_.LogicalName)' TO N'$dir\$file'"
|
||||
}
|
||||
|
||||
$sql = "RESTORE DATABASE [$dbName] FROM DISK = N'$bakFile' WITH $($moves -join ', '), REPLACE, STATS = 10"
|
||||
Write-Output "Restoring $dbName ..."
|
||||
& $sqlcmd -S . -E -Q $sql -t 3600
|
||||
if ($LASTEXITCODE -ne 0) { throw "RESTORE failed with exit code $LASTEXITCODE" }
|
||||
loop: "{{ db_backups }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
timeout: 7200
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
mssql_instance_name: MSSQLSERVER
|
||||
mssql_features: SQLENGINE,AS,IS,CONN
|
||||
mssql_collation: SQL_Latin1_General_CP1_CI_AS
|
||||
mssql_iso_drive: "E:\\"
|
||||
|
||||
mssql_security_mode: SQL
|
||||
mssql_sa_password: ""
|
||||
mssql_sysadmin_accounts:
|
||||
- "CWX\\srvadmin-dandric"
|
||||
- "{{ inventory_hostname_short }}\\Administrator"
|
||||
|
||||
mssql_sql_svc_account: "NT AUTHORITY\\NETWORK SERVICE"
|
||||
mssql_sql_svc_password: ""
|
||||
mssql_agt_svc_account: "NT AUTHORITY\\NETWORK SERVICE"
|
||||
mssql_agt_svc_password: ""
|
||||
mssql_as_svc_account: "NT AUTHORITY\\NETWORK SERVICE"
|
||||
mssql_is_svc_account: "NT AUTHORITY\\NETWORK SERVICE"
|
||||
|
||||
mssql_as_server_mode: "TABULAR"
|
||||
|
||||
mssql_data_dir: 'F:\Data'
|
||||
mssql_log_dir: 'G:\Log'
|
||||
mssql_backup_dir: 'I:\Backup'
|
||||
mssql_tempdb_dir: 'H:\TempDB\Data'
|
||||
mssql_tempdb_log_dir: 'H:\TempDB\Log'
|
||||
|
||||
mssql_max_memory_mb: 12288
|
||||
mssql_min_memory_mb: 4096
|
||||
|
||||
mssql_tcp_enabled: 1
|
||||
mssql_tcp_port: 1433
|
||||
132
configuration/ansible_mssql/roles/mssql_install/tasks/main.yml
Normal file
132
configuration/ansible_mssql/roles/mssql_install/tasks/main.yml
Normal file
@@ -0,0 +1,132 @@
|
||||
---
|
||||
- name: Create SQL Server data directories
|
||||
ansible.windows.win_file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
loop:
|
||||
- "{{ mssql_data_dir }}"
|
||||
- "{{ mssql_log_dir }}"
|
||||
- "{{ mssql_backup_dir }}"
|
||||
- "{{ mssql_tempdb_dir }}"
|
||||
- "{{ mssql_tempdb_log_dir }}"
|
||||
|
||||
- name: Install SQL Server 2022
|
||||
block:
|
||||
- name: Run SQL Server 2022 setup
|
||||
ansible.windows.win_powershell:
|
||||
script: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$sapwd = '{{ mssql_sa_password | replace("'", "''") }}'
|
||||
|
||||
$proc = Start-Process `
|
||||
-FilePath '{{ mssql_iso_drive }}\setup.exe' `
|
||||
-ArgumentList @(
|
||||
'/Action=Install',
|
||||
'/QUIET',
|
||||
'/IACCEPTSQLSERVERLICENSETERMS',
|
||||
'/FEATURES={{ mssql_features }}',
|
||||
'/INSTANCENAME={{ mssql_instance_name }}',
|
||||
'/SQLCOLLATION={{ mssql_collation }}',
|
||||
'/SECURITYMODE={{ mssql_security_mode }}',
|
||||
"/SAPWD=$sapwd",
|
||||
'/SQLSYSADMINACCOUNTS="{{ mssql_sysadmin_accounts | join('" "') }}"',
|
||||
'/SQLSVCACCOUNT="{{ mssql_sql_svc_account }}"',
|
||||
'/AGTSVCACCOUNT="{{ mssql_agt_svc_account }}"',
|
||||
'/AGTSVCSTARTUPTYPE=Automatic',
|
||||
'/BROWSERSVCSTARTUPTYPE=Automatic',
|
||||
'/TCPENABLED={{ mssql_tcp_enabled }}',
|
||||
'/SQLUSERDBDIR="{{ mssql_data_dir }}"',
|
||||
'/SQLUSERDBLOGDIR="{{ mssql_log_dir }}"',
|
||||
'/SQLBACKUPDIR="{{ mssql_backup_dir }}"',
|
||||
'/SQLTEMPDBDIR="{{ mssql_tempdb_dir }}"',
|
||||
'/SQLTEMPDBLOGDIR="{{ mssql_tempdb_log_dir }}"',
|
||||
'/ASSVCACCOUNT="{{ mssql_as_svc_account }}"',
|
||||
'/ASSERVERMODE={{ mssql_as_server_mode }}',
|
||||
'/ASSYSADMINACCOUNTS="{{ mssql_sysadmin_accounts | join('" "') }}"',
|
||||
'/ISSVCACCOUNT="{{ mssql_is_svc_account }}"',
|
||||
'/UPDATEENABLED=False'
|
||||
) `
|
||||
-Wait -PassThru
|
||||
|
||||
if ($proc.ExitCode -notin @(0, 3010)) {
|
||||
throw "SQL Server setup failed with exit code: $($proc.ExitCode)"
|
||||
}
|
||||
|
||||
$Ansible.Result = $proc.ExitCode
|
||||
register: mssql_install_result
|
||||
timeout: 3600
|
||||
|
||||
- name: Report install exit code
|
||||
ansible.builtin.debug:
|
||||
msg: "SQL Server installed (exit code {{ mssql_install_result.result }}{% if mssql_install_result.result == 3010 %} — reboot required{% endif %})"
|
||||
|
||||
rescue:
|
||||
- name: Display setup summary log
|
||||
ansible.windows.win_powershell:
|
||||
script: |
|
||||
$log = Get-ChildItem 'C:\Program Files\Microsoft SQL Server' -Filter 'Summary*.txt' `
|
||||
-Recurse -ErrorAction SilentlyContinue |
|
||||
Sort-Object LastWriteTime | Select-Object -Last 1
|
||||
if ($log) {
|
||||
Write-Output "=== $($log.FullName) ==="
|
||||
Get-Content $log.FullName | Select-Object -Last 100
|
||||
} else {
|
||||
Write-Output "No summary log found."
|
||||
}
|
||||
register: setup_log_content
|
||||
|
||||
- name: Show log
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ setup_log_content.output }}"
|
||||
|
||||
- name: Fail
|
||||
ansible.builtin.fail:
|
||||
msg: "SQL Server installation failed — see log output above."
|
||||
|
||||
- name: Ensure SQL Server service is started
|
||||
ansible.windows.win_service:
|
||||
name: "{{ 'MSSQLSERVER' if mssql_instance_name == 'MSSQLSERVER' else 'MSSQL$' + mssql_instance_name }}"
|
||||
state: started
|
||||
start_mode: auto
|
||||
|
||||
- name: Ensure SQL Server Agent is started
|
||||
ansible.windows.win_service:
|
||||
name: "{{ 'SQLSERVERAGENT' if mssql_instance_name == 'MSSQLSERVER' else 'SQLAgent$' + mssql_instance_name }}"
|
||||
state: started
|
||||
start_mode: auto
|
||||
|
||||
- name: Open SQL Server firewall port
|
||||
community.windows.win_firewall_rule:
|
||||
name: "SQL Server ({{ mssql_instance_name }}) TCP {{ mssql_tcp_port }}"
|
||||
localport: "{{ mssql_tcp_port }}"
|
||||
action: allow
|
||||
direction: in
|
||||
protocol: tcp
|
||||
state: present
|
||||
enabled: true
|
||||
|
||||
- name: Configure SQL Server memory limits
|
||||
ansible.windows.win_powershell:
|
||||
script: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$sqlcmd = Get-ChildItem 'C:\Program Files\Microsoft SQL Server' -Filter 'sqlcmd.exe' `
|
||||
-Recurse -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.FullName -notlike '*\x86\*' } |
|
||||
Sort-Object LastWriteTime -Descending |
|
||||
Select-Object -First 1 -ExpandProperty FullName
|
||||
if (-not $sqlcmd) { throw 'sqlcmd.exe not found under C:\Program Files\Microsoft SQL Server' }
|
||||
|
||||
$instance = if ('{{ mssql_instance_name }}' -eq 'MSSQLSERVER') { '.' } `
|
||||
else { '.\{{ mssql_instance_name }}' }
|
||||
|
||||
$queries = @(
|
||||
"EXEC sp_configure 'show advanced options', 1; RECONFIGURE;",
|
||||
"EXEC sp_configure 'max server memory (MB)', {{ mssql_max_memory_mb }}; RECONFIGURE;",
|
||||
"EXEC sp_configure 'min server memory (MB)', {{ mssql_min_memory_mb }}; RECONFIGURE;"
|
||||
)
|
||||
foreach ($q in $queries) {
|
||||
& $sqlcmd -S $instance -E -Q $q | Out-Null
|
||||
}
|
||||
$Ansible.Result = "max={{ mssql_max_memory_mb }} MB, min={{ mssql_min_memory_mb }} MB"
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
mssql_staging_dir: 'C:\SQLInstall'
|
||||
|
||||
mssql_bootstrapper_url: "https://go.microsoft.com/fwlink/p/?linkid=2215158&clcid=0x409&culture=en-us&country=US"
|
||||
mssql_bootstrapper_path: 'C:\SQLInstall\SQLServer2022-setup.exe'
|
||||
|
||||
mssql_iso_path: 'C:\SQLInstall\SQLServer2022-x64-ENU.iso'
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
- name: Create SQL Server staging directory
|
||||
ansible.windows.win_file:
|
||||
path: "{{ mssql_staging_dir }}"
|
||||
state: directory
|
||||
|
||||
- name: Download SQL Server 2022 setup bootstrapper
|
||||
ansible.windows.win_get_url:
|
||||
url: "{{ mssql_bootstrapper_url }}"
|
||||
dest: "{{ mssql_bootstrapper_path }}"
|
||||
timeout: 120
|
||||
|
||||
- name: Download SQL Server 2022 ISO via bootstrapper
|
||||
ansible.windows.win_command: >-
|
||||
"{{ mssql_bootstrapper_path }}"
|
||||
/Action=Download
|
||||
/MEDIATYPE=ISO
|
||||
/MEDIAPATH="{{ mssql_staging_dir }}"
|
||||
/QUIET
|
||||
args:
|
||||
creates: "{{ mssql_iso_path }}"
|
||||
timeout: 3600
|
||||
|
||||
- name: Find downloaded SQL Server ISO
|
||||
ansible.windows.win_find:
|
||||
paths: "{{ mssql_staging_dir }}"
|
||||
patterns: "*.iso"
|
||||
register: iso_files
|
||||
|
||||
- name: Fail if no ISO found in staging directory
|
||||
ansible.builtin.fail:
|
||||
msg: >-
|
||||
No ISO file found in {{ mssql_staging_dir }}.
|
||||
The bootstrapper download may have failed.
|
||||
Check {{ mssql_staging_dir }} on the target host.
|
||||
when: iso_files.files | length == 0
|
||||
|
||||
- name: Mount SQL Server 2022 ISO
|
||||
ansible.windows.win_powershell:
|
||||
script: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$imagePath = '{{ iso_files.files[0].path }}'
|
||||
|
||||
$diskImage = Get-DiskImage -ImagePath $imagePath
|
||||
if (-not $diskImage.Attached) {
|
||||
Mount-DiskImage -ImagePath $imagePath | Out-Null
|
||||
}
|
||||
|
||||
$driveLetter = (Get-DiskImage -ImagePath $imagePath | Get-Volume).DriveLetter
|
||||
if (-not $driveLetter) {
|
||||
throw "ISO mounted but no drive letter was assigned"
|
||||
}
|
||||
|
||||
$Ansible.Result = "${driveLetter}:"
|
||||
register: mount_result
|
||||
|
||||
- name: Set SQL Server ISO drive letter fact
|
||||
ansible.builtin.set_fact:
|
||||
mssql_iso_drive: "{{ mount_result.result }}"
|
||||
|
||||
- name: Show mounted drive letter
|
||||
ansible.builtin.debug:
|
||||
msg: "SQL Server 2022 ISO mounted at {{ mssql_iso_drive }} ({{ iso_files.files[0].path }})"
|
||||
Reference in New Issue
Block a user