From a87ba63b54e40ec5e463dd908336a35c32fff3ba Mon Sep 17 00:00:00 2001 From: Amit Karsale Date: Thu, 12 Feb 2026 15:15:23 +0530 Subject: [PATCH 1/5] (MODULES-11615) Add support for SQL Server 2025 --- .github/workflows/ci.yml | 35 ++- .github/workflows/nightly.yml | 35 ++- README.md | 8 +- lib/puppet_x/sqlserver/features.rb | 16 +- lib/puppet_x/sqlserver/server_helper.rb | 1 + metadata.json | 3 +- .../manifests/install_oledb_driver.pp | 106 +++++++ spec/acceptance/sqlserver_role_spec.rb | 43 ++- spec/acceptance/sqlserver_user_spec.rb | 46 ++- .../z_last_sqlserver_features_spec.rb | 56 ++-- spec/spec_helper_acceptance_local.rb | 288 ++++++++++++++++-- spec/sql_testing_helpers.rb | 10 +- 12 files changed, 522 insertions(+), 125 deletions(-) create mode 100644 spec/acceptance/manifests/install_oledb_driver.pp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 77691e0c..961a3f53 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ on: workflow_dispatch: env: - SERVICE_URL: https://facade-maint-config-windows-use-ssh-6f3kfepqcq-ew.a.run.app/v1/provision + SERVICE_URL: https://facade-release-6f3kfepqcq-ew.a.run.app/v1/provision PUPPET_FORGE_TOKEN: ${{ secrets.PUPPET_FORGE_TOKEN || secrets.PUPPET_FORGE_TOKEN_PUBLIC }} BUNDLE_RUBYGEMS___PUPPETCORE__PUPPET__COM: "forge-key:${{ secrets.PUPPET_FORGE_TOKEN || secrets.PUPPET_FORGE_TOKEN_PUBLIC }}" @@ -72,6 +72,24 @@ jobs: with: service-key: ${{ secrets.TWINGATE_PUBLIC_REPO_KEY }} + - name: Fix DNS + run: | + echo "=== Remove Azure DNS from eth0 interface ===" + sudo resolvectl dns eth0 "" + + echo "=== Configure Twingate DNS properly ===" + sudo resolvectl dns sdwan0 100.95.0.251 100.95.0.252 + sudo resolvectl domain sdwan0 delivery.puppetlabs.net + + echo "=== Flush DNS cache ===" + sudo resolvectl flush-caches + + echo "=== Check new configuration ===" + resolvectl status + + echo "=== Test DNS resolution ===" + nslookup artifactory.delivery.puppetlabs.net + - name: Checkout uses: actions/checkout@v3 with: @@ -106,24 +124,29 @@ jobs: run: | bundle exec rake 'litmus:install_module' + - name: Install Chocolatey and MSOLEDBSQL driver + run: | + bundle exec bolt command run "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'))" --targets all --inventoryfile spec/fixtures/litmus_inventory.yaml + bundle exec bolt command run "choco install msoledbsql -y --force" --targets all --inventoryfile spec/fixtures/litmus_inventory.yaml + - name: Authenticate with GCP run: | echo '${{ secrets.GCP_CONNECTION }}' >> creds.json - bundle exec bolt file upload creds.json C:\\creds.json --targets ssh_nodes --inventoryfile spec/fixtures/litmus_inventory.yaml - bundle exec bolt command run "gcloud auth activate-service-account --key-file C:\\creds.json" --targets ssh_nodes --inventoryfile spec/fixtures/litmus_inventory.yaml + bundle exec bolt file upload creds.json C:\\creds.json --targets all --inventoryfile spec/fixtures/litmus_inventory.yaml + bundle exec bolt command run "gcloud auth activate-service-account --key-file C:\\creds.json" --targets all --inventoryfile spec/fixtures/litmus_inventory.yaml - name: Download OS ISO run: | - bundle exec bolt command run 'gsutil -q cp gs://artifactory-modules/windows/en_windows_server_2019_updated_july_2020_x64_dvd_94453821.iso C:\\' --targets ssh_nodes --inventoryfile spec/fixtures/litmus_inventory.yaml + bundle exec bolt command run 'gsutil -q cp gs://artifactory-modules/windows/en_windows_server_2019_updated_july_2020_x64_dvd_94453821.iso C:\\' --targets all --inventoryfile spec/fixtures/litmus_inventory.yaml - name: Download SQLServer ISO run: | - bundle exec bolt command run 'gsutil -q cp gs://artifactory-modules/puppetlabs-sqlserver/SQLServer2019CTP2.4-x64-ENU.iso C:\\' --targets ssh_nodes --inventoryfile spec/fixtures/litmus_inventory.yaml + bundle exec bolt command run 'gsutil -q cp gs://artifactory-modules/puppetlabs-sqlserver/SQLServer2019CTP2.4-x64-ENU.iso C:\\' --targets all --inventoryfile spec/fixtures/litmus_inventory.yaml - name: Set Environment Variable run: | pass=`grep -oP '(?<=password: ).*' spec/fixtures/litmus_inventory.yaml` - bundle exec bolt command run "[Environment]::SetEnvironmentVariable('pass', '$pass', 'Machine')" --targets ssh_nodes --inventoryfile spec/fixtures/litmus_inventory.yaml + bundle exec bolt command run "[Environment]::SetEnvironmentVariable('pass', '$pass', 'Machine')" --targets all --inventoryfile spec/fixtures/litmus_inventory.yaml - name: Run acceptance tests run: | diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index cda9b33c..1979fe98 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -5,7 +5,7 @@ on: - cron: '0 0 * * *' env: - SERVICE_URL: https://facade-maint-config-windows-use-ssh-6f3kfepqcq-ew.a.run.app/v1/provision + SERVICE_URL: https://facade-release-6f3kfepqcq-ew.a.run.app/v1/provision PUPPET_FORGE_TOKEN: ${{ secrets.PUPPET_FORGE_TOKEN || secrets.PUPPET_FORGE_TOKEN_PUBLIC }} BUNDLE_RUBYGEMS___PUPPETCORE__PUPPET__COM: "forge-key:${{ secrets.PUPPET_FORGE_TOKEN || secrets.PUPPET_FORGE_TOKEN_PUBLIC }}" @@ -69,6 +69,24 @@ jobs: with: service-key: ${{ secrets.TWINGATE_PUBLIC_REPO_KEY }} + - name: Fix DNS + run: | + echo "=== Remove Azure DNS from eth0 interface ===" + sudo resolvectl dns eth0 "" + + echo "=== Configure Twingate DNS properly ===" + sudo resolvectl dns sdwan0 100.95.0.251 100.95.0.252 + sudo resolvectl domain sdwan0 delivery.puppetlabs.net + + echo "=== Flush DNS cache ===" + sudo resolvectl flush-caches + + echo "=== Check new configuration ===" + resolvectl status + + echo "=== Test DNS resolution ===" + nslookup artifactory.delivery.puppetlabs.net + - name: Checkout uses: actions/checkout@v3 with: @@ -103,24 +121,29 @@ jobs: run: | bundle exec rake 'litmus:install_module' + - name: Install Chocolatey and MSOLEDBSQL driver + run: | + bundle exec bolt command run "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'))" --targets all --inventoryfile spec/fixtures/litmus_inventory.yaml + bundle exec bolt command run "choco install msoledbsql -y --force" --targets all --inventoryfile spec/fixtures/litmus_inventory.yaml + - name: Authenitcate with GCP run: | echo '${{ secrets.GCP_CONNECTION }}' >> creds.json - bundle exec bolt file upload creds.json C:\\creds.json --targets ssh_nodes --inventoryfile spec/fixtures/litmus_inventory.yaml - bundle exec bolt command run "gcloud auth activate-service-account --key-file C:\\creds.json" --targets ssh_nodes --inventoryfile spec/fixtures/litmus_inventory.yaml + bundle exec bolt file upload creds.json C:\\creds.json --targets all --inventoryfile spec/fixtures/litmus_inventory.yaml + bundle exec bolt command run "gcloud auth activate-service-account --key-file C:\\creds.json" --targets all --inventoryfile spec/fixtures/litmus_inventory.yaml - name: Download OS ISO run: | - bundle exec bolt command run 'gsutil -q cp gs://artifactory-modules/windows/en_windows_server_2019_updated_july_2020_x64_dvd_94453821.iso C:\\' --targets ssh_nodes --inventoryfile spec/fixtures/litmus_inventory.yaml + bundle exec bolt command run 'gsutil -q cp gs://artifactory-modules/windows/en_windows_server_2019_updated_july_2020_x64_dvd_94453821.iso C:\\' --targets all --inventoryfile spec/fixtures/litmus_inventory.yaml - name: Download SQLServer ISO run: | - bundle exec bolt command run 'gsutil -q cp gs://artifactory-modules/puppetlabs-sqlserver/SQLServer2019CTP2.4-x64-ENU.iso C:\\' --targets ssh_nodes --inventoryfile spec/fixtures/litmus_inventory.yaml + bundle exec bolt command run 'gsutil -q cp gs://artifactory-modules/puppetlabs-sqlserver/SQLServer2019CTP2.4-x64-ENU.iso C:\\' --targets all --inventoryfile spec/fixtures/litmus_inventory.yaml - name: Set Environment Variable run: | pass=`grep -oP '(?<=password: ).*' spec/fixtures/litmus_inventory.yaml` - bundle exec bolt command run "[Environment]::SetEnvironmentVariable('pass', '$pass', 'Machine')" --targets ssh_nodes --inventoryfile spec/fixtures/litmus_inventory.yaml + bundle exec bolt command run "[Environment]::SetEnvironmentVariable('pass', '$pass', 'Machine')" --targets all --inventoryfile spec/fixtures/litmus_inventory.yaml - name: Run acceptance tests run: | diff --git a/README.md b/README.md index d3eeb7cf..4a2c38e4 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ ## Overview -The sqlserver module installs and manages Microsoft SQL Server 2014, 2016, 2017, 2019 and 2022 on Windows systems. +The sqlserver module installs and manages Microsoft SQL Server 2014, 2016, 2017, 2019, 2022 and 2025 on Windows systems. ## Module Description @@ -273,11 +273,11 @@ For information on the classes and types, see the [REFERENCE.md](https://github. ## Limitations -SQL 2017, 2019 and 2022 detection support has been added. This support is limited to functionality already present for other versions. +SQL 2017, 2019, 2022 and 2025 detection support has been added. This support is limited to functionality already present for other versions. -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) +The MSOLEDBSQL driver is required to use this module. You can use this chocolatey [package](https://community.chocolatey.org/packages/msoledbsql) for installation. -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. +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. 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. diff --git a/lib/puppet_x/sqlserver/features.rb b/lib/puppet_x/sqlserver/features.rb index 90491602..3abe8110 100644 --- a/lib/puppet_x/sqlserver/features.rb +++ b/lib/puppet_x/sqlserver/features.rb @@ -7,8 +7,9 @@ SQL_2017 = 'SQL_2017' SQL_2019 = 'SQL_2019' SQL_2022 = 'SQL_2022' +SQL_2025 = 'SQL_2025' -ALL_SQL_VERSIONS = [SQL_2014, SQL_2016, SQL_2017, SQL_2019, SQL_2022].freeze +ALL_SQL_VERSIONS = [SQL_2014, SQL_2016, SQL_2017, SQL_2019, SQL_2022, SQL_2025].freeze # rubocop:disable Style/ClassAndModuleChildren module PuppetX @@ -35,12 +36,15 @@ class Features # rubocop:disable Style/Documentation major_version: 15, registry_path: '150' }, - SQL_2022 => { - major_version: 16, - registry_path: '160' - } + SQL_2022 => { + major_version: 16, + registry_path: '160' + }, + SQL_2025 => { + major_version: 17, + registry_path: '170' + } }.freeze - SQL_REG_ROOT = 'Software\Microsoft\Microsoft SQL Server' HKLM = 'HKEY_LOCAL_MACHINE' diff --git a/lib/puppet_x/sqlserver/server_helper.rb b/lib/puppet_x/sqlserver/server_helper.rb index 68537817..5fc09ef4 100644 --- a/lib/puppet_x/sqlserver/server_helper.rb +++ b/lib/puppet_x/sqlserver/server_helper.rb @@ -43,6 +43,7 @@ def self.sql_version_from_install_source(source_dir) ver = content.match('"(.+)"') return nil if ver.nil? + return SQL_2025 if ver[1].start_with?('17.') return SQL_2022 if ver[1].start_with?('16.') return SQL_2019 if ver[1].start_with?('15.') return SQL_2017 if ver[1].start_with?('14.') diff --git a/metadata.json b/metadata.json index 388b598b..f3451c39 100644 --- a/metadata.json +++ b/metadata.json @@ -2,7 +2,7 @@ "name": "puppetlabs-sqlserver", "version": "5.0.5", "author": "puppetlabs", - "summary": "The `sqlserver` module installs and manages MS SQL Server 2014, 2016, 2017, 2019 and 2022 on Windows systems.", + "summary": "The `sqlserver` module installs and manages MS SQL Server 2014, 2016, 2017, 2019, 2022 and 2025 on Windows systems.", "license": "proprietary", "source": "https://github.com/puppetlabs/puppetlabs-sqlserver", "project_page": "https://github.com/puppetlabs/puppetlabs-sqlserver", @@ -45,6 +45,7 @@ "sql2017", "sql2019", "sql2022", + "sql2025", "tsql", "database" ], diff --git a/spec/acceptance/manifests/install_oledb_driver.pp b/spec/acceptance/manifests/install_oledb_driver.pp new file mode 100644 index 00000000..90a7e087 --- /dev/null +++ b/spec/acceptance/manifests/install_oledb_driver.pp @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +# Install the Microsoft OLE DB Driver for SQL Server via Chocolatey +# Using Puppet Exec with PowerShell provider for reliability + +# Desired minimum version for MSOLEDBSQL; update as needed +$desired_version = '19.2.23273.0' + +exec { 'install_chocolatey': + provider => 'powershell', + 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'))", + unless => "Test-Path 'C:\\ProgramData\\chocolatey\\choco.exe'", + tries => 3, + try_sleep=> 10, + timeout => 600, +} + +$oledb_install_script = @(EOT) +$choco = 'C:\ProgramData\chocolatey\bin\choco.exe' +$reg = 'HKLM:\SOFTWARE\Microsoft\MSOLEDBSQL' +$ver = '18.6.0.0' +$pkgUrl = "https://community.chocolatey.org/api/v2/package/msoledbsql/$ver" + +function Test-Installed { + try { + $v = (Get-ItemProperty $reg -ErrorAction SilentlyContinue).InstalledVersion + return ($v -and $v -like '18.*') + } catch { return $false } +} + +function Invoke-Retry([scriptblock] $action, [int] $retries = 3, [int] $delay = 10) { + for ($i = 1; $i -le $retries; $i++) { + try { + & $action + return $true + } catch { + Write-Host "[msoledbsql] Attempt $i failed: $($_.Exception.Message)" + if ($i -lt $retries) { Start-Sleep -Seconds $delay } + } + } + return $false +} + +Write-Host "[msoledbsql] Target version: $ver" +if (Test-Installed) { + Write-Host "[msoledbsql] Already installed (v18.x)." + exit 0 +} + +# Remove incompatible v19+ if present +try { + $v = (Get-ItemProperty $reg -ErrorAction SilentlyContinue).InstalledVersion + if ($v -and $v -like '19.*') { + Write-Host "[msoledbsql] Uninstalling incompatible v19.x: $v" + & $choco uninstall msoledbsql -y | Out-Host + Start-Sleep -Seconds 5 + } +} catch { } + +# Primary install from Chocolatey community feed +Invoke-Retry { + Write-Host "[msoledbsql] Installing from Chocolatey feed..." + & $choco install msoledbsql --version $ver -y --force --source 'https://community.chocolatey.org/api/v2/' --no-progress | Out-Host + if (-not (Test-Installed)) { throw "Install from feed did not register in registry" } +} 3 15 | Out-Null +Start-Sleep -Seconds 10 + +# Fallback: download nupkg directly and install from file +if (-not (Test-Installed)) { + try { + Write-Host "[msoledbsql] Falling back to direct package download: $pkgUrl" + $nupkg = "$env:TEMP\msoledbsql.$ver.nupkg" + Invoke-WebRequest -Uri $pkgUrl -OutFile $nupkg -UseBasicParsing + Invoke-Retry { + & $choco install $nupkg -y --force --no-progress | Out-Host + if (-not (Test-Installed)) { throw "Install from nupkg did not register in registry" } + } 3 15 | Out-Null + Start-Sleep -Seconds 10 + } catch { Write-Host "[msoledbsql] Fallback install failed: $($_.Exception.Message)" } +} + +if (Test-Installed) { + Write-Host "[msoledbsql] Install succeeded." + exit 0 +} else { + Write-Host "[msoledbsql] Install failed." + exit 1 +} +EOT + +exec { 'install_oledb_driver': + provider => 'powershell', + command => $oledb_install_script, + require => Exec['install_chocolatey'], + unless => @(EOT) +try { + $v = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\MSOLEDBSQL' -ErrorAction SilentlyContinue).InstalledVersion + if ($v -and $v -like '18.*') { exit 0 } else { exit 1 } +} catch { exit 1 } +EOT + , + returns => [0], + tries => 3, + try_sleep=> 10, + timeout => 1200, +} diff --git a/spec/acceptance/sqlserver_role_spec.rb b/spec/acceptance/sqlserver_role_spec.rb index 1c23a1e5..c4869140 100644 --- a/spec/acceptance/sqlserver_role_spec.rb +++ b/spec/acceptance/sqlserver_role_spec.rb @@ -4,10 +4,6 @@ require 'securerandom' require 'erb' -hostname = Helper.instance.run_shell('hostname').stdout.upcase.strip - -# database name -db_name = "DB#{SecureRandom.hex(4)}".upcase LOGIN1 = "Login1_#{SecureRandom.hex(2)}".freeze LOGIN2 = "Login2_#{SecureRandom.hex(2)}".freeze LOGIN3 = "Login3_#{SecureRandom.hex(2)}".freeze @@ -47,8 +43,11 @@ def ensure_sqlserver_logins_users(db_name) context 'Start testing sqlserver::role' do before(:all) do + # Initialize hostname and db_name once per context + @hostname = Helper.instance.run_shell('hostname').stdout.upcase.strip + @db_name = "DB#{SecureRandom.hex(4)}".upcase # Create database users - ensure_sqlserver_logins_users(db_name) + ensure_sqlserver_logins_users(@db_name) end before(:each) do @@ -76,7 +75,7 @@ def ensure_sqlserver_logins_users(db_name) admin_pass => 'Pupp3t1@', } sqlserver::user{'#{USER1}': - database => '#{db_name}', + database => '#{@db_name}', ensure => 'absent', } MANIFEST @@ -100,7 +99,7 @@ def ensure_sqlserver_logins_users(db_name) apply_manifest(pp, catch_failures: true) # validate that the database-specific role '#{@role}' is successfully created with specified permissions': - query = "USE #{db_name}; + query = "USE #{@db_name}; SELECT spr.principal_id, spr.name, spe.state_desc, spe.permission_name FROM sys.server_principals AS spr @@ -108,17 +107,17 @@ def ensure_sqlserver_logins_users(db_name) ON spe.grantee_principal_id = spr.principal_id WHERE spr.name = '#{@role}';" - run_sql_query(query:, server: hostname, expected_row_count: 2) + run_sql_query(query:, server: @hostname, expected_row_count: 2) # validate that the database-specific role '#{@role}' has correct authorization #{LOGIN1} - query = "USE #{db_name}; + query = "USE #{@db_name}; SELECT spr.name, sl.name FROM sys.server_principals AS spr JOIN sys.sql_logins AS sl ON spr.owning_principal_id = sl.principal_id WHERE sl.name = '#{LOGIN1}';" - run_sql_query(query:, server: hostname, expected_row_count: 1) + run_sql_query(query:, server: @hostname, expected_row_count: 1) end it "Create database-specific role #{@role}" do @@ -130,7 +129,7 @@ def ensure_sqlserver_logins_users(db_name) sqlserver::role{'DatabaseRole': ensure => 'present', role => '#{@role}', - database => '#{db_name}', + database => '#{@db_name}', permissions => {'GRANT' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'CONTROL', 'ALTER']}, type => 'DATABASE', } @@ -138,7 +137,7 @@ def ensure_sqlserver_logins_users(db_name) apply_manifest(pp, catch_failures: true) # validate that the database-specific role '#{@role}' is successfully created with specified permissions': - query = "USE #{db_name}; + query = "USE #{@db_name}; SELECT pr.principal_id, pr.name, pr.type_desc, pr.authentication_type_desc, pe.state_desc, pe.permission_name FROM sys.database_principals AS pr @@ -146,7 +145,7 @@ def ensure_sqlserver_logins_users(db_name) ON pe.grantee_principal_id = pr.principal_id WHERE pr.name = '#{@role}';" - run_sql_query(query:, server: hostname, expected_row_count: 6) + run_sql_query(query:, server: @hostname, expected_row_count: 6) end 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) sqlserver::role{'DatabaseRole_1': ensure => 'present', role => '#{@role}', - database => '#{db_name}', + database => '#{@db_name}', permissions => {'GRANT' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'CONTROL', 'ALTER']}, type => 'DATABASE', } @@ -180,11 +179,11 @@ def ensure_sqlserver_logins_users(db_name) FROM sys.database_principals AS pr JOIN sys.database_permissions AS pe ON pe.grantee_principal_id = pr.principal_id - JOIN #{db_name}.sys.database_principals as dbpr + JOIN #{@db_name}.sys.database_principals as dbpr on pr.name = dbpr.name WHERE pr.name = '#{@role}';" - run_sql_query(query:, server: hostname, expected_row_count: 6) + run_sql_query(query:, server: @hostname, expected_row_count: 6) end it "Create server role #{@role} with optional members and optional members-purge" do @@ -205,7 +204,7 @@ def ensure_sqlserver_logins_users(db_name) apply_manifest(pp, catch_failures: true) # validate that the server role '#{@role}' is successfully created with specified permissions': - query = "USE #{db_name}; + query = "USE #{@db_name}; SELECT spr.principal_id AS ID, spr.name AS Server_Role, spe.state_desc, spe.permission_name FROM sys.server_principals AS spr @@ -213,10 +212,10 @@ def ensure_sqlserver_logins_users(db_name) ON spe.grantee_principal_id = spr.principal_id WHERE spr.name = '#{@role}';" - run_sql_query(query:, server: hostname, expected_row_count: 2) + run_sql_query(query:, server: @hostname, expected_row_count: 2) # validate that the t server role '#{@role}' has correct members (Login1, 2, 3) - query = "USE #{db_name}; + query = "USE #{@db_name}; SELECT spr.principal_id AS ID, spr.name AS ServerRole FROM sys.server_principals AS spr JOIN sys.server_role_members m @@ -226,7 +225,7 @@ def ensure_sqlserver_logins_users(db_name) OR spr.name = '#{LOGIN3}' OR spr.name = 'LOGIN4';" - run_sql_query(query:, server: hostname, expected_row_count: 3) + run_sql_query(query:, server: @hostname, expected_row_count: 3) puts "Create server role #{@role} with optional members_purge:" pp = <<-MANIFEST @@ -247,7 +246,7 @@ def ensure_sqlserver_logins_users(db_name) apply_manifest(pp, catch_failures: true) # validate that the t server role '#{@role}' has correct members (only Login3) - query = "USE #{db_name}; + query = "USE #{@db_name}; SELECT spr.principal_id AS ID, spr.name AS ServerRole FROM sys.server_principals AS spr JOIN sys.server_role_members m @@ -256,7 +255,7 @@ def ensure_sqlserver_logins_users(db_name) OR spr.name = '#{LOGIN2}' OR spr.name = '#{LOGIN3}';" - run_sql_query(query:, server: hostname, expected_row_count: 1) + run_sql_query(query:, server: @hostname, expected_row_count: 1) end end end diff --git a/spec/acceptance/sqlserver_user_spec.rb b/spec/acceptance/sqlserver_user_spec.rb index c5059445..2c93bffd 100644 --- a/spec/acceptance/sqlserver_user_spec.rb +++ b/spec/acceptance/sqlserver_user_spec.rb @@ -4,11 +4,6 @@ require 'securerandom' require 'erb' -hostname = Helper.instance.run_shell('hostname').stdout.upcase.strip - -# database name -db_name = "DB#{SecureRandom.hex(4)}".upcase - describe 'sqlserver::user test' do def ensure_sqlserver_database(db_name, _ensure_val = 'present') pp = <<-MANIFEST @@ -35,8 +30,11 @@ def ensure_sqlserver_database(db_name, _ensure_val = 'present') context 'Create database users with optional attributes' do before(:all) do + # Initialize hostname and db_name once per context + @hostname = Helper.instance.run_shell('hostname').stdout.upcase.strip + @db_name = "DB#{SecureRandom.hex(4)}".upcase # Create new database - ensure_sqlserver_database(db_name) + ensure_sqlserver_database(@db_name) end before(:each) do @@ -56,7 +54,7 @@ def ensure_sqlserver_database(db_name, _ensure_val = 'present') password => 'Pupp3t1@', } sqlserver::user{'#{@db_user}': - database => '#{db_name}', + database => '#{@db_name}', user => '#{@db_user}', default_schema => 'guest', require => Sqlserver::Login['#{@db_user}'], @@ -65,13 +63,13 @@ def ensure_sqlserver_database(db_name, _ensure_val = 'present') apply_manifest(pp, catch_failures: true) # validate that the database user '#{@db_user}' is successfully created with default schema 'guest': - query = "USE #{db_name}; + query = "USE #{@db_name}; SELECT name AS Database_User_Name, default_schema_name FROM SYS.DATABASE_PRINCIPALS WHERE name = '#{@db_user}' AND default_schema_name = 'guest';" - run_sql_query(query:, server: hostname, expected_row_count: 1) + run_sql_query(query:, server: @hostname, expected_row_count: 1) end it 'Create database user with optional instance' do @@ -87,7 +85,7 @@ def ensure_sqlserver_database(db_name, _ensure_val = 'present') } sqlserver::user{'#{@db_user}': instance => 'MSSQLSERVER', - database => '#{db_name}', + database => '#{@db_name}', user => '#{@db_user}', require => Sqlserver::Login['#{@db_user}'], } @@ -95,11 +93,11 @@ def ensure_sqlserver_database(db_name, _ensure_val = 'present') apply_manifest(pp, catch_failures: true) # validate that the database user '#{@db_user}' is successfully created: - query = "USE #{db_name}; + query = "USE #{@db_name}; SELECT name AS Database_User_Name FROM SYS.DATABASE_PRINCIPALS WHERE name = '#{@db_user}';" - run_sql_query(query:, server: hostname, expected_row_count: 1) + run_sql_query(query:, server: @hostname, expected_row_count: 1) end it 'Create database user with optional login' do @@ -115,7 +113,7 @@ def ensure_sqlserver_database(db_name, _ensure_val = 'present') } sqlserver::user{'#{@db_user}': instance => 'MSSQLSERVER', - database => '#{db_name}', + database => '#{@db_name}', login => '#{@new_sql_login}', user => '#{@db_user}', require => Sqlserver::Login['#{@new_sql_login}'], @@ -124,12 +122,12 @@ def ensure_sqlserver_database(db_name, _ensure_val = 'present') apply_manifest(pp, catch_failures: true) # validate that the database user '#{@db_user}' is mapped with sql login '#{@new_sql_login}': - query = "USE #{db_name}; + query = "USE #{@db_name}; SELECT d.name AS Database_User, l.name as Associated_sql_login FROM SYS.DATABASE_PRINCIPALS d, MASTER.SYS.SQL_LOGINS l WHERE d.sid = l.sid AND d.name = '#{@db_user}';" - run_sql_query(query:, server: hostname, expected_row_count: 1) + run_sql_query(query:, server: @hostname, expected_row_count: 1) end it 'Create database user with optional password' do @@ -145,7 +143,7 @@ def ensure_sqlserver_database(db_name, _ensure_val = 'present') } sqlserver::user{'#{@db_user}': instance => 'MSSQLSERVER', - database => '#{db_name}', + database => '#{@db_name}', login => '#{@new_sql_login}', user => '#{@db_user}', password => 'databaseUserPasswd', @@ -155,8 +153,8 @@ def ensure_sqlserver_database(db_name, _ensure_val = 'present') apply_manifest(pp, catch_failures: true) puts "validate that the database user '#{@db_user}' is successfully created:" - query = "USE #{db_name}; SELECT * FROM SYS.DATABASE_PRINCIPALS WHERE name = '#{@db_user}';" - run_sql_query(query:, server: hostname, expected_row_count: 1) + query = "USE #{@db_name}; SELECT * FROM SYS.DATABASE_PRINCIPALS WHERE name = '#{@db_user}';" + run_sql_query(query:, server: @hostname, expected_row_count: 1) end it 'Delete database user' do @@ -171,15 +169,15 @@ def ensure_sqlserver_database(db_name, _ensure_val = 'present') password => 'Pupp3t1@', } sqlserver::user{'#{@db_user}': - database => '#{db_name}', + database => '#{@db_name}', require => Sqlserver::Login['#{@db_user}'], } MANIFEST apply_manifest(pp, catch_failures: true) # validate that the database user '#{@db_user}' is successfully created: - query = "USE #{db_name}; SELECT * FROM SYS.DATABASE_PRINCIPALS WHERE name = '#{@db_user}';" - run_sql_query(query:, server: hostname, expected_row_count: 1) + query = "USE #{@db_name}; SELECT * FROM SYS.DATABASE_PRINCIPALS WHERE name = '#{@db_user}';" + run_sql_query(query:, server: @hostname, expected_row_count: 1) pp = <<-MANIFEST sqlserver::config{'MSSQLSERVER': @@ -188,13 +186,13 @@ def ensure_sqlserver_database(db_name, _ensure_val = 'present') } sqlserver::user{'#{@db_user}': ensure => 'absent', - database => '#{db_name}', + database => '#{@db_name}', } MANIFEST apply_manifest(pp, catch_failures: true) # validate that the database user '#{@db_user}' should be deleted: - query = "USE #{db_name}; SELECT * FROM SYS.DATABASE_PRINCIPALS WHERE name = '#{@db_user}';" - run_sql_query(query:, server: hostname, expected_row_count: 0) + query = "USE #{@db_name}; SELECT * FROM SYS.DATABASE_PRINCIPALS WHERE name = '#{@db_user}';" + run_sql_query(query:, server: @hostname, expected_row_count: 0) end end end diff --git a/spec/acceptance/z_last_sqlserver_features_spec.rb b/spec/acceptance/z_last_sqlserver_features_spec.rb index 382a8ead..c48069d9 100644 --- a/spec/acceptance/z_last_sqlserver_features_spec.rb +++ b/spec/acceptance/z_last_sqlserver_features_spec.rb @@ -53,10 +53,12 @@ def bind_and_apply_failing_manifest(features, ensure_val = 'present') end context 'can install' do - # Client Tools removed in Server2022 (Backwards Compatibility, Connectivity, SDK) - features = if version.to_i == 2022 - ['IS', 'MDS', 'DQC'] - elsif version.to_i >= 2016 && version.to_i < 2022 + # Client Tools removed in Server2019+ (Backwards Compatibility, Connectivity, SDK) + # SQL Server 2019 CTP does not properly support BC, Conn, SDK, IS, MDS features + # DQC (Data Quality Client) is not available in SQL Server 2019+ + features = if version.to_i >= 2019 + [] + elsif version.to_i >= 2016 && version.to_i < 2019 ['BC', 'Conn', 'SDK', 'IS', 'MDS', 'DQC'] else ['BC', 'Conn', 'SSMS', 'ADV_SSMS', 'SDK', 'IS', 'MDS', 'DQC'] @@ -70,23 +72,21 @@ def bind_and_apply_failing_manifest(features, ensure_val = 'present') ensure_sql_features(features) validate_sql_install(version:) do |r| - # Client Tools removed in Server2022 - unless version.to_i == 2022 + # Client Tools removed in Server2019+ + unless version.to_i >= 2019 expect(r.stdout).to match(%r{Client Tools Connectivity}) expect(r.stdout).to match(%r{Client Tools Backwards Compatibility}) expect(r.stdout).to match(%r{Client Tools SDK}) + expect(r.stdout).to match(%r{Integration Services}) + expect(r.stdout).to match(%r{Master Data Services}) end - expect(r.stdout).to match(%r{Integration Services}) - expect(r.stdout).to match(%r{Master Data Services}) end end end context 'can remove' do - features = if version.to_i == 2022 - ['IS', 'MDS', 'DQC'] - elsif version.to_i >= 2016 && version.to_i < 2022 - ['BC', 'Conn', 'SDK', 'IS', 'MDS', 'DQC'] + features = if version.to_i >= 2019 + [] else ['BC', 'Conn', 'SSMS', 'ADV_SSMS', 'SDK', 'IS', 'MDS', 'DQC'] end @@ -95,8 +95,8 @@ def bind_and_apply_failing_manifest(features, ensure_val = 'present') ensure_sql_features(features, 'absent') validate_sql_install(version:) do |r| - # Client Tools removed in Server2022 - unless version.to_i == 2022 + # Client Tools removed in Server2019+ + unless version.to_i >= 2019 expect(r.stdout).not_to match(%r{Client Tools Connectivity}) expect(r.stdout).not_to match(%r{Client Tools Backwards Compatibility}) expect(r.stdout).not_to match(%r{Client Tools SDK}) @@ -108,9 +108,9 @@ def bind_and_apply_failing_manifest(features, ensure_val = 'present') end context 'can remove independent feature' do - features = if version.to_i == 2022 - ['IS', 'MDS', 'DQC'] - elsif version.to_i >= 2016 && version.to_i < 2022 + features = if version.to_i >= 2019 + ['IS', 'MDS'] + elsif version.to_i >= 2016 && version.to_i < 2019 ['BC', 'Conn', 'SDK', 'IS', 'MDS', 'DQC'] else ['BC', 'Conn', 'SSMS', 'ADV_SSMS', 'SDK', 'IS', 'MDS', 'DQC'] @@ -124,7 +124,7 @@ def bind_and_apply_failing_manifest(features, ensure_val = 'present') ensure_sql_features(features, 'absent') end - it "'BC'", unless: version.to_i == 2022 do + it "'BC'", unless: version.to_i >= 2019 do ensure_sql_features(features - ['BC']) validate_sql_install(version:) do |r| @@ -143,7 +143,7 @@ def bind_and_apply_failing_manifest(features, ensure_val = 'present') end end - it "'SDK' + 'IS", unless: version.to_i == 2022 do + it "'SDK' + 'IS", unless: version.to_i >= 2019 do ensure_sql_features(features - ['SDK', 'IS']) validate_sql_install(version:) do |r| @@ -168,9 +168,9 @@ def bind_and_apply_failing_manifest(features, ensure_val = 'present') context 'with no installed instances' do # Currently this test can only be run on a machine once and will error if run a second time context 'can install' do - features = if version.to_i == 2022 - ['IS', 'MDS', 'DQC'] - elsif version.to_i >= 2016 && version.to_i < 2022 + features = if version.to_i >= 2019 + [] + elsif version.to_i >= 2016 && version.to_i < 2019 ['BC', 'Conn', 'SDK', 'IS', 'MDS', 'DQC'] else ['BC', 'Conn', 'SSMS', 'ADV_SSMS', 'SDK', 'IS', 'MDS', 'DQC'] @@ -204,11 +204,13 @@ def remove_sql_instance expect(r.stdout).not_to match(%r{MSSQLSERVER\s+Database Engine Services}) expect(r.stdout).not_to match(%r{MSSQLSERVER\s+SQL Server Replication}) expect(r.stdout).not_to match(%r{MSSQLSERVER\s+Data Quality Services}) - expect(r.stdout).to match(%r{Client Tools Connectivity}) unless version.to_i >= 2016 - expect(r.stdout).to match(%r{Client Tools Backwards Compatibility}) unless version.to_i >= 2016 - expect(r.stdout).to match(%r{Client Tools SDK}) unless version.to_i >= 2016 - expect(r.stdout).to match(%r{Integration Services}) - expect(r.stdout).to match(%r{Master Data Services}) + unless version.to_i >= 2016 + expect(r.stdout).to match(%r{Client Tools Connectivity}) + expect(r.stdout).to match(%r{Client Tools Backwards Compatibility}) + expect(r.stdout).to match(%r{Client Tools SDK}) + expect(r.stdout).to match(%r{Integration Services}) + expect(r.stdout).to match(%r{Master Data Services}) + end end end end diff --git a/spec/spec_helper_acceptance_local.rb b/spec/spec_helper_acceptance_local.rb index f886db7d..0fd4891f 100644 --- a/spec/spec_helper_acceptance_local.rb +++ b/spec/spec_helper_acceptance_local.rb @@ -11,6 +11,7 @@ class Helper WIN_ISO_ROOT = 'https://artifactory.delivery.puppetlabs.net/artifactory/generic__iso/iso/windows' WIN_2019_ISO = 'en_windows_server_2019_updated_july_2020_x64_dvd_94453821.iso' QA_RESOURCE_ROOT = 'https://artifactory.delivery.puppetlabs.net/artifactory/generic__iso/iso/SQLServer' +SQL_2025_ISO = 'SQLServer2025-x64-ENU-Dev.iso' SQL_2022_ISO = 'SQLServer2022-x64-ENU-Dev.iso' SQL_2019_ISO = 'SQLServer2019CTP2.4-x64-ENU.iso' SQL_2017_ISO = 'SQLServer2017-x64-ENU.iso' @@ -18,7 +19,8 @@ class Helper SQL_2014_ISO = 'SQLServer2014SP3-FullSlipstream-x64-ENU.iso' SQL_ADMIN_USER = 'sa' SQL_ADMIN_PASS = 'Pupp3t1@' -USER = Helper.instance.run_shell('$env:UserName').stdout.chomp +# USER constant removed - was causing connection failures during file load +# Tests should call Helper.instance.run_shell('$env:UserName').stdout.chomp directly when needed def retry_on_error_matching(max_retry_count = 3, retry_wait_interval_secs = 5, error_matcher = nil) try = 0 @@ -34,21 +36,56 @@ def retry_on_error_matching(max_retry_count = 3, retry_wait_interval_secs = 5, e end RSpec.configure do |c| - c.before(:suite) do + include PuppetLitmus + c.before :suite do + # Install archive module dependency first + run_shell('puppet module install puppet/archive') + # Install stdlib, needed by many modules including puppet_agent + Helper.instance.run_shell('puppet module install puppetlabs-stdlib') + + # Install OLEDB driver (required for Puppet types to connect to SQL Server) + puts 'Installing Microsoft OLE DB Driver for SQL Server via Puppet manifest...' + pp = File.read(File.join(File.dirname(__FILE__), 'acceptance', 'manifests', 'install_oledb_driver.pp')) + apply_manifest(pp, catch_failures: true) + + # Make sure all the instances are torn down + # and the directories are clean + pp = <<-MANIFEST + sqlserver_instance{ ['MSSQLSERVER', 'MYINSTANCE']: + ensure => absent, + } + file{['C:/Program Files/Microsoft SQL Server', 'C:/Program Files (x86)/Microsoft SQL Server']: + ensure => absent, + force => true, + recurse => true, + } + MANIFEST + apply_manifest(pp) + + # Pre-install Puppet agent dependencies and helper modules + # We need mount_iso provider to work on Windows Helper.instance.run_shell('puppet module install puppetlabs-mount_iso') - Helper.instance.run_shell('puppet module install puppet/archive') + # Ensure puppetlabs-puppet_agent module is present before including class + # Use Ruby-side guard to avoid complex PowerShell quoting issues + modules_list = Helper.instance.run_shell('puppet module list') + unless modules_list.stdout.include?('puppetlabs-puppet_agent') + Helper.instance.run_shell('puppet module install puppetlabs-puppet_agent') + end + + # Rerun the setup, but with the agent's path + # This is a workaround for the module's helper not being in the load path + Helper.instance.run_shell('puppet apply -e "include puppet_agent"') + + # Install the OS features and mount the ISOs + # OS iso mounts to I drive + # SQL iso mounts to H drive iso_opts = { folder: WIN_ISO_ROOT, file: WIN_2019_ISO, drive_letter: 'I' } - # Allows litmus to use SSH, by explicitly setting specinfra - # os family to windows (would fail when using ssh on windows) - set :os, family: 'windows', release: nil, arch: nil - mount_iso(iso_opts) - base_install(sql_version?) end end @@ -130,23 +167,36 @@ def base_install(sql_version) file: SQL_2022_ISO, drive_letter: 'H' } + when 2025 + iso_opts = { + folder: QA_RESOURCE_ROOT, + file: SQL_2025_ISO, + drive_letter: 'H' + } end # Mount the ISO on the agent mount_iso(iso_opts) # Install Microsoft SQL on the agent before running any tests features = ['DQ', 'FullText', 'Replication', 'SQLEngine'] install_sqlserver(features) + + ensure_oledb_installed + ensure_sql_service_ready end def install_sqlserver(features) # this method installs SQl server on a given host + puts "[SQL Server Install] Starting installation with features: #{features}" + user = Helper.instance.run_shell('$env:UserName').stdout.chomp + puts "[SQL Server Install] Installing for user: #{user}" + pp = <<-MANIFEST sqlserver_instance{'MSSQLSERVER': source => 'H:', features => #{features}, security_mode => 'SQL', sa_pwd => 'Pupp3t1@', - sql_sysadmin_accounts => ['#{USER}'], + sql_sysadmin_accounts => ['#{user}'], install_switches => { 'UpdateEnabled' => 'false', 'TCPENABLED' => 1, @@ -160,9 +210,171 @@ def install_sqlserver(features) windows_feature_source => 'I:\\sources\\sxs', } MANIFEST + + puts '[SQL Server Install] Applying manifest with retry logic...' retry_on_error_matching(10, 5, %r{apply manifest failed}) do Helper.instance.apply_manifest(pp) end + puts '[SQL Server Install] Installation completed successfully' +rescue StandardError => e + puts "[SQL Server Install] FAILED: #{e.message}" + puts '[SQL Server Install] Checking setup logs...' + log_cmd = 'Get-ChildItem -Path "C:\\Program Files\\Microsoft SQL Server" -Recurse ' \ + '-Filter "Summary*.txt" -ErrorAction SilentlyContinue | ' \ + 'Select-Object -First 1 -ExpandProperty FullName' + log_check = Helper.instance.run_shell(log_cmd) + if log_check.exit_code == 0 && !log_check.stdout.strip.empty? + puts "[SQL Server Install] Setup log found at: #{log_check.stdout.strip}" + log_content = Helper.instance.run_shell("Get-Content '#{log_check.stdout.strip}' -Tail 50") + puts "[SQL Server Install] Last 50 lines of setup log:\n#{log_content.stdout}" + end + raise e +end + +def ensure_oledb_installed + # Check for MSOLEDBSQL driver and install if not present + # Downloads and installs Chocolatey if not already installed + # See: https://docs.microsoft.com/en-us/sql/connect/oledb/applications/installing-oledb-driver-for-sql-server + choco_install_path = 'C:\ProgramData\chocolatey\choco.exe' + choco_path = 'C:\ProgramData\chocolatey\bin\choco.exe' + registry_key = 'HKLM:\SOFTWARE\Microsoft\MSOLEDBSQL' + + cmd = <<-POWERSHELL +if (-not (Test-Path '#{choco_install_path}')) { + [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072 + iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) +} + +# Check if v18.x is installed, exit early if yes +function Test-Installed { + try { + $v = (Get-ItemProperty '#{registry_key}' -ErrorAction SilentlyContinue).InstalledVersion + return ($v -and $v -like '18.*') + } catch { return $false } +} + +function Invoke-Retry([scriptblock] $action, [int] $retries = 3, [int] $delay = 10) { + for ($i = 1; $i -le $retries; $i++) { + try { + & $action + return $true + } catch { + Write-Host "[msoledbsql] Attempt $i failed: $($_.Exception.Message)" + if ($i -lt $retries) { Start-Sleep -Seconds $delay } + } + } + return $false +} + +Write-Host "[msoledbsql] Target version: 18.6.0.0" +if (Test-Installed) { Write-Host "[msoledbsql] Already installed (v18.x)."; exit 0 } + +# Uninstall v19+ if present +try { + $installed = Get-ItemProperty '#{registry_key}' -ErrorAction SilentlyContinue + if ($installed -and $installed.InstalledVersion -like '19.*') { + Write-Host "[msoledbsql] Uninstalling incompatible v19.x: $($installed.InstalledVersion)" + & '#{choco_path}' uninstall msoledbsql -y + Start-Sleep -Seconds 5 + } +} catch { } + +# Install v18.6.0.0 from Chocolatey feed +Invoke-Retry { + Write-Host "[msoledbsql] Installing from Chocolatey feed..." + & '#{choco_path}' install msoledbsql --version 18.6.0.0 -y --force --source 'https://community.chocolatey.org/api/v2/' --no-progress + if (-not (Test-Installed)) { throw "Install from feed did not register" } +} 3 15 | Out-Null +Start-Sleep -Seconds 10 + +# Fallback: download nupkg directly and install from file +if (-not (Test-Installed)) { + try { + Write-Host "[msoledbsql] Falling back to direct package download." + $nupkg = "$env:TEMP\\msoledbsql.18.6.0.0.nupkg" + Invoke-WebRequest -Uri 'https://community.chocolatey.org/api/v2/package/msoledbsql/18.6.0.0' -OutFile $nupkg -UseBasicParsing + Invoke-Retry { + & '#{choco_path}' install $nupkg -y --force --no-progress + if (-not (Test-Installed)) { throw "Install from nupkg did not register" } + } 3 15 | Out-Null + Start-Sleep -Seconds 10 + } catch { Write-Host "[msoledbsql] Fallback install failed: $($_.Exception.Message)" } +} + +# Final validation +if (Test-Installed) { Write-Host "[msoledbsql] Install succeeded."; exit 0 } else { Write-Host "[msoledbsql] Install failed."; exit 1 } +POWERSHELL + + retry_on_error_matching(3, 10, %r{Failed to install MSOLEDBSQL driver}) do + r = Helper.instance.run_shell(cmd) + raise "Failed to install MSOLEDBSQL driver (exit code #{r.exit_code}):\n#{r.stderr}" if r.exit_code != 0 + end +end + +def ensure_sql_service_ready + puts '[SQL Service] Checking if MSSQLSERVER service exists and is ready...' + + ps_script = <<~POWERSHELL + $svc = Get-Service -Name MSSQLSERVER -ErrorAction SilentlyContinue + if ($null -eq $svc) { + Write-Host '[SQL Service] ERROR: MSSQLSERVER service does not exist' + Write-Host '[SQL Service] Installed services matching SQL:' + Get-Service | Where-Object { $_.Name -like '*SQL*' } | Format-Table -AutoSize | Out-String | Write-Host + exit 1 + } + + Write-Host ('[SQL Service] Service found. Current status: ' + $svc.Status) + + if ($svc.Status -ne 'Running') { + Write-Host '[SQL Service] Attempting to start service...' + try { + Start-Service -Name MSSQLSERVER -ErrorAction Stop + Write-Host '[SQL Service] Service start command issued' + } catch { + Write-Host ('[SQL Service] ERROR starting service: ' + $_.Exception.Message) + Write-Host '[SQL Service] Checking SQL Server error logs...' + $logPath = Get-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Microsoft SQL Server\\MSSQL*\\MSSQLServer\\Parameters' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty 'SQLArg0' -ErrorAction SilentlyContinue + if ($logPath -and (Test-Path $logPath.Replace('-e', ''))) { + Get-Content $logPath.Replace('-e', '') -Tail 30 | Write-Host + } + exit 1 + } + } + + Write-Host '[SQL Service] Waiting up to 5 minutes for service to be fully running...' + $limit = (Get-Date).AddMinutes(5) + $lastStatus = '' + while ((Get-Date) -lt $limit) { + $svc = Get-Service -Name MSSQLSERVER -ErrorAction SilentlyContinue + if ($svc.Status -ne $lastStatus) { + Write-Host ('[SQL Service] Status: ' + $svc.Status) + $lastStatus = $svc.Status + } + if ($svc.Status -eq 'Running') { + Write-Host '[SQL Service] Service is running and ready' + exit 0 + } + Start-Sleep -Seconds 5 + } + + Write-Host '[SQL Service] ERROR: Service did not start within timeout' + Write-Host ('[SQL Service] Final status: ' + (Get-Service -Name MSSQLSERVER).Status) + exit 1 + POWERSHELL + + # Use base64 encoding to avoid all quoting/escaping issues + require 'base64' + encoded = Base64.strict_encode64(ps_script.encode('UTF-16LE')) + cmd = "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand #{encoded}" + r = Helper.instance.run_shell(cmd) + + unless r.exit_code.zero? + puts "[SQL Service] STDOUT:\n#{r.stdout}" + puts "[SQL Service] STDERR:\n#{r.stderr}" unless r.stderr.empty? + raise 'MSSQLSERVER service not ready - see diagnostics above' + end + + puts '[SQL Service] Service is ready' end def run_sql_query(opts = {}, &block) @@ -185,13 +397,14 @@ def run_sql_query(opts = {}, &block) # Below is a work-around for it (remove "-S server\instance" from the connection string) powershell.dup.gsub!("-S #{server}\\#{instance}", '') if instance.nil? || instance == 'MSSQLSERVER' + user = Helper.instance.run_shell('$env:UserName').stdout.chomp Tempfile.open 'tmp.ps1' do |tempfile| File.open(tempfile.path, 'w') { |file| file.puts powershell } - bolt_upload_file(tempfile.path, "c:\\users\\#{USER}\\tmp.ps1") + bolt_upload_file(tempfile.path, "c:\\users\\#{user}\\tmp.ps1") end # create_remote_file('tmp.ps1', powershell) - Helper.instance.run_shell("powershell -NonInteractive -NoLogo -File 'c:\\users\\#{USER}\\tmp.ps1'") do |r| + Helper.instance.run_shell("powershell -NonInteractive -NoLogo -File 'c:\\users\\#{user}\\tmp.ps1'") do |r| match = %r{(\d*) rows affected}.match(r.stdout) raise 'Could not match number of rows for SQL query' unless match @@ -212,13 +425,25 @@ def run_sql_query(opts = {}, &block) def validate_sql_install(opts = {}, &block) bootstrap_dir, setup_dir = get_install_paths(opts[:version]) - # NOTE: executing a fully qualified setup.exe quoted this way fails - # but that can be circumvented by first changing directories - cmd = "cd \"#{setup_dir}\"; ./setup.exe /Action=RunDiscovery /q" + ps_test = "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command \"Test-Path '#{setup_dir}'\"" + exists = setup_dir && Helper.instance.run_shell(ps_test).stdout.strip.casecmp('True').zero? + cmd = if exists + "cd \"#{setup_dir}\"; ./setup.exe /Action=RunDiscovery /q" + else + "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command \"Start-Process -FilePath 'H:\\setup.exe' -ArgumentList '/Action=RunDiscovery','/q' -Wait\"" + end Helper.instance.run_shell(cmd) - cmd = "type \"#{bootstrap_dir}\\Log\\Summary.txt\"" - result = Helper.instance.run_shell(cmd) + ps_summary = [ + "if (Test-Path '#{bootstrap_dir}\\Log\\Summary.txt') {", + " Get-Content '#{bootstrap_dir}\\Log\\Summary.txt'", + '} else {', + " $s = Get-ChildItem -Path 'C:\\Program Files\\Microsoft SQL Server' -Filter Summary.txt -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1", + ' if ($s) { Get-Content $s.FullName }', + '}', + ].join("\n") + sum_cmd = "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command \"#{ps_summary}\"" + result = Helper.instance.run_shell(sum_cmd) return unless block case block.arity @@ -230,19 +455,28 @@ def validate_sql_install(opts = {}, &block) end def get_install_paths(version) - vers = { '2014' => '120', '2016' => '130', '2017' => '140', '2019' => '150', '2022' => '160' } + vers = { '2014' => '120', '2016' => '130', '2017' => '140', '2019' => '150', '2022' => '160', '2025' => '170' } raise _('Valid version must be specified') unless vers.key?(version) dir = "C://Program Files/Microsoft SQL Server/#{vers[version]}/Setup Bootstrap" - sql_directory = case version - when '2022', '2017' - "SQL#{version}" - when '2019' - "SQL#{version}CTP2.4" - else - "SQLServer#{version}" - end - - [dir, "#{dir}\\#{sql_directory}"] + + # Discover the actual Setup Bootstrap subdirectory dynamically to avoid hard-coding CTP names + ps_cmd = "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command \"Get-ChildItem -Path '#{dir}' -Directory | Select-Object -ExpandProperty Name\"" + result = Helper.instance.run_shell(ps_cmd) + names = result.stdout.split(%r{\r?\n}).map(&:strip).reject(&:empty?) + + # Prefer canonical folder names; fall back to any directory containing the version + preferred_prefixes = + case version + when '2014', '2016' + ["SQLServer#{version}"] + else + ["SQL#{version}", "SQLServer#{version}"] + end + + chosen = names.find { |n| preferred_prefixes.any? { |p| n.start_with?(p) } } || + names.find { |n| n =~ %r{#{Regexp.escape(version)}}i } + + [dir, chosen ? "#{dir}\\#{chosen}" : nil] end diff --git a/spec/sql_testing_helpers.rb b/spec/sql_testing_helpers.rb index ed07a376..c01c8e7b 100644 --- a/spec/sql_testing_helpers.rb +++ b/spec/sql_testing_helpers.rb @@ -120,6 +120,12 @@ def base_install(sql_version) file: SQL_2022_ISO, drive_letter: 'H' } + when 2025 + iso_opts = { + folder: QA_RESOURCE_ROOT, + file: SQL_2025_ISO, + drive_letter: 'H' + } end host = find_only_one('sql_host') # Mount the ISO on the agent @@ -163,13 +169,13 @@ def remove_sql_instances(host, opts = {}) end def get_install_paths(version) - vers = { '2014' => '120', '2016' => '130', '2017' => '140', '2019' => '150', '2022' => '160' } + vers = { '2014' => '120', '2016' => '130', '2017' => '140', '2019' => '150', '2022' => '160', '2025' => '170' } raise _('Valid version must be specified') unless vers.key?(version) dir = "C://Program Files/Microsoft SQL Server/#{vers[version]}/Setup Bootstrap" sql_directory = case version - when '2022', '2017' + when '2025', '2022', '2017' "SQL#{version}" when '2019' "SQL#{version}CTP2.4" From b242826e63445d8d4d19826737b10222e771e994 Mon Sep 17 00:00:00 2001 From: Shubham Shinde Date: Mon, 16 Feb 2026 19:36:48 +0530 Subject: [PATCH 2/5] (MODULES-11613) Provision windows environment --- .github/workflows/ci.yml | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 961a3f53..b408e6dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -110,6 +110,7 @@ jobs: - name: Provision test environment run: | bundle exec rake "litmus:provision[${{matrix.platforms.provider}},${{ matrix.platforms.image }}]" + cat spec/fixtures/litmus_inventory.yaml - name: Install agent run: | @@ -146,14 +147,6 @@ jobs: - name: Set Environment Variable run: | pass=`grep -oP '(?<=password: ).*' spec/fixtures/litmus_inventory.yaml` - bundle exec bolt command run "[Environment]::SetEnvironmentVariable('pass', '$pass', 'Machine')" --targets all --inventoryfile spec/fixtures/litmus_inventory.yaml + bundle exec bolt command run "[Environment]::SetEnvironmentVariable('pass', '$pass', 'Machine')" --targets ssh_nodes --inventoryfile spec/fixtures/litmus_inventory.yaml + cat spec/fixtures/litmus_inventory.yaml - - name: Run acceptance tests - run: | - bundle exec rake 'litmus:acceptance:parallel' - - - name: Remove test environment - if: ${{ always() }} - continue-on-error: true - run: | - bundle exec rake 'litmus:tear_down' From 2e38bb13bd24bf1635ef6860ccd49b99568b1657 Mon Sep 17 00:00:00 2001 From: Shubham Shinde Date: Mon, 16 Feb 2026 23:07:37 +0530 Subject: [PATCH 3/5] Enable setup steps --- .github/workflows/ci.yml | 4 + metadata.json | 6 +- spec/acceptance/sqlserver_database_spec.rb | 303 --------------- spec/acceptance/sqlserver_instance_spec.rb | 206 ---------- spec/acceptance/sqlserver_login_spec.rb | 357 ------------------ spec/acceptance/sqlserver_role_spec.rb | 261 ------------- spec/acceptance/sqlserver_tsql_spec.rb | 231 ------------ spec/acceptance/sqlserver_user_spec.rb | 198 ---------- .../z_last_sqlserver_features_spec.rb | 218 ----------- 9 files changed, 5 insertions(+), 1779 deletions(-) delete mode 100644 spec/acceptance/sqlserver_database_spec.rb delete mode 100644 spec/acceptance/sqlserver_instance_spec.rb delete mode 100644 spec/acceptance/sqlserver_login_spec.rb delete mode 100644 spec/acceptance/sqlserver_role_spec.rb delete mode 100644 spec/acceptance/sqlserver_tsql_spec.rb delete mode 100644 spec/acceptance/sqlserver_user_spec.rb delete mode 100644 spec/acceptance/z_last_sqlserver_features_spec.rb diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b408e6dc..1cd5f53f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -150,3 +150,7 @@ jobs: bundle exec bolt command run "[Environment]::SetEnvironmentVariable('pass', '$pass', 'Machine')" --targets ssh_nodes --inventoryfile spec/fixtures/litmus_inventory.yaml cat spec/fixtures/litmus_inventory.yaml + - name: Run acceptance tests + run: | + bundle exec rake 'litmus:acceptance:parallel' + diff --git a/metadata.json b/metadata.json index f3451c39..f51f1b08 100644 --- a/metadata.json +++ b/metadata.json @@ -21,11 +21,7 @@ { "operatingsystem": "Windows", "operatingsystemrelease": [ - "2012", - "2012 R2", - "2016", - "2019", - "2022" + "2019" ] } ], diff --git a/spec/acceptance/sqlserver_database_spec.rb b/spec/acceptance/sqlserver_database_spec.rb deleted file mode 100644 index c579e8cf..00000000 --- a/spec/acceptance/sqlserver_database_spec.rb +++ /dev/null @@ -1,303 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper_acceptance' -require 'securerandom' - -describe 'Test sqlserver::database' do - # Return options for run_sql_query - def run_sql_query_opts(query, expected_row_count) - { - query:, - sql_admin_user: 'sa', - sql_admin_pass: 'Pupp3t1@', - expected_row_count: - } - end - - context 'Start testing...' do - before(:each) do - @db_name = "DB#{SecureRandom.hex(4)}".upcase - @table_name = "Tables_#{SecureRandom.hex(3)}" - end - - after(:each) do - # delete created database: - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => Sensitive('sa'), - admin_pass => Sensitive('Pupp3t1@'), - } - sqlserver::database{'#{@db_name}': - ensure => 'absent', - } - sqlserver::database{'#{@db_name}-2': - ensure => 'absent', - } - MANIFEST - apply_manifest(pp, catch_failures: true) - end - - it 'Test Case C89019: Create a database' do - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => Sensitive('sa'), - admin_pass => Sensitive('Pupp3t1@'), - } - sqlserver::database{'#{@db_name}': - } - sqlserver_tsql{'testsqlserver_tsql': - instance => 'MSSQLSERVER', - command => "use #{@db_name}; CREATE TABLE #{@table_name} (id INT, name VARCHAR(20), email VARCHAR(20));", - require => Sqlserver::Database['#{@db_name}'], - } - MANIFEST - apply_manifest(pp, catch_failures: true) - - puts "Validate the Database '#{@db_name}' and table '#{@table_name}' are successfully created:" - query = "USE #{@db_name}; SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE table_name = '#{@table_name}';" - run_sql_query(run_sql_query_opts(query, 1)) - end - - it 'Delete a database' do - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::database{'#{@db_name}': - } - MANIFEST - apply_manifest(pp, catch_failures: true) - - puts "Validate the Database '#{@db_name}' exists" - query = "SELECT database_id from sys.databases WHERE name = '#{@db_name}';" - run_sql_query(run_sql_query_opts(query, 1)) - - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::database{'#{@db_name}': - ensure => absent, - } - MANIFEST - apply_manifest(pp, catch_failures: true) - - puts "Validate the Database '#{@db_name}' does not exist" - query = "SELECT database_id from sys.databases WHERE name = '#{@db_name}';" - run_sql_query(run_sql_query_opts(query, 0)) - end - - it 'Test Case C89076: Create database with optional collation_name' do - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::database{'#{@db_name}': - collation_name => 'SQL_Estonian_CP1257_CS_AS', - } - sqlserver_tsql{'testsqlserver_tsql': - instance => 'MSSQLSERVER', - command => "use #{@db_name};CREATE TABLE #{@table_name} (id INT, name VARCHAR(20), email VARCHAR(20));", - require => Sqlserver::Database['#{@db_name}'], - } - MANIFEST - apply_manifest(pp, catch_failures: true) - - puts 'Validate that a table can be created in the database:' - query = "USE #{@db_name}; SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE table_name = '#{@table_name}';" - run_sql_query(run_sql_query_opts(query, 1)) - - puts "validate the Database '#{@db_name}' has correct collation name:" - query = "SELECT name AS Database_Name, collation_name - FROM sys.databases - WHERE name = '#{@db_name}' - AND collation_name = 'SQL_Estonian_CP1257_CS_AS';" - - run_sql_query(run_sql_query_opts(query, 1)) - end - - it 'Test Case C89077: Create database with optional compatibility' do - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::database{'#{@db_name}': - compatibility => 100, - } - sqlserver_tsql{'testsqlserver_tsql': - instance => 'MSSQLSERVER', - command => "use #{@db_name};CREATE TABLE #{@table_name} (id INT, name VARCHAR(20), email VARCHAR(20));", - require => Sqlserver::Database['#{@db_name}'], - } - MANIFEST - apply_manifest(pp, catch_failures: true) - - puts 'Validate that a table can be created in the database:' - query = "USE #{@db_name}; SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE table_name = '#{@table_name}';" - run_sql_query(run_sql_query_opts(query, 1)) - - puts "validate the Database '#{@db_name}' has correct compatibility level:" - query = "SELECT name AS Database_Name, compatibility_level - FROM sys.databases - WHERE name = '#{@db_name}' - AND compatibility_level = '100';" - - run_sql_query(run_sql_query_opts(query, 1)) - end - - it 'Test Case C89078: Create database with optional containment' do - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::sp_configure{ 'sp_config4db': - config_name => 'contained database authentication', - value => 1, - reconfigure => true, - instance => 'MSSQLSERVER', - } - sqlserver::database{ '#{@db_name}': - containment => 'PARTIAL', - require => Sqlserver::Sp_configure['sp_config4db'] - } - sqlserver_tsql{'testsqlserver_tsql': - instance => 'MSSQLSERVER', - command => "use #{@db_name};CREATE TABLE #{@table_name} (id INT, name VARCHAR(20), email VARCHAR(20));", - require => Sqlserver::Database['#{@db_name}'], - } - MANIFEST - apply_manifest(pp, catch_failures: true) - - puts 'Validate that a table can be created in the database:' - query = "USE #{@db_name}; SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE table_name = '#{@table_name}';" - run_sql_query(run_sql_query_opts(query, 1)) - - puts "validate the Database '#{@db_name}' has correct containment:" - query = "SELECT name AS Database_Name, containment_desc - FROM sys.databases - WHERE name = '#{@db_name}' - AND containment_desc = 'PARTIAL';" - - run_sql_query(run_sql_query_opts(query, 1)) - end - - it 'Test Case C89079: Create database with optional db_chaining' do - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::sp_configure{ 'sp_config4db': - config_name => 'contained database authentication', - value => 1, - reconfigure => true, - instance => 'MSSQLSERVER', - } - sqlserver::database{ '#{@db_name}': - containment => 'PARTIAL', - db_chaining => 'ON', - require => Sqlserver::Sp_configure['sp_config4db'] - } - sqlserver_tsql{'testsqlserver_tsql': - instance => 'MSSQLSERVER', - command => "use #{@db_name};CREATE TABLE #{@table_name} (id INT, name VARCHAR(20), email VARCHAR(20));", - require => Sqlserver::Database['#{@db_name}'], - } - MANIFEST - apply_manifest(pp, catch_failures: true) - - puts 'Validate that a table can be created in the database:' - query = "USE #{@db_name}; SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE table_name = '#{@table_name}';" - run_sql_query(run_sql_query_opts(query, 1)) - - puts "validate the Database '#{@db_name}' has correct db_chaing setting:" - query = "SELECT name AS Database_Name, is_db_chaining_on - FROM sys.databases - WHERE name = '#{@db_name}' - AND is_db_chaining_on = '1';" - - run_sql_query(run_sql_query_opts(query, 1)) - end - - it 'Test Case C89080: Create database with optional default_fulltext_language', tier_low: true do - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::sp_configure{ 'sp_config4db': - config_name => 'contained database authentication', - value => 1, - reconfigure => true, - instance => 'MSSQLSERVER', - } - sqlserver::database{ '#{@db_name}': - containment => 'PARTIAL', - default_fulltext_language => 'Japanese', - require => Sqlserver::Sp_configure['sp_config4db'] - } - sqlserver_tsql{'testsqlserver_tsql': - instance => 'MSSQLSERVER', - command => "use #{@db_name};CREATE TABLE #{@table_name} (id INT, name VARCHAR(20), email VARCHAR(20));", - require => Sqlserver::Database['#{@db_name}'], - } - MANIFEST - apply_manifest(pp, catch_failures: true) - - puts 'Validate that a table can be created in the database:' - query = "USE #{@db_name}; SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE table_name = '#{@table_name}';" - run_sql_query(run_sql_query_opts(query, 1)) - - puts "validate the Database '#{@db_name}' has correct default_fulltext_language_name setting:" - query = "SELECT name AS Database_Name, default_fulltext_language_name - FROM sys.databases - WHERE name = '#{@db_name}' - AND default_fulltext_language_name = 'Japanese';" - - run_sql_query(run_sql_query_opts(query, 1)) - end - - it 'Test Case C89081: Create database with optional default_language' do - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::sp_configure{ 'sp_config4db': - config_name => 'contained database authentication', - value => 1, - reconfigure => true, - instance => 'MSSQLSERVER', - } - sqlserver::database{ '#{@db_name}': - containment => 'PARTIAL', - default_language => 'Traditional Chinese', - require => Sqlserver::Sp_configure['sp_config4db'] - } - sqlserver_tsql{'testsqlserver_tsql': - instance => 'MSSQLSERVER', - command => "use #{@db_name};CREATE TABLE #{@table_name} (id INT, name VARCHAR(20), email VARCHAR(20));", - require => Sqlserver::Database['#{@db_name}'], - } - MANIFEST - apply_manifest(pp, catch_failures: true) - - puts 'Validate that a table can be created in the database:' - query = "USE #{@db_name}; SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE table_name = '#{@table_name}';" - run_sql_query(run_sql_query_opts(query, 1)) - - puts "validate the Database '#{@db_name}' has correct default_language setting:" - query = "SELECT name AS Database_Name, default_language_name - FROM sys.databases - WHERE name = '#{@db_name}' - AND default_language_lcid = '1028';" - - run_sql_query(run_sql_query_opts(query, 1)) - end - end -end diff --git a/spec/acceptance/sqlserver_instance_spec.rb b/spec/acceptance/sqlserver_instance_spec.rb deleted file mode 100644 index 98d1ec84..00000000 --- a/spec/acceptance/sqlserver_instance_spec.rb +++ /dev/null @@ -1,206 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper_acceptance' -require 'securerandom' -require 'erb' - -def new_random_instance_name - "MSSQL#{SecureRandom.hex(4)}".upcase.to_s -end - -describe 'sqlserver_instance' do - version = sql_version? - - def ensure_sqlserver_instance(features, inst_name, ensure_val = 'present', sysadmin_accounts = []) - user = Helper.instance.run_shell('$env:UserName').stdout.chomp - sysadmin_accounts << user - # If no password env variable set (by CI), then default to vagrant - password = Helper.instance.run_shell('$env:pass').stdout.chomp - password = 'vagrant' if password.empty? - - pp = <<-MANIFEST - sqlserver_instance{'#{inst_name}': - name => '#{inst_name}', - ensure => #{ensure_val}, - source => 'H:', - security_mode => 'SQL', - sa_pwd => 'Pupp3t1@', - features => #{features}, - sql_sysadmin_accounts => #{sysadmin_accounts}, - agt_svc_account => '#{user}', - agt_svc_password => '#{password}', - windows_feature_source => 'I:\\sources\\sxs', - install_switches => { - 'UpdateEnabled' => 'false', - }, - } - MANIFEST - idempotent_apply(pp) - end - - # Return options for run_sql_query - def run_sql_query_opts(inst_name, query, expected_row_count) - { - query:, - instance: inst_name, - server: '.', - sql_admin_user: 'sa', - sql_admin_pass: 'Pupp3t1@', - expected_row_count: - } - end - - def sql_query_is_user_sysadmin(username) - <<-QUERY - Select [Name] - FROM SYS.Server_Principals - WHERE (type = 'S' or type = 'U') - AND [Name] like '%\\#{username}' - AND is_disabled = 0 AND IS_SRVROLEMEMBER('sysadmin', name) = 1; - QUERY - end - - context 'Create an instance' do - before(:context) do - # Use a username with a space to test argument parsing works correctly - @extra_admin_user = 'ExtraSQLAdmin' - @user = Helper.instance.run_shell('$env:UserName').stdout.chomp - pp = <<-MANIFEST - user { '#{@extra_admin_user}': - ensure => present, - password => 'Puppet01!', - } - MANIFEST - apply_manifest(pp, catch_failures: true) - end - - after(:context) do - pp = <<-MANIFEST - user { '#{@extra_admin_user}': - ensure => absent, - } - MANIFEST - apply_manifest(pp, catch_failures: true) - end - - inst_name = new_random_instance_name - features = ['SQLEngine', 'Replication', 'FullText', 'DQ'] - - it "create #{inst_name} instance" do - host_computer_name = run_shell('CMD /C ECHO %COMPUTERNAME%').stdout.chomp - ensure_sqlserver_instance(features, inst_name, 'present', ["#{host_computer_name}\\#{@extra_admin_user}"]) - - validate_sql_install(version:) do |r| - expect(r.stdout).to match(%r{#{Regexp.new(inst_name)}}) - end - end - - it "#{inst_name} instance has logged in user as an Administrator" do - run_sql_query(run_sql_query_opts(inst_name, sql_query_is_user_sysadmin(@user), 1)) - end - - it "#{inst_name} instance has ExtraSQLAdmin as a sysadmin" do - run_sql_query(run_sql_query_opts(inst_name, sql_query_is_user_sysadmin(@extra_admin_user), 1)) - end - - it "remove #{inst_name} instance" do - ensure_sqlserver_instance(features, inst_name, 'absent') - - # Ensure all features for this instance are removed and the defaults are left alone - validate_sql_install(version:) do |r| - expect(r.stdout).to match(%r{MSSQLSERVER\s+Database Engine Services}) - expect(r.stdout).to match(%r{MSSQLSERVER\s+SQL Server Replication}) - expect(r.stdout).to match(%r{MSSQLSERVER\s+Data Quality Services}) - expect(r.stdout).not_to match(%r{#{inst_name}\s+Database Engine Services}) - expect(r.stdout).not_to match(%r{#{inst_name}\s+SQL Server Replication}) - expect(r.stdout).not_to match(%r{#{inst_name}\s+Data Quality Services}) - end - end - end - - context "Feature has only one 'RS'" do - inst_name = new_random_instance_name - features = ['RS'] - - after(:all) do - ensure_sqlserver_instance(features, inst_name, 'absent') - end - - it "create #{inst_name} instance with only one RS feature", unless: version.to_i >= 2017 do - ensure_sqlserver_instance(features, inst_name) - - validate_sql_install(version:) do |r| - expect(r.stdout).to match(%r{#{inst_name}\s+Reporting Services}) - end - end - end - - # Ensure that the instance can be created with deferred values - # for service account and password, which are resolved at the time of - # the instance creation. - # This is useful for scenarios where the values are not known at the time - # of the Puppet run, such as when using Hiera to fetch values from a - # secure vault or when the values are dynamically generated. - def ensure_sqlserver_instance_with_deferred_values(inst_name) - features = ['SQLEngine', 'Replication', 'FullText', 'DQ'] - host_computer_name = run_shell('CMD /C ECHO %COMPUTERNAME%').stdout.chomp - sysadmin_accounts = ["#{host_computer_name}\\travis"] - user = Helper.instance.run_shell('$env:UserName').stdout.chomp - password = Helper.instance.run_shell('$env:pass').stdout.chomp - password = 'Hunter-2' if password.empty? - - pp = <<-MANIFEST - sqlserver_instance{'#{inst_name}': - name => '#{inst_name}', - ensure => 'present', - source => 'H:', - security_mode => 'SQL', - sa_pwd => 'Pupp3t1@', - features => #{features}, - sql_sysadmin_accounts => #{sysadmin_accounts}, - agt_svc_account => Deferred('pick', ['#{user}']), - agt_svc_password => Deferred('pick', ['#{password}']), - windows_feature_source => 'I:\\sources\\sxs', - install_switches => { - 'UpdateEnabled' => 'false', - }, - } - MANIFEST - - idempotent_apply(pp) - end - - context 'Deferred values' do - before(:context) do - @deferred_user = 'travis' - pp = <<-MANIFEST - user { '#{@deferred_user}': - ensure => present, - password => 'Puppet01!', - } - MANIFEST - apply_manifest(pp, catch_failures: true) - end - - after(:context) do - pp = <<-MANIFEST - user { '#{@deferred_user}': - ensure => absent, - } - MANIFEST - apply_manifest(pp, catch_failures: true) - end - - it 'validate deferred values' do - inst_name = new_random_instance_name - expect { ensure_sqlserver_instance_with_deferred_values(inst_name) }.not_to raise_error - end - - it 'apply deferred values' do - inst_name = new_random_instance_name - ensure_sqlserver_instance_with_deferred_values(inst_name) - - run_sql_query(run_sql_query_opts(inst_name, sql_query_is_user_sysadmin(@deferred_user), 1)) - end - end -end diff --git a/spec/acceptance/sqlserver_login_spec.rb b/spec/acceptance/sqlserver_login_spec.rb deleted file mode 100644 index 5cf98146..00000000 --- a/spec/acceptance/sqlserver_login_spec.rb +++ /dev/null @@ -1,357 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper_acceptance' -require 'securerandom' - -db_name = "DB#{SecureRandom.hex(4)}".upcase -table_name = "Tables_#{SecureRandom.hex(3)}" - -# Covers testrail => ['89118', '89119', '89120', '89121', '89122', '89123', '89124', '89125', '89540'] -describe 'Test sqlserver::login' do - # Return options for run_sql_query - def run_sql_query_opts(user, passwd, query, expected_row_count) - { - query:, - sql_admin_user: user, - sql_admin_pass: passwd, - expected_row_count: - } - end - - def run_sql_query_opts_as_sa(query, expected_row_count) - run_sql_query_opts('sa', 'Pupp3t1@', query, expected_row_count) - end - - def remove_test_logins(_host) - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::login{'#{@login_user}': - instance => 'MSSQLSERVER', - ensure => 'absent', - } - sqlserver::login{'#{@login_windows_user}': - instance => 'MSSQLSERVER', - ensure => 'absent', - } - sqlserver::login{'#{@login_windows_group}': - instance => 'MSSQLSERVER', - ensure => 'absent', - } - MANIFEST - apply_manifest(pp, catch_failures: true) - end - - def create_login_manifest(testcase, login_name, login_password, options = {}) - case testcase - when 'SQL_LOGIN user' - login_type = 'SQL_LOGIN' - when 'WINDOWS_LOGIN user', 'WINDOWS_LOGIN group' - login_type = 'WINDOWS_LOGIN' - else - raise "Unknown testcase name #{testcase}" - end - - pp = <<-MANIFEST # rubocop:disable Lint/UselessAssignment - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::login{'#{login_name}': - instance => 'MSSQLSERVER', - login_type => '#{login_type}', - #{"password => '#{login_password}'," if testcase == 'SQL_LOGIN user'} - #{options['svrroles'].nil? ? "svrroles => {'sysadmin' => 1}," : "svrroles => #{options['svrroles']},"} - #{"check_expiration => #{options['check_expiration']}," unless options['check_expiration'].nil?} - #{"check_policy => #{options['check_policy']}," unless options['check_policy'].nil?} - #{"default_database => '#{options['default_database']}'," unless options['default_database'].nil?} - #{"default_language => '#{options['default_language']}'," unless options['default_language'].nil?} - #{"disabled => #{options['disabled']}," unless options['disabled'].nil?} - #{"ensure => '#{options['ensure']}'," unless options['ensure'].nil?} - } - MANIFEST - end - - before(:all) do - @login_user = "Login#{SecureRandom.hex(4)}" - @login_passwd = "Password1!#{SecureRandom.hex(5)}" - @windows_user = "User#{SecureRandom.hex(4)}" - @windows_group = "Group#{SecureRandom.hex(4)}" - - host_shortname = Helper.instance.run_shell('$env:computername').stdout.upcase.strip # Require the NETBIOS name for later database searches - @login_windows_user = "#{host_shortname}\\#{@windows_user}" - @login_windows_group = "#{host_shortname}\\#{@windows_group}" - - # Create a database, a simple table and windows accounts fixtures - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => Sensitive('sa'), - admin_pass => Sensitive('Pupp3t1@'), - } - sqlserver::database{'#{db_name}': - } - sqlserver_tsql{'testsqlserver_tsql': - instance => 'MSSQLSERVER', - database => '#{db_name}', - command => "CREATE TABLE #{table_name} (id INT, name VARCHAR(20), email VARCHAR(20));", - require => Sqlserver::Database['#{db_name}'], - } - - user {'#{@windows_user}': - password => Sensitive('#{@login_passwd}'), - ensure => 'present', - } - group {'#{@windows_group}': - ensure => 'present', - } - MANIFEST - apply_manifest(pp, catch_failures: true) - end - - # Delete all test fixtures - after(:all) do - remove_test_logins(host) - - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::database{'#{db_name}': - instance => 'MSSQLSERVER', - ensure => 'absent', - } - user {'#{@windows_user}': - ensure => 'absent', - } - group {'#{@windows_group}': - ensure => 'absent', - } - MANIFEST - apply_manifest(pp, catch_failures: true) - end - - ['SQL_LOGIN user', 'WINDOWS_LOGIN user', 'WINDOWS_LOGIN group'].each do |testcase| - context "#{testcase} tests" do - before(:all) do - case testcase - when 'SQL_LOGIN user' - @login_under_test = @login_user - @sql_principal_type = 'S' - when 'WINDOWS_LOGIN user' - @login_under_test = @login_windows_user - @sql_principal_type = 'U' - when 'WINDOWS_LOGIN group' - @login_under_test = @login_windows_group - @sql_principal_type = 'G' - else - raise "Unknown testcase name #{testcase}" - end - end - - describe "Create deafult #{testcase} login" do - before(:all) { remove_test_logins(host) } - - it "can create a default #{testcase} idempotently" do - pp = create_login_manifest(testcase, @login_under_test, @login_passwd) - idempotent_apply(pp) - end - - it 'exists in the principals table' do - query = "SELECT principal_id FROM SYS.server_principals WHERE name = '#{@login_under_test}' AND [type] = '#{@sql_principal_type}'" - run_sql_query(run_sql_query_opts_as_sa(query, 1)) - end - - if testcase == 'SQL_LOGIN user' - it 'can login to SQL Server' do - puts "Validate the login '#{@login_under_test}' is successfully created and able to access database '#{db_name}':" - query = "USE #{db_name}; SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE table_name = '#{table_name}';" - run_sql_query(run_sql_query_opts(@login_under_test, @login_passwd, query, 1)) - end - end - end - - describe "Create #{testcase} login with 'check_expiration','check_policy'", if: testcase == 'SQL_LOGIN user' do - # check_expiration and check_policy are only applicable to SQL based logins - - before(:all) { remove_test_logins(host) } - - it "can create an #{testcase} with 'check_expiration','check_policy' set" do - options = { 'check_expiration' => true, 'check_policy' => true } - pp = create_login_manifest(testcase, @login_under_test, @login_passwd, options) - apply_manifest(pp, catch_failures: true) - end - - it 'has is_expiration_checked set' do - query = "SELECT name as LOGIN_NAME, is_expiration_checked FROM SYS.SQL_LOGINS WHERE is_expiration_checked = '1' AND name = '#{@login_under_test}';" - run_sql_query(run_sql_query_opts_as_sa(query, 1)) - end - - it 'has is_policy_checked set' do - query = "SELECT name as LOGIN_NAME, is_policy_checked FROM SYS.SQL_LOGINS WHERE is_policy_checked = '1' AND name = '#{@login_under_test}';" - run_sql_query(run_sql_query_opts_as_sa(query, 1)) - end - end - - describe "Create #{testcase} login with 'default_database','default_language'" do - before(:all) { remove_test_logins(host) } - - it "can create a #{testcase} with 'default_database','default_language'" do - options = { 'default_database' => db_name.to_s, 'default_language' => 'Spanish' } - pp = create_login_manifest(testcase, @login_under_test, @login_passwd, options) - apply_manifest(pp, catch_failures: true) - end - - it 'exists in the principals table' do - query = "SELECT principal_id FROM SYS.server_principals WHERE name = '#{@login_under_test}' AND [type] = '#{@sql_principal_type}'" - run_sql_query(run_sql_query_opts_as_sa(query, 1)) - end - - it 'has the specified default database' do - query = "SELECT principal_id FROM SYS.server_principals WHERE name = '#{@login_under_test}' AND [type] = '#{@sql_principal_type}' AND default_database_name = '#{db_name}'" - run_sql_query(run_sql_query_opts_as_sa(query, 1)) - end - - it 'has the specified default langauge' do - query = "SELECT principal_id FROM SYS.server_principals WHERE name = '#{@login_under_test}' AND [type] = '#{@sql_principal_type}' AND default_language_name = 'Spanish'" - run_sql_query(run_sql_query_opts_as_sa(query, 1)) - end - end - - describe "Create #{testcase} login with 'disabled'" do - before(:all) { remove_test_logins(host) } - - it "can create #{testcase} with optional 'disabled'" do - options = { 'disabled' => true } - pp = create_login_manifest(testcase, @login_under_test, @login_passwd, options) - apply_manifest(pp, catch_failures: true) - end - - if testcase == 'WINDOWS_LOGIN group' - it 'has DENY CONNECT SQL set' do - query = "SELECT sp.[state] FROM sys.server_principals p - INNER JOIN sys.server_permissions sp ON p.principal_id = sp.grantee_principal_id - WHERE sp.permission_name = 'CONNECT SQL' AND sp.class = 100 AND sp.state = 'D' AND p.name = '#{@login_under_test}' AND p.[type] = '#{@sql_principal_type}'" - run_sql_query(run_sql_query_opts_as_sa(query, 1)) - end - else - it 'has is_disabled set' do - query = "SELECT principal_id FROM SYS.server_principals WHERE name = '#{@login_under_test}' AND [type] = '#{@sql_principal_type}' AND is_disabled = '1';" - run_sql_query(run_sql_query_opts_as_sa(query, 1)) - end - end - end - - describe "Modify a #{testcase} login" do - before(:all) { remove_test_logins(host) } - - it "creates an initial #{testcase}" do - options = { 'svrroles' => '{\'sysadmin\' => 1}' } - pp = create_login_manifest(testcase, @login_under_test, @login_passwd, options) - apply_manifest(pp, catch_failures: true) - end - - it 'exists in the principals table on creation' do - query = "SELECT principal_id FROM SYS.server_principals WHERE name = '#{@login_under_test}' AND [type] = '#{@sql_principal_type}'" - run_sql_query(run_sql_query_opts_as_sa(query, 1)) - end - - it "modifies a #{testcase} login" do - options = { 'disabled' => true, - 'default_database' => db_name.to_s, - 'default_language' => 'Spanish', - 'check_expiration' => true, - 'check_policy' => true, - 'svrroles' => '{\'sysadmin\' => 1, \'serveradmin\' => 1}' } - pp = create_login_manifest(testcase, @login_under_test, @login_passwd, options) - apply_manifest(pp, catch_failures: true) - end - - if testcase == 'SQL_LOGIN user' - it 'has is_expiration_checked set' do - query = "SELECT name as LOGIN_NAME, is_expiration_checked FROM SYS.SQL_LOGINS WHERE is_expiration_checked = '1' AND name = '#{@login_under_test}';" - run_sql_query(run_sql_query_opts_as_sa(query, 1)) - end - - it 'has is_policy_checked set' do - query = "SELECT name as LOGIN_NAME, is_policy_checked FROM SYS.SQL_LOGINS WHERE is_policy_checked = '1' AND name = '#{@login_under_test}';" - run_sql_query(run_sql_query_opts_as_sa(query, 1)) - end - end - - it 'has the specified sysadmin role' do - # Note - IS_SRVROLEMEMBER always returns false for a disabled WINDOWS_LOGIN user login - query = "SELECT pri.name from sys.server_role_members member - JOIN sys.server_principals rol ON member.role_principal_id = rol.principal_id - JOIN sys.server_principals pri ON member.member_principal_id = pri.principal_id - WHERE rol.type_desc = 'SERVER_ROLE' - AND rol.name = 'sysadmin' - AND pri.name = '#{@login_under_test}'" - run_sql_query(run_sql_query_opts_as_sa(query, 1)) - end - - it 'has the specified serveradmin role' do - # Note - IS_SRVROLEMEMBER always returns false for a disabled WINDOWS_LOGIN user login - query = "SELECT pri.name from sys.server_role_members member - JOIN sys.server_principals rol ON member.role_principal_id = rol.principal_id - JOIN sys.server_principals pri ON member.member_principal_id = pri.principal_id - WHERE rol.type_desc = 'SERVER_ROLE' - AND rol.name = 'serveradmin' - AND pri.name = '#{@login_under_test}'" - run_sql_query(run_sql_query_opts_as_sa(query, 1)) - end - - it 'has the specified default database' do - query = "SELECT principal_id FROM SYS.server_principals WHERE name = '#{@login_under_test}' AND [type] = '#{@sql_principal_type}' AND default_database_name = '#{db_name}'" - run_sql_query(run_sql_query_opts_as_sa(query, 1)) - end - - it 'has the specified default langauge' do - query = "SELECT principal_id FROM SYS.server_principals WHERE name = '#{@login_under_test}' AND [type] = '#{@sql_principal_type}' AND default_language_name = 'Spanish'" - run_sql_query(run_sql_query_opts_as_sa(query, 1)) - end - - if testcase == 'WINDOWS_LOGIN group' - it 'has DENY CONNECT SQL set' do - query = "SELECT sp.[state] FROM sys.server_principals p - INNER JOIN sys.server_permissions sp ON p.principal_id = sp.grantee_principal_id - WHERE sp.permission_name = 'CONNECT SQL' AND sp.class = 100 AND sp.state = 'D' AND p.name = '#{@login_under_test}' AND p.[type] = '#{@sql_principal_type}'" - run_sql_query(run_sql_query_opts_as_sa(query, 1)) - end - else - it 'has is_disabled set' do - query = "SELECT principal_id FROM SYS.server_principals WHERE name = '#{@login_under_test}' AND [type] = '#{@sql_principal_type}' AND is_disabled = '1';" - run_sql_query(run_sql_query_opts_as_sa(query, 1)) - end - end - end - - describe "Delete #{testcase} login" do - before(:all) { remove_test_logins(host) } - - it "creates an initial #{testcase}" do - pp = create_login_manifest(testcase, @login_under_test, @login_passwd) - apply_manifest(pp, catch_failures: true) - end - - it 'exists in the principals table on creation' do - query = "SELECT principal_id FROM SYS.server_principals WHERE name = '#{@login_under_test}' AND [type] = '#{@sql_principal_type}'" - run_sql_query(run_sql_query_opts_as_sa(query, 1)) - end - - it "removes a #{testcase} on ensure => absent idempotently" do - options = { 'ensure' => 'absent' } - pp = create_login_manifest(testcase, @login_under_test, @login_passwd, options) - idempotent_apply(pp) - end - - it 'does not exist in the principals table after deletion' do - query = "SELECT principal_id FROM SYS.server_principals WHERE name = '#{@login_under_test}' AND [type] = '#{@sql_principal_type}'" - run_sql_query(run_sql_query_opts_as_sa(query, 0)) - end - end - end - end -end diff --git a/spec/acceptance/sqlserver_role_spec.rb b/spec/acceptance/sqlserver_role_spec.rb deleted file mode 100644 index c4869140..00000000 --- a/spec/acceptance/sqlserver_role_spec.rb +++ /dev/null @@ -1,261 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper_acceptance' -require 'securerandom' -require 'erb' - -LOGIN1 = "Login1_#{SecureRandom.hex(2)}".freeze -LOGIN2 = "Login2_#{SecureRandom.hex(2)}".freeze -LOGIN3 = "Login3_#{SecureRandom.hex(2)}".freeze -USER1 = "User1_#{SecureRandom.hex(2)}".freeze - -describe 'Test sqlserver::role' do - def ensure_sqlserver_logins_users(db_name) - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::database{ '#{db_name}': - } - sqlserver::login{'#{LOGIN1}': - login_type => 'SQL_LOGIN', - password => 'Pupp3t1@', - } - sqlserver::login{'#{LOGIN2}': - login_type => 'SQL_LOGIN', - password => 'Pupp3t1@', - } - sqlserver::login{'#{LOGIN3}': - login_type => 'SQL_LOGIN', - password => 'Pupp3t1@', - } - sqlserver::user{'#{USER1}': - database => '#{db_name}', - user => '#{USER1}', - login => '#{LOGIN1}', - default_schema => 'guest', - require => Sqlserver::Login['#{LOGIN1}'], - } - MANIFEST - apply_manifest(pp, catch_failures: true) - end - - context 'Start testing sqlserver::role' do - before(:all) do - # Initialize hostname and db_name once per context - @hostname = Helper.instance.run_shell('hostname').stdout.upcase.strip - @db_name = "DB#{SecureRandom.hex(4)}".upcase - # Create database users - ensure_sqlserver_logins_users(@db_name) - end - - before(:each) do - @role = "Role_#{SecureRandom.hex(2)}" - end - - after(:each) do - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::role{'#{@role}': - ensure => 'absent', - } - MANIFEST - apply_manifest(pp, catch_failures: true) - end - - after(:all) do - # remove all newly created logins - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::user{'#{USER1}': - database => '#{@db_name}', - ensure => 'absent', - } - MANIFEST - apply_manifest(pp, catch_failures: true) - end - - it "Create server role #{@role} with optional authorization" do - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::role{'ServerRole': - ensure => 'present', - authorization => '#{LOGIN1}', - role => '#{@role}', - permissions => {'GRANT' => ['CREATE ENDPOINT', 'CREATE ANY DATABASE']}, - type => 'SERVER', - } - MANIFEST - apply_manifest(pp, catch_failures: true) - - # validate that the database-specific role '#{@role}' is successfully created with specified permissions': - query = "USE #{@db_name}; - SELECT spr.principal_id, spr.name, - spe.state_desc, spe.permission_name - FROM sys.server_principals AS spr - JOIN sys.server_permissions AS spe - ON spe.grantee_principal_id = spr.principal_id - WHERE spr.name = '#{@role}';" - - run_sql_query(query:, server: @hostname, expected_row_count: 2) - - # validate that the database-specific role '#{@role}' has correct authorization #{LOGIN1} - query = "USE #{@db_name}; - SELECT spr.name, sl.name - FROM sys.server_principals AS spr - JOIN sys.sql_logins AS sl - ON spr.owning_principal_id = sl.principal_id - WHERE sl.name = '#{LOGIN1}';" - - run_sql_query(query:, server: @hostname, expected_row_count: 1) - end - - it "Create database-specific role #{@role}" do - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::role{'DatabaseRole': - ensure => 'present', - role => '#{@role}', - database => '#{@db_name}', - permissions => {'GRANT' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'CONTROL', 'ALTER']}, - type => 'DATABASE', - } - MANIFEST - apply_manifest(pp, catch_failures: true) - - # validate that the database-specific role '#{@role}' is successfully created with specified permissions': - query = "USE #{@db_name}; - SELECT pr.principal_id, pr.name, pr.type_desc, - pr.authentication_type_desc, pe.state_desc, pe.permission_name - FROM sys.database_principals AS pr - JOIN sys.database_permissions AS pe - ON pe.grantee_principal_id = pr.principal_id - WHERE pr.name = '#{@role}';" - - run_sql_query(query:, server: @hostname, expected_row_count: 6) - end - - it 'Create a database-specific role with the same name on two databases' do - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::role{'DatabaseRole_1': - ensure => 'present', - role => '#{@role}', - database => '#{@db_name}', - permissions => {'GRANT' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'CONTROL', 'ALTER']}, - type => 'DATABASE', - } - sqlserver::role{'DatabaseRole_2': - ensure => 'present', - role => '#{@role}', - database => 'master', - permissions => {'GRANT' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'CONTROL', 'ALTER']}, - type => 'DATABASE', - } - MANIFEST - apply_manifest(pp, catch_failures: true) - - # validate that the database-specific role '#{@role}' is successfully created with specified permissions': - # and that it exists in both the MASTER database and the 'db_name' database. - query = "USE MASTER; - SELECT pr.principal_id, pr.name, pr.type_desc, - pr.authentication_type_desc, pe.state_desc, pe.permission_name, dbpr.name - FROM sys.database_principals AS pr - JOIN sys.database_permissions AS pe - ON pe.grantee_principal_id = pr.principal_id - JOIN #{@db_name}.sys.database_principals as dbpr - on pr.name = dbpr.name - WHERE pr.name = '#{@role}';" - - run_sql_query(query:, server: @hostname, expected_row_count: 6) - end - - it "Create server role #{@role} with optional members and optional members-purge" do - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::role{'ServerRole': - instance => 'MSSQLSERVER', - ensure => 'present', - role => '#{@role}', - permissions => {'GRANT' => ['CREATE ENDPOINT', 'CREATE ANY DATABASE']}, - type => 'SERVER', - members => ['#{LOGIN1}', '#{LOGIN2}', '#{LOGIN3}'], - } - MANIFEST - apply_manifest(pp, catch_failures: true) - - # validate that the server role '#{@role}' is successfully created with specified permissions': - query = "USE #{@db_name}; - SELECT spr.principal_id AS ID, spr.name AS Server_Role, - spe.state_desc, spe.permission_name - FROM sys.server_principals AS spr - JOIN sys.server_permissions AS spe - ON spe.grantee_principal_id = spr.principal_id - WHERE spr.name = '#{@role}';" - - run_sql_query(query:, server: @hostname, expected_row_count: 2) - - # validate that the t server role '#{@role}' has correct members (Login1, 2, 3) - query = "USE #{@db_name}; - SELECT spr.principal_id AS ID, spr.name AS ServerRole - FROM sys.server_principals AS spr - JOIN sys.server_role_members m - ON spr.principal_id = m.member_principal_id - WHERE spr.name = '#{LOGIN1}' - OR spr.name = '#{LOGIN2}' - OR spr.name = '#{LOGIN3}' - OR spr.name = 'LOGIN4';" - - run_sql_query(query:, server: @hostname, expected_row_count: 3) - - puts "Create server role #{@role} with optional members_purge:" - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::role{'ServerRole': - instance => 'MSSQLSERVER', - ensure => 'present', - role => '#{@role}', - permissions => {'GRANT' => ['CREATE ENDPOINT', 'CREATE ANY DATABASE']}, - type => 'SERVER', - members => ['#{LOGIN3}'], - members_purge => true, - } - MANIFEST - apply_manifest(pp, catch_failures: true) - - # validate that the t server role '#{@role}' has correct members (only Login3) - query = "USE #{@db_name}; - SELECT spr.principal_id AS ID, spr.name AS ServerRole - FROM sys.server_principals AS spr - JOIN sys.server_role_members m - ON spr.principal_id = m.member_principal_id - WHERE spr.name = '#{LOGIN1}' - OR spr.name = '#{LOGIN2}' - OR spr.name = '#{LOGIN3}';" - - run_sql_query(query:, server: @hostname, expected_row_count: 1) - end - end -end diff --git a/spec/acceptance/sqlserver_tsql_spec.rb b/spec/acceptance/sqlserver_tsql_spec.rb deleted file mode 100644 index bd827004..00000000 --- a/spec/acceptance/sqlserver_tsql_spec.rb +++ /dev/null @@ -1,231 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper_acceptance' -require 'securerandom' -require 'erb' - -version = sql_version? - -# database name -db_name = "DB#{SecureRandom.hex(4)}".upcase - -# database user: -DB_LOGIN_USER = "loginuser#{SecureRandom.hex(2)}".freeze - -describe 'sqlserver_tsql test' do - def ensure_sqlserver_database(db_name, _ensure_val = 'present') - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::database{'#{db_name}': - instance => 'MSSQLSERVER', - } - MANIFEST - apply_manifest(pp, catch_failures: true) - end - - context 'Test sqlserver_tsql with Windows based authentication' do - before(:all) do - # Create new database - @table_name = "Tables_#{SecureRandom.hex(3)}" - @query = "USE #{db_name}; SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE table_name = '#{@table_name}';" - - ensure_sqlserver_database(db_name) - end - - after(:all) do - # remove the newly created instance - ensure_sqlserver_database('absent') - end - - it 'Run a simple tsql command via sqlserver_tsql:' do - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - instance_name => 'MSSQLSERVER', - admin_login_type => 'WINDOWS_LOGIN', - } - sqlserver_tsql{'testsqlserver_tsql': - instance => 'MSSQLSERVER', - database => '#{db_name}', - command => "CREATE TABLE #{@table_name} (id INT, name VARCHAR(20), email VARCHAR(20));", - } - MANIFEST - apply_manifest(pp, catch_failures: true) - - puts "validate the result of tsql command and table #{@table_name} should be created:" - run_sql_query_opts = { - query: @query, - sql_admin_user: @admin_user, - sql_admin_pass: @admin_pass, - expected_row_count: 1 - } - run_sql_query(run_sql_query_opts) - end - end - - context 'Test sqlserver_tsql with default SQL Server based authentication' do - before(:all) do - # Create new database - @table_name = "Tables_#{SecureRandom.hex(3)}" - @query = "USE #{db_name}; SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE table_name = '#{@table_name}';" - - ensure_sqlserver_database(db_name) - end - - after(:all) do - # remove the newly created instance - ensure_sqlserver_database('absent') - end - - it 'Run a simple tsql command via sqlserver_tsql:' do - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - instance_name => 'MSSQLSERVER', - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver_tsql{'testsqlserver_tsql': - instance => 'MSSQLSERVER', - database => '#{db_name}', - command => "CREATE TABLE #{@table_name} (id INT, name VARCHAR(20), email VARCHAR(20));", - } - MANIFEST - apply_manifest(pp, catch_failures: true) - - puts "validate the result of tsql command and table #{@table_name} should be created:" - run_sql_query_opts = { - query: @query, - sql_admin_user: @admin_user, - sql_admin_pass: @admin_pass, - expected_row_count: 1 - } - run_sql_query(run_sql_query_opts) - end - - it 'Run sqlserver_tsql WITH onlyif is true:', if: version.to_i != 2016 do - # Timeout issues with command run on Sql Server 2016. Functionality of test covered by test below. - # Initilize a new table name: - @table_name = "Table_#{SecureRandom.hex(3)}" - @query = "USE #{db_name}; SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE table_name = '#{@table_name}';" - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - instance_name => 'MSSQLSERVER', - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver_tsql{'testsqlserver_tsql': - instance => 'MSSQLSERVER', - database => '#{db_name}', - command => "CREATE TABLE #{@table_name} (id INT, name VARCHAR(20), email VARCHAR(20));", - onlyif => "IF (SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES) < 10000" - } - MANIFEST - apply_manifest(pp, catch_failures: true) - - puts "Validate #{@table_name} is successfully created:" - run_sql_query_opts = { - query: @query, - sql_admin_user: @admin_user, - sql_admin_pass: @admin_pass, - expected_row_count: 1 - } - run_sql_query(run_sql_query_opts) - end - - it 'Run sqlserver_tsql WITH onlyif is false:' do - # Initilize a new table name: - @table_name = "Table_#{SecureRandom.hex(3)}" - @query = "USE #{db_name}; SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE table_name = '#{@table_name}';" - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - instance_name => 'MSSQLSERVER', - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver_tsql{'testsqlserver_tsql': - instance => 'MSSQLSERVER', - database => '#{db_name}', - command => "CREATE TABLE #{@table_name} (id INT, name VARCHAR(20), email VARCHAR(20));", - onlyif => "IF (SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES) > 10000 - THROW 5300, 'Too many tables', 10" - } - MANIFEST - apply_manifest(pp, catch_failures: true) - - puts "Validate #{@table_name} is NOT created:" - run_sql_query_opts = { - query: @query, - sql_admin_user: @admin_user, - sql_admin_pass: @admin_pass, - expected_row_count: 0 - } - run_sql_query(run_sql_query_opts) - end - - it 'Run sqlserver_tsql WITH onlyif that does a table insert:' do - # Initilize a new table name: - @table_name = "Table_#{SecureRandom.hex(3)}" - @query = "USE #{db_name}; SELECT * FROM #{@table_name} WHERE id = 2;" - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - instance_name => 'MSSQLSERVER', - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver_tsql{'testsqlserver_tsql': - instance => 'MSSQLSERVER', - database => '#{db_name}', - command => "INSERT #{@table_name} VALUES(2, 'name2', 'email2@domain.tld');", - onlyif => "CREATE TABLE #{@table_name} (id INT, name VARCHAR(20), email VARCHAR(20)); - INSERT #{@table_name} VALUES(1, 'name', 'email@domain.tld'); - THROW 5300, 'Throw to trigger second INSERT statement in command property', 10" - } - MANIFEST - apply_manifest(pp, catch_failures: true) - - puts "Validate a row is inserted into #{@table_name} by the command:" - run_sql_query_opts = { - query: @query, - sql_admin_user: @admin_user, - sql_admin_pass: @admin_pass, - expected_row_count: 1 - } - run_sql_query(run_sql_query_opts) - end - - it 'Negative test: Run tsql with invalid command:' do - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - instance_name => 'MSSQLSERVER', - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver_tsql{'testsqlserver_tsql': - instance => 'MSSQLSERVER', - database => '#{db_name}', - command => "invalid-tsql-command", - } - MANIFEST - apply_manifest(pp, expect_failures: true) - end - - it 'Negative test: Run tsql with non-existing database:' do - @table_name = "Table_#{SecureRandom.hex(3)}" - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - instance_name => 'MSSQLSERVER', - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver_tsql{'testsqlserver_tsql': - instance => 'MSSQLSERVER', - database => 'Non-Existing-Database', - command => "CREATE TABLE #{@table_name} (id INT, name VARCHAR(20), email VARCHAR(20));", - } - MANIFEST - apply_manifest(pp, expect_failures: true) - end - end -end diff --git a/spec/acceptance/sqlserver_user_spec.rb b/spec/acceptance/sqlserver_user_spec.rb deleted file mode 100644 index 2c93bffd..00000000 --- a/spec/acceptance/sqlserver_user_spec.rb +++ /dev/null @@ -1,198 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper_acceptance' -require 'securerandom' -require 'erb' - -describe 'sqlserver::user test' do - def ensure_sqlserver_database(db_name, _ensure_val = 'present') - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::sp_configure{ 'spconfig1': - config_name => 'contained database authentication', - value => 1, - reconfigure => true, - instance => 'MSSQLSERVER', - } - sqlserver::database{ '#{db_name}': - instance => 'MSSQLSERVER', - collation_name => 'SQL_Estonian_CP1257_CS_AS', - compatibility => 100, - containment => 'PARTIAL', - require => Sqlserver::Sp_configure['spconfig1'] - } - MANIFEST - apply_manifest(pp, catch_failures: true) - end - - context 'Create database users with optional attributes' do - before(:all) do - # Initialize hostname and db_name once per context - @hostname = Helper.instance.run_shell('hostname').stdout.upcase.strip - @db_name = "DB#{SecureRandom.hex(4)}".upcase - # Create new database - ensure_sqlserver_database(@db_name) - end - - before(:each) do - @new_sql_login = "Login#{SecureRandom.hex(2)}" - @db_user = "DBuser#{SecureRandom.hex(2)}" - end - - it 'Create database user with optional default_schema' do - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::login{'#{@db_user}': - instance => 'MSSQLSERVER', - login_type => 'SQL_LOGIN', - password => 'Pupp3t1@', - } - sqlserver::user{'#{@db_user}': - database => '#{@db_name}', - user => '#{@db_user}', - default_schema => 'guest', - require => Sqlserver::Login['#{@db_user}'], - } - MANIFEST - apply_manifest(pp, catch_failures: true) - - # validate that the database user '#{@db_user}' is successfully created with default schema 'guest': - query = "USE #{@db_name}; - SELECT name AS Database_User_Name, default_schema_name - FROM SYS.DATABASE_PRINCIPALS - WHERE name = '#{@db_user}' - AND - default_schema_name = 'guest';" - run_sql_query(query:, server: @hostname, expected_row_count: 1) - end - - it 'Create database user with optional instance' do - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::login{'#{@db_user}': - instance => 'MSSQLSERVER', - login_type => 'SQL_LOGIN', - password => 'Pupp3t1@', - } - sqlserver::user{'#{@db_user}': - instance => 'MSSQLSERVER', - database => '#{@db_name}', - user => '#{@db_user}', - require => Sqlserver::Login['#{@db_user}'], - } - MANIFEST - apply_manifest(pp, catch_failures: true) - - # validate that the database user '#{@db_user}' is successfully created: - query = "USE #{@db_name}; - SELECT name AS Database_User_Name - FROM SYS.DATABASE_PRINCIPALS - WHERE name = '#{@db_user}';" - run_sql_query(query:, server: @hostname, expected_row_count: 1) - end - - it 'Create database user with optional login' do - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::login{'#{@new_sql_login}': - instance => 'MSSQLSERVER', - login_type => 'SQL_LOGIN', - password => 'Pupp3t1@', - } - sqlserver::user{'#{@db_user}': - instance => 'MSSQLSERVER', - database => '#{@db_name}', - login => '#{@new_sql_login}', - user => '#{@db_user}', - require => Sqlserver::Login['#{@new_sql_login}'], - } - MANIFEST - apply_manifest(pp, catch_failures: true) - - # validate that the database user '#{@db_user}' is mapped with sql login '#{@new_sql_login}': - query = "USE #{@db_name}; - SELECT d.name AS Database_User, l.name as Associated_sql_login - FROM SYS.DATABASE_PRINCIPALS d, MASTER.SYS.SQL_LOGINS l - WHERE d.sid = l.sid - AND d.name = '#{@db_user}';" - run_sql_query(query:, server: @hostname, expected_row_count: 1) - end - - it 'Create database user with optional password' do - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::login{'#{@new_sql_login}': - instance => 'MSSQLSERVER', - login_type => 'SQL_LOGIN', - password => 'Pupp3t1@', - } - sqlserver::user{'#{@db_user}': - instance => 'MSSQLSERVER', - database => '#{@db_name}', - login => '#{@new_sql_login}', - user => '#{@db_user}', - password => 'databaseUserPasswd', - require => Sqlserver::Login['#{@new_sql_login}'], - } - MANIFEST - apply_manifest(pp, catch_failures: true) - - puts "validate that the database user '#{@db_user}' is successfully created:" - query = "USE #{@db_name}; SELECT * FROM SYS.DATABASE_PRINCIPALS WHERE name = '#{@db_user}';" - run_sql_query(query:, server: @hostname, expected_row_count: 1) - end - - it 'Delete database user' do - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::login{'#{@db_user}': - instance => 'MSSQLSERVER', - login_type => 'SQL_LOGIN', - password => 'Pupp3t1@', - } - sqlserver::user{'#{@db_user}': - database => '#{@db_name}', - require => Sqlserver::Login['#{@db_user}'], - } - MANIFEST - apply_manifest(pp, catch_failures: true) - - # validate that the database user '#{@db_user}' is successfully created: - query = "USE #{@db_name}; SELECT * FROM SYS.DATABASE_PRINCIPALS WHERE name = '#{@db_user}';" - run_sql_query(query:, server: @hostname, expected_row_count: 1) - - pp = <<-MANIFEST - sqlserver::config{'MSSQLSERVER': - admin_user => 'sa', - admin_pass => 'Pupp3t1@', - } - sqlserver::user{'#{@db_user}': - ensure => 'absent', - database => '#{@db_name}', - } - MANIFEST - apply_manifest(pp, catch_failures: true) - # validate that the database user '#{@db_user}' should be deleted: - query = "USE #{@db_name}; SELECT * FROM SYS.DATABASE_PRINCIPALS WHERE name = '#{@db_user}';" - run_sql_query(query:, server: @hostname, expected_row_count: 0) - end - end -end diff --git a/spec/acceptance/z_last_sqlserver_features_spec.rb b/spec/acceptance/z_last_sqlserver_features_spec.rb deleted file mode 100644 index c48069d9..00000000 --- a/spec/acceptance/z_last_sqlserver_features_spec.rb +++ /dev/null @@ -1,218 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper_acceptance' -require 'erb' -require 'json' - -version = sql_version? - -describe 'sqlserver_features', if: version.to_i != 2012 do - def ensure_sql_features(features, ensure_val = 'present') - user = Helper.instance.run_shell('$env:UserName').stdout.chomp - # If no password env variable set (by CI), then default to vagrant - password = Helper.instance.run_shell('$env:pass').stdout.chomp - password = 'vagrant' if password.empty? - - pp = <<-MANIFEST - sqlserver::config{ 'MSSQLSERVER': - admin_pass => '<%= SQL_ADMIN_PASS %>', - admin_user => '<%= SQL_ADMIN_USER %>', - } - sqlserver_features{ 'MSSQLSERVER': - ensure => #{ensure_val}, - source => 'H:', - is_svc_account => "#{user}", - is_svc_password => '#{password}', - features => #{features}, - windows_feature_source => 'I:\\sources\\sxs', - install_switches => { - 'UpdateEnabled' => 'false', - } - } - MANIFEST - - apply_manifest(pp, catch_failures: true) - end - - def bind_and_apply_failing_manifest(features, ensure_val = 'present') - user = Helper.instance.run_shell('$env:UserName').stdout.chomp - pp = <<-MANIFEST - sqlserver::config{ 'MSSQLSERVER': - admin_pass => '<%= SQL_ADMIN_PASS %>', - admin_user => '<%= SQL_ADMIN_USER %>', - } - sqlserver_features{ 'MSSQLSERVER': - ensure => #{ensure_val}, - source => 'H:', - is_svc_account => "#{user}", - features => #{features}, - } - MANIFEST - - apply_manifest(pp, expect_failures: true) - end - - context 'can install' do - # Client Tools removed in Server2019+ (Backwards Compatibility, Connectivity, SDK) - # SQL Server 2019 CTP does not properly support BC, Conn, SDK, IS, MDS features - # DQC (Data Quality Client) is not available in SQL Server 2019+ - features = if version.to_i >= 2019 - [] - elsif version.to_i >= 2016 && version.to_i < 2019 - ['BC', 'Conn', 'SDK', 'IS', 'MDS', 'DQC'] - else - ['BC', 'Conn', 'SSMS', 'ADV_SSMS', 'SDK', 'IS', 'MDS', 'DQC'] - end - - before(:all) do - ensure_sql_features(features, 'absent') - end - - it 'all possible features' do - ensure_sql_features(features) - - validate_sql_install(version:) do |r| - # Client Tools removed in Server2019+ - unless version.to_i >= 2019 - expect(r.stdout).to match(%r{Client Tools Connectivity}) - expect(r.stdout).to match(%r{Client Tools Backwards Compatibility}) - expect(r.stdout).to match(%r{Client Tools SDK}) - expect(r.stdout).to match(%r{Integration Services}) - expect(r.stdout).to match(%r{Master Data Services}) - end - end - end - end - - context 'can remove' do - features = if version.to_i >= 2019 - [] - else - ['BC', 'Conn', 'SSMS', 'ADV_SSMS', 'SDK', 'IS', 'MDS', 'DQC'] - end - - it 'all possible features' do - ensure_sql_features(features, 'absent') - - validate_sql_install(version:) do |r| - # Client Tools removed in Server2019+ - unless version.to_i >= 2019 - expect(r.stdout).not_to match(%r{Client Tools Connectivity}) - expect(r.stdout).not_to match(%r{Client Tools Backwards Compatibility}) - expect(r.stdout).not_to match(%r{Client Tools SDK}) - end - expect(r.stdout).not_to match(%r{Integration Services}) - expect(r.stdout).not_to match(%r{Master Data Services}) - end - end - end - - context 'can remove independent feature' do - features = if version.to_i >= 2019 - ['IS', 'MDS'] - elsif version.to_i >= 2016 && version.to_i < 2019 - ['BC', 'Conn', 'SDK', 'IS', 'MDS', 'DQC'] - else - ['BC', 'Conn', 'SSMS', 'ADV_SSMS', 'SDK', 'IS', 'MDS', 'DQC'] - end - - before(:all) do - ensure_sql_features(features) - end - - after(:all) do - ensure_sql_features(features, 'absent') - end - - it "'BC'", unless: version.to_i >= 2019 do - ensure_sql_features(features - ['BC']) - - validate_sql_install(version:) do |r| - expect(r.stdout).not_to match(%r{Client Tools Backwards Compatibility}) - end - end - - it "'ADV_SSMS'", unless: version.to_i >= 2016 do - ensure_sql_features(features - ['ADV_SSMS']) - - validate_sql_install(version:) do |r| - expect(r.stdout).not_to match(%r{Management Tools - Complete}) - - # also verify SSMS is still present - expect(r.stdout).to match(%r{Management Tools - Basic}) - end - end - - it "'SDK' + 'IS", unless: version.to_i >= 2019 do - ensure_sql_features(features - ['SDK', 'IS']) - - validate_sql_install(version:) do |r| - expect(r.stdout).not_to match(%r{Client Tools SDK}) - end - end - end - - context 'with negative test cases' do - it 'fails when an is_svc_account is supplied and a password is not' do - features = ['IS'] - bind_and_apply_failing_manifest(features) - end - - it 'fails when ADV_SSMS is supplied but SSMS is not - FM-2712', unless: version.to_i >= 2016 do - pending('error not shown on Sql Server 2014') if version.to_i == 2014 - features = ['BC', 'Conn', 'ADV_SSMS', 'SDK'] - bind_and_apply_failing_manifest(features) - end - end - - context 'with no installed instances' do - # Currently this test can only be run on a machine once and will error if run a second time - context 'can install' do - features = if version.to_i >= 2019 - [] - elsif version.to_i >= 2016 && version.to_i < 2019 - ['BC', 'Conn', 'SDK', 'IS', 'MDS', 'DQC'] - else - ['BC', 'Conn', 'SSMS', 'ADV_SSMS', 'SDK', 'IS', 'MDS', 'DQC'] - end - - def remove_sql_instance - user = Helper.instance.run_shell('$env:UserName').stdout.chomp - pp = <<-MANIFEST - sqlserver_instance{'MSSQLSERVER': - ensure => absent, - source => 'H:', - sql_sysadmin_accounts => ['#{user}'], - } - MANIFEST - idempotent_apply(pp) - end - - before(:all) do - remove_sql_instance - end - - after(:all) do - ensure_sql_features(features, 'absent') - end - - it 'all possible features' do - ensure_sql_features(features) - - validate_sql_install(version:) do |r| - # SQL Server 2016+ will not install the client tools features. - expect(r.stdout).not_to match(%r{MSSQLSERVER\s+Database Engine Services}) - expect(r.stdout).not_to match(%r{MSSQLSERVER\s+SQL Server Replication}) - expect(r.stdout).not_to match(%r{MSSQLSERVER\s+Data Quality Services}) - unless version.to_i >= 2016 - expect(r.stdout).to match(%r{Client Tools Connectivity}) - expect(r.stdout).to match(%r{Client Tools Backwards Compatibility}) - expect(r.stdout).to match(%r{Client Tools SDK}) - expect(r.stdout).to match(%r{Integration Services}) - expect(r.stdout).to match(%r{Master Data Services}) - end - end - end - end - end -end From 4714b9f32c04413e73367689aa498f837fe2a954 Mon Sep 17 00:00:00 2001 From: Shubham Shinde Date: Mon, 16 Feb 2026 23:41:46 +0530 Subject: [PATCH 4/5] Fix ci.yml --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1cd5f53f..2e8f54d7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -148,7 +148,6 @@ jobs: run: | pass=`grep -oP '(?<=password: ).*' spec/fixtures/litmus_inventory.yaml` bundle exec bolt command run "[Environment]::SetEnvironmentVariable('pass', '$pass', 'Machine')" --targets ssh_nodes --inventoryfile spec/fixtures/litmus_inventory.yaml - cat spec/fixtures/litmus_inventory.yaml - name: Run acceptance tests run: | From 865368de02814da961791792be51c92fe0c4cfcf Mon Sep 17 00:00:00 2001 From: Shubham Shinde Date: Tue, 17 Feb 2026 01:30:31 +0530 Subject: [PATCH 5/5] Separate out the cat statement --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e8f54d7..19d8c6cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -110,6 +110,9 @@ jobs: - name: Provision test environment run: | bundle exec rake "litmus:provision[${{matrix.platforms.provider}},${{ matrix.platforms.image }}]" + + - name: Print file contents + run: | cat spec/fixtures/litmus_inventory.yaml - name: Install agent