New module: win dsc (#24872)

* Added win_dsc module file

* mute output and track reboot requirements

* added tests

* proper conditionals for test

* Added moar conditionals for test

* ci fixes

* Added metadata

* fixed integration test yaml

* ci fix

* ci fix

* added module_version param and output, no longer chokes on multiple versions found.

* ci fix

* code review improvements, make return vars more pythonic, cleanup
removed reference to handles in commit message

* Fixed tests, clearer documentation

* fixed trailing whitespace
This commit is contained in:
Trond Hindenes 2017-06-01 23:50:12 +02:00 committed by Matt Davis
parent 055cc32830
commit 055fd6f5f5
5 changed files with 475 additions and 0 deletions

View file

@ -0,0 +1,241 @@
#!powershell
# (c) 2015, Trond Hindenes <trond@hindenes.com>, and others
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# WANT_JSON
# POWERSHELL_COMMON
#Temporary fix
#Set-StrictMode -Off
$params = Parse-Args $args -supports_check_mode $true
$result = @{
changed = $false
}
#Check that we're on at least Powershell version 5
if ($PSVersionTable.PSVersion.Major -lt 5)
{
Fail-Json -obj $Result -message "This module only runs on Powershell version 5 or higher"
}
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
$resourcename = Get-AnsibleParam -obj $params -name "resource_name" -type "str" -failifempty $true -resultobj $result
$module_version = Get-AnsibleParam -obj $params -name "module_version" -type "str" -default "latest"
#From Ansible 2.3 onwards, params is now a Hash Array
$Attributes = $params.GetEnumerator() | where {$_.key -ne "resource_name"} | where {$_.key -notlike "_ansible_*"}
if (!($Attributes))
{
Fail-Json -obj $result -message "No attributes specified"
}
#Always return some basic info
$result["resource_name"] = $resourcename
$result["attributes"] = $Attributes
$result["reboot_required"] = $null
# Build Attributes Hashtable for DSC Resource Propertys
$Attrib = @{}
foreach ($key in $Attributes)
{
$result[$key.name] = $key.value
$Attrib.Add($Key.Key,$Key.Value)
}
$result["dsc_attributes"] = $attrib
$Config = @{
Name = ($resourcename)
Property = @{
}
}
#Get the latest version of the module
if ($module_version -eq "latest")
{
$Resource = Get-DscResource -Name $resourcename -ErrorAction SilentlyContinue | sort Version | select -Last 1
}
else
{
$Resource = Get-DscResource -Name $resourcename -ErrorAction SilentlyContinue | where {$_.Version -eq $module_version}
}
if (!$Resource)
{
if ($module_version -eq "latest")
{
Fail-Json -obj $result -message "Resource $resourcename not found"
}
else
{
Fail-Json -obj $result -message "Resource $resourcename with version $module_version not found"
}
}
#Get the Module that provides the resource. Will be used as
#mandatory argument for Invoke-DscResource
$Module = $Resource.ModuleName
$result["module_version"] = $Module.Version.ToString()
#Convert params to correct datatype and inject
$attrib.Keys | foreach-object {
$Key = $_.replace("item_name", "name")
$prop = $resource.Properties | where {$_.Name -eq $key}
if (!$prop)
{
#If its a credential specified as "credential", Ansible will support credential_username and credential_password. Need to check for that
$prop = $resource.Properties | where {$_.Name -eq $key.Replace("_username","")}
if ($prop)
{
#We need to construct a cred object. At this point keyvalue is the username, so grab the password
$PropUserNameValue = $attrib.Item($_)
$PropPassword = $key.Replace("_username","_password")
$PropPasswordValue = $attrib.$PropPassword
$cred = New-Object System.Management.Automation.PSCredential ($PropUserNameValue, ($PropPasswordValue | ConvertTo-SecureString -AsPlainText -Force))
[System.Management.Automation.PSCredential]$KeyValue = $cred
$config.Property.Add($key.Replace("_username",""),$KeyValue)
}
ElseIf ($key.Contains("_password"))
{
#Do nothing. We suck in the password in the handler for _username, so we can just skip it.
}
Else
{
Fail-Json -obj $result -message "Property $key in resource $resourcename is not a valid property"
}
}
ElseIf ($prop.PropertyType -eq "[string]")
{
[String]$KeyValue = $attrib.Item($_)
$config.Property.Add($key,$KeyValue)
}
ElseIf ($prop.PropertyType -eq "[string[]]")
{
#KeyValue is an array of strings
[String]$TempKeyValue = $attrib.Item($_)
[String[]]$KeyValue = $TempKeyValue.Split(",").Trim()
$config.Property.Add($key,$KeyValue)
}
ElseIf ($prop.PropertyType -eq "[UInt32[]]")
{
#KeyValue is an array of integers
[String]$TempKeyValue = $attrib.Item($_)
[UInt32[]]$KeyValue = $attrib.Item($_.split(",").Trim())
$config.Property.Add($key,$KeyValue)
}
ElseIf ($prop.PropertyType -eq "[bool]")
{
if ($attrib.Item($_) -like "true")
{
[bool]$KeyValue = $true
}
ElseIf ($attrib.Item($_) -like "false")
{
[bool]$KeyValue = $false
}
$config.Property.Add($key,$KeyValue)
}
ElseIf ($prop.PropertyType -eq "[int]")
{
[int]$KeyValue = $attrib.Item($_)
$config.Property.Add($key,$KeyValue)
}
ElseIf ($prop.PropertyType -eq "[CimInstance[]]")
{
#KeyValue is an array of CimInstance
[CimInstance[]]$KeyVal = @()
[String]$TempKeyValue = $attrib.Item($_)
#Need to split on the string }, because some property values have commas in them
[String[]]$KeyValueStr = $TempKeyValue -split("},")
#Go through each string of properties and create a hash of them
foreach($str in $KeyValueStr)
{
[string[]]$properties = $str.Split("{")[1].Replace("}","").Trim().Split([environment]::NewLine).Trim()
$prph = @{}
foreach($p in $properties)
{
$pArr = $p -split "="
#if the value can be an int we must convert it to an int
if([bool]($pArr[1] -as [int] -is [int]))
{
$prph.Add($pArr[0].Trim(),$pArr[1].Trim() -as [int])
}
else
{
$prph.Add($pArr[0].Trim(),$pArr[1].Trim())
}
}
#create the new CimInstance
$cim = New-CimInstance -ClassName $str.Split("{")[0].Trim() -Property $prph -ClientOnly
#add the new CimInstance to the array
$KeyVal += $cim
}
$config.Property.Add($key,$KeyVal)
}
ElseIf ($prop.PropertyType -eq "[Int32]")
{
# Add Supoort for Int32
[int]$KeyValue = $attrib.Item($_)
$config.Property.Add($key,$KeyValue)
}
ElseIf ($prop.PropertyType -eq "[UInt32]")
{
# Add Support for [UInt32]
[UInt32]$KeyValue = $attrib.Item($_)
$config.Property.Add($key,$KeyValue)
}
}
try
{
#Defined variables in strictmode
$TestError, $TestError = $null
$TestResult = Invoke-DscResource @Config -Method Test -ModuleName $Module -ErrorVariable TestError -ErrorAction SilentlyContinue
if ($TestError)
{
throw ($TestError[0].Exception.Message)
}
ElseIf (($TestResult.InDesiredState) -ne $true)
{
if ($check_mode -eq $False)
{
$SetResult = Invoke-DscResource -Method Set @Config -ModuleName $Module -ErrorVariable SetError -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
$result["reboot_required"] = $SetResult.RebootRequired
}
$result["changed"] = $true
if ($SetError)
{
throw ($SetError[0].Exception.Message)
}
}
}
Catch
{
Fail-Json -obj $result -message $_[0].Exception.Message
}
#set-attr -obj $result -name "property" -value $property
Exit-Json -obj $result

View file

@ -0,0 +1,110 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2015, Trond Hindenes <trond@hindenes.com>, and others
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# this is a windows documentation stub. actual code lives in the .ps1
# file of the same name
ANSIBLE_METADATA = {'metadata_version': '1.0',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: win_dsc
version_added: "2.4"
short_description: Invokes a PowerShell DSC configuration
description: |
Invokes a PowerShell DSC Configuration. Requires PowerShell version 5 (February release or newer).
Most of the parameters for this module are dynamic and will vary depending on the DSC Resource.
In order to find the required parameters for a given DSC resource, you can use the following on-liner:
'Get-DscResource <dsc_resource> | select -ExpandProperty properties'
Also note that credentials are handled as follows: If the resource accepts a credential type property called "cred",
the ansible parameters would be cred_username and cred_password.
These will be used to inject a credential object on the fly for the DSC resource.
options:
resource_name:
description:
- The DSC Resource to use. Must be accessible to PowerShell using any of the default paths.
required: true
module_version:
description: |
Can be used to configure the exact version of the dsc resource to be invoked.
Useful if the target node has multiple versions installed of the module containing the DSC resource.
If not specified, the module will follow standard Powershell convention and use the highest version available.
default: latest
author: Trond Hindenes
'''
EXAMPLES = r'''
# Playbook example
- name: Extract zip file
win_dsc:
resource_name: archive
ensure: Present
path: "C:\\Temp\\zipfile.zip"
destination: "C:\\Temp\\Temp2"
- name: Invoke DSC with check mode
win_dsc:
resource_name: windowsfeature
name: telnet-client
'''
RETURN = r'''
resource_name:
description: The name of the invoked resource
returned: always
type: string
sample: windowsfeature
module_version:
description: The version of the dsc resource/module used.
returned: success
type: string
sample: "1.0.1"
attributes:
description: The attributes/parameters passed in to the DSC resource as key/value pairs
returned: always
type: complex
sample:
contains:
Key:
description: Attribute key
Value:
description: Attribute value
dsc_attributes:
description: The attributes/parameters as returned from the DSC engine in dict format
returned: always
type: complex
contains:
Key:
description: Attribute key
Value:
description: Attribute value
reboot_required:
description: flag returned from the DSC engine indicating whether or not the machine requires a reboot for the invoked changes to take effect
returned: always
type: boolean
sample: True
message:
description: any error message from invoking the DSC resource
returned: error
type: string
sample: Multiple DSC modules found with resource name xyz
'''

View file

@ -0,0 +1 @@
windows/ci/group3

View file

@ -0,0 +1,4 @@
---
# Feature not normally installed by default.
test_win_feature_name: Telnet-Client

View file

@ -0,0 +1,119 @@
# test code for the win_feature module
# (c) 2014, Chris Church <chris@ninemoreminutes.com>
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
- name: check whether servermanager module is available (windows 2008 r2 or later)
raw: PowerShell -Command Import-Module ServerManager
register: win_feature_has_servermanager
ignore_errors: true
- name: start with feature absent
win_feature:
name: "{{ test_win_feature_name }}"
state: absent
when: (win_feature_has_servermanager|success) and (ansible_powershell_version is defined) and (ansible_powershell_version >= 5)
- name: Invoke DSC with check mode
win_dsc:
resource_name: windowsfeature
name: "{{ test_win_feature_name }}"
check_mode: yes
register: win_dsc_checkmode_result
when: (win_feature_has_servermanager|success) and (ansible_powershell_version is defined) and (ansible_powershell_version >= 5)
- name: check result of Invoke DSC with check mode
assert:
that:
- "win_dsc_checkmode_result|changed"
- "win_dsc_checkmode_result|success"
when: (win_feature_has_servermanager|success) and (ansible_powershell_version is defined) and (ansible_powershell_version >= 5)
- name: Make sure the feature is still absent
win_dsc:
resource_name: windowsfeature
name: "{{ test_win_feature_name }}"
ensure: absent
register: win_dsc_1_result
when: (win_feature_has_servermanager|success) and (ansible_powershell_version is defined) and (ansible_powershell_version >= 5)
- name: check result of Make sure the feature is still absent
assert:
that:
- "not win_dsc_1_result|changed"
- "win_dsc_1_result|success"
when: (win_feature_has_servermanager|success) and (ansible_powershell_version is defined) and (ansible_powershell_version >= 5)
- name: Install feature for realz
win_dsc:
resource_name: windowsfeature
name: "{{ test_win_feature_name }}"
register: win_dsc_2_result
when: (win_feature_has_servermanager|success) and (ansible_powershell_version is defined) and (ansible_powershell_version >= 5)
- name: check result of Install feature for realz
assert:
that:
- "win_dsc_2_result|changed"
- "win_dsc_2_result|success"
when: (win_feature_has_servermanager|success) and (ansible_powershell_version is defined) and (ansible_powershell_version >= 5)
- name: Ensure clean failure
win_dsc:
resource_name: some_unknown_resource_that_wont_ever_exist
name: "{{ test_win_feature_name }}"
register: win_dsc_3_result
ignore_errors: true
when: (win_feature_has_servermanager|success) and (ansible_powershell_version is defined) and (ansible_powershell_version >= 5)
- name: check result of Ensure clean failure
assert:
that:
- "not win_dsc_3_result|changed"
- "not win_dsc_3_result|success"
when: (win_feature_has_servermanager|success) and (ansible_powershell_version is defined) and (ansible_powershell_version >= 5)
- name: Make sure the feature is absent
win_dsc:
resource_name: windowsfeature
name: "{{ test_win_feature_name }}"
ensure: absent
register: win_dsc_4_result
when: (win_feature_has_servermanager|success) and (ansible_powershell_version is defined) and (ansible_powershell_version >= 5)
- name: check result of Make sure the feature is absent
assert:
that:
- "win_dsc_4_result|changed"
- "win_dsc_4_result|success"
when: (win_feature_has_servermanager|success) and (ansible_powershell_version is defined) and (ansible_powershell_version >= 5)
- name: Make sure the feature is still absent with no changes
win_dsc:
resource_name: windowsfeature
name: "{{ test_win_feature_name }}"
ensure: absent
register: win_dsc_5_result
when: (win_feature_has_servermanager|success) and (ansible_powershell_version is defined) and (ansible_powershell_version >= 5)
- name: check result of Make sure the feature is absent
assert:
that:
- "not win_dsc_5_result|changed"
- "win_dsc_5_result|success"
when: (win_feature_has_servermanager|success) and (ansible_powershell_version is defined) and (ansible_powershell_version >= 5)