diff --git a/docs/Get-AdoStorageKey.md b/docs/Get-AdoStorageKey.md new file mode 100644 index 0000000..1b10b4f --- /dev/null +++ b/docs/Get-AdoStorageKey.md @@ -0,0 +1,175 @@ + + + + + +# Get-AdoStorageKey + +## SYNOPSIS + +Resolve a descriptor to a storage key in an Azure DevOps organization. + +## SYNTAX + +### __AllParameterSets + +```powershell +Get-AdoStorageKey [[-CollectionUri] ] [-SubjectDescriptor] [[-Version] ] + [] +``` + +## ALIASES + +This cmdlet has the following aliases, +- N/A + +## DESCRIPTION + +This function resolve a descriptor to a storage key in an Azure DevOps organization through REST API. + +## EXAMPLES + +### EXAMPLE 1 + +#### PowerShell + +```powershell +Get-AdoStorageKey -SubjectDescriptor 'aad.00000000-0000-0000-0000-000000000000' +``` + +Resolve a descriptor to a storage key using the default collection URI from environment variable. + +### EXAMPLE 2 + +#### PowerShell + +```powershell +$params = @{ + CollectionUri = 'https://dev.azure.com/my-org' + SubjectDescriptor = 'aad.00000000-0000-0000-0000-000000000000' +} +Get-AdoStorageKey @params +``` + +Resolve a descriptor to a storage key. + +### EXAMPLE 3 + +#### PowerShell + +```powershell +$params = @{ + CollectionUri = 'https://dev.azure.com/my-org' +} +@( + 'aad.00000000-0000-0000-0000-000000000001', + 'aad.00000000-0000-0000-0000-000000000002' +) | Get-AdoStorageKey @params +``` + +Resolves multiple descriptors to their corresponding storage keys, demonstrating pipeline input. + +## PARAMETERS + +### -CollectionUri + +Optional. +The collection URI of the Azure DevOps collection/organization, e.g., . + +```yaml +Type: System.String +DefaultValue: $env:DefaultAdoCollectionUri +SupportsWildcards: false +Aliases: [] +ParameterSets: +- Name: (All) + Position: 0 + IsRequired: false + ValueFromPipeline: false + ValueFromPipelineByPropertyName: true + ValueFromRemainingArguments: false +DontShow: false +AcceptedValues: [] +HelpMessage: '' +``` + +### -SubjectDescriptor + +Mandatory. +The descriptor of the Graph entity to resolve. + +```yaml +Type: System.String +DefaultValue: '' +SupportsWildcards: false +Aliases: [] +ParameterSets: +- Name: (All) + Position: 1 + IsRequired: true + ValueFromPipeline: false + ValueFromPipelineByPropertyName: true + ValueFromRemainingArguments: false +DontShow: false +AcceptedValues: [] +HelpMessage: '' +``` + +### -Version + +The API version to use. +Default is '7.2-preview.1'. +The -preview flag must be supplied in the api-version for this request to work. + +```yaml +Type: System.String +DefaultValue: 7.2-preview.1 +SupportsWildcards: false +Aliases: +- ApiVersion +ParameterSets: +- Name: (All) + Position: 2 + IsRequired: false + ValueFromPipeline: false + ValueFromPipelineByPropertyName: false + ValueFromRemainingArguments: false +DontShow: false +AcceptedValues: +- '7.1' +- '7.2-preview.1' +HelpMessage: '' +``` + +### CommonParameters + +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, +-InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, +-ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see +[about_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +- N/A + +## OUTPUTS + +### PSCustomObject + +## NOTES + +- Requires authentication to Azure DevOps. Use `Set-AdoDefault` to configure default organization and project values. +- The cmdlet automatically retrieves authentication through `Invoke-AdoRestMethod` which calls `New-AdoAuthHeader`. + +## RELATED LINKS + +- diff --git a/src/Azure.DevOps.PSModule/Azure.DevOps.PSModule.psd1 b/src/Azure.DevOps.PSModule/Azure.DevOps.PSModule.psd1 index 0572ec0..591c64b 100644 --- a/src/Azure.DevOps.PSModule/Azure.DevOps.PSModule.psd1 +++ b/src/Azure.DevOps.PSModule/Azure.DevOps.PSModule.psd1 @@ -89,6 +89,7 @@ 'Get-AdoProject' 'Get-AdoRepository' 'Get-AdoServiceEndpoint' + 'Get-AdoStorageKey' 'Get-AdoTeam' 'Get-AdoTeamFieldValue' 'Get-AdoTeamIteration' diff --git a/src/Azure.DevOps.PSModule/Public/Graph/StorageKeys/Get-AdoStorageKey.ps1 b/src/Azure.DevOps.PSModule/Public/Graph/StorageKeys/Get-AdoStorageKey.ps1 new file mode 100644 index 0000000..5232239 --- /dev/null +++ b/src/Azure.DevOps.PSModule/Public/Graph/StorageKeys/Get-AdoStorageKey.ps1 @@ -0,0 +1,113 @@ +function Get-AdoStorageKey { + <# + .SYNOPSIS + Resolve a descriptor to a storage key in an Azure DevOps organization. + + .DESCRIPTION + This function resolve a descriptor to a storage key in an Azure DevOps organization through REST API. + + .PARAMETER CollectionUri + Optional. The collection URI of the Azure DevOps collection/organization, e.g., https://vssps.dev.azure.com/my-org. + + .PARAMETER SubjectDescriptor + Mandatory. The descriptor of the Graph entity to resolve. + + .PARAMETER Version + The API version to use. Default is '7.2-preview.1'. + The -preview flag must be supplied in the api-version for this request to work. + + .OUTPUTS + PSCustomObject + + .LINK + - https://learn.microsoft.com/en-us/rest/api/azure/devops/graph/storage-keys/get + + .EXAMPLE + Get-AdoStorageKey -SubjectDescriptor 'aad.00000000-0000-0000-0000-000000000000' + + Resolve a descriptor to a storage key using the default collection URI from environment variable. + + .EXAMPLE + $params = @{ + CollectionUri = 'https://dev.azure.com/my-org' + SubjectDescriptor = 'aad.00000000-0000-0000-0000-000000000000' + } + Get-AdoStorageKey @params + + Resolve a descriptor to a storage key. + + .EXAMPLE + $params = @{ + CollectionUri = 'https://dev.azure.com/my-org' + } + @( + 'aad.00000000-0000-0000-0000-000000000001', + 'aad.00000000-0000-0000-0000-000000000002' + ) | Get-AdoStorageKey @params + + Resolves multiple descriptors to their corresponding storage keys, demonstrating pipeline input. + #> + [CmdletBinding()] + param ( + [Parameter(ValueFromPipelineByPropertyName)] + [string]$CollectionUri = $env:DefaultAdoCollectionUri, + + [Parameter(Mandatory, ValueFromPipelineByPropertyName)] + [string]$SubjectDescriptor, + + [Parameter(HelpMessage = 'The -preview flag must be supplied in the api-version for this request to work.')] + [Alias('ApiVersion')] + [ValidateSet('7.1-preview.1', '7.2-preview.1')] + [string]$Version = '7.2-preview.1' + ) + + begin { + Write-Verbose ("Command: $($MyInvocation.MyCommand.Name)") + Write-Debug ("CollectionUri: $CollectionUri") + Write-Debug ("SubjectDescriptor: $SubjectDescriptor") + Write-Debug ("Version: $Version") + + Confirm-Default -Defaults ([ordered]@{ + 'CollectionUri' = $CollectionUri + }) + + if ($CollectionUri -notmatch 'vssps\.') { + $CollectionUri = $CollectionUri -replace 'https://', 'https://vssps.' + } + } + + process { + try { + $params = @{ + Uri = "$CollectionUri/_apis/graph/storagekeys/$SubjectDescriptor" + Version = $Version + Method = 'GET' + } + + try { + $result = (Invoke-AdoRestMethod @params).value + + if ($null -ne $result) { + [PSCustomObject]@{ + subjectDescriptor = $SubjectDescriptor + value = $result + collectionUri = $CollectionUri + } + } + } catch { + if ($_.ErrorDetails.Message -match 'StorageKeyNotFoundException') { + Write-Warning "The storage key for descriptor $SubjectDescriptor could not be found, skipping." + } else { + throw $_ + } + } + + } catch { + throw $_ + } + } + + end { + Write-Verbose ("Exit: $($MyInvocation.MyCommand.Name)") + } +} diff --git a/src/Azure.DevOps.PSModule/Tests/Graph/StorageKeys/Get-AdoStorageKey.Tests.ps1 b/src/Azure.DevOps.PSModule/Tests/Graph/StorageKeys/Get-AdoStorageKey.Tests.ps1 new file mode 100644 index 0000000..67f9d73 --- /dev/null +++ b/src/Azure.DevOps.PSModule/Tests/Graph/StorageKeys/Get-AdoStorageKey.Tests.ps1 @@ -0,0 +1,242 @@ +BeforeAll { + # Import the module + $modulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\..\..' + $moduleName = Join-Path -Path $modulePath -ChildPath 'Azure.DevOps.PSModule\Azure.DevOps.PSModule.psd1' + + # Remove module if already loaded + Get-Module Azure.DevOps.PSModule | Remove-Module -Force + + # Import the module + Import-Module $moduleName -Force -Verbose:$false +} + +Describe 'Get-AdoStorageKey' { + BeforeAll { + # Sample storage key response data for mocking + $mockStorageKeyValue = '00000000-0000-0000-0000-000000000001' + + $mockStorageKeyResponse = @{ + value = $mockStorageKeyValue + } + + $mockSubjectDescriptor = 'aad.NDUzOGJhZjItN2M2OS03YzhjLWJiY2QtOGEzZTg3NzEzZjY0' + $mockCollectionUri = 'https://dev.azure.com/my-org' + } + + Context 'Core Functionality Tests' { + BeforeEach { + Mock -ModuleName Azure.DevOps.PSModule Invoke-AdoRestMethod { return $mockStorageKeyResponse } + Mock -ModuleName Azure.DevOps.PSModule Confirm-Default { } + } + + It 'Should resolve a descriptor to its storage key' { + # Act + $result = Get-AdoStorageKey -CollectionUri $mockCollectionUri -SubjectDescriptor $mockSubjectDescriptor + + # Assert + $result | Should -Not -BeNullOrEmpty + $result.value | Should -Be $mockStorageKeyValue + $result.subjectDescriptor | Should -Be $mockSubjectDescriptor + $result.collectionUri | Should -Be 'https://vssps.dev.azure.com/my-org' + } + + It 'Should return PSCustomObject with expected properties' { + # Act + $result = Get-AdoStorageKey -CollectionUri $mockCollectionUri -SubjectDescriptor $mockSubjectDescriptor + + # Assert + $result.PSObject.Properties.Name | Should -Contain 'subjectDescriptor' + $result.PSObject.Properties.Name | Should -Contain 'value' + $result.PSObject.Properties.Name | Should -Contain 'collectionUri' + } + + It 'Should construct API URI correctly' { + # Act + Get-AdoStorageKey -CollectionUri $mockCollectionUri -SubjectDescriptor $mockSubjectDescriptor + + # Assert + Should -Invoke Invoke-AdoRestMethod -ModuleName Azure.DevOps.PSModule -Times 1 -ParameterFilter { + $Uri -eq "https://vssps.dev.azure.com/my-org/_apis/graph/storagekeys/$mockSubjectDescriptor" + } + } + + It 'Should use GET method for API call' { + # Act + Get-AdoStorageKey -CollectionUri $mockCollectionUri -SubjectDescriptor $mockSubjectDescriptor + + # Assert + Should -Invoke Invoke-AdoRestMethod -ModuleName Azure.DevOps.PSModule -Times 1 -ParameterFilter { + $Method -eq 'GET' + } + } + + It 'Should use default API version 7.2-preview.1' { + # Act + Get-AdoStorageKey -CollectionUri $mockCollectionUri -SubjectDescriptor $mockSubjectDescriptor + + # Assert + Should -Invoke Invoke-AdoRestMethod -ModuleName Azure.DevOps.PSModule -Times 1 -ParameterFilter { + $Version -eq '7.2-preview.1' + } + } + + It 'Should use specified API version when provided' { + # Act + Get-AdoStorageKey -CollectionUri $mockCollectionUri -SubjectDescriptor $mockSubjectDescriptor -Version '7.1-preview.1' + + # Assert + Should -Invoke Invoke-AdoRestMethod -ModuleName Azure.DevOps.PSModule -Times 1 -ParameterFilter { + $Version -eq '7.1-preview.1' + } + } + + It 'Should use ApiVersion alias for Version parameter' { + # Act + Get-AdoStorageKey -CollectionUri $mockCollectionUri -SubjectDescriptor $mockSubjectDescriptor -ApiVersion '7.1-preview.1' + + # Assert + Should -Invoke Invoke-AdoRestMethod -ModuleName Azure.DevOps.PSModule -Times 1 -ParameterFilter { + $Version -eq '7.1-preview.1' + } + } + } + + Context 'Pipeline Support Tests' { + BeforeEach { + Mock -ModuleName Azure.DevOps.PSModule Invoke-AdoRestMethod { return $mockStorageKeyResponse } + Mock -ModuleName Azure.DevOps.PSModule Confirm-Default { } + } + + It 'Should accept descriptor with collectionUri from pipeline' { + # Arrange + $objects = @( + [PSCustomObject]@{ SubjectDescriptor = 'aad.00000000-0000-0000-0000-000000000001'; CollectionUri = $mockCollectionUri } + [PSCustomObject]@{ SubjectDescriptor = 'aad.00000000-0000-0000-0000-000000000002'; CollectionUri = $mockCollectionUri } + [PSCustomObject]@{ SubjectDescriptor = 'aad.00000000-0000-0000-0000-000000000003'; CollectionUri = $mockCollectionUri } + ) + + # Act + $result = $objects | Get-AdoStorageKey + + # Assert + $result | Should -HaveCount 3 + Should -Invoke Invoke-AdoRestMethod -ModuleName Azure.DevOps.PSModule -Times 3 + } + + It 'Should accept objects with SubjectDescriptor property from pipeline' { + # Arrange + $objects = @( + [PSCustomObject]@{ SubjectDescriptor = 'aad.00000000-0000-0000-0000-000000000001' } + [PSCustomObject]@{ SubjectDescriptor = 'aad.00000000-0000-0000-0000-000000000002' } + ) + + # Act + $result = $objects | Get-AdoStorageKey -CollectionUri $mockCollectionUri + + # Assert + $result | Should -HaveCount 2 + Should -Invoke Invoke-AdoRestMethod -ModuleName Azure.DevOps.PSModule -Times 2 + } + } + + Context 'Error Handling Tests' { + BeforeEach { + Mock -ModuleName Azure.DevOps.PSModule Confirm-Default { } + } + + It 'Should handle StorageKeyNotFoundException gracefully with warning' { + # Arrange + $storageKeyNotFoundError = [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new('Not found'), + 'StorageKeyNotFoundException', + [System.Management.Automation.ErrorCategory]::ObjectNotFound, + $null + ) + $storageKeyNotFoundError.ErrorDetails = [System.Management.Automation.ErrorDetails]::new('{"message":"StorageKeyNotFoundException: Storage key not found"}') + + Mock -ModuleName Azure.DevOps.PSModule Invoke-AdoRestMethod { throw $storageKeyNotFoundError } + Mock -ModuleName Azure.DevOps.PSModule Write-Warning { } + + # Act + $result = Get-AdoStorageKey -CollectionUri $mockCollectionUri -SubjectDescriptor $mockSubjectDescriptor -WarningAction SilentlyContinue + + # Assert + $result | Should -BeNullOrEmpty + Should -Invoke Write-Warning -ModuleName Azure.DevOps.PSModule -Times 1 + } + + It 'Should propagate non-StorageKeyNotFoundException errors' { + # Arrange + $genericError = [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new('Server error'), + 'ServerError', + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $null + ) + $genericError.ErrorDetails = [System.Management.Automation.ErrorDetails]::new('{"message":"Internal server error"}') + + Mock -ModuleName Azure.DevOps.PSModule Invoke-AdoRestMethod { throw $genericError } + + # Act & Assert + { Get-AdoStorageKey -CollectionUri $mockCollectionUri -SubjectDescriptor $mockSubjectDescriptor } | Should -Throw + } + + It 'Should return null when API returns null result' { + # Arrange + Mock -ModuleName Azure.DevOps.PSModule Invoke-AdoRestMethod { return @{ value = $null } } + + # Act + $result = Get-AdoStorageKey -CollectionUri $mockCollectionUri -SubjectDescriptor $mockSubjectDescriptor + + # Assert + $result | Should -BeNullOrEmpty + } + } + + Context 'CollectionUri Handling Tests' { + BeforeEach { + Mock -ModuleName Azure.DevOps.PSModule Invoke-AdoRestMethod { return $mockStorageKeyResponse } + Mock -ModuleName Azure.DevOps.PSModule Confirm-Default { } + } + + It 'Should use environment default CollectionUri when not specified' { + # Arrange + $env:DefaultAdoCollectionUri = 'https://dev.azure.com/default-org' + + # Act + Get-AdoStorageKey -SubjectDescriptor $mockSubjectDescriptor + + # Assert + Should -Invoke Invoke-AdoRestMethod -ModuleName Azure.DevOps.PSModule -Times 1 -ParameterFilter { + $Uri -match 'https://vssps.dev.azure.com/default-org' + } + } + + It 'Should accept vssps.dev.azure.com CollectionUri without modification' { + # Arrange + $vsspsUri = 'https://vssps.dev.azure.com/my-org' + + # Act + Get-AdoStorageKey -CollectionUri $vsspsUri -SubjectDescriptor $mockSubjectDescriptor + + # Assert + Should -Invoke Invoke-AdoRestMethod -ModuleName Azure.DevOps.PSModule -Times 1 -ParameterFilter { + $Uri -eq "$vsspsUri/_apis/graph/storagekeys/$mockSubjectDescriptor" + } + } + + It 'Should transform dev.azure.com to vssps.dev.azure.com' { + # Arrange + $devAzureUri = 'https://dev.azure.com/my-org' + $expectedUri = "https://vssps.dev.azure.com/my-org/_apis/graph/storagekeys/$mockSubjectDescriptor" + + # Act + Get-AdoStorageKey -CollectionUri $devAzureUri -SubjectDescriptor $mockSubjectDescriptor + + # Assert + Should -Invoke Invoke-AdoRestMethod -ModuleName Azure.DevOps.PSModule -Times 1 -ParameterFilter { + $Uri -eq $expectedUri + } + } + } +}