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..8b7501e7 --- /dev/null +++ b/spec/acceptance/manifests/install_oledb_driver.pp @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# Download and install the Microsoft OLE DB Driver for SQL Server +# Using individual PowerShell commands to avoid quote escaping issues + +exec { 'download_oledb_driver': + command => 'powershell.exe -Command "Invoke-WebRequest -Uri https://go.microsoft.com/fwlink/?linkid=2274906 -OutFile C:\\Windows\\Temp\\msoledbsql.msi -UseBasicParsing"', + creates => 'C:/Windows/Temp/msoledbsql.msi', + timeout => 300, +} + +exec { 'install_oledb_driver': + command => 'powershell.exe -Command "Start-Process msiexec.exe -ArgumentList \'/i\', \'C:\\Windows\\Temp\\msoledbsql.msi\', \'/quiet\', \'/norestart\' -Wait"', + require => Exec['download_oledb_driver'], + unless => 'powershell.exe -Command "if (Test-Path \'HKLM:\\SOFTWARE\\Microsoft\\Microsoft SQL Server\\*\\OLE DB Provider for SQL Server\') { exit 0 } else { exit 1 }"', + timeout => 600, +} 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..902ab204 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,47 @@ 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 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 + # We need this for the mount_iso provider to work Helper.instance.run_shell('puppet module install puppetlabs-mount_iso') - Helper.instance.run_shell('puppet module install puppet/archive') + # 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,6 +158,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 # Mount the ISO on the agent mount_iso(iso_opts) @@ -140,13 +174,14 @@ def base_install(sql_version) def install_sqlserver(features) # this method installs SQl server on a given host + user = Helper.instance.run_shell('$env:UserName').stdout.chomp 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, @@ -185,13 +220,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 @@ -230,13 +266,13 @@ 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' + when '2025', '2022', '2017' "SQL#{version}" when '2019' "SQL#{version}CTP2.4" 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"