17 changes: 16 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a

## [Unreleased]

## [2.4.0] - 2019-03-12

### Added

- Get agent jobs Bolt task ([MODULES-8692](https://tickets.puppetlabs.com/browse/MODULES-8692))
- Get sql logins Bolt task ([MODULES-8606](https://tickets.puppetlabs.com/browse/MODULES-8606))
- Set sql logins Bolt task ([MODULES-8606](https://tickets.puppetlabs.com/browse/MODULES-8606))

### Fixed

- Cannot manage a role with the same name on two instances or two databases ([MODULES-8677](https://tickets.puppetlabs.com/browse/MODULES-8677)) (Thanks [Dylan Ratcliffe](https://github.com/dylanratcliffe))
- Removing a SQL Login via `ensure => absent` in a sqlserver::login resource is not idempotent. ([MODULES-8685](https://tickets.puppetlabs.com/browse/MODULES-8685)) (Thanks [Dylan Ratcliffe](https://github.com/dylanratcliffe))

## [2.3.0] - 2019-01-22

### Added
Expand Down Expand Up @@ -209,5 +222,7 @@ attempts to reinstall an instance that already exists ([MODULES-6022](https://ti

Initial release.

[Unreleased]: https://github.com/puppetlabs/puppetlabs-sqlserver/compare/2.4.0..master
[2.4.0]: https://github.com/puppetlabs/puppetlabs-sqlserver/compare/2.3.0..2.4.0
[2.3.0]: https://github.com/puppetlabs/puppetlabs-sqlserver/compare/2.2.0..2.3.0
[2.2.0]: https://github.com/puppetlabs/puppetlabs-sqlserver/compare/2.1.1..2.2.0
[2.3.0]: https://github.com/puppetlabs/puppetlabs-sqlserver/compare/2.2.0..2.3.0
126 changes: 126 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
* [Manage the user's permissions](#manage-the-above-users-permissions)
* [Run custom TSQL statements](#run-custom-tsql-statements)
5. [Reference - An under-the-hood peek at what the module is doing and how](#reference)
* [Types](#types)
* [Defined Types](#defined-types)
* [Bolt Tasks](#bolt-tasks)
6. [Limitations - OS compatibility, etc.](#limitations)
7. [Development - Guide for contributing to the module](#development)

Expand Down Expand Up @@ -1056,6 +1059,129 @@ Default: `false`.
> * [Reconfigure](http://msdn.microsoft.com/en-us/library/ms176069.aspx)
> * [Server Configuration Options](http://msdn.microsoft.com/en-us/library/ms189631.aspx)
### Bolt Tasks

#### sqlserver::get_sql_logins

This task retrieves information about a login, or a set of logins, from the sql instances running on a node.

When no parameters are specified, it returns summary level information about all of the logins configured for all sql instances running on a node or node set.

Use the `detailed` parameter to return more detailed information, including the SID's and the name of the instance a login was retrieved from.

##### parameters

* **instance_name**

The name of the instance to query for logins. By default, leave blank for all instances running on a node.
Pass the values `.`, `MSSQLSERVER`, or the node name to query just the default instance.
Refer to named instances either by the short name of the instance or by `<COMPUTERNAME>\<INSTANCE_NAME>`.
This is an optional parameter which accepts a single string or array of strings as input.

* **login_name**

The name of a particular login to search for, or the search pattern for a set of logins.
If no value is passed to this variable, all logins are returned.
By default any values passed to this parameter are treated like a search string.
Searches are done using the PowerShell `-match` operator.
For example, if the string `sql` is passed to this parameter, logins such as `NT SERVICE\SQLWriter`
and `##MS_PolicyTsqlExecutionLogin##` will be returned in the result set.
If the `exact_match` parameter is set to true, only exact matches are accepted and neither of those logins would have been returned.
This is an optional parameter which accepts a single string or array of strings as input.

* **exact_match**

Set this to true to change the behavior of the `login_name` parameter so that only logins exactly matching one of the provided login_names is returned in the result set.
This is an optional parameter which accepts either `true` or `false`. It's default value is false.

* **detailed**

This parameter causes the task to return more detailed information for each login.
By default, the list of properties returned about each login is: `Name`,`isDisabled`,`isLocked`,`IsPasswordExpired`,`CreateDate`,`DateLastModified`.
Setting this parameter to true adds in the following properties: `DefaultDatabase`,`DenyWindowsLogin`,`HasAccess`,`ID`,`IsSystemObject`,`Language`,`LanguageAlias`,`,LoginType`,`MustChangePassword`,`PasswordExpirationEnabled`,`PasswordHashAlgorithm`,`PasswordPolicyEnforced`,`SQLSID`,`ADSid`,`WindowsLoginAccessType`,`UserData`,`State`,`IsDesignMode`,`InstanceName`

> **Note about `SQLSID` and `ADSID`.**
>
> The `SQLSID` property is this module's property name for the binary representation of a SID that SQLServer keeps internally in tables like `sys.server_principals`.
> It will look like `0x01` for accounts like `sa`, or something longer like `0x0106000000000009010000005FB6DAC7F7DB546D706711B128B5063888B01770` for other accounts.
> This `sid` does not look like a normal `sid` you might see outside of SQLServer, but it is returned as part of the detailed information to make it easier to correlate the logins returned by this module and query results from SQLServer.
> The `ADSID` property is a more normal looking `sid` you might get from PowerShell Active Directory (AD) query tools.
> It is a direct translation of that `SQLSID` into the Microsoft string `sid` form and looks like `S-1-5-80-1402415987-66678372-3059512406-1823130485-2345841878`.
> This translation is done to make it easier to correlate SQLServer logins with AD users and detect when something like a user has been disconnected from its real AD SID.
> If the detailed information for a login does not contain a value for the AD SID property, it means that the login is internal to SQLServer and does not have a valid AD format `sid`.
#### sqlserver::set_sql_logins

##### parameters

* **instance_name**

The name of the instance to find the login you are setting properties on.
By default, this parameter only uses the default instance.
Refer to named instances by either the short name of the instance, or by `<COMPUTERNAME>\<INSTANCE_NAME>`.
Specifying an instance name will access only that instance. To affect a login on more than one instance,
specify all of the required instance names as an array of values.
Pass the values `.`, `MSSQLSERVER`, or the node name to query only the default instance.
This is an optional parameter which will accept a single string or array of strings as input.

* **login_name**

The name of a particular login to to set properties on.
By default, this parameter expects an exact match. To use pattern matching see the `fuzzy_match` parameter.
This is an optional parameter which accepts a single string or array of strings as input.

* **fuzzy_match**

Modifies the behavior of the `login_name` parameter so that the value given is a login name pattern to match.
Searches are done using the PowerShell `-match` operator.
For example, if the string `sql` is passed to the `login_name` parameter, while `fuzzy_match` is set to true, logins such as `NT SERVICE\SQLWriter`
and `##MS_PolicyTsqlExecutionLogin##` will be returned in the result set.
This is an
optional parameter which accepts either `true` or `false`. Its default value is false.

* **enabled**

Set this parameter to true to enable a login. Set to false to disable it.
This is an optional boolean parameter. The return value will be an element in the json return
specifying that the new value for the `isDisabled` property of the login object will be either `true` or `false`.

* **password**

Provide a value specifying the new password to use for a login.
Note that there are possible string parsing issues when using this parameter.
For example, attempting to set a password `'pa$ssword#"'` may not parse correctly.
To get the double quote to end up in the password correctly, triple quote escape the double quote,
for example, `'pa$ssword#"""'`.
To ensure your password is interpreted and set correctly, try to echo the password out using `bolt command run` first to ensure that the characters end up on the target node correctly, for example, `bolt command run 'Write-Host ''This is """awesome""".'''`

##### noop

This task supports the `--noop` flag. The task will return json values indicating the
actions it would have taken and the logins it would have affected without actually taking any action.
It will not inspect and return to you what the state of a property was before taking the action.
Use this parameter — especially when using the `fuzzy_match` parameter — to ensure that you are affecting only the logins you intend to.

#### sqlserver::get_sqlagent_jobs

##### parameters

* **instance_name**

The name of the instance to get job information from. Leave this variable blank to return information from all instances by default.
Refer to named instances by either the short name of the instance or by `<COMPUTERNAME>\<INSTANCE_NAME>`. Specifying an instance name will access only that instance.
Pass the values `.`, `MSSQLSERVER`, or the node name to query only the default instance.
This is an optional parameter which accepts a single string or array of strings as input.

* **job_name**

The name of a job, or the pattern, to match on a jobs name.
This is an optional parameter which accepts a single string or array of strings as input.

* **exact_match**

Set this to true to change the behavior of the `job_name` parameter so that only jobs with names exactly matching one of the provided job_names is returned in the result set.
This is an optional parameter which accepts either `true` or `false`. It's default value is false.

### Microsoft SQL Server terms

Terminology differs somewhat between various database systems; please refer to this list of terms for clarification.
Expand Down
2 changes: 1 addition & 1 deletion manifests/role.pp
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
}

if size($members) > 0 or $members_purge == true {
sqlserver_tsql{ "role-${role}-members":
sqlserver_tsql{ "${sqlserver_tsql_title}-members":
command => template('sqlserver/create/role/members.sql.erb'),
onlyif => template('sqlserver/query/role/member_exists.sql.erb'),
instance => $instance,
Expand Down
2 changes: 1 addition & 1 deletion manifests/role/permissions.pp
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
##
# Parameters required in template are _state, role, _upermissions, database, type, with_grant_option
##
sqlserver_tsql{ "role-permissions-${role}-${_state}${_grant_option}-${instance}":
sqlserver_tsql{ "role-permissions-${role}-${_state}${_grant_option}-${instance}-${database}":
instance => $instance,
command => template('sqlserver/create/role/permissions.sql.erb'),
onlyif => template('sqlserver/query/role/permission_exists.sql.erb'),
Expand Down
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.3.0",
"version": "2.4.0",
"author": "Puppet Inc",
"summary": "The `sqlserver` module installs and manages MS SQL Server 2012, 2014, 2016, 2017, and 2019 on Windows systems.",
"license": "proprietary",
Expand Down
6 changes: 6 additions & 0 deletions spec/acceptance/sqlserver_login_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,12 @@ def create_login_manifest (testcase,login_name,login_password,options = {})
ensure_manifest_execute(pp)
end

it "remove a #{testcase} should be idempotent", :tier_low => true do
options = { 'ensure' => 'absent' }
pp = create_login_manifest(testcase,@login_under_test,@login_passwd,options)
execute_manifest(pp, :catch_changes => true)
end

it "should not exist in the principals table after deletion", :tier_low => true do
query = "SELECT principal_id FROM SYS.server_principals WHERE name = '#{@login_under_test}' AND [type] = '#{@sql_principal_type}'";
run_sql_query(host, run_sql_query_opts_as_sa(query, expected_row_count = 0))
Expand Down
41 changes: 41 additions & 0 deletions spec/acceptance/sqlserver_role_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,47 @@ def ensure_sqlserver_logins_users(db_name)
run_sql_query(host, { :query => query, :server => hostname, :expected_row_count => 6 })
end

it "Create a database-specific role with the same name on two databases", :tier_low => true 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
require 'pry'; binding.pry;
execute_manifest(pp, opts = {}) do |r|
expect(r.stderr).not_to match(/Error/i)
end

# 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(host, { :query => query, :server => hostname, :expected_row_count => 6 })
end

it "Create server role #{@role} with optional members and optional members-purge", :tier_low => true do
pp = <<-MANIFEST
sqlserver::config{'MSSQLSERVER':
Expand Down
7 changes: 4 additions & 3 deletions spec/acceptance/z_last_sqlserver_features_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,10 @@ def bind_and_apply_failing_manifest(features, ensure_val = 'present')
ensure_sql_features(features)

validate_sql_install(host, {:version => sql_version}) do |r|
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/)
# SQL Server 2016 will not install the client tools features.
expect(r.stdout).to match(/Client Tools Connectivity/) unless sql_version.to_i >= 2016
expect(r.stdout).to match(/Client Tools Backwards Compatibility/) unless sql_version.to_i >= 2016
expect(r.stdout).to match(/Client Tools SDK/) unless sql_version.to_i >= 2016
expect(r.stdout).to match(/Integration Services/)
expect(r.stdout).to match(/Master Data Services/)
end
Expand Down
5 changes: 3 additions & 2 deletions spec/defines/role/permissions_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
RSpec.describe 'sqlserver::role::permissions' do
include_context 'manifests' do
let(:title) { 'myTitle' }
let(:sqlserver_tsql_title) { 'role-permissions-myCustomRole-GRANT-MSSQLSERVER' }
let(:sqlserver_tsql_title) { 'role-permissions-myCustomRole-GRANT-MSSQLSERVER-master' }
let(:params) { {
:role => 'myCustomRole',
:permissions => %w(INSERT UPDATE DELETE SELECT),
Expand Down Expand Up @@ -116,6 +116,7 @@
describe 'customDatabase' do
let(:additional_params) { {:database => 'customDatabase'} }
let(:should_contain_command) { ['USE [customDatabase];'] }
let(:sqlserver_tsql_title) { 'role-permissions-myCustomRole-GRANT-MSSQLSERVER-customDatabase' }
it_behaves_like 'sqlserver_tsql command'
let(:should_contain_onlyif) { ['USE [customDatabase];'] }
it_behaves_like 'sqlserver_tsql onlyif'
Expand All @@ -135,7 +136,7 @@
:instance => instance
} }
it {
should contain_sqlserver_tsql("role-permissions-myCustomRole-GRANT-#{instance}").with_instance(instance)
should contain_sqlserver_tsql("role-permissions-myCustomRole-GRANT-#{instance}-master").with_instance(instance)
}
end
end
Expand Down
4 changes: 2 additions & 2 deletions spec/defines/role_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@
end

context 'members =>' do
let(:sqlserver_tsql_title) { 'role-myCustomRole-members' }
let(:sqlserver_tsql_title) { 'role-MSSQLSERVER-master-myCustomRole-members' }
describe '[test these users]' do
let(:additional_params) { {
:members => %w(test these users),
Expand All @@ -156,7 +156,7 @@
end
end
context 'members_purge =>' do
let(:sqlserver_tsql_title) { 'role-myCustomRole-members' }
let(:sqlserver_tsql_title) { 'role-MSSQLSERVER-master-myCustomRole-members' }
context 'true' do
describe 'type => SERVER and members => []' do
let(:additional_params) { {
Expand Down
8 changes: 4 additions & 4 deletions spec/spec_helper_acceptance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
require 'beaker/testmode_switcher/dsl'


WIN_ISO_ROOT = "http://int-resources.ops.puppetlabs.net/ISO/Windows/2012"
WIN_ISO_ROOT = "https://artifactory.delivery.puppetlabs.net/artifactory/generic__iso/iso/windows"
WIN_2012R2_ISO = "en_windows_server_2012_r2_with_update_x64_dvd_6052708.iso"
QA_RESOURCE_ROOT = "http://int-resources.ops.puppetlabs.net/QA_resources/microsoft_sql/iso/"
SQL_2019_ISO = "en_sql_server_2019_developer_x64_CTP2.iso"
QA_RESOURCE_ROOT = "https://artifactory.delivery.puppetlabs.net/artifactory/generic__iso/iso/SQLServer"
SQL_2019_ISO = "SQLServer-2019-CTP2-x64-ENU.iso"
SQL_2016_ISO = "en_sql_server_2016_enterprise_with_service_pack_1_x64_dvd_9542382.iso"
SQL_2014_ISO = "SQLServer2014-x64-ENU.iso"
SQL_2014_ISO = "SQLServer2014SP3-FullSlipstream-x64-ENU.iso"
SQL_2012_ISO = "SQLServer2012SP1-FullSlipstream-ENU-x64.iso"
SQL_ADMIN_USER = 'sa'
SQL_ADMIN_PASS = 'Pupp3t1@'
Expand Down
24 changes: 24 additions & 0 deletions tasks/get_sql_logins.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"puppet_task_version": 1,
"supports_noop": false,
"input_method": "powershell",
"description": "Retrieve information about the logins configured for a SQL Server instance.",
"parameters": {
"instance_name": {
"description": "The name of the SQL Instance running on the machine to connect to. Leave blank for the default instance of MSSQLSERVER",
"type": "Optional[Variant[Array[String], String]]"
},
"login_name": {
"description": "The name of a particular login to search for. You can use partial names and any pattern that will work with the PowerShell '-match' operator.",
"type": "Optional[Variant[Array[String], String]]"
},
"exact_match": {
"description": "If set to true it will force names passed to the LoginName parameter to be an exact match to a SQL Login to pass the filter.",
"type": "Optional[Boolean]"
},
"detailed": {
"description": "Return more detailed information from the server instead of the default summary information",
"type": "Optional[Boolean]"
}
}
}
Loading