From f8bb5f167aa293061f25e52fce817ef8d20a1b3e Mon Sep 17 00:00:00 2001 From: Maurice Kevenaar Date: Thu, 28 May 2020 17:08:38 +0200 Subject: [PATCH] (GH-20) add cChocoConfig resource This resource allows users to change Chocolatey Config settings using an DSC resource. --- DSCResources/cChocoConfig/cChocoConfig.psm1 | 188 ++++++++++++++++++ .../cChocoConfig/cChocoConfig.schema.mof | 9 + Examples/cChocoConfigExample.ps1 | 39 ++++ Tests/cChocoConfig_Tests.ps1 | 105 ++++++++++ 4 files changed, 341 insertions(+) create mode 100644 DSCResources/cChocoConfig/cChocoConfig.psm1 create mode 100644 DSCResources/cChocoConfig/cChocoConfig.schema.mof create mode 100644 Examples/cChocoConfigExample.ps1 create mode 100644 Tests/cChocoConfig_Tests.ps1 diff --git a/DSCResources/cChocoConfig/cChocoConfig.psm1 b/DSCResources/cChocoConfig/cChocoConfig.psm1 new file mode 100644 index 0000000..3163c83 --- /dev/null +++ b/DSCResources/cChocoConfig/cChocoConfig.psm1 @@ -0,0 +1,188 @@ +# Copyright (c) 2017 Chocolatey Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +<# +.Description +Returns the configuration for cChocoConfig. + +.Example +Get-TargetResource -ConfigName cacheLocation -Ensure 'Present' -Value 'c:\temp\choco' +#> +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([Hashtable])] + param + ( + [parameter(Mandatory = $true)] + [string] + $ConfigName, + + [ValidateSet('Present','Absent')] + [string] + $Ensure='Present', + + [parameter(Mandatory = $false)] + [string] + $Value + ) + + Write-Verbose "Starting cChocoConfig Get-TargetResource - Config Name: $ConfigName, Ensure: $Ensure" + + $returnValue = @{ + ConfigName = $ConfigName + Ensure = $Ensure + Value = $Value + } + + $returnValue + +} + +<# +.Description +Performs the set for the cChocoConfig resource. + +.Example +Set-TargetResource -ConfigName cacheLocation -Ensure 'Present' -Value 'c:\temp\choco' + +#> +function Set-TargetResource +{ + [CmdletBinding(SupportsShouldProcess=$true)] + param + ( + [parameter(Mandatory = $true)] + [string] + $ConfigName, + + [ValidateSet('Present','Absent')] + [string] + $Ensure='Present', + + [parameter(Mandatory = $false)] + [string] + $Value + ) + + + Write-Verbose "Starting cChocoConfig Set-TargetResource - Config Name: $ConfigName, Ensure: $Ensure" + + if ($pscmdlet.ShouldProcess("Choco config $ConfigName will be ensured $Ensure.")) + { + if ($Ensure -eq 'Present') + { + Write-Verbose "Setting choco config $ConfigName." + choco config set --name "'$ConfigName'" --value "'$Value'" + } + else + { + Write-Verbose "Unsetting choco config $ConfigName." + choco config unset --name "'$ConfigName'" + } + } + +} + +<# +.Description +Performs the test for cChocoFeature. + +.Example +Test-TargetResource -ConfigName cacheLocation -Ensure 'Present' -Value 'c:\temp\choco' +#> +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([Boolean])] + param + ( + [parameter(Mandatory = $true)] + [string] + $ConfigName, + + [ValidateSet('Present','Absent')] + [string] + $Ensure='Present', + + [parameter(Mandatory = $false)] + [string] + $Value + ) + + Write-Verbose "Starting cChocoConfig Test-TargetResource - Config Name: $ConfigName, Ensure: $Ensure." + + # validate value is given when ensure present + if ($Ensure -eq 'Present' -and (-not $PSBoundParameters.ContainsKey('Value') -or [String]::IsNullOrEmpty($Value))) { + throw "Missing parameter 'Value' when ensuring config is present!" + } + + if($env:ChocolateyInstall -eq "" -or $null -eq $env:ChocolateyInstall) + { + $command = Get-Command -Name choco.exe -ErrorAction SilentlyContinue + + if(!$command) { + throw "Unable to find choco.exe. Please make sure Chocolatey is installed correctly." + } + + $chocofolder = Split-Path $command.Source + + if( $chocofolder.EndsWith("bin") ) + { + $chocofolder = Split-Path $chocofolder + } + } + else + { + $chocofolder = $env:ChocolateyInstall + } + + if(!(Get-Item -Path $chocofolder -ErrorAction SilentlyContinue)) { + throw "Unable to find Chocolatey installation folder. Please make sure Chocolatey is installed and configured properly." + } + + $configfolder = Join-Path -Path $chocofolder -ChildPath "config" + $configfile = Get-ChildItem -Path $configfolder | Where-Object {$_.Name -match "chocolatey.config$"} + + if(!(Get-Item -Path $configfile.FullName -ErrorAction SilentlyContinue)) { + throw "Unable to find Chocolatey config file. Please make sure Chocolatey is installed and configured properly." + } + + # There is currently no choco command that only returns the settings in an CSV format. + # choco config list -r shows settings, sources, features and a note about API keys. + $xml = [xml](Get-Content -Path $configfile.FullName) + $settings = $xml.chocolatey.config.add + foreach($setting in $settings) + { + # If the config name matches and it should be present, check the value and + # if it matches it returns true. + if($setting.key -eq $ConfigName -and $Ensure -eq 'Present') + { + return ($setting.value -eq $Value) + } + # If the config name matches and it should be absent, check the value and + # if it is null or empty, return true + elseif($setting.key -eq $ConfigName -and $Ensure -eq 'Absent') + { + return ([String]::IsNullOrEmpty($setting.value)) + } + } + + # If we get this far, the configuraion item hasn't been found. + # There is currently no value, so return false if it should be present. + # True otherwise. + return !($Ensure -eq 'Present') +} + +Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/cChocoConfig/cChocoConfig.schema.mof b/DSCResources/cChocoConfig/cChocoConfig.schema.mof new file mode 100644 index 0000000..a371774 --- /dev/null +++ b/DSCResources/cChocoConfig/cChocoConfig.schema.mof @@ -0,0 +1,9 @@ + +[ClassVersion("1.0.0.0"), FriendlyName("cChocoConfig")] +class cChocoConfig : OMI_BaseResource +{ + [Key] String ConfigName; + [Write,ValueMap{"Present", "Absent"},Values{"Present", "Absent"}] String Ensure; + [Write] String Value; +}; + diff --git a/Examples/cChocoConfigExample.ps1 b/Examples/cChocoConfigExample.ps1 new file mode 100644 index 0000000..d0a3693 --- /dev/null +++ b/Examples/cChocoConfigExample.ps1 @@ -0,0 +1,39 @@ +# Copyright (c) 2017 Chocolatey Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +configuration ChocoConfig { + + Import-DscResource -ModuleName cChoco + + Node 'localhost' { + + cChocoConfig webRequestTimeoutSeconds { + ConfigName = "webRequestTimeoutSeconds" + Ensure = 'Present' + Value = 30 + } + + cChocoConfig proxy { + ConfigName = "proxy" + Ensure = 'Absent' + } + } + +} + + +$config = ChocoConfig + +Start-DscConfiguration -Path $config.psparentpath -Wait -Verbose -Force diff --git a/Tests/cChocoConfig_Tests.ps1 b/Tests/cChocoConfig_Tests.ps1 new file mode 100644 index 0000000..8a5e897 --- /dev/null +++ b/Tests/cChocoConfig_Tests.ps1 @@ -0,0 +1,105 @@ +# Copyright (c) 2017 Chocolatey Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +$ResourceName = ((Split-Path $MyInvocation.MyCommand.Path -Leaf) -split '_')[0] +$ResourceFile = (Get-DscResource -Name $ResourceName).Path + +$TestsPath = (split-path -path $MyInvocation.MyCommand.Path -Parent) +$ResourceFile = Get-ChildItem -Recurse $TestsPath\.. -File | Where-Object {$_.name -eq "$ResourceName.psm1"} + +Import-Module -Name $ResourceFile.FullName + + +#---------------------------------# +# Pester tests for cChocoConfig # +#---------------------------------# +Describe "Testing cChocoConfig" { + + Context "Test-TargetResource" { + + mock -ModuleName cChocoConfig -CommandName Get-Content -MockWith {' + + + + + + + + +' + } -Verifiable + + it 'Test-TargetResource returns true when Present and Configured.' { + Test-TargetResource -ConfigName 'commandExecutionTimeoutSeconds' -Ensure 'Present' -Value '1339' | Should be $true + } + + it 'Test-TargetResource returns false when Present and Not configured' { + Test-TargetResource -ConfigName 'proxy' -Ensure 'Present' -Value 'http://myproxy.url' | Should be $false + } + + it 'Test-TargetResource returns false when Present and Unknown' { + Test-TargetResource -ConfigName 'MyParam' -Ensure 'Present' -Value 'MyValue' | Should be $false + } + + it 'Test-TargetResource throws when Present and no value' { + { Test-TargetResource -ConfigName 'MyParam' -Ensure 'Present' } | Should -Throw "Missing parameter 'Value' when ensuring config is present!" + } + + it 'Test-TargetResource throws when Present and no value' { + { Test-TargetResource -ConfigName 'MyParam' -Ensure 'Present' -Value '' } | Should -Throw "Missing parameter 'Value' when ensuring config is present!" + } + + it 'Test-TargetResource throws when Present and no value' { + { Test-TargetResource -ConfigName 'MyParam' -Ensure 'Present' -Value $null } | Should -Throw "Missing parameter 'Value' when ensuring config is present!" + } + + it 'Test-TargetResource returns false when Absent and Configured' { + Test-TargetResource -ConfigName 'commandExecutionTimeoutSeconds' -Ensure 'Absent' | Should be $false + } + + it 'Test-TargetResource returns true when Absent and Not configured' { + Test-TargetResource -ConfigName 'proxy' -Ensure 'Absent' | Should be $true + } + + it 'Test-TargetResource returns true when Absent and Unknown' { + Test-TargetResource -ConfigName 'MyParam' -Ensure 'Absent' | Should be $true + } + + } + + Context "Set-TargetResource" { + + InModuleScope -ModuleName cChocoConfig -ScriptBlock { + function choco {} + mock choco {} + } + + Set-TargetResource -ConfigName "TestConfig" -Ensure "Present" -Value "MyValue" + + it "Present - Should have called choco, with set" { + Assert-MockCalled -CommandName choco -ModuleName cChocoConfig -ParameterFilter { + $args -contains "'MyValue'" + } + } + + Set-TargetResource -ConfigName "TestConfig" -Ensure "Absent" + + it "Absent - Should have called choco, with unset" { + Assert-MockCalled -CommandName choco -ModuleName cChocoConfig -ParameterFilter { + $args -contains "unset" + } + } + } +} \ No newline at end of file