Skip to content

Commit c3f6edc

Browse files
authored
Merge pull request #493 from puppetlabs/MODULES-11615
(MODULES-11615) Add support for SQL Server 2025
2 parents 032751f + 44edfc2 commit c3f6edc

10 files changed

Lines changed: 462 additions & 111 deletions

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
## Overview
2323

24-
The sqlserver module installs and manages Microsoft SQL Server 2014, 2016, 2017, 2019 and 2022 on Windows systems.
24+
The sqlserver module installs and manages Microsoft SQL Server 2014, 2016, 2017, 2019, 2022 and 2025 on Windows systems.
2525

2626
## Module Description
2727

@@ -273,11 +273,11 @@ For information on the classes and types, see the [REFERENCE.md](https://github.
273273

274274
## Limitations
275275

276-
SQL 2017, 2019 and 2022 detection support has been added. This support is limited to functionality already present for other versions.
276+
SQL 2017, 2019, 2022 and 2025 detection support has been added. This support is limited to functionality already present for other versions.
277277

278-
The MSOLEDBSQL driver is now required to use this module. You can use this chocolatey [package](https://community.chocolatey.org/packages/msoledbsql) for installation. but it must version 18.x or earlier. (v19+ is not currently supported)
278+
The MSOLEDBSQL driver is required to use this module. You can use this chocolatey [package](https://community.chocolatey.org/packages/msoledbsql) for installation.
279279

280-
This module can manage only a single version of SQL Server on a given host (one and only one of SQL Server 2014, 2016, 2017, 2019 or 2022). The module is able to manage multiple SQL Server instances of the same version.
280+
This module can manage only a single version of SQL Server on a given host (one and only one of SQL Server 2014, 2016, 2017, 2019, 2022 or 2025). The module is able to manage multiple SQL Server instances of the same version.
281281

282282
This module cannot manage the SQL Server Native Client SDK (also known as SNAC_SDK). The SQL Server installation media can install the SDK, but it is not able to uninstall the SDK. Note that the 'sqlserver_features' fact detects the presence of the SDK.
283283

lib/puppet_x/sqlserver/features.rb

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
SQL_2017 = 'SQL_2017'
88
SQL_2019 = 'SQL_2019'
99
SQL_2022 = 'SQL_2022'
10+
SQL_2025 = 'SQL_2025'
1011

11-
ALL_SQL_VERSIONS = [SQL_2014, SQL_2016, SQL_2017, SQL_2019, SQL_2022].freeze
12+
ALL_SQL_VERSIONS = [SQL_2014, SQL_2016, SQL_2017, SQL_2019, SQL_2022, SQL_2025].freeze
1213

1314
# rubocop:disable Style/ClassAndModuleChildren
1415
module PuppetX
@@ -35,12 +36,15 @@ class Features # rubocop:disable Style/Documentation
3536
major_version: 15,
3637
registry_path: '150'
3738
},
38-
SQL_2022 => {
39-
major_version: 16,
40-
registry_path: '160'
41-
}
39+
SQL_2022 => {
40+
major_version: 16,
41+
registry_path: '160'
42+
},
43+
SQL_2025 => {
44+
major_version: 17,
45+
registry_path: '170'
46+
}
4247
}.freeze
43-
4448
SQL_REG_ROOT = 'Software\Microsoft\Microsoft SQL Server'
4549
HKLM = 'HKEY_LOCAL_MACHINE'
4650

lib/puppet_x/sqlserver/server_helper.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ def self.sql_version_from_install_source(source_dir)
4343
ver = content.match('"(.+)"')
4444
return nil if ver.nil?
4545

46+
return SQL_2025 if ver[1].start_with?('17.')
4647
return SQL_2022 if ver[1].start_with?('16.')
4748
return SQL_2019 if ver[1].start_with?('15.')
4849
return SQL_2017 if ver[1].start_with?('14.')

metadata.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "puppetlabs-sqlserver",
33
"version": "5.0.5",
44
"author": "puppetlabs",
5-
"summary": "The `sqlserver` module installs and manages MS SQL Server 2014, 2016, 2017, 2019 and 2022 on Windows systems.",
5+
"summary": "The `sqlserver` module installs and manages MS SQL Server 2014, 2016, 2017, 2019, 2022 and 2025 on Windows systems.",
66
"license": "proprietary",
77
"source": "https://github.com/puppetlabs/puppetlabs-sqlserver",
88
"project_page": "https://github.com/puppetlabs/puppetlabs-sqlserver",
@@ -45,6 +45,7 @@
4545
"sql2017",
4646
"sql2019",
4747
"sql2022",
48+
"sql2025",
4849
"tsql",
4950
"database"
5051
],
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# frozen_string_literal: true
2+
3+
# Install the Microsoft OLE DB Driver for SQL Server via Chocolatey
4+
# Using Puppet Exec with PowerShell provider for reliability
5+
6+
# Desired minimum version for MSOLEDBSQL; update as needed
7+
$desired_version = '19.2.23273.0'
8+
9+
exec { 'install_chocolatey':
10+
provider => 'powershell',
11+
command => "Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))",
12+
unless => "Test-Path 'C:\\ProgramData\\chocolatey\\choco.exe'",
13+
tries => 3,
14+
try_sleep=> 10,
15+
timeout => 600,
16+
}
17+
18+
$oledb_install_script = @(EOT)
19+
$choco = 'C:\ProgramData\chocolatey\bin\choco.exe'
20+
$reg = 'HKLM:\SOFTWARE\Microsoft\MSOLEDBSQL'
21+
$ver = '18.6.0.0'
22+
$pkgUrl = "https://community.chocolatey.org/api/v2/package/msoledbsql/$ver"
23+
24+
function Test-Installed {
25+
try {
26+
$v = (Get-ItemProperty $reg -ErrorAction SilentlyContinue).InstalledVersion
27+
return ($v -and $v -like '18.*')
28+
} catch { return $false }
29+
}
30+
31+
function Invoke-Retry([scriptblock] $action, [int] $retries = 3, [int] $delay = 10) {
32+
for ($i = 1; $i -le $retries; $i++) {
33+
try {
34+
& $action
35+
return $true
36+
} catch {
37+
Write-Host "[msoledbsql] Attempt $i failed: $($_.Exception.Message)"
38+
if ($i -lt $retries) { Start-Sleep -Seconds $delay }
39+
}
40+
}
41+
return $false
42+
}
43+
44+
Write-Host "[msoledbsql] Target version: $ver"
45+
if (Test-Installed) {
46+
Write-Host "[msoledbsql] Already installed (v18.x)."
47+
exit 0
48+
}
49+
50+
# Remove incompatible v19+ if present
51+
try {
52+
$v = (Get-ItemProperty $reg -ErrorAction SilentlyContinue).InstalledVersion
53+
if ($v -and $v -like '19.*') {
54+
Write-Host "[msoledbsql] Uninstalling incompatible v19.x: $v"
55+
& $choco uninstall msoledbsql -y | Out-Host
56+
Start-Sleep -Seconds 5
57+
}
58+
} catch { }
59+
60+
# Primary install from Chocolatey community feed
61+
Invoke-Retry {
62+
Write-Host "[msoledbsql] Installing from Chocolatey feed..."
63+
& $choco install msoledbsql --version $ver -y --force --source 'https://community.chocolatey.org/api/v2/' --no-progress | Out-Host
64+
if (-not (Test-Installed)) { throw "Install from feed did not register in registry" }
65+
} 3 15 | Out-Null
66+
Start-Sleep -Seconds 10
67+
68+
# Fallback: download nupkg directly and install from file
69+
if (-not (Test-Installed)) {
70+
try {
71+
Write-Host "[msoledbsql] Falling back to direct package download: $pkgUrl"
72+
$nupkg = "$env:TEMP\msoledbsql.$ver.nupkg"
73+
Invoke-WebRequest -Uri $pkgUrl -OutFile $nupkg -UseBasicParsing
74+
Invoke-Retry {
75+
& $choco install $nupkg -y --force --no-progress | Out-Host
76+
if (-not (Test-Installed)) { throw "Install from nupkg did not register in registry" }
77+
} 3 15 | Out-Null
78+
Start-Sleep -Seconds 10
79+
} catch { Write-Host "[msoledbsql] Fallback install failed: $($_.Exception.Message)" }
80+
}
81+
82+
if (Test-Installed) {
83+
Write-Host "[msoledbsql] Install succeeded."
84+
exit 0
85+
} else {
86+
Write-Host "[msoledbsql] Install failed."
87+
exit 1
88+
}
89+
EOT
90+
91+
exec { 'install_oledb_driver':
92+
provider => 'powershell',
93+
command => $oledb_install_script,
94+
require => Exec['install_chocolatey'],
95+
unless => @(EOT)
96+
try {
97+
$v = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\MSOLEDBSQL' -ErrorAction SilentlyContinue).InstalledVersion
98+
if ($v -and $v -like '18.*') { exit 0 } else { exit 1 }
99+
} catch { exit 1 }
100+
EOT
101+
,
102+
returns => [0],
103+
tries => 3,
104+
try_sleep=> 10,
105+
timeout => 1200,
106+
}

spec/acceptance/sqlserver_role_spec.rb

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@
44
require 'securerandom'
55
require 'erb'
66

7-
hostname = Helper.instance.run_shell('hostname').stdout.upcase.strip
8-
9-
# database name
10-
db_name = "DB#{SecureRandom.hex(4)}".upcase
117
LOGIN1 = "Login1_#{SecureRandom.hex(2)}".freeze
128
LOGIN2 = "Login2_#{SecureRandom.hex(2)}".freeze
139
LOGIN3 = "Login3_#{SecureRandom.hex(2)}".freeze
@@ -47,8 +43,11 @@ def ensure_sqlserver_logins_users(db_name)
4743

4844
context 'Start testing sqlserver::role' do
4945
before(:all) do
46+
# Initialize hostname and db_name once per context
47+
@hostname = Helper.instance.run_shell('hostname').stdout.upcase.strip
48+
@db_name = "DB#{SecureRandom.hex(4)}".upcase
5049
# Create database users
51-
ensure_sqlserver_logins_users(db_name)
50+
ensure_sqlserver_logins_users(@db_name)
5251
end
5352

5453
before(:each) do
@@ -76,7 +75,7 @@ def ensure_sqlserver_logins_users(db_name)
7675
admin_pass => 'Pupp3t1@',
7776
}
7877
sqlserver::user{'#{USER1}':
79-
database => '#{db_name}',
78+
database => '#{@db_name}',
8079
ensure => 'absent',
8180
}
8281
MANIFEST
@@ -100,25 +99,25 @@ def ensure_sqlserver_logins_users(db_name)
10099
apply_manifest(pp, catch_failures: true)
101100

102101
# validate that the database-specific role '#{@role}' is successfully created with specified permissions':
103-
query = "USE #{db_name};
102+
query = "USE #{@db_name};
104103
SELECT spr.principal_id, spr.name,
105104
spe.state_desc, spe.permission_name
106105
FROM sys.server_principals AS spr
107106
JOIN sys.server_permissions AS spe
108107
ON spe.grantee_principal_id = spr.principal_id
109108
WHERE spr.name = '#{@role}';"
110109

111-
run_sql_query(query:, server: hostname, expected_row_count: 2)
110+
run_sql_query(query:, server: @hostname, expected_row_count: 2)
112111

113112
# validate that the database-specific role '#{@role}' has correct authorization #{LOGIN1}
114-
query = "USE #{db_name};
113+
query = "USE #{@db_name};
115114
SELECT spr.name, sl.name
116115
FROM sys.server_principals AS spr
117116
JOIN sys.sql_logins AS sl
118117
ON spr.owning_principal_id = sl.principal_id
119118
WHERE sl.name = '#{LOGIN1}';"
120119

121-
run_sql_query(query:, server: hostname, expected_row_count: 1)
120+
run_sql_query(query:, server: @hostname, expected_row_count: 1)
122121
end
123122

124123
it "Create database-specific role #{@role}" do
@@ -130,23 +129,23 @@ def ensure_sqlserver_logins_users(db_name)
130129
sqlserver::role{'DatabaseRole':
131130
ensure => 'present',
132131
role => '#{@role}',
133-
database => '#{db_name}',
132+
database => '#{@db_name}',
134133
permissions => {'GRANT' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'CONTROL', 'ALTER']},
135134
type => 'DATABASE',
136135
}
137136
MANIFEST
138137
apply_manifest(pp, catch_failures: true)
139138

140139
# validate that the database-specific role '#{@role}' is successfully created with specified permissions':
141-
query = "USE #{db_name};
140+
query = "USE #{@db_name};
142141
SELECT pr.principal_id, pr.name, pr.type_desc,
143142
pr.authentication_type_desc, pe.state_desc, pe.permission_name
144143
FROM sys.database_principals AS pr
145144
JOIN sys.database_permissions AS pe
146145
ON pe.grantee_principal_id = pr.principal_id
147146
WHERE pr.name = '#{@role}';"
148147

149-
run_sql_query(query:, server: hostname, expected_row_count: 6)
148+
run_sql_query(query:, server: @hostname, expected_row_count: 6)
150149
end
151150

152151
it 'Create a database-specific role with the same name on two databases' do
@@ -158,7 +157,7 @@ def ensure_sqlserver_logins_users(db_name)
158157
sqlserver::role{'DatabaseRole_1':
159158
ensure => 'present',
160159
role => '#{@role}',
161-
database => '#{db_name}',
160+
database => '#{@db_name}',
162161
permissions => {'GRANT' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'CONTROL', 'ALTER']},
163162
type => 'DATABASE',
164163
}
@@ -180,11 +179,11 @@ def ensure_sqlserver_logins_users(db_name)
180179
FROM sys.database_principals AS pr
181180
JOIN sys.database_permissions AS pe
182181
ON pe.grantee_principal_id = pr.principal_id
183-
JOIN #{db_name}.sys.database_principals as dbpr
182+
JOIN #{@db_name}.sys.database_principals as dbpr
184183
on pr.name = dbpr.name
185184
WHERE pr.name = '#{@role}';"
186185

187-
run_sql_query(query:, server: hostname, expected_row_count: 6)
186+
run_sql_query(query:, server: @hostname, expected_row_count: 6)
188187
end
189188

190189
it "Create server role #{@role} with optional members and optional members-purge" do
@@ -205,18 +204,18 @@ def ensure_sqlserver_logins_users(db_name)
205204
apply_manifest(pp, catch_failures: true)
206205

207206
# validate that the server role '#{@role}' is successfully created with specified permissions':
208-
query = "USE #{db_name};
207+
query = "USE #{@db_name};
209208
SELECT spr.principal_id AS ID, spr.name AS Server_Role,
210209
spe.state_desc, spe.permission_name
211210
FROM sys.server_principals AS spr
212211
JOIN sys.server_permissions AS spe
213212
ON spe.grantee_principal_id = spr.principal_id
214213
WHERE spr.name = '#{@role}';"
215214

216-
run_sql_query(query:, server: hostname, expected_row_count: 2)
215+
run_sql_query(query:, server: @hostname, expected_row_count: 2)
217216

218217
# validate that the t server role '#{@role}' has correct members (Login1, 2, 3)
219-
query = "USE #{db_name};
218+
query = "USE #{@db_name};
220219
SELECT spr.principal_id AS ID, spr.name AS ServerRole
221220
FROM sys.server_principals AS spr
222221
JOIN sys.server_role_members m
@@ -226,7 +225,7 @@ def ensure_sqlserver_logins_users(db_name)
226225
OR spr.name = '#{LOGIN3}'
227226
OR spr.name = 'LOGIN4';"
228227

229-
run_sql_query(query:, server: hostname, expected_row_count: 3)
228+
run_sql_query(query:, server: @hostname, expected_row_count: 3)
230229

231230
puts "Create server role #{@role} with optional members_purge:"
232231
pp = <<-MANIFEST
@@ -247,7 +246,7 @@ def ensure_sqlserver_logins_users(db_name)
247246
apply_manifest(pp, catch_failures: true)
248247

249248
# validate that the t server role '#{@role}' has correct members (only Login3)
250-
query = "USE #{db_name};
249+
query = "USE #{@db_name};
251250
SELECT spr.principal_id AS ID, spr.name AS ServerRole
252251
FROM sys.server_principals AS spr
253252
JOIN sys.server_role_members m
@@ -256,7 +255,7 @@ def ensure_sqlserver_logins_users(db_name)
256255
OR spr.name = '#{LOGIN2}'
257256
OR spr.name = '#{LOGIN3}';"
258257

259-
run_sql_query(query:, server: hostname, expected_row_count: 1)
258+
run_sql_query(query:, server: @hostname, expected_row_count: 1)
260259
end
261260
end
262261
end

0 commit comments

Comments
 (0)