From 18cddf0ccb3a332159de7951e62cc1d060d007cc Mon Sep 17 00:00:00 2001 From: Iristyle Date: Mon, 1 Jun 2015 13:23:49 -0700 Subject: [PATCH 1/6] (FM-2445) Features tests without instances installed - Previously, tests relied on the assumption that SQL Server instances were installed already. Add a new test that verifies all shared features can be installed through a manifest, even with no SQL instances installed. - This test is necessary to validate the reimplementation of the underlying feature detection code from using sqlcmd.exe to using a WMI / Registry approach. --- spec/acceptance/sqlserver_features_spec.rb | 32 ++++++++++++++++++++++ spec/sql_testing_helpers.rb | 8 ++++++ 2 files changed, 40 insertions(+) diff --git a/spec/acceptance/sqlserver_features_spec.rb b/spec/acceptance/sqlserver_features_spec.rb index f9e843c1..37e1af88 100644 --- a/spec/acceptance/sqlserver_features_spec.rb +++ b/spec/acceptance/sqlserver_features_spec.rb @@ -233,4 +233,36 @@ def bind_and_apply_failing_manifest(host, features, ensure_val = 'present') ensure_sql_features(host, features) end end + + context 'with no installed instances' do + + context 'can install' do + + features = ['Tools', 'BC', 'Conn', 'SSMS', 'ADV_SSMS', 'SDK', 'IS', 'MDS'] + + before(:all) do + # this assumes that only default MSSQLSERVER has been installed so far + # be careful about running tests out of sequence + remove_sql_instances(host, {:version => version, :instance_names => ['MSSQLSERVER']}) + end + + after(:all) do + remove_sql_features(host, {:features => features, :version => version}) + end + + it 'all possible features' do + ensure_sql_features(host, features) + + validate_sql_install(host, {:version => version}) do |r| + expect(r.stdout).to match(/Management Tools - Basic/) + expect(r.stdout).to match(/Management Tools - Complete/) + expect(r.stdout).to match(/Client Tools Connectivity/) + expect(r.stdout).to match(/Client Tools Backwards Compatibility/) + expect(r.stdout).to match(/Client Tools SDK/) + expect(r.stdout).to match(/Integration Services/) + expect(r.stdout).to match(/Master Data Services/) + end + end + end + end end diff --git a/spec/sql_testing_helpers.rb b/spec/sql_testing_helpers.rb index ffb3f865..e6f31bb6 100644 --- a/spec/sql_testing_helpers.rb +++ b/spec/sql_testing_helpers.rb @@ -123,6 +123,14 @@ def remove_sql_features(host, opts = {}) on(host, "cmd.exe /c \"#{cmd}\"", {:acceptable_exit_codes => [0, 1, 2]}) end +def remove_sql_instances(host, opts = {}) + bootstrap_dir, setup_dir = get_install_paths(opts[:version]) + opts[:instance_names].each do |instance_name| + cmd = "cd \\\"#{setup_dir}\\\" && setup.exe /Action=uninstall /Q /IACCEPTSQLSERVERLICENSETERMS /FEATURES=SQL,AS,RS /INSTANCENAME=#{instance_name}" + on(host, "cmd.exe /c \"#{cmd}\"", {:acceptable_exit_codes => [0]}) + end +end + def get_install_paths(version) vers = { '2012' => '110', '2014' => '120' } From ab32fbef4980cb96cbce4e4de7b8ba87b391eab5 Mon Sep 17 00:00:00 2001 From: Iristyle Date: Wed, 27 May 2015 02:06:45 -0700 Subject: [PATCH 2/6] (FM-2303) WMI / Registry impl of Discovery Report - Provide a nearly drop-in replacement for the existing SQL discovery report based on sqlcmd.exe - but by using WMI queries and Registry spleunking. This code: - does not shell to PowerShell - does not call sqlcmd.exe - does not parse XML - runs all code in the Ruby process - As a result: - it's a bit quicker to execute - it doesn't require any heuristics to find files - it doesn't need to write / secure temporary files - it can use native Ruby symbols - Of note, the public API on PuppetX::SqlServer::Features is rather small and comprises only 3 public methods: - get_instances - returns a hash of all instance specific details for both SQL_2012 and SQL_2014 instances munged together given in a side by side SQL install, name collisions are not allowed - get_features - returns a hash of SQL_2012 / SQL_2014 shared features - get_instance_info(version, instance_name) - returns a hash for a given instance with a given version. Called by get_instances - The shape of the data returned is slightly different than what run_discovery_report previously used, but it was close to a drop-in replacement. - Note that since the module doesn't specify a SQL version for install and the current code is not set to handle multiple versions. The Features.get_instances returns info for both SQL_2014 and SQL_2012 should they both be installed. For the sake of consumers of the discovery_script code path, only feature shared feature data for one version of SQL will be installed, with SQL_2014 prioritized. - Note that when run under Puppet 32-bit, the flag is passed for Registry access to ensure that the right hive is used. Furthermore, the WMI scripting access passes the 64-bit flag, so that WMI data can be queried correctly for 64-bit SQL server from a 32-bit Ruby process. --- lib/puppet/provider/sqlserver.rb | 43 +--- .../provider/sqlserver_features/mssql.rb | 8 +- .../provider/sqlserver_instance/mssql.rb | 10 +- lib/puppet_x/sqlserver/features.rb | 235 ++++++++++++++++++ 4 files changed, 250 insertions(+), 46 deletions(-) create mode 100644 lib/puppet_x/sqlserver/features.rb diff --git a/lib/puppet/provider/sqlserver.rb b/lib/puppet/provider/sqlserver.rb index e196a30a..a42bcd94 100644 --- a/lib/puppet/provider/sqlserver.rb +++ b/lib/puppet/provider/sqlserver.rb @@ -1,4 +1,5 @@ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'lib/puppet_x/sqlserver/server_helper')) +require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'lib/puppet_x/sqlserver/features')) require File.expand_path(File.join(File.dirname(__FILE__), 'sqlserver')) require 'tempfile' @@ -39,44 +40,14 @@ def not_nil_and_not_empty?(obj) end def self.run_discovery_script - discovery = <<-DISCOVERY - if(Test-Path 'C:\\Program Files\\Microsoft SQL Server\\120\\Setup Bootstrap\\SQLServer2014\\setup.exe'){ - pushd 'C:\\Program Files\\Microsoft SQL Server\\120\\Setup Bootstrap\\SQLServer2014\\' - Start-Process -FilePath .\\setup.exe -ArgumentList @("/Action=RunDiscovery","/q") -Wait -WindowStyle Hidden - popd -}elseif(Test-Path 'C:\\Program Files\\Microsoft SQL Server\\110\\Setup Bootstrap\\SQLServer2012\\setup.exe'){ - pushd 'C:\\Program Files\\Microsoft SQL Server\\110\\Setup Bootstrap\\SQLServer2012\\' - Start-Process -FilePath .\\setup.exe -ArgumentList @("/Action=RunDiscovery","/q") -Wait -WindowStyle Hidden - popd -} + features = PuppetX::Sqlserver::Features.get_features -$file = gci 'C:\\Program Files\\Microsoft SQL Server\\*\\Setup Bootstrap\\Log\\*\\SqlDiscoveryReport.xml' -ErrorAction Ignore | sort -Descending | select -First 1 -if($file -ne $null) { - [xml] $xml = cat $file - $json = $xml.ArrayOfDiscoveryInformation.DiscoveryInformation - $hash = @{"instances" = @();"TimeStamp"= ("{0:yyyy-MM-dd HH:mm:ss}" -f $file.CreationTime)} - foreach($instance in ($json | % { $_.Instance } | Get-Unique )){ - $features = @() - $json | %{ - if($_.instance -eq $instance){ - $features += $_.feature - } - } - if($instance -eq "" ){ - $hash.Add("Generic Features",$features) - }else{ - $hash["instances"] += $instance - $hash.Add($instance,@{"features"=$features}) - } + instances = { + # SQL instance names are unique over side-by-side installs + :instances => PuppetX::Sqlserver::Features.get_instances.values.inject(:merge), + # but features across versions are different + :features => !features[SQL_2014].empty? ? features[SQL_2014] : features[SQL_2012] } - $file.Directory.Delete($true) - Write-Host (ConvertTo-Json $hash) -}else{ - Write-host ("{}") -} - DISCOVERY - result = powershell([discovery]) - JSON.parse(result) end def self.run_install_dot_net diff --git a/lib/puppet/provider/sqlserver_features/mssql.rb b/lib/puppet/provider/sqlserver_features/mssql.rb index 5cb4c516..56fec2b2 100644 --- a/lib/puppet/provider/sqlserver_features/mssql.rb +++ b/lib/puppet/provider/sqlserver_features/mssql.rb @@ -8,14 +8,14 @@ Puppet::Type::type(:sqlserver_features).provide(:mssql, :parent => Puppet::Provider::Sqlserver) do def self.instances instances = [] - jsonResult = Puppet::Provider::Sqlserver.run_discovery_script - debug "Parsing json result #{jsonResult}" - if jsonResult.has_key?('Generic Features') + result = Puppet::Provider::Sqlserver.run_discovery_script + debug "Parsing result #{result}" + if !result[:features].empty? existing_instance = {:name => "Generic Features", :ensure => :present, :features => PuppetX::Sqlserver::ServerHelper.translate_features( - jsonResult['Generic Features']).sort! + result[:features]).sort! } debug "Parsed features = #{existing_instance[:features]}" diff --git a/lib/puppet/provider/sqlserver_instance/mssql.rb b/lib/puppet/provider/sqlserver_instance/mssql.rb index ceeb5d01..a95ca503 100644 --- a/lib/puppet/provider/sqlserver_instance/mssql.rb +++ b/lib/puppet/provider/sqlserver_instance/mssql.rb @@ -9,19 +9,17 @@ Puppet::Type::type(:sqlserver_instance).provide(:mssql, :parent => Puppet::Provider::Sqlserver) do def self.instances instances = [] - jsonResult = Puppet::Provider::Sqlserver.run_discovery_script - debug "Parsing json result #{jsonResult}" - if jsonResult.has_key?('instances') - jsonResult['instances'].each do |instance_name| + result = Puppet::Provider::Sqlserver.run_discovery_script + debug "Parsing result #{result}" + result[:instances].keys.each do |instance_name| existing_instance = {:name => instance_name, :ensure => :present, :features => PuppetX::Sqlserver::ServerHelper.translate_features( - jsonResult[instance_name]['features']).sort! + result[:instances][instance_name][:features]).sort! } instance = new(existing_instance) instances << instance - end end instances end diff --git a/lib/puppet_x/sqlserver/features.rb b/lib/puppet_x/sqlserver/features.rb new file mode 100644 index 00000000..11c22945 --- /dev/null +++ b/lib/puppet_x/sqlserver/features.rb @@ -0,0 +1,235 @@ +SQL_2012 = 'SQL_2012' +SQL_2014 = 'SQL_2014' + +module PuppetX + module Sqlserver + # https://msdn.microsoft.com/en-us/library/ms143786.aspx basic feature docs + class Features + private + + SQL_WMI_PATH = { + SQL_2012 => 'ComputerManagement11', + SQL_2014 => 'ComputerManagement12', + } + + SQL_REG_ROOT = 'Software\Microsoft\Microsoft SQL Server' + + # http://msdn.microsoft.com/en-us/library/windows/desktop/aa384129(v=vs.85).aspx + KEY_WOW64_64KEY ||= 0x100 + KEY_READ ||= 0x20019 + + def self.connect(version) + require 'win32ole' + ver = SQL_WMI_PATH[version] + context = WIN32OLE.new('WbemScripting.SWbemNamedValueSet') + context.Add("__ProviderArchitecture", 64) + locator = WIN32OLE.new('WbemScripting.SWbemLocator') + locator.ConnectServer('', "root/Microsoft/SqlServer/#{ver}", '', '', nil, nil, nil, context) + end + + def self.get_parent_path(key_path) + # should be the same as SQL_REG_ROOT + key_path.slice(0, key_path.rindex('\\')) + end + + def self.get_reg_key_val(win32_reg_key, val_name, reg_type) + win32_reg_key[val_name, reg_type] + rescue + nil + end + + def self.get_sql_reg_val_features(key_name, reg_val_feat_hash) + require 'win32/registry' + + vals = [] + + begin + hklm = Win32::Registry::HKEY_LOCAL_MACHINE + vals = hklm.open(key_name, KEY_READ | KEY_WOW64_64KEY) do |key| + reg_val_feat_hash + .select { |val_name, _| get_reg_key_val(key, val_name, Win32::Registry::REG_DWORD) == 1 } + .map { |_, feat_name| feat_name } + end + rescue Win32::Registry::Error # subkey doesn't exist + end + + vals + end + + def self.get_sql_reg_key_features(key_name, reg_key_feat_hash, instance_name) + require 'win32/registry' + + installed = reg_key_feat_hash.select do |subkey, feat_name| + begin + hklm = Win32::Registry::HKEY_LOCAL_MACHINE + hklm.open("#{key_name}\\#{subkey}", KEY_READ | KEY_WOW64_64KEY) do |feat_key| + get_reg_key_val(feat_key, instance_name, Win32::Registry::REG_SZ) + end + rescue Win32::Registry::Error # subkey doesn't exist + end + end + + installed.values + end + + def self.get_wmi_property_values(wmi, query, prop_name = 'PropertyStrValue') + vals = [] + + wmi.ExecQuery(query).each do |v| + vals.push(v.Properties_(prop_name).Value) + end + + vals + end + + def self.get_instance_names_by_ver(version) + query = 'SELECT InstanceName FROM ServerSettings' + get_wmi_property_values(connect(version), query, 'InstanceName') + rescue WIN32OLERuntimeError => e # version doesn't exist + # WBEM_E_INVALID_NAMESPACE from wbemcli.h + return [] if e.message =~ /8004100e/im + raise + end + + def self.get_sql_property_values(version, instance_name, property_name) + query = <<-END + SELECT * FROM SqlServiceAdvancedProperty + WHERE PropertyName='#{property_name}' + AND SqlServiceType=1 AND ServiceName LIKE '%#{instance_name}' + END + # WMI LIKE query to substring match since ServiceName will be of the format + # MSSQLSERVER (first install) or MSSQL$MSSQLSERVER (second install) + + get_wmi_property_values(connect(version), query) + end + + def self.get_wmi_instance_info(version, instance_name) + { + :name => instance_name, + :version_friendly => version, + :version => get_sql_property_values(version, instance_name, 'VERSION').first, + # typically Software\Microsoft\Microsoft SQL Server\MSSQL11.MSSQLSERVER + :reg_root => get_sql_property_values(version, instance_name, 'REGROOT').first, + } + end + + def self.get_instance_features(reg_root, instance_name) + instance_features = { + # also reg Replication/IsInstalled set to 1 + 'SQL_Replication_Core_Inst' => 'SQL Server Replication', + # also WMI: SqlService WHERE SQLServiceType = 1 # MSSQLSERVER + 'SQL_Engine_Core_Inst' => 'Database Engine Services', + 'SQL_FullText_Adv' => 'Full-Text and Semantic Extractions for Search', + 'SQL_DQ_Full' => 'Data Quality Services' + } + + feat_root = "#{reg_root}\\ConfigurationState" + features = get_sql_reg_val_features(feat_root, instance_features) + + # https://msdn.microsoft.com/en-us/library/ms179591.aspx + # WMI equivalents require trickier name parsing + parent_subkey_features = { + # also WMI: SqlService WHERE SQLServiceType = 5 # MSSQLServerOLAPService + 'OLAP' => 'Analysis Services', + # also WMI: SqlService WHERE SQLServiceType = 6 # ReportServer + 'RS' => 'Reporting Services - Native' + } + + # instance features found in non-parented reg keys + feat_root = "#{get_parent_path(reg_root)}\\Instance Names" + parent_features = get_sql_reg_key_features(feat_root, parent_subkey_features, instance_name) + + features + parent_features + end + + def self.get_shared_features(version) + shared_features = { + 'Connectivity_Full' => 'Client Tools Connectivity', + 'SDK_Full' => 'Client Tools SDK', + 'MDSCoreFeature' => 'Master Data Services', + 'Tools_Legacy_Full' => 'Client Tools Backwards Compatibility', + 'SQL_SSMS_Full' => 'Management Tools - Complete', + 'SQL_SSMS_Adv' => 'Management Tools - Basic', # also SQL_PowerShell_Tools_ANS + # also WMI: SqlService WHERE SQLServiceType = 4 # MsDtsServer + 'SQL_DTS_Full' => 'Integration Services' + # currently ignoring Reporting Services Shared + } + + reg_ver = (version == SQL_2014 ? '120' : '110') + reg_root = "#{SQL_REG_ROOT}\\#{reg_ver}\\ConfigurationState" + + get_sql_reg_val_features(reg_root, shared_features) + end + + public + + # return a hash of version => instance info + # + # { + # "SQL_2012" => {}, + # "SQL_2014" => { + # "MSSQLSERVER" => { + # :name => "MSSQLSERVER", + # :version_friendly => "SQL_2014", + # :version => "12.0.2000.8", + # :reg_root => "Software\\Microsoft\\Microsoft SQL Server\\MSSQL12.MSSQLSERVER", + # :features => [ + # "SQL Server Replication", + # "Database Engine Services", + # "Full-Text and Semantic Extractions for Search", + # "Data Quality Services", + # "Analysis Services", + # "Reporting Services - Native" + # ] + # } + # } + # } + def self.get_instances + version_instance_map = [SQL_2012, SQL_2014] + .map do |version| + instances = get_instance_names_by_ver(version) + .map { |name| [ name, get_instance_info(version, name) ] } + + [ version, Hash[instances] ] + end + + Hash[version_instance_map] + end + + # return a hash of version => shared features array + # + # { + # "SQL_2012" => ["Conn", "SDK", "MDS", "BC", "SSMS", "ADV_SSMS", "IS"], + # "SQL_2014" => [] + # } + def self.get_features + { + SQL_2012 => get_shared_features(SQL_2012), + SQL_2014 => get_shared_features(SQL_2014), + } + end + + # returns a hash containing instance details + # + # { + # :name => "MSSQLSERVER2", + # :version_friendly => "SQL_2014", + # :version => "12.0.2000.8", + # :reg_root => "Software\\Microsoft\\Microsoft SQL Server\\MSSQL12.MSSQLSERVER2", + # :features => [ + # "SQLServer Replication", + # "Database Engine Services", + # "Full-Text and Semantic Extractions for Search", + # "Data Quality Services", + # "Analysis Services", + # "Reporting Services - Native" + # ] + # } + def self.get_instance_info(version = SQL_2014, instance_name) + sql_instance = get_wmi_instance_info(version, instance_name) + feats = get_instance_features(sql_instance[:reg_root], sql_instance[:name]) + sql_instance.merge({:features => feats}) + end + end + end +end From b410922864e53e934a0bc52564fbbcd9ed3506f2 Mon Sep 17 00:00:00 2001 From: Iristyle Date: Wed, 27 May 2015 02:23:26 -0700 Subject: [PATCH 3/6] (FM-2790) Return SQL installations as Facter facts - Fact 'sqlserver_instances' contains all detailed instance information and is not segregated by SQL version given instance names are unique even in a side by side installation - Fact 'sqlserver_features' contains all shared features segregated by SQL version - run_discovery_script method has been completely removed in favor of leveraging Facts - note that to make the output compatible with Facter, the Ruby symbols used in the hashes returned from PuppetX::Sqlserver::Features have all been changed to bare strings --- lib/facter/sqlserver_features.rb | 9 +++++ lib/facter/sqlserver_instances.rb | 9 +++++ lib/puppet/provider/sqlserver.rb | 11 ----- .../provider/sqlserver_features/mssql.rb | 7 ++-- .../provider/sqlserver_instance/mssql.rb | 7 ++-- lib/puppet_x/sqlserver/features.rb | 40 +++++++++---------- 6 files changed, 46 insertions(+), 37 deletions(-) create mode 100644 lib/facter/sqlserver_features.rb create mode 100644 lib/facter/sqlserver_instances.rb diff --git a/lib/facter/sqlserver_features.rb b/lib/facter/sqlserver_features.rb new file mode 100644 index 00000000..391871dd --- /dev/null +++ b/lib/facter/sqlserver_features.rb @@ -0,0 +1,9 @@ +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'puppet_x/sqlserver/features')) + +Facter.add(:sqlserver_features) do + confine :osfamily => :windows + + setcode do + PuppetX::Sqlserver::Features.get_features + end +end diff --git a/lib/facter/sqlserver_instances.rb b/lib/facter/sqlserver_instances.rb new file mode 100644 index 00000000..8ba8cfdb --- /dev/null +++ b/lib/facter/sqlserver_instances.rb @@ -0,0 +1,9 @@ +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'puppet_x/sqlserver/features')) + +Facter.add(:sqlserver_instances) do + confine :osfamily => :windows + + setcode do + PuppetX::Sqlserver::Features.get_instances + end +end diff --git a/lib/puppet/provider/sqlserver.rb b/lib/puppet/provider/sqlserver.rb index a42bcd94..040993fa 100644 --- a/lib/puppet/provider/sqlserver.rb +++ b/lib/puppet/provider/sqlserver.rb @@ -39,17 +39,6 @@ def not_nil_and_not_empty?(obj) !obj.nil? and !obj.empty? end - def self.run_discovery_script - features = PuppetX::Sqlserver::Features.get_features - - instances = { - # SQL instance names are unique over side-by-side installs - :instances => PuppetX::Sqlserver::Features.get_instances.values.inject(:merge), - # but features across versions are different - :features => !features[SQL_2014].empty? ? features[SQL_2014] : features[SQL_2012] - } - end - def self.run_install_dot_net install_dot_net = <<-DOTNET Install-WindowsFeature NET-Framework-Core diff --git a/lib/puppet/provider/sqlserver_features/mssql.rb b/lib/puppet/provider/sqlserver_features/mssql.rb index 56fec2b2..8171b0bc 100644 --- a/lib/puppet/provider/sqlserver_features/mssql.rb +++ b/lib/puppet/provider/sqlserver_features/mssql.rb @@ -8,14 +8,15 @@ Puppet::Type::type(:sqlserver_features).provide(:mssql, :parent => Puppet::Provider::Sqlserver) do def self.instances instances = [] - result = Puppet::Provider::Sqlserver.run_discovery_script + result = Facter.value(:sqlserver_features) debug "Parsing result #{result}" - if !result[:features].empty? + result = !result[SQL_2014].empty? ? result[SQL_2014] : result[SQL_2012] + if !result.empty? existing_instance = {:name => "Generic Features", :ensure => :present, :features => PuppetX::Sqlserver::ServerHelper.translate_features( - result[:features]).sort! + result).sort! } debug "Parsed features = #{existing_instance[:features]}" diff --git a/lib/puppet/provider/sqlserver_instance/mssql.rb b/lib/puppet/provider/sqlserver_instance/mssql.rb index a95ca503..c1558b7a 100644 --- a/lib/puppet/provider/sqlserver_instance/mssql.rb +++ b/lib/puppet/provider/sqlserver_instance/mssql.rb @@ -9,14 +9,15 @@ Puppet::Type::type(:sqlserver_instance).provide(:mssql, :parent => Puppet::Provider::Sqlserver) do def self.instances instances = [] - result = Puppet::Provider::Sqlserver.run_discovery_script + result = Facter.value(:sqlserver_instances) debug "Parsing result #{result}" - result[:instances].keys.each do |instance_name| + result = result.values.inject(:merge) + result.keys.each do |instance_name| existing_instance = {:name => instance_name, :ensure => :present, :features => PuppetX::Sqlserver::ServerHelper.translate_features( - result[:instances][instance_name][:features]).sort! + result[instance_name]['features']).sort! } instance = new(existing_instance) instances << instance diff --git a/lib/puppet_x/sqlserver/features.rb b/lib/puppet_x/sqlserver/features.rb index 11c22945..afcf4804 100644 --- a/lib/puppet_x/sqlserver/features.rb +++ b/lib/puppet_x/sqlserver/features.rb @@ -1,5 +1,5 @@ -SQL_2012 = 'SQL_2012' -SQL_2014 = 'SQL_2014' +SQL_2012 ||= 'SQL_2012' +SQL_2014 ||= 'SQL_2014' module PuppetX module Sqlserver @@ -7,12 +7,12 @@ module Sqlserver class Features private - SQL_WMI_PATH = { + SQL_WMI_PATH ||= { SQL_2012 => 'ComputerManagement11', SQL_2014 => 'ComputerManagement12', } - SQL_REG_ROOT = 'Software\Microsoft\Microsoft SQL Server' + SQL_REG_ROOT ||= 'Software\Microsoft\Microsoft SQL Server' # http://msdn.microsoft.com/en-us/library/windows/desktop/aa384129(v=vs.85).aspx KEY_WOW64_64KEY ||= 0x100 @@ -105,11 +105,11 @@ def self.get_sql_property_values(version, instance_name, property_name) def self.get_wmi_instance_info(version, instance_name) { - :name => instance_name, - :version_friendly => version, - :version => get_sql_property_values(version, instance_name, 'VERSION').first, + 'name' => instance_name, + 'version_friendly' => version, + 'version' => get_sql_property_values(version, instance_name, 'VERSION').first, # typically Software\Microsoft\Microsoft SQL Server\MSSQL11.MSSQLSERVER - :reg_root => get_sql_property_values(version, instance_name, 'REGROOT').first, + 'reg_root' => get_sql_property_values(version, instance_name, 'REGROOT').first, } end @@ -169,11 +169,11 @@ def self.get_shared_features(version) # "SQL_2012" => {}, # "SQL_2014" => { # "MSSQLSERVER" => { - # :name => "MSSQLSERVER", - # :version_friendly => "SQL_2014", - # :version => "12.0.2000.8", - # :reg_root => "Software\\Microsoft\\Microsoft SQL Server\\MSSQL12.MSSQLSERVER", - # :features => [ + # "name" => "MSSQLSERVER", + # "version_friendly" => "SQL_2014", + # "version" => "12.0.2000.8", + # "reg_root" => "Software\\Microsoft\\Microsoft SQL Server\\MSSQL12.MSSQLSERVER", + # "features" => [ # "SQL Server Replication", # "Database Engine Services", # "Full-Text and Semantic Extractions for Search", @@ -212,11 +212,11 @@ def self.get_features # returns a hash containing instance details # # { - # :name => "MSSQLSERVER2", - # :version_friendly => "SQL_2014", - # :version => "12.0.2000.8", - # :reg_root => "Software\\Microsoft\\Microsoft SQL Server\\MSSQL12.MSSQLSERVER2", - # :features => [ + # "name" => "MSSQLSERVER2", + # "version_friendly" => "SQL_2014", + # "version" => "12.0.2000.8", + # "reg_root" => "Software\\Microsoft\\Microsoft SQL Server\\MSSQL12.MSSQLSERVER2", + # "features" => [ # "SQLServer Replication", # "Database Engine Services", # "Full-Text and Semantic Extractions for Search", @@ -227,8 +227,8 @@ def self.get_features # } def self.get_instance_info(version = SQL_2014, instance_name) sql_instance = get_wmi_instance_info(version, instance_name) - feats = get_instance_features(sql_instance[:reg_root], sql_instance[:name]) - sql_instance.merge({:features => feats}) + feats = get_instance_features(sql_instance['reg_root'], sql_instance['name']) + sql_instance.merge({'features' => feats}) end end end From bb903ddebbcf9c27f86fd6b29252225720a0e424 Mon Sep 17 00:00:00 2001 From: Iristyle Date: Wed, 27 May 2015 08:37:00 -0700 Subject: [PATCH 4/6] (FM-2303) Remove feature name translation - Now that Ruby code is used to build the description of a current installation, it's no longer necessary to perform string translation from SQL feature names such as 'SQL Server Replication' to their command line switch counterparts. This should remove any concerns around localization since command line switches are universal. - Remove code that's no longer needed as a result. - Fact values for sqlserver_instances and sqlserver_features now contain these command line switches instead of human-friendly feature names. --- .../provider/sqlserver_features/mssql.rb | 4 +- .../provider/sqlserver_instance/mssql.rb | 4 +- lib/puppet_x/sqlserver/features.rb | 52 +++++++++---------- lib/puppet_x/sqlserver/server_helper.rb | 26 ---------- spec/unit/puppet_x/server_helper_spec.rb | 26 ---------- 5 files changed, 28 insertions(+), 84 deletions(-) diff --git a/lib/puppet/provider/sqlserver_features/mssql.rb b/lib/puppet/provider/sqlserver_features/mssql.rb index 8171b0bc..1280f54b 100644 --- a/lib/puppet/provider/sqlserver_features/mssql.rb +++ b/lib/puppet/provider/sqlserver_features/mssql.rb @@ -14,9 +14,7 @@ def self.instances if !result.empty? existing_instance = {:name => "Generic Features", :ensure => :present, - :features => - PuppetX::Sqlserver::ServerHelper.translate_features( - result).sort! + :features => result.sort } debug "Parsed features = #{existing_instance[:features]}" diff --git a/lib/puppet/provider/sqlserver_instance/mssql.rb b/lib/puppet/provider/sqlserver_instance/mssql.rb index c1558b7a..edf4dff3 100644 --- a/lib/puppet/provider/sqlserver_instance/mssql.rb +++ b/lib/puppet/provider/sqlserver_instance/mssql.rb @@ -15,9 +15,7 @@ def self.instances result.keys.each do |instance_name| existing_instance = {:name => instance_name, :ensure => :present, - :features => - PuppetX::Sqlserver::ServerHelper.translate_features( - result[instance_name]['features']).sort! + :features => result[instance_name]['features'].sort } instance = new(existing_instance) instances << instance diff --git a/lib/puppet_x/sqlserver/features.rb b/lib/puppet_x/sqlserver/features.rb index afcf4804..bd3a3f02 100644 --- a/lib/puppet_x/sqlserver/features.rb +++ b/lib/puppet_x/sqlserver/features.rb @@ -116,11 +116,11 @@ def self.get_wmi_instance_info(version, instance_name) def self.get_instance_features(reg_root, instance_name) instance_features = { # also reg Replication/IsInstalled set to 1 - 'SQL_Replication_Core_Inst' => 'SQL Server Replication', + 'SQL_Replication_Core_Inst' => 'Replication', # SQL Server Replication # also WMI: SqlService WHERE SQLServiceType = 1 # MSSQLSERVER - 'SQL_Engine_Core_Inst' => 'Database Engine Services', - 'SQL_FullText_Adv' => 'Full-Text and Semantic Extractions for Search', - 'SQL_DQ_Full' => 'Data Quality Services' + 'SQL_Engine_Core_Inst' => 'SQLEngine', # Database Engine Services + 'SQL_FullText_Adv' => 'FullText', # Full-Text and Semantic Extractions for Search + 'SQL_DQ_Full' => 'DQ', # Data Quality Services } feat_root = "#{reg_root}\\ConfigurationState" @@ -130,9 +130,9 @@ def self.get_instance_features(reg_root, instance_name) # WMI equivalents require trickier name parsing parent_subkey_features = { # also WMI: SqlService WHERE SQLServiceType = 5 # MSSQLServerOLAPService - 'OLAP' => 'Analysis Services', + 'OLAP' => 'AS', # Analysis Services, # also WMI: SqlService WHERE SQLServiceType = 6 # ReportServer - 'RS' => 'Reporting Services - Native' + 'RS' => 'RS' # Reporting Services - Native } # instance features found in non-parented reg keys @@ -144,14 +144,14 @@ def self.get_instance_features(reg_root, instance_name) def self.get_shared_features(version) shared_features = { - 'Connectivity_Full' => 'Client Tools Connectivity', - 'SDK_Full' => 'Client Tools SDK', - 'MDSCoreFeature' => 'Master Data Services', - 'Tools_Legacy_Full' => 'Client Tools Backwards Compatibility', - 'SQL_SSMS_Full' => 'Management Tools - Complete', - 'SQL_SSMS_Adv' => 'Management Tools - Basic', # also SQL_PowerShell_Tools_ANS + 'Connectivity_Full' => 'Conn', # Client Tools Connectivity + 'SDK_Full' => 'SDK', # Client Tools SDK + 'MDSCoreFeature' => 'MDS', # Master Data Services + 'Tools_Legacy_Full' => 'BC', # Client Tools Backwards Compatibility + 'SQL_SSMS_Full' => 'ADV_SSMS', # Management Tools - Complete + 'SQL_SSMS_Adv' => 'SSMS', # Management Tools - Basic # also WMI: SqlService WHERE SQLServiceType = 4 # MsDtsServer - 'SQL_DTS_Full' => 'Integration Services' + 'SQL_DTS_Full' => 'IS', # Integration Services # currently ignoring Reporting Services Shared } @@ -174,12 +174,12 @@ def self.get_shared_features(version) # "version" => "12.0.2000.8", # "reg_root" => "Software\\Microsoft\\Microsoft SQL Server\\MSSQL12.MSSQLSERVER", # "features" => [ - # "SQL Server Replication", - # "Database Engine Services", - # "Full-Text and Semantic Extractions for Search", - # "Data Quality Services", - # "Analysis Services", - # "Reporting Services - Native" + # "Replication", + # "SQLEngine", + # "FullText", + # "DQ", + # "AS", + # "RS" # ] # } # } @@ -216,13 +216,13 @@ def self.get_features # "version_friendly" => "SQL_2014", # "version" => "12.0.2000.8", # "reg_root" => "Software\\Microsoft\\Microsoft SQL Server\\MSSQL12.MSSQLSERVER2", - # "features" => [ - # "SQLServer Replication", - # "Database Engine Services", - # "Full-Text and Semantic Extractions for Search", - # "Data Quality Services", - # "Analysis Services", - # "Reporting Services - Native" + # "features" =>[ + # "Replication", + # "SQLEngine", + # "FullText", + # "DQ", + # "AS", + # "RS" # ] # } def self.get_instance_info(version = SQL_2014, instance_name) diff --git a/lib/puppet_x/sqlserver/server_helper.rb b/lib/puppet_x/sqlserver/server_helper.rb index 886bf674..4b429e53 100644 --- a/lib/puppet_x/sqlserver/server_helper.rb +++ b/lib/puppet_x/sqlserver/server_helper.rb @@ -1,37 +1,11 @@ module PuppetX module Sqlserver class ServerHelper - @features_hash = { - :AS => 'Analysis Services', - :RS => 'Reporting Services - Native', - :SQLEngine => 'Database Engine Services', - :Replication => 'SQL Server Replication', - :FullText => 'Full-Text and Semantic Extractions for Search', - :DQ => 'Data Quality Services', - :BC => 'Client Tools Backwards Compatibility', - :SSMS => 'Management Tools - Basic', - :ADV_SSMS => 'Management Tools - Complete', - :Conn => 'Client Tools Connectivity', - :SDK => 'Client Tools SDK', - :IS => 'Integration Services', - :MDS => 'Master Data Services', - } @super_feature_hash = { :SQL => [:DQ, :FullText, :Replication, :SQLEngine], :Tools => [:SSMS, :ADV_SSMS, :Conn] } - - def self.translate_features(features) - translated = [] - Array.new(features).each do |feature| - if @features_hash.has_value?(feature) - translated << @features_hash.key(feature).to_s - end - end - translated - end - def self.get_sub_features(super_feature) @super_feature_hash[super_feature.to_sym] end diff --git a/spec/unit/puppet_x/server_helper_spec.rb b/spec/unit/puppet_x/server_helper_spec.rb index 2f1543ec..53fc8976 100644 --- a/spec/unit/puppet_x/server_helper_spec.rb +++ b/spec/unit/puppet_x/server_helper_spec.rb @@ -12,32 +12,6 @@ end end - shared_examples 'translate_features' do - it { - expect(PuppetX::Sqlserver::ServerHelper.translate_features(features)).to eq(translated) - } - end - - describe 'features parser' do - {:AS => 'Analysis Services', - :RS => 'Reporting Services - Native', - :SQLEngine => 'Database Engine Services', - :Replication => 'SQL Server Replication', - :FullText => 'Full-Text and Semantic Extractions for Search', - :DQ => 'Data Quality Services', - :BC => 'Client Tools Backwards Compatibility', - :SSMS => 'Management Tools - Basic', - :ADV_SSMS => 'Management Tools - Complete', - :Conn => 'Client Tools Connectivity', - :SDK => 'Client Tools SDK', - :IS => 'Integration Services', - :MDS => 'Master Data Services'}.each do |k, v| - it_behaves_like 'translate_features' do - let(:features) { [v] } - let(:translated) { [k.to_s] } - end - end - end describe 'when calling with a local user' do ['mysillyuser', 'mybox\localuser'].each do |user| it_should_behave_like 'when calling with', user, false From cc5faa105c6d6cfc998612d2aae3f7d10feb7bdd Mon Sep 17 00:00:00 2001 From: Iristyle Date: Mon, 1 Jun 2015 13:21:10 -0700 Subject: [PATCH 5/6] (fix) SQL super features missing sub-features - According to the MSDN documentation for the 'SQL' feature https://msdn.microsoft.com/en-us/library/ms144259.aspx#Feature The following feature parameters should be included: BC Conn SSMS ADV_SSMS SDK --- lib/puppet_x/sqlserver/server_helper.rb | 2 +- spec/unit/puppet/provider/sqlserver_features_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/puppet_x/sqlserver/server_helper.rb b/lib/puppet_x/sqlserver/server_helper.rb index 4b429e53..38eca0b4 100644 --- a/lib/puppet_x/sqlserver/server_helper.rb +++ b/lib/puppet_x/sqlserver/server_helper.rb @@ -3,7 +3,7 @@ module Sqlserver class ServerHelper @super_feature_hash = { :SQL => [:DQ, :FullText, :Replication, :SQLEngine], - :Tools => [:SSMS, :ADV_SSMS, :Conn] + :Tools => [:BC, :SSMS, :ADV_SSMS, :Conn, :SDK] } def self.get_sub_features(super_feature) diff --git a/spec/unit/puppet/provider/sqlserver_features_spec.rb b/spec/unit/puppet/provider/sqlserver_features_spec.rb index f4aa21da..69455359 100644 --- a/spec/unit/puppet/provider/sqlserver_features_spec.rb +++ b/spec/unit/puppet/provider/sqlserver_features_spec.rb @@ -61,7 +61,7 @@ context 'it should expand the superset for features' do include_context 'features' let(:additional_params) { {:features => %w(Tools)} } - let(:munged_args) { {:features => %w(ADV_SSMS Conn SSMS)} } + let(:munged_args) { {:features => %w(ADV_SSMS BC Conn SDK SSMS)} } it_should_behave_like 'create' end @@ -109,7 +109,7 @@ context 'it should install the expanded tools set' do include_context 'features' @feature_params[:features] = %w(Tools) - let(:feature_add) { %w(ADV_SSMS Conn SSMS) } + let(:feature_add) { %w(ADV_SSMS BC Conn SDK SSMS) } it_should_behave_like 'features=', @feature_params end From 24d7d8a88780f2e064f7ebfd5053621857b536b7 Mon Sep 17 00:00:00 2001 From: Iristyle Date: Mon, 1 Jun 2015 23:11:30 -0700 Subject: [PATCH 6/6] (maint) Use agent facts when removing SQL instances - Previously the test that removes instance was hard-coded to use 'MSSQLSERVER', but this makes the test a bit more fragile. Instead, use Beaker to load the agents facts, passing the module sqlserver/lib/facter directory in via FACTERLIB so that the names of all instances can be retrieved. --- spec/acceptance/sqlserver_features_spec.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/spec/acceptance/sqlserver_features_spec.rb b/spec/acceptance/sqlserver_features_spec.rb index 37e1af88..8d61dbc6 100644 --- a/spec/acceptance/sqlserver_features_spec.rb +++ b/spec/acceptance/sqlserver_features_spec.rb @@ -241,9 +241,12 @@ def bind_and_apply_failing_manifest(host, features, ensure_val = 'present') features = ['Tools', 'BC', 'Conn', 'SSMS', 'ADV_SSMS', 'SDK', 'IS', 'MDS'] before(:all) do - # this assumes that only default MSSQLSERVER has been installed so far - # be careful about running tests out of sequence - remove_sql_instances(host, {:version => version, :instance_names => ['MSSQLSERVER']}) + # use agents fact to get instance names + distmoduledir = on(host, "echo #{host['distmoduledir']}").raw_output.chomp + facter_opts = {:environment => {'FACTERLIB' => "#{distmoduledir}/sqlserver/lib/facter" }} + + names = eval(fact_on(host, 'sqlserver_instances', facter_opts)).values.inject(:merge).keys + remove_sql_instances(host, {:version => version, :instance_names => names}) end after(:all) do