Skip to content

Commit 2b2a4f9

Browse files
authored
Merge pull request #758 from Gijsreyn/add-credential-support
Add credential support on PowerShell adapters
2 parents a42e0d9 + 4041e8a commit 2b2a4f9

File tree

5 files changed

+269
-130
lines changed

5 files changed

+269
-130
lines changed

powershell-adapter/Tests/TestClassResource/0.0.1/TestClassResource.psm1

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ class TestClassResource : BaseTestClass
2929
[DscProperty()]
3030
[string] $EnumProp
3131

32+
[DscProperty()]
33+
[PSCredential] $Credential
34+
3235
[string] $NonDscProperty # This property shouldn't be in results data
3336

3437
hidden

powershell-adapter/Tests/powershellgroup.config.tests.ps1

+36
Original file line numberDiff line numberDiff line change
@@ -237,4 +237,40 @@ Describe 'PowerShell adapter resource tests' {
237237
}
238238
}
239239
}
240+
241+
It 'Config works with credential object' {
242+
$yaml = @"
243+
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
244+
resources:
245+
- name: Class-resource Info
246+
type: TestClassResource/TestClassResource
247+
properties:
248+
Name: 'TestClassResource'
249+
Credential:
250+
UserName: 'User'
251+
Password: 'Password'
252+
"@
253+
$out = dsc config get -i $yaml | ConvertFrom-Json
254+
$LASTEXITCODE | Should -Be 0
255+
$out.results.result.actualstate.Credential.UserName | Should -Be 'User'
256+
$out.results.result.actualState.result.Credential.Password.Length | Should -Not -BeNullOrEmpty
257+
}
258+
259+
It 'Config does not work when credential properties are missing required fields' {
260+
$yaml = @"
261+
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
262+
resources:
263+
- name: Class-resource credential info
264+
type: TestClassResource/TestClassResource
265+
properties:
266+
Name: 'TestClassResource'
267+
Credential:
268+
UserName: 'User'
269+
OtherProperty: 'Password'
270+
"@
271+
$out = dsc config get -i $yaml 2>&1 | Out-String
272+
$LASTEXITCODE | Should -Be 2
273+
$out | Should -Not -BeNullOrEmpty
274+
$out | Should -BeLike "*ERROR*Credential object 'Credential' requires both 'username' and 'password' properties*"
275+
}
240276
}

powershell-adapter/Tests/win_powershellgroup.tests.ps1

+167-110
Original file line numberDiff line numberDiff line change
@@ -3,107 +3,110 @@
33

44
Describe 'WindowsPowerShell adapter resource tests - requires elevated permissions' {
55

6-
BeforeAll {
7-
if ($isWindows) {
8-
winrm quickconfig -quiet -force
9-
}
10-
$OldPSModulePath = $env:PSModulePath
11-
$env:PSModulePath += [System.IO.Path]::PathSeparator + $PSScriptRoot
12-
13-
$winpsConfigPath = Join-path $PSScriptRoot "winps_resource.dsc.yaml"
14-
if ($isWindows) {
15-
$cacheFilePath_v5 = Join-Path $env:LocalAppData "dsc" "WindowsPSAdapterCache.json"
16-
}
17-
}
18-
AfterAll {
19-
$env:PSModulePath = $OldPSModulePath
6+
BeforeAll {
7+
if ($isWindows) {
8+
winrm quickconfig -quiet -force
209
}
10+
$OldPSModulePath = $env:PSModulePath
11+
$env:PSModulePath += [System.IO.Path]::PathSeparator + $PSScriptRoot
2112

22-
BeforeEach {
23-
if ($isWindows) {
24-
Remove-Item -Force -ea SilentlyContinue -Path $cacheFilePath_v5
25-
}
13+
$winpsConfigPath = Join-path $PSScriptRoot "winps_resource.dsc.yaml"
14+
if ($isWindows) {
15+
$cacheFilePath_v5 = Join-Path $env:LocalAppData "dsc" "WindowsPSAdapterCache.json"
2616
}
17+
}
18+
AfterAll {
19+
$env:PSModulePath = $OldPSModulePath
2720

28-
It 'Windows PowerShell adapter supports File resource' -Skip:(!$IsWindows){
21+
# Remove after all the tests are done
22+
Remove-Module $script:winPSModule -Force -ErrorAction Ignore
23+
}
2924

30-
$r = dsc resource list --adapter Microsoft.Windows/WindowsPowerShell
31-
$LASTEXITCODE | Should -Be 0
32-
$resources = $r | ConvertFrom-Json
33-
($resources | Where-Object {$_.Type -eq 'PSDesiredStateConfiguration/File'}).Count | Should -Be 1
25+
BeforeEach {
26+
if ($isWindows) {
27+
Remove-Item -Force -ea SilentlyContinue -Path $cacheFilePath_v5
3428
}
29+
}
3530

36-
It 'Get works on Binary "File" resource' -Skip:(!$IsWindows){
31+
It 'Windows PowerShell adapter supports File resource' -Skip:(!$IsWindows) {
3732

38-
$testFile = "$testdrive\test.txt"
39-
'test' | Set-Content -Path $testFile -Force
40-
$r = '{"DestinationPath":"' + $testFile.replace('\','\\') + '"}' | dsc resource get -r 'PSDesiredStateConfiguration/File' -f -
41-
$LASTEXITCODE | Should -Be 0
42-
$res = $r | ConvertFrom-Json
43-
$res.actualState.DestinationPath | Should -Be "$testFile"
44-
}
33+
$r = dsc resource list --adapter Microsoft.Windows/WindowsPowerShell
34+
$LASTEXITCODE | Should -Be 0
35+
$resources = $r | ConvertFrom-Json
36+
($resources | Where-Object { $_.Type -eq 'PSDesiredStateConfiguration/File' }).Count | Should -Be 1
37+
}
4538

46-
It 'Set works on Binary "File" resource' -Skip:(!$IsWindows){
39+
It 'Get works on Binary "File" resource' -Skip:(!$IsWindows) {
4740

48-
$testFile = "$testdrive\test.txt"
49-
$null = '{"DestinationPath":"' + $testFile.replace('\','\\') + '", type: File, contents: HelloWorld, Ensure: present}' | dsc resource set -r 'PSDesiredStateConfiguration/File' -f -
50-
$LASTEXITCODE | Should -Be 0
51-
Get-Content -Raw -Path $testFile | Should -Be "HelloWorld"
52-
}
41+
$testFile = "$testdrive\test.txt"
42+
'test' | Set-Content -Path $testFile -Force
43+
$r = '{"DestinationPath":"' + $testFile.replace('\', '\\') + '"}' | dsc resource get -r 'PSDesiredStateConfiguration/File' -f -
44+
$LASTEXITCODE | Should -Be 0
45+
$res = $r | ConvertFrom-Json
46+
$res.actualState.DestinationPath | Should -Be "$testFile"
47+
}
5348

54-
It 'Get works on traditional "Script" resource' -Skip:(!$IsWindows){
49+
It 'Set works on Binary "File" resource' -Skip:(!$IsWindows) {
5550

56-
$testFile = "$testdrive\test.txt"
57-
'test' | Set-Content -Path $testFile -Force
58-
$r = '{"GetScript": "@{result = $(Get-Content ' + $testFile.replace('\','\\') + ')}", "SetScript": "throw", "TestScript": "throw"}' | dsc resource get -r 'PSDesiredStateConfiguration/Script' -f -
59-
$LASTEXITCODE | Should -Be 0
60-
$res = $r | ConvertFrom-Json
61-
$res.actualState.result | Should -Be 'test'
62-
}
51+
$testFile = "$testdrive\test.txt"
52+
$null = '{"DestinationPath":"' + $testFile.replace('\', '\\') + '", type: File, contents: HelloWorld, Ensure: present}' | dsc resource set -r 'PSDesiredStateConfiguration/File' -f -
53+
$LASTEXITCODE | Should -Be 0
54+
Get-Content -Raw -Path $testFile | Should -Be "HelloWorld"
55+
}
6356

64-
It 'Get works on config with File resource for WinPS' -Skip:(!$IsWindows){
57+
It 'Get works on traditional "Script" resource' -Skip:(!$IsWindows) {
6558

66-
$testFile = "$testdrive\test.txt"
67-
'test' | Set-Content -Path $testFile -Force
68-
$r = (Get-Content -Raw $winpsConfigPath).Replace('c:\test.txt',"$testFile") | dsc config get -f -
69-
$LASTEXITCODE | Should -Be 0
70-
$res = $r | ConvertFrom-Json
71-
$res.results[0].result.actualState.result[0].properties.DestinationPath | Should -Be "$testFile"
72-
}
59+
$testFile = "$testdrive\test.txt"
60+
'test' | Set-Content -Path $testFile -Force
61+
$r = '{"GetScript": "@{result = $(Get-Content ' + $testFile.replace('\', '\\') + ')}", "SetScript": "throw", "TestScript": "throw"}' | dsc resource get -r 'PSDesiredStateConfiguration/Script' -f -
62+
$LASTEXITCODE | Should -Be 0
63+
$res = $r | ConvertFrom-Json
64+
$res.actualState.result | Should -Be 'test'
65+
}
7366

74-
It 'Verify that there are no cache rebuilds for several sequential executions' -Skip:(!$IsWindows) {
75-
# remove cache file
76-
$cacheFilePath = Join-Path $env:LocalAppData "dsc\WindowsPSAdapterCache.json"
77-
Remove-Item -Force -Path $cacheFilePath -ErrorAction Ignore
67+
It 'Get works on config with File resource for WinPS' -Skip:(!$IsWindows) {
7868

79-
# first execution should build the cache
80-
dsc -l trace resource list -a Microsoft.Windows/WindowsPowerShell 2> $TestDrive/tracing.txt
81-
"$TestDrive/tracing.txt" | Should -FileContentMatchExactly 'Constructing Get-DscResource cache'
69+
$testFile = "$testdrive\test.txt"
70+
'test' | Set-Content -Path $testFile -Force
71+
$r = (Get-Content -Raw $winpsConfigPath).Replace('c:\test.txt', "$testFile") | dsc config get -f -
72+
$LASTEXITCODE | Should -Be 0
73+
$res = $r | ConvertFrom-Json
74+
$res.results[0].result.actualState.result[0].properties.DestinationPath | Should -Be "$testFile"
75+
}
8276

83-
# next executions following shortly after should Not rebuild the cache
84-
1..3 | ForEach-Object {
85-
dsc -l trace resource list -a Microsoft.Windows/WindowsPowerShell 2> $TestDrive/tracing.txt
86-
"$TestDrive/tracing.txt" | Should -Not -FileContentMatchExactly 'Constructing Get-DscResource cache'
87-
}
77+
It 'Verify that there are no cache rebuilds for several sequential executions' -Skip:(!$IsWindows) {
78+
# remove cache file
79+
$cacheFilePath = Join-Path $env:LocalAppData "dsc\WindowsPSAdapterCache.json"
80+
Remove-Item -Force -Path $cacheFilePath -ErrorAction Ignore
81+
82+
# first execution should build the cache
83+
dsc -l trace resource list -a Microsoft.Windows/WindowsPowerShell 2> $TestDrive/tracing.txt
84+
"$TestDrive/tracing.txt" | Should -FileContentMatchExactly 'Constructing Get-DscResource cache'
85+
86+
# next executions following shortly after should Not rebuild the cache
87+
1..3 | ForEach-Object {
88+
dsc -l trace resource list -a Microsoft.Windows/WindowsPowerShell 2> $TestDrive/tracing.txt
89+
"$TestDrive/tracing.txt" | Should -Not -FileContentMatchExactly 'Constructing Get-DscResource cache'
8890
}
91+
}
8992

90-
It 'Verify if assertion is used that no module is cleared in the cache' -Skip:(!$IsWindows) {
91-
# create a test file in the test drive
92-
$testFile = "$testdrive\test.txt"
93-
New-Item -Path $testFile -ItemType File -Force | Out-Null
93+
It 'Verify if assertion is used that no module is cleared in the cache' -Skip:(!$IsWindows) {
94+
# create a test file in the test drive
95+
$testFile = "$testdrive\test.txt"
96+
New-Item -Path $testFile -ItemType File -Force | Out-Null
9497

95-
# remove cache file
96-
$cacheFilePath = Join-Path $env:LocalAppData "dsc\WindowsPSAdapterCache.json"
97-
Remove-Item -Force -Path $cacheFilePath -ErrorAction Ignore
98+
# remove cache file
99+
$cacheFilePath = Join-Path $env:LocalAppData "dsc\WindowsPSAdapterCache.json"
100+
Remove-Item -Force -Path $cacheFilePath -ErrorAction Ignore
98101

99-
# build the cache
100-
dsc resource list --adapter Microsoft.Windows/WindowsPowerShell | Out-Null
102+
# build the cache
103+
dsc resource list --adapter Microsoft.Windows/WindowsPowerShell | Out-Null
101104

102-
# Create a test module in the test drive
103-
$testModuleDir = "$testdrive\TestModule\1.0.0"
104-
New-Item -Path $testModuleDir -ItemType Directory -Force | Out-Null
105+
# Create a test module in the test drive
106+
$testModuleDir = "$testdrive\TestModule\1.0.0"
107+
New-Item -Path $testModuleDir -ItemType Directory -Force | Out-Null
105108

106-
$manifestContent = @"
109+
$manifestContent = @"
107110
@{
108111
RootModule = 'TestModule.psm1'
109112
ModuleVersion = '1.0.0'
@@ -120,17 +123,17 @@ Describe 'WindowsPowerShell adapter resource tests - requires elevated permissio
120123
AliasesToExport = @()
121124
}
122125
"@
123-
Set-Content -Path "$testModuleDir\TestModule.psd1" -Value $manifestContent
126+
Set-Content -Path "$testModuleDir\TestModule.psd1" -Value $manifestContent
124127

125-
$scriptContent = @"
128+
$scriptContent = @"
126129
Write-Host 'The DSC world!'
127130
"@
128-
Set-Content -Path "$testModuleDir\TestModule.psm1" -Value $scriptContent
131+
Set-Content -Path "$testModuleDir\TestModule.psm1" -Value $scriptContent
129132

130-
# Add the test module directory to PSModulePath
131-
$env:PSModulePath += [System.IO.Path]::PathSeparator + $testdrive
133+
# Add the test module directory to PSModulePath
134+
$env:PSModulePath += [System.IO.Path]::PathSeparator + $testdrive
132135

133-
$yaml = @"
136+
$yaml = @"
134137
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
135138
resources:
136139
- name: File
@@ -164,25 +167,25 @@ resources:
164167
- "[resourceId('Microsoft.Windows/WindowsPowerShell', 'File')]"
165168
- "[resourceId('Microsoft.DSC/Assertion', 'File present')]"
166169
"@
167-
# output to file for Windows PowerShell 5.1
168-
$filePath = "$testdrive\test.assertion.dsc.resource.yaml"
169-
$yaml | Set-Content -Path $filePath -Force
170-
dsc config test -f $filePath 2> "$TestDrive/error.txt"
171-
$LASTEXITCODE | Should -Be 2
172-
173-
$cache = Get-Content -Path $cacheFilePath -Raw | ConvertFrom-Json
174-
$cache.ResourceCache.Type | Should -Contain 'PSTestModule/TestPSRepository'
175-
$cache.ResourceCache.Type | Should -Contain 'PSDesiredStateConfiguration/File'
176-
}
170+
# output to file for Windows PowerShell 5.1
171+
$filePath = "$testdrive\test.assertion.dsc.resource.yaml"
172+
$yaml | Set-Content -Path $filePath -Force
173+
dsc config test -f $filePath 2> "$TestDrive/error.txt"
174+
$LASTEXITCODE | Should -Be 2
175+
176+
$cache = Get-Content -Path $cacheFilePath -Raw | ConvertFrom-Json
177+
$cache.ResourceCache.Type | Should -Contain 'PSTestModule/TestPSRepository'
178+
$cache.ResourceCache.Type | Should -Contain 'PSDesiredStateConfiguration/File'
179+
}
177180

178-
It '_inDesiredState is returned correction: <Context>' -Skip:(!$IsWindows) -TestCases @(
179-
@{ Context = 'Both running'; FirstState = 'Running'; SecondState = 'Running' }
180-
@{ Context = 'Both stopped'; FirstState = 'Stopped'; SecondState = 'Stopped' }
181-
@{ Context = 'First Stopped'; FirstState = 'Stopped'; SecondState = 'Running' }
182-
@{ Context = 'First Running'; FirstState = 'Running'; SecondState = 'Stopped' }
183-
) {
184-
param($Context, $FirstState, $SecondState)
185-
$yaml = @"
181+
It '_inDesiredState is returned correction: <Context>' -Skip:(!$IsWindows) -TestCases @(
182+
@{ Context = 'Both running'; FirstState = 'Running'; SecondState = 'Running' }
183+
@{ Context = 'Both stopped'; FirstState = 'Stopped'; SecondState = 'Stopped' }
184+
@{ Context = 'First Stopped'; FirstState = 'Stopped'; SecondState = 'Running' }
185+
@{ Context = 'First Running'; FirstState = 'Running'; SecondState = 'Stopped' }
186+
) {
187+
param($Context, $FirstState, $SecondState)
188+
$yaml = @"
186189
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
187190
resources:
188191
- name: Use Windows PowerShell resources
@@ -201,14 +204,68 @@ resources:
201204
State: $SecondState
202205
"@
203206

204-
$inDesiredState = if ($FirstState -eq $SecondState) {
205-
$FirstState -eq (Get-Service Spooler).Status
206-
} else {
207-
$false
208-
}
207+
$inDesiredState = if ($FirstState -eq $SecondState) {
208+
$FirstState -eq (Get-Service Spooler).Status
209+
}
210+
else {
211+
$false
212+
}
213+
214+
$out = dsc config test -i $yaml | ConvertFrom-Json
215+
$LASTEXITCODE | Should -Be 0
216+
$out.results[0].result.inDesiredState | Should -Be $inDesiredState
217+
}
209218

210-
$out = dsc config test -i $yaml | ConvertFrom-Json
211-
$LASTEXITCODE | Should -Be 0
212-
$out.results[0].result.inDesiredState | Should -Be $inDesiredState
219+
It 'Config works with credential object' -Skip:(!$IsWindows) {
220+
BeforeDiscovery {
221+
$script:winPSModule = Resolve-Path -Path (Join-Path $PSScriptRoot '..' 'psDscAdapter' 'win_psDscAdapter.psm1') | Select-Object -ExpandProperty Path
222+
Import-Module $winPSModule -Force -ErrorAction Stop
223+
224+
# Mock the command to work on GitHub runners because Microsoft.PowerShell.Security is not available
225+
Mock -CommandName ConvertTo-SecureString -MockWith { [System.Security.SecureString]::new() }
213226
}
227+
228+
$jsonInput = @{
229+
resources = @{
230+
name = 'Service info'
231+
type = 'PSDesiredStateConfiguration/Service'
232+
properties = @{
233+
Name = 'Spooler'
234+
Credential = @{
235+
UserName = 'User'
236+
Password = 'Password'
237+
}
238+
}
239+
}
240+
} | ConvertTo-Json -Depth 10
241+
242+
# Instead of calling dsc.exe we call the cmdlet directly to be able to test the output and mocks
243+
$resourceObject = Get-DscResourceObject -jsonInput $jsonInput
244+
$cacheEntry = Invoke-DscCacheRefresh -Module PSDesiredStateConfiguration
245+
246+
$out = Invoke-DscOperation -Operation Test -DesiredState $resourceObject -dscResourceCache $cacheEntry
247+
$LASTEXITCODE | Should -Be 0
248+
$out.properties.InDesiredState.InDesiredState | Should -Be $false
249+
250+
Should -Invoke -CommandName ConvertTo-SecureString -Exactly -Times 1 -Scope It
251+
}
252+
253+
It 'Config does not work when credential properties are missing required fields' -Skip:(!$IsWindows) {
254+
$yaml = @"
255+
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
256+
resources:
257+
- name: Service info
258+
type: PsDesiredStateConfiguration/Service
259+
properties:
260+
Name: Spooler
261+
Credential:
262+
UserName: 'User'
263+
OtherProperty: 'Password'
264+
"@
265+
# Compared to PowerShell we use test here as it filters out the properties
266+
$out = dsc config test -i $yaml 2>&1 | Out-String
267+
$LASTEXITCODE | Should -Be 2
268+
$out | Should -Not -BeNullOrEmpty
269+
$out | Should -BeLike "*ERROR*Credential object 'Credential' requires both 'username' and 'password' properties*"
270+
}
214271
}

0 commit comments

Comments
 (0)