Skip to content

Commit fcff2e0

Browse files
authored
PSResourceRepository: Add property Reasons (#402)
* PSResourceRepository: Fix property `Reasons` * Fix/reasons psrepository integ test and unit tests (#3) * Fix unit test for PowerShell and Windows PowerShell * Remove Reasons class * Fix correct type for Reasons * Fix review comments * Fix correct type * Fix integration test * Fix review comment
1 parent ff57072 commit fcff2e0

6 files changed

Lines changed: 258 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
- The class-based resources are now re-using the module DscResource.Base - Fixes [Issue #404](https://github.com/dsccommunity/ComputerManagementDsc/issues/404).
1212
- Removed the file `source/build.psd1` as it is no longer required for the
1313
build pipeline.
14+
- PSResourceRepository
15+
- The resource now supports the read-only property `Reasons` that the
16+
compliance part (audit via Azure Policy) of Azure AutoManage Machine
17+
Configuration uses.
1418

1519
## [9.0.0] - 2023-02-22
1620

source/Classes/001.CMReason.ps1

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<#
2+
.SYNOPSIS
3+
The reason a property of a DSC resource is not in desired state.
4+
5+
.DESCRIPTION
6+
A DSC resource can have a read-only property `Reasons` that the compliance
7+
part (audit via Azure Policy) of Azure AutoManage Machine Configuration
8+
uses. The property Reasons holds an array of CMReason. Each CMReason
9+
explains why a property of a DSC resource is not in desired state.
10+
#>
11+
12+
class CMReason
13+
{
14+
[DscProperty()]
15+
[System.String]
16+
$Code
17+
18+
[DscProperty()]
19+
[System.String]
20+
$Phrase
21+
}

source/Classes/020.PSResourceRepository.ps1

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@
4848
and PackageManagementProvider may not be used in conjunction with Default.
4949
When the Default parameter is used, properties are not enforced when PSGallery properties are changed outside of Dsc.
5050
51+
.PARAMETER Reasons
52+
Returns the reason a property is not in desired state.
53+
5154
.EXAMPLE
5255
Invoke-DscResource -ModuleName ComputerManagementDsc -Name PSResourceRepository -Method Get -Property @{
5356
Name = 'PSTestRepository'
@@ -96,7 +99,7 @@ class PSResourceRepository : ResourceBase
9699
$Proxy
97100

98101
[DscProperty()]
99-
[pscredential]
102+
[PSCredential]
100103
$ProxyCredential
101104

102105
[DscProperty()]
@@ -112,6 +115,10 @@ class PSResourceRepository : ResourceBase
112115
[Nullable[System.Boolean]]
113116
$Default
114117

118+
[DscProperty(NotConfigurable)]
119+
[CMReason[]]
120+
$Reasons
121+
115122
# Passing the module's base directory to the base constructor so it finds localization files.
116123
PSResourceRepository () : base ($PSScriptRoot)
117124
{
@@ -124,12 +131,12 @@ class PSResourceRepository : ResourceBase
124131

125132
[PSResourceRepository] Get()
126133
{
127-
return ([ResourceBase]$this).Get()
134+
return ([ResourceBase] $this).Get()
128135
}
129136

130137
[void] Set()
131138
{
132-
([ResourceBase]$this).Set()
139+
([ResourceBase] $this).Set()
133140
}
134141

135142
[Boolean] Test()
@@ -226,7 +233,7 @@ class PSResourceRepository : ResourceBase
226233
$returnValue.PublishLocation = $repository.PublishLocation
227234
$returnValue.ScriptPublishLocation = $repository.ScriptPublishLocation
228235
$returnValue.Proxy = $repository.Proxy
229-
$returnValue.ProxyCredential = $repository.ProxyCredental
236+
$returnValue.ProxyCredential = $repository.ProxyCredential
230237
$returnValue.InstallationPolicy = $repository.InstallationPolicy
231238
$returnValue.PackageManagementProvider = $repository.PackageManagementProvider
232239
}

tests/Integration/Classes/PSResourceRepository.integration.tests.ps1

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ try
8888
$resourceCurrentState.PackageManagementProvider | Should -Be 'NuGet'
8989
$resourceCurrentState.InstallationPolicy | Should -Be 'Untrusted'
9090

91+
# Read-only properties
92+
$resourceCurrentState.Reasons | Should -BeNullOrEmpty
9193
}
9294

9395
It 'Should return $true when Test-DscConfiguration is run' {
@@ -152,6 +154,9 @@ try
152154

153155
# Defaulted properties
154156
$resourceCurrentState.Ensure | Should -Be $shouldBeData.Ensure
157+
158+
# Read-only properties
159+
$resourceCurrentState.Reasons | Should -BeNullOrEmpty
155160
}
156161

157162
It 'Should return $true when Test-DscConfiguration is run' {
@@ -218,6 +223,9 @@ try
218223

219224
# Ensure will be Absent
220225
$resourceCurrentState.Ensure | Should -Be 'Absent'
226+
227+
# Read-only properties
228+
$resourceCurrentState.Reasons | Should -BeNullOrEmpty
221229
}
222230

223231
It 'Should return $true when Test-DscConfiguration is run' {
@@ -226,7 +234,72 @@ try
226234
}
227235

228236
Wait-ForIdleLcm -Clear
237+
}
238+
239+
Context 'When using Invoke-DscResource' {
240+
BeforeAll {
241+
$script:mockInvokeDscResourceParameters = @{
242+
ModuleName = $script:dscModuleName
243+
Name = $script:dscResourceFriendlyName
244+
Verbose = $true
245+
}
246+
}
247+
248+
BeforeEach {
249+
Wait-ForIdleLcm -Clear
250+
}
229251

252+
Context 'When the configuration is not in desired state' {
253+
Context 'When calling method Get()' {
254+
It 'Should not throw and return the correct values for each property' {
255+
{
256+
$script:resourceCurrentState = Invoke-DscResource @mockInvokeDscResourceParameters -Method 'Get' -Property @{
257+
Name = 'PSTestGallery'
258+
Ensure = 'Present'
259+
SourceLocation = 'https://www.nuget.org/api/v2'
260+
PublishLocation = 'https://www.nuget.org/api/v2/package/'
261+
ScriptSourceLocation = 'https://www.nuget.org/api/v2/items/psscript/'
262+
ScriptPublishLocation = 'https://www.nuget.org/api/v2/package/'
263+
InstallationPolicy = 'Trusted'
264+
PackageManagementProvider = 'NuGet'
265+
}
266+
} | Should -Not -Throw
267+
268+
$resourceCurrentState.Name | Should -Be 'PSTestGallery'
269+
$resourceCurrentState.Ensure | Should -Be 'Absent'
270+
$resourceCurrentState.PackageManagementProvider | Should -BeNullOrEmpty
271+
$resourceCurrentState.Proxy | Should -BeNullOrEmpty
272+
$resourceCurrentState.ProxyCredential | Should -BeNullOrEmpty
273+
$resourceCurrentState.PublishLocation | Should -BeNullOrEmpty
274+
$resourceCurrentState.ScriptPublishLocation | Should -BeNullOrEmpty
275+
$resourceCurrentState.ScriptSourceLocation | Should -BeNullOrEmpty
276+
$resourceCurrentState.SourceLocation | Should -BeNullOrEmpty
277+
278+
$resourceCurrentState.Reasons | Should -HaveCount 7
279+
280+
$resourceCurrentState.Reasons.Code | Should -Contain 'PSResourceRepository:PSResourceRepository:ScriptPublishLocation'
281+
$resourceCurrentState.Reasons.Phrase | Should -Contain 'The property ScriptPublishLocation should be "https://www.nuget.org/api/v2/package/", but was ""'
282+
283+
$resourceCurrentState.Reasons.Code | Should -Contain 'PSResourceRepository:PSResourceRepository:InstallationPolicy'
284+
$resourceCurrentState.Reasons.Phrase | Should -Contain 'The property InstallationPolicy should be "Trusted", but was ""'
285+
286+
$resourceCurrentState.Reasons.Code | Should -Contain 'PSResourceRepository:PSResourceRepository:Ensure'
287+
$resourceCurrentState.Reasons.Phrase | Should -Contain 'The property Ensure should be "Present", but was "Absent"'
288+
289+
$resourceCurrentState.Reasons.Code | Should -Contain 'PSResourceRepository:PSResourceRepository:PackageManagementProvider'
290+
$resourceCurrentState.Reasons.Phrase | Should -Contain 'The property PackageManagementProvider should be "NuGet", but was ""'
291+
292+
$resourceCurrentState.Reasons.Code | Should -Contain 'PSResourceRepository:PSResourceRepository:ScriptSourceLocation'
293+
$resourceCurrentState.Reasons.Phrase | Should -Contain 'The property ScriptSourceLocation should be "https://www.nuget.org/api/v2/items/psscript/", but was ""'
294+
295+
$resourceCurrentState.Reasons.Code | Should -Contain 'PSResourceRepository:PSResourceRepository:PublishLocation'
296+
$resourceCurrentState.Reasons.Phrase | Should -Contain 'The property PublishLocation should be "https://www.nuget.org/api/v2/package/", but was ""'
297+
298+
$resourceCurrentState.Reasons.Code | Should -Contain 'PSResourceRepository:PSResourceRepository:SourceLocation'
299+
$resourceCurrentState.Reasons.Phrase | Should -Contain 'The property SourceLocation should be "https://www.nuget.org/api/v2", but was ""'
300+
}
301+
}
302+
}
230303
}
231304
#endregion
232305
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<#
2+
.SYNOPSIS
3+
Unit test for PSResourceRepository DSC resource.
4+
#>
5+
6+
# Suppressing this rule because Script Analyzer does not understand Pester's syntax.
7+
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
8+
param ()
9+
10+
try
11+
{
12+
if (-not (Get-Module -Name 'DscResource.Test'))
13+
{
14+
# Assumes dependencies has been resolved, so if this module is not available, run 'noop' task.
15+
if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable))
16+
{
17+
# Redirect all streams to $null, except the error stream (stream 2)
18+
& "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null
19+
}
20+
21+
# If the dependencies has not been resolved, this will throw an error.
22+
Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop'
23+
}
24+
}
25+
catch [System.IO.FileNotFoundException]
26+
{
27+
throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.'
28+
}
29+
30+
try
31+
{
32+
$script:dscModuleName = 'ComputerManagementDsc'
33+
34+
Import-Module -Name $script:dscModuleName
35+
36+
$PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName
37+
$PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName
38+
$PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName
39+
40+
Describe 'CMReason' -Tag 'CMReason' {
41+
Context 'When instantiating the class' {
42+
It 'Should not throw an error' {
43+
$script:mockCMReasonInstance = InModuleScope -ScriptBlock {
44+
[CMReason]::new()
45+
}
46+
}
47+
48+
It 'Should be of the correct type' {
49+
$mockCMReasonInstance | Should -Not -BeNullOrEmpty
50+
$mockCMReasonInstance.GetType().Name | Should -Be 'CMReason'
51+
}
52+
}
53+
54+
Context 'When setting and reading values' {
55+
It 'Should be able to set value in instance' {
56+
$script:mockCMReasonInstance = InModuleScope -ScriptBlock {
57+
$CMReasonInstance = [CMReason]::new()
58+
59+
$CMReasonInstance.Code = 'SqlAudit:SqlAudit:Ensure'
60+
$CMReasonInstance.Phrase = 'The property Ensure should be "Present", but was "Absent"'
61+
62+
return $CMReasonInstance
63+
}
64+
}
65+
66+
It 'Should be able read the values from instance' {
67+
$mockCMReasonInstance.Code | Should -Be 'SqlAudit:SqlAudit:Ensure'
68+
$mockCMReasonInstance.Phrase = 'The property Ensure should be "Present", but was "Absent"'
69+
}
70+
}
71+
}
72+
}
73+
finally
74+
{
75+
$PSDefaultParameterValues.Remove('InModuleScope:ModuleName')
76+
$PSDefaultParameterValues.Remove('Mock:ModuleName')
77+
$PSDefaultParameterValues.Remove('Should:ModuleName')
78+
79+
# Unload the module being tested so that it doesn't impact any other tests.
80+
Get-Module -Name $script:dscModuleName -All | Remove-Module -Force
81+
}

0 commit comments

Comments
 (0)