11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
## 2017-11-15 - Supported Release 2.0.1

### Summary
Small release with bug fixes and documentation updates.

#### Fixed

- Allow connections over TLS 1.1+ by replacing OLEDB driver with SQL Native Client ([MODULES-5693](https://tickets.puppetlabs.com/browse/MODULES-5693))
- Ensure instances without SQL Engine are discoverable ([MODULES-5566](https://tickets.puppetlabs.com/browse/MODULES-5566))
- Updated documentation to include 2016 as a supported version of SQL Server

## 2017-08-10 - Supported Release 2.0.0

### Summary
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

## Overview

The sqlserver module installs and manages Microsoft SQL Server 2012 and 2014 on Windows systems.
The sqlserver module installs and manages Microsoft SQL Server 2012, 2014 and 2016 on Windows systems.

## Module Description

Expand All @@ -32,10 +32,9 @@ Microsoft SQL Server is a database platform for Windows. The sqlserver module le

The sqlserver module requires the following:

* Puppet Enterprise 3.7 or later.
* .NET 3.5. (Installed automatically if not present. This might require an internet connection.)
* The contents of the SQL Server ISO file, mounted or extracted either locally or on a network share.
* Windows Server 2012 or 2012 R2.
* Windows Server 2012, 2012 R2, or 2016.

### Beginning with sqlserver

Expand Down
22 changes: 0 additions & 22 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,3 @@ task :validate do
sh "erb -P -x -T '-' #{template} | ruby -c"
end
end

require 'rspec/core/rake_task'
desc 'test tiering'
RSpec::Core::RakeTask.new(:test_tier) do |t|
# Setup rspec opts
t.rspec_opts = ['--color']

# TEST_TIERS env variable is a comma separated list of tiers to run. e.g. low, medium, high
if ENV['TEST_TIERS']
test_tiers = ENV['TEST_TIERS'].split(',')
raise 'TEST_TIERS env variable must have at least 1 tier specified. low, medium or high (comma separated).' if test_tiers.count == 0
test_tiers.each do |tier|
raise "#{tier} not a valid test tier." unless %w(low medium high).include?(tier)
t.rspec_opts.push("--tag tier_#{tier}")
end
else
puts 'TEST_TIERS env variable not defined. Defaulting to run all tests.'
end

# Implement an override for the pattern with BEAKER_PATTERN env variable.
t.pattern = ENV['BEAKER_PATTERN'] ? ENV['BEAKER_PATTERN'] : 'spec/acceptance'
end
127 changes: 52 additions & 75 deletions lib/puppet_x/sqlserver/features.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require 'puppet/util/windows'

SQL_2012 ||= 'SQL_2012'
SQL_2014 ||= 'SQL_2014'
SQL_2016 ||= 'SQL_2016'
Expand All @@ -10,39 +12,26 @@ module Sqlserver
class Features
private

include Puppet::Util::Windows::Registry
extend Puppet::Util::Windows::Registry

SQL_CONFIGURATION ||= {
SQL_2012 => {
:major_version => 11,
:wmi_path => 'ComputerManagement11',
:registry_path => '110',
},
SQL_2014 => {
:major_version => 12,
:wmi_path => 'ComputerManagement12',
:registry_path => '120',
},
SQL_2016 => {
:major_version => 13,
:wmi_path => 'ComputerManagement13',
:registry_path => '130',
}
}

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_CONFIGURATION[version][:wmi_path]
context = WIN32OLE.new('WbemScripting.SWbemNamedValueSet')
context.Add("__ProviderArchitecture", 64)
locator = WIN32OLE.new('WbemScripting.SWbemLocator')
# WMI Path must use backslashes. Ruby 2.3 can cause crashes with forward slashes
locator.ConnectServer(nil, "root\\Microsoft\\SqlServer\\#{ver}", nil, nil, nil, nil, nil, context)
end
HKLM ||= 'HKEY_LOCAL_MACHINE'

def self.get_parent_path(key_path)
# should be the same as SQL_REG_ROOT
Expand All @@ -55,81 +44,66 @@ def self.get_reg_key_val(win32_reg_key, val_name, reg_type)
nil
end

def self.get_sql_reg_val_features(key_name, reg_val_feat_hash)
require 'win32/registry'
def self.key_exists?(path)
begin
open(HKLM, path, KEY_READ | KEY64) {}
return true
rescue
return false
end
end

def self.get_sql_reg_val_features(key_name, reg_val_feat_hash)
vals = []

begin
hklm = Win32::Registry::HKEY_LOCAL_MACHINE
vals = hklm.open(key_name, KEY_READ | KEY_WOW64_64KEY) do |key|
vals = open(HKLM, key_name, KEY_READ | KEY64) 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
rescue Puppet::Util::Windows::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'
def self.get_reg_instance_info(friendly_version)
instance_root = 'SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names'
return [] unless key_exists?(instance_root)
discovered = {}
open(HKLM, "#{instance_root}", KEY_READ | KEY64) do |registry|
each_key(registry) do |instance_type, _|
open(HKLM, "#{instance_root}\\#{instance_type}", KEY_READ | KEY64) do |instance|
each_value(instance) do |short_name, _, long_name|
root = "Software\\Microsoft\\Microsoft SQL Server\\#{long_name}"
discovered[short_name] ||= {
'name' => short_name,
'reg_root' => [],
'version' => open(HKLM, "#{root}\\MSSQLServer\\CurrentVersion", KEY_READ | KEY64) { |r| values(r)['CurrentVersion'] },
'version_friendly' => friendly_version
}

discovered[short_name]['reg_root'].push(root)
end
end
end
end
discovered.values
end

def self.get_sql_reg_key_features(key_name, reg_key_feat_hash, instance_name)
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|
open(HKLM, "#{key_name}\\#{subkey}", KEY_READ | KEY64) do |feat_key|
get_reg_key_val(feat_key, instance_name, Win32::Registry::REG_SZ)
end
rescue Win32::Registry::Error # subkey doesn't exist
rescue Puppet::Util::Windows::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
Expand Down Expand Up @@ -215,8 +189,9 @@ def self.get_instances
.map do |version|
major_version = SQL_CONFIGURATION[version][:major_version]

instances = get_instance_names_by_ver(version)
.map { |name| [ name, get_instance_info(version, name) ] }
instances = get_reg_instance_info(version).map do |instance|
[instance['name'], get_instance_info(version,instance)]
end

# Instance names are unique on a single host, but not for a particular SQL Server version therefore
# it's possible to request information for a valid instance_name but not for version. In this case
Expand Down Expand Up @@ -266,15 +241,17 @@ def self.get_features
# "RS"
# ]
# }
def self.get_instance_info(version, instance_name)
def self.get_instance_info(version, sql_instance)
return nil if version.nil?
sql_instance = get_wmi_instance_info(version, instance_name)
# Instance names are unique on a single host, but not for a particular SQL Server version therefore
# it's possible to request information for a valid instance_name but not for version. In this case
# we just return nil.
return nil if sql_instance['reg_root'].nil?
feats = get_instance_features(sql_instance['reg_root'], sql_instance['name'])
sql_instance.merge({'features' => feats})

feats = sql_instance['reg_root'].map do |reg_root|
get_instance_features(reg_root, sql_instance['name'])
end
sql_instance.merge({'features' => feats.uniq})
end
end
end
Expand Down
9 changes: 5 additions & 4 deletions lib/puppet_x/sqlserver/sql_connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ def open(config)

def get_connection_string(config)
params = {
'Provider' => 'SQLOLEDB.1',
'Initial Catalog' => config[:database] || 'master',
'Application Name' => 'Puppet',
'Data Source' => '.'
'Provider' => 'SQLNCLI11',
'Initial Catalog' => config[:database] || 'master',
'Application Name' => 'Puppet',
'Data Source' => '.',
'DataTypeComptibility' => 80
}

admin_user = config[:admin_user] || ''
Expand Down
1 change: 1 addition & 0 deletions locales/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ gettext:
# Patterns for +Dir.glob+ used to find all files that might contain
# translatable content, relative to the project root directory
source_files:
- './lib/**/*.rb'

2 changes: 1 addition & 1 deletion metadata.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "puppetlabs-sqlserver",
"version": "2.0.0",
"version": "2.0.1",
"author": "Puppet Inc",
"summary": "The `sqlserver` module installs and manages MS SQL Server 2012, 2014 and 2016 on Windows systems.",
"license": "proprietary",
Expand Down
7 changes: 2 additions & 5 deletions readmes/README_ja_JP.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

## 概要

sqlserverモジュールは、WindowsシステムにMicrosoft SQL Server 2012および2014をインストールし、管理します
Microsoft SQL Server 2012、2014、2016は、sqlserverモジュールにより、Windowsシステムでインストールと管理を行います

## モジュールの説明

Expand All @@ -32,10 +32,9 @@ Microsoft SQL Serverは、Windows用のデータベースプラットフォー

sqlserverモジュールの要件は次のとおりです。

* Puppet Enterprise 3.7以降
* .NET 3.5. (存在しない場合、自動的にインストールされます。そのためにインターネット接続が必要になる場合があります)
* SQL Server ISOファイルの内容(ローカルまたはネットワーク共有上にマウントまたは展開されていること)
* Windows Server 2012または2012 R2
* Windows Server 2012、2012 R2、2016。

### sqlserverを開始する

Expand Down Expand Up @@ -1069,8 +1068,6 @@ sys.configurationsで管理するオプションを指定します。有効な

## 制限事項

本モジュールは、Windows Server 2012または2012 R2のみで使用でき、Puppet Enterprise 3.7以降でのみ動作します。

このモジュールは、指定ホスト上のSQL Serverの単独バージョンのみ管理できます(SQL Server 2012、2014、2016のうちいずれか1つのみ)。このモジュールでは同一バージョンの複数のSQL Serverインスタンスを管理できます。

このモジュールは、SQL Server Native Client SDK (別名SNAC_SDK)を管理できません。SQL ServerのインストールメディアはSDKをインストールできますが、SDKをアンインストールすることはできません。'sqlserver_features' factはSDKの存在を検出します。
Expand Down
2 changes: 1 addition & 1 deletion spec/acceptance/sqlserver_config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def ensure_sqlserver_instance(inst_name, ensure_val = 'present')

after(:all) do
# remove the newly created instance
ensure_sqlserver_instance('absent')
ensure_sqlserver_instance(inst_name, 'absent')
end

it 'Create New Admin Login:', :tier_low => true do
Expand Down
13 changes: 2 additions & 11 deletions spec/acceptance/z_last_sqlserver_features_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -204,17 +204,8 @@ def bind_and_apply_failing_manifest(features, ensure_val = 'present')

before(:all) do
puppet_version = (on host, puppet('--version')).stdout.chomp

if puppet_version =~ /^4\.\d+\.\d+/
json_result = JSON.parse((on host, puppet('facts --render-as json')).raw_output)["values"]["sqlserver_instances"]
names = json_result.collect { |k, v| json_result[k].keys }.flatten
else
# 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
end
json_result = JSON.parse((on host, puppet('facts --render-as json')).raw_output)["values"]["sqlserver_instances"]
names = json_result.collect { |k, v| json_result[k].keys }.flatten
remove_sql_instances(host, {:version => sql_version, :instance_names => names})
end

Expand Down
Loading