Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 72 additions & 38 deletions src/dsc/psresourceget.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ class Repository {
[int]$priority
[string]$repositoryType
[bool]$_exist
[Nullable[bool]]$_inDesiredState

Repository([string]$name) {
$this.name = $name
Expand All @@ -223,15 +224,6 @@ class Repository {
$this._exist = $true
}

Repository([PSCustomObject]$repositoryInfo) {
$this.name = $repositoryInfo.Name
$this.uri = $repositoryInfo.Uri
$this.trusted = $repositoryInfo.Trusted
$this.priority = $repositoryInfo.Priority
$this.repositoryType = $repositoryInfo.ApiVersion
$this._exist = $true
}

Repository([string]$name, [bool]$exist) {
$this.name = $name
$this._exist = $exist
Expand All @@ -241,6 +233,10 @@ class Repository {
[string] ToJson() {
return ($this | ConvertTo-Json -Compress -EnumsAsStrings)
}

[string] ToJsonForExport() {
return ($this | Select-Object -Property * -ExcludeProperty _inDesiredState | ConvertTo-Json -Compress -EnumsAsStrings)
}
}

function Write-Trace {
Expand Down Expand Up @@ -407,6 +403,23 @@ function GetPSResourceList {
PopulatePSResourceListObjectByRepository -resourcesExist $resolvedResources -inputResources $inputResources -repositoryName $inputPSResourceList.RepositoryName -trustedRepository $inputPSResourceList.trustedRepository
}

function GetRepository {
param([string]$Name)

try {
$rep = Get-PSResourceRepository -Name $Name -ErrorAction Stop
}
catch {
if ($_.FullyQualifiedErrorId -eq 'ErrorGettingSpecifiedRepo,Microsoft.PowerShell.PSResourceGet.Cmdlets.GetPSResourceRepository') {
Write-Trace -message "Repository not found: $Name. Returning _exist = false" -level debug
return [Repository]::new($Name, $false)
}
throw
}

return [Repository]::new($rep.Name, $rep.Uri, $rep.Trusted, $rep.Priority, $rep.ApiVersion)
}

function GetOperation {
param(
[string]$ResourceType
Expand All @@ -423,32 +436,7 @@ function GetOperation {

switch ($ResourceType) {
'repository' {
$inputRepository = [Repository]::new($inputObj)
$rep = Get-PSResourceRepository -Name $inputRepository.Name -ErrorVariable err -ErrorAction SilentlyContinue
Write-Trace -message "Get-PSResourceRepository returned: $($rep | ConvertTo-Json -Compress)" -level trace

$ret = if ($err.FullyQualifiedErrorId -eq 'ErrorGettingSpecifiedRepo,Microsoft.PowerShell.PSResourceGet.Cmdlets.GetPSResourceRepository') {
Write-Trace -message "Repository not found: $($inputRepository.Name). Returning _exist = false" -level debug
[Repository]::new(
$InputRepository.Name,
$false
)
}
else {
[Repository]::new(
$rep.Name,
$rep.Uri,
$rep.Trusted,
$rep.Priority,
$rep.ApiVersion
)

Write-Trace -message "Returning repository object for: $($ret.Name)" -level trace
}

Write-Trace -message "Serialized JSON output for Get operation: $($ret.ToJson())" -level trace

return ( $ret.ToJson() )
return (GetRepository -Name $inputObj.name).ToJson()
}

'repositorylist' {
Expand All @@ -469,6 +457,53 @@ function GetOperation {
}
}

function TestRepositoryUri {
param([string]$DesiredUri, [string]$ActualUri)

if ([string]::Equals($DesiredUri, $ActualUri, [StringComparison]::Ordinal)) {
return $true
}

# Compare a local path and its file URI in the same form used by the repository cmdlets.
$desired = $null
$actual = $null
return ([uri]::TryCreate($DesiredUri, [UriKind]::Absolute, [ref]$desired) -and $desired.IsFile -and
[uri]::TryCreate($ActualUri, [UriKind]::Absolute, [ref]$actual) -and $actual.IsFile -and
[string]::Equals($desired.AbsoluteUri, $actual.AbsoluteUri, [StringComparison]::Ordinal))
}

function TestRepository {
param([PSCustomObject]$inputObj)

$actual = GetRepository -Name $inputObj.name
$shouldExist = $inputObj._exist -ne $false
$differingProperties = @(
if ($actual._exist -ne $shouldExist) {
'_exist'
}
elseif ($shouldExist) {
foreach ($property in 'name', 'uri', 'trusted', 'priority', 'repositoryType') {
if ($null -eq $inputObj.PSObject.Properties[$property]) {
continue
}

if ($property -eq 'uri') {
if (-not (TestRepositoryUri -DesiredUri $inputObj.uri -ActualUri $actual.uri)) {
$property
}
}
elseif ($actual.$property -cne $inputObj.$property) {
$property
Comment thread
dleontev marked this conversation as resolved.
Resolved
}
}
}
)

$actual._inDesiredState = $differingProperties.Count -eq 0
$actual.ToJson()
ConvertTo-Json -InputObject $differingProperties -Compress
}

function TestPSResourceList {
param(
[PSCustomObject]$inputObj
Expand Down Expand Up @@ -516,8 +551,7 @@ function TestOperation {

switch ($ResourceType) {
'repository' {
Write-Trace -level error -message "Test operation is not implemented for Repository resource."
exit [ExitCode]::TestNotImplemented
TestRepository -inputObj $inputObj
}
'repositorylist' {
Write-Trace -level error -message "Test operation is not implemented for RepositoryList resource."
Expand Down Expand Up @@ -555,7 +589,7 @@ function ExportOperation {
$_.Trusted,
$_.Priority,
$_.ApiVersion
).ToJson()
).ToJsonForExport()
}
}

Expand Down
27 changes: 26 additions & 1 deletion src/dsc/repository.dsc.resource.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@
],
"input": "stdin"
},
"test": {
"executable": "pwsh",
"args": [
"-NoLogo",
"-NonInteractive",
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-Command",
"$Input | ./psresourceget.ps1 -resourcetype 'repository' -operation 'test'"
],
"input": "stdin",
"return": "stateAndDiff"
},
"delete": {
"executable": "pwsh",
"args": [
Expand Down Expand Up @@ -127,6 +141,9 @@
},
"_exist": {
"$ref": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/exist.json"
},
"_inDesiredState": {
"$ref": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/inDesiredState.json"
}
},
"$defs": {
Expand Down Expand Up @@ -154,8 +171,16 @@
false,
true
]
},
"https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/inDesiredState.json": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/inDesiredState.json",
"title": "Instance is in desired state",
"description": "Indicates whether the repository is in the desired state.",
"type": ["boolean", "null"],
"readOnly": true
}
}
}
}
}
}
109 changes: 109 additions & 0 deletions test/DscResource/PSResourceGetDSCResource.Tests.ps1
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

$modPath = "$psscriptroot/../PSGetTestUtils.psm1"
Import-Module $modPath -Force -Verbose

Expand Down Expand Up @@ -497,6 +500,7 @@ Describe 'E2E tests for Repository resource' -Tags 'CI' {
$testRepo.name | Should -BeExactly 'TestRepository'
$testRepo.uri | Should -BeExactly 'https://www.powershellgallery.com/api/v2'
$testRepo._exist | Should -Be $true
$testRepo.PSObject.Properties.Name | Should -Not -Contain '_inDesiredState'
}

It 'Unregister test repository via DSC configuration' {
Expand All @@ -505,6 +509,111 @@ Describe 'E2E tests for Repository resource' -Tags 'CI' {
$repo = Get-PSResourceRepository -Name 'TestRepository' -ErrorAction SilentlyContinue
$repo | Should -BeNullOrEmpty
}

It 'Applies a local repository path only when it changes' {
$repoName = 'DscLocalRepo' + [guid]::NewGuid().ToString('N')
$initialPath = (New-Item -ItemType Directory -Path (Join-Path $TestDrive 'initial repository')).FullName
$updatedPath = (New-Item -ItemType Directory -Path (Join-Path $TestDrive 'updated repository')).FullName
$properties = @{ name = $repoName; uri = $initialPath }
$configuration = @{
'$schema' = 'https://aka.ms/dsc/schemas/v3/bundled/config/document.json'
resources = @(@{
name = 'Local repository'
type = 'Microsoft.PowerShell.PSResourceGet/Repository'
properties = $properties
})
}

try {
$configJson = $configuration | ConvertTo-Json -Depth 5 -Compress
$initial = & $dscExe config set --input $configJson -o json | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0
$initial.hadErrors | Should -Be $false
$initial.results.result.afterState._exist | Should -Be $true

$test = & $dscExe config test --input $configJson -o json | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0
$test.results.result.inDesiredState | Should -Be $true
$test.results.result.differingProperties | Should -BeNullOrEmpty

$repeat = & $dscExe config set --input $configJson -o json | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0
$repeat.hadErrors | Should -Be $false
$repeat.results.result.changedProperties | Should -BeNullOrEmpty

$properties.uri = $updatedPath
$configJson = $configuration | ConvertTo-Json -Depth 5 -Compress
$test = & $dscExe config test --input $configJson -o json | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0
$test.results.result.inDesiredState | Should -Be $false
$test.results.result.differingProperties | Should -Be @('uri')

$updated = & $dscExe config set --input $configJson -o json | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0
([uri]$updated.results.result.afterState.uri).LocalPath | Should -BeExactly $updatedPath
}
finally {
Get-PSResourceRepository -Name $repoName -ErrorAction SilentlyContinue |
Unregister-PSResourceRepository -ErrorAction Stop
}
}

It 'Converges when only <Property> changes' -TestCases @(
@{ Property = 'trusted'; DesiredValue = $false }
@{ Property = 'priority'; DesiredValue = 0 }
@{ Property = 'repositoryType'; DesiredValue = 'V3' }
) {
param($Property, $DesiredValue)

$repoName = 'DscPropertyRepo' + [guid]::NewGuid().ToString('N')
$properties = @{
name = $repoName
uri = 'https://example.invalid/feed/index.json'
trusted = $true
priority = 50
repositoryType = 'V2'
}
$configuration = @{
'$schema' = 'https://aka.ms/dsc/schemas/v3/bundled/config/document.json'
resources = @(@{
name = 'Repository property drift'
type = 'Microsoft.PowerShell.PSResourceGet/Repository'
properties = $properties
})
}

try {
$configJson = $configuration | ConvertTo-Json -Depth 5 -Compress
$initial = & $dscExe config set --input $configJson -o json | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0
$initial.hadErrors | Should -Be $false
$initial.results.result.afterState.$Property | Should -Be $properties[$Property]

$properties[$Property] = $DesiredValue
$configJson = $configuration | ConvertTo-Json -Depth 5 -Compress
$drift = & $dscExe config test --input $configJson -o json | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0
$drift.hadErrors | Should -Be $false
$drift.results.result.inDesiredState | Should -Be $false
@($drift.results.result.differingProperties).Count | Should -Be 1
$drift.results.result.differingProperties[0] | Should -BeExactly $Property

$updated = & $dscExe config set --input $configJson -o json | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0
$updated.hadErrors | Should -Be $false
$updated.results.result.afterState.$Property | Should -Be $DesiredValue

$converged = & $dscExe config test --input $configJson -o json | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0
$converged.hadErrors | Should -Be $false
$converged.results.result.inDesiredState | Should -Be $true
$converged.results.result.differingProperties | Should -BeNullOrEmpty
}
finally {
Get-PSResourceRepository -Name $repoName -ErrorAction SilentlyContinue |
Unregister-PSResourceRepository -ErrorAction Stop
}
}
}

Describe 'E2E tests for PSResourceList resource' -Tags 'CI' {
Expand Down