Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Extension Management #78

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@

function Get-AzDoExtension {
<#
.SYNOPSIS
Retrieves installed Azure DevOps extensions for a given organization.

.DESCRIPTION
The Get-AzDoExtension function retrieves installed extensions from an Azure DevOps organization.
It supports filtering by extension name and extension ID. The function uses the Azure DevOps REST API
to fetch the extensions and returns detailed information about each extension.

.PARAMETER CollectionUri
The URI of the Azure DevOps organization collection. This parameter is mandatory and accepts a string.

.PARAMETER ExtensionName
The name(s) of the extension(s) to look for. This parameter accepts an array of strings and is optional.

.PARAMETER ExtensionId
The ID(s) of the extension(s) to look for. This parameter accepts an array of strings and is optional.

.EXAMPLE
PS> Get-AzDoExtension -CollectionUri "https://dev.azure.com/organization" -ExtensionName "extension1"

Retrieves the extension named "extension1" from the specified Azure DevOps organization.

.EXAMPLE
PS> Get-AzDoExtension -CollectionUri "https://dev.azure.com/organization" -ExtensionId "extension-id-123"

Retrieves the extension with the ID "extension-id-123" from the specified Azure DevOps organization.

.NOTES
This function uses the Azure DevOps REST API to fetch the installed extensions.
Ensure you have the necessary permissions to access the API.

.LINK
https://learn.microsoft.com/en-us/rest/api/azure/devops/extensionmanagement/installed-extensions/get?view=azure-devops-rest-7.1&tabs=HTTP
#>
[CmdletBinding(SupportsShouldProcess)]
param (
# Collection Uri of the organization
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[ValidateScript({ Validate-CollectionUri -CollectionUri $_ })]
[string]
$CollectionUri,

# Name(s) of the extension(s) to look for
[Parameter(ValueFromPipelineByPropertyName, ValueFromPipeline)]
[string[]]
$ExtensionName,

# Id(s) of the extension(s) to look for
[Parameter(ValueFromPipelineByPropertyName, ValueFromPipeline)]
[string[]]
$ExtensionId
)

begin {
Write-Verbose "Starting function: Get-AzDoExtension"
}

process {
# For extensions a different base URI is used: https://learn.microsoft.com/en-us/rest/api/azure/devops/extensionmanagement/installed-extensions/get?view=azure-devops-rest-7.1&tabs=HTTP
$extensionCollectionUri = $CollectionUri -replace "//dev", "//extmgmt.dev"

$params = @{
uri = "$extensionCollectionUri/_apis/extensionmanagement/installedextensions"
version = "7.1-preview.1"
method = "GET"
}

if ($PSCmdlet.ShouldProcess($CollectionUri, "Get Extension(s) in $CollectionUri")) {
$result = (Invoke-AzDoRestMethod @params).value | Where-Object { -not $ExtensionName -and -not $ExtensionId -or $_.extensionName -in $ExtensionName -or $_.extensionId -in $ExtensionId }
} else {
Write-Verbose "Calling Invoke-AzDoRestMethod with $($params| ConvertTo-Json -Depth 10)"
}
}
end {
if ($result) {
$result | ForEach-Object {
[PSCustomObject]@{
CollectionURI = $CollectionUri
ExtensionCollectionURI = $extensionCollectionUri
ExtensionId = $_.extensionId
ExtensionName = $_.extensionName
ExtensionPublisherId = $_.PublisherId
ExtensionPublisherName = $_.PublisherName
ExtensionVersion = $_.version
ExtensionBaseUri = $_.baseUri
ExtensionFallbackBaseUri = $_.fallbackBaseUri
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
function New-AzDoExtension {
<#
.SYNOPSIS
Installs an Azure DevOps extension in the specified organization.

.DESCRIPTION
The New-AzDoExtension cmdlet installs an Azure DevOps extension in the specified organization.
It uses the Azure DevOps REST API to perform the installation.

.PARAMETER CollectionUri
The URI of the Azure DevOps organization.

.PARAMETER ExtensionId
The ID of the extension to install.

.PARAMETER ExtensionPublisherId
The publisher ID of the extension to install.

.PARAMETER ExtensionVersion
The version of the extension to install. If not specified, the latest version will be installed.

.EXAMPLE
PS> New-AzDoExtension -CollectionUri "https://dev.azure.com/yourorganization" -ExtensionId "extensionId" -ExtensionPublisherId "publisherId"

This command installs the specified extension in the given Azure DevOps organization.

.EXAMPLE
PS> New-AzDoExtension -CollectionUri "https://dev.azure.com/yourorganization" -ExtensionId "extensionId" -ExtensionPublisherId "publisherId" -ExtensionVersion "1.0.0"

This command installs version 1.0.0 of the specified extension in the given Azure DevOps organization.

.NOTES
This cmdlet requires the Azure DevOps REST API and appropriate permissions to install extensions.

.LINK
https://learn.microsoft.com/en-us/rest/api/azure/devops/extensionmanagement/installed-extensions/get?view=azure-devops-rest-7.1&tabs=HTTP
#>
[CmdletBinding(SupportsShouldProcess)]
param (
# Collection Uri of the organization
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[ValidateScript({ Validate-CollectionUri -CollectionUri $_ })]
[string]
$CollectionUri,

# Name(s) of the extension(s) to look for
[Parameter(Mandatory, ValueFromPipelineByPropertyName, ValueFromPipeline)]
[string]
$ExtensionId,

# Id(s) of the extension(s) to look for
[Parameter(Mandatory, ValueFromPipelineByPropertyName, ValueFromPipeline)]
[string]
$ExtensionPublisherId,

# Version of the extension to install
[Parameter(ValueFromPipelineByPropertyName, ValueFromPipeline)]
[string]
$ExtensionVersion
)

begin {
Write-Verbose "Starting function: New-AzDoExtensions"
}

process {
# For extensions a different base URI is used: https://learn.microsoft.com/en-us/rest/api/azure/devops/extensionmanagement/installed-extensions/get?view=azure-devops-rest-7.1&tabs=HTTP
$extensionCollectionUri = $CollectionUri -replace "//dev", "//extmgmt.dev"

if ($ExtensionVersion) {
$uri = "$extensionCollectionUri/_apis/extensionmanagement/installedextensionsbyname/$ExtensionPublisherId/$ExtensionId/$ExtensionVersion"
} else {
$uri = "$extensionCollectionUri/_apis/extensionmanagement/installedextensionsbyname/$ExtensionPublisherId/$ExtensionId"
}

$params = @{
uri = $uri
version = "7.1-preview.1"
method = "POST"
}

if ($PSCmdlet.ShouldProcess($CollectionUri, "Install Extension $ExtensionName in $CollectionUri")) {
$result = (Invoke-AzDoRestMethod @params).value
} else {
Write-Verbose "Calling Invoke-AzDoRestMethod with $($params| ConvertTo-Json -Depth 10)"
}
}
end {
$result
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
function Remove-AzDoExtension {
<#
.SYNOPSIS
Removes an Azure DevOps extension from an organization.

.DESCRIPTION
The `Remove-AzDoExtension` cmdlet removes an Azure DevOps extension from a specified organization.
It uses the Azure DevOps REST API to perform the deletion.

.PARAMETER CollectionUri
Specifies the URI of the Azure DevOps organization. This parameter is mandatory and accepts a string.

.PARAMETER ExtensionId
Specifies the ID of the extension to be removed. This parameter is mandatory and accepts a string.

.PARAMETER ExtensionPublisherId
Specifies the publisher ID of the extension to be removed. This parameter is mandatory and accepts a string.

.EXAMPLE
PS> Remove-AzDoExtension -CollectionUri "https://dev.azure.com/yourorganization" -ExtensionId "yourExtensionId" -ExtensionPublisherId "yourPublisherId"

This command removes the specified extension from the specified Azure DevOps organization.

.NOTES
For more information on the Azure DevOps REST API, see:
https://learn.microsoft.com/en-us/rest/api/azure/devops/extensionmanagement/installed-extensions/get?view=azure-devops-rest-7.1&tabs=HTTP
#>
[CmdletBinding(SupportsShouldProcess)]
param (
# Collection Uri of the organization
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[ValidateScript({ Validate-CollectionUri -CollectionUri $_ })]
[string]
$CollectionUri,

# Name(s) of the extension(s) to look for
[Parameter(Mandatory, ValueFromPipelineByPropertyName, ValueFromPipeline)]
[string]
$ExtensionId,

# Id(s) of the extension(s) to look for
[Parameter(Mandatory, ValueFromPipelineByPropertyName, ValueFromPipeline)]
[string]
$ExtensionPublisherId
)

begin {
Write-Verbose "Starting function: Remove-AzDoExtension"
}

process {
# For extensions a different base URI is used: https://learn.microsoft.com/en-us/rest/api/azure/devops/extensionmanagement/installed-extensions/get?view=azure-devops-rest-7.1&tabs=HTTP
$extensionCollectionUri = $CollectionUri -replace "//dev", "//extmgmt.dev"

$params = @{
uri = "$extensionCollectionUri/_apis/extensionmanagement/installedextensionsbyname/$ExtensionPublisherId/$ExtensionId"
version = "7.1-preview.1"
method = "DELETE"
}

if ($PSCmdlet.ShouldProcess($CollectionUri, "Remove $ExtensionId from organization $CollectionUri")) {
$result = (Invoke-AzDoRestMethod @params).value
} else {
Write-Verbose "Calling Invoke-AzDoRestMethod with $($params| ConvertTo-Json -Depth 10)"
}
}
end {
$result
}
}
128 changes: 128 additions & 0 deletions tests/Api/ExtensionManagement/InstalledExtensions/Extensions.tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
BeforeDiscovery {
$ModuleName = 'AzureDevOpsPowerShell'
Get-Module $ModuleName | Remove-Module -Force -ErrorAction Ignore
$path = Join-Path -Path $PSScriptRoot -ChildPath "..\..\..\..\$ModuleName\$ModuleName.psm1" | Resolve-Path
Import-Module -Name $path -Verbose:$false -ErrorAction Stop
}

InModuleScope $ModuleName {
BeforeAll {
$collectionUri = "https://dev.azure.com/AzureDevOpsPowerShell"

$params = @{
CollectionUri = $collectionUri
Confirm = $false
}
}

Describe "Get-AzDoExtension" -Tag Local {
BeforeAll {
Mock Invoke-AzDoRestMethod {
@{
value = @(
[PSCustomObject]@{
CollectionURI = $CollectionUri
ExtensionCollectionURI = $extensionCollectionUri
ExtensionId = 'vss-testextension'
ExtensionName = 'extensionTest'
ExtensionPublisherId = 'rbnmk'
ExtensionPublisherName = 'RobinM'
ExtensionVersion = '0.0.1'
ExtensionBaseUri = 'baseUri'
ExtensionFallbackBaseUri = 'fallbackBaseUri'
}
[PSCustomObject]@{
CollectionURI = $CollectionUri
ExtensionCollectionURI = $extensionCollectionUri
ExtensionId = 'vss-testextension2'
ExtensionName = 'extensionTest2'
ExtensionPublisherId = 'rbnmk2'
ExtensionPublisherName = 'RobinM2'
ExtensionVersion = '0.0.1'
ExtensionBaseUri = 'baseUri2'
ExtensionFallbackBaseUri = 'fallbackBaseUri2'
}
[PSCustomObject]@{
CollectionURI = $CollectionUri
ExtensionCollectionURI = $extensionCollectionUri
ExtensionId = 'vss-testextension3'
ExtensionName = 'extensionTest3'
ExtensionPublisherId = 'rbnmk3'
ExtensionPublisherName = 'RobinM3'
ExtensionVersion = '0.0.1'
ExtensionBaseUri = 'baseUri3'
ExtensionFallbackBaseUri = 'fallbackBaseUri3'
}
)
}
}
}

It "It provides users with feedback via ShouldProcess when using WhatIf" {
Get-AzDoExtension @params -WhatIf -Verbose 4>&1 | Out-String | Should -BeLike "*Calling Invoke-AzDoRestMethod with {*"
}

It "Outputs all extensions when no value to ExtensionName or ExtensionId was provided" {
(Get-AzDoExtension @params | Measure-Object).Count | Should -BeGreaterThan 1
}

It "Outputs extension which matches the name of ExtensionName" {
(Get-AzDoExtension @params -ExtensionName "extensionTest3").ExtensionName | Should -Be "extensionTest3"
}

It "Outputs extension which matches the Id of ExtensionId" {
(Get-AzDoExtension @params -ExtensionId "vss-testextension2").ExtensionId | Should -Be "vss-testextension2"
}

It "Outputs extensions which matches the Id of ExtensionId AND ExtensionName" {
(Get-AzDoExtension @params -ExtensionId "vss-testextension2" -ExtensionName "extensionTest3" | Measure-Object).count | Should -BeExactly 2
}

}

Describe "New-AzDoExtension" -Tag Local {
BeforeAll {

$params.Add("ExtensionId", "vss-testextension")
$params.Add("ExtensionPublisherId", "rbnmk")

Mock Invoke-AzDoRestMethod { $null }

}

It "It provides users with feedback via ShouldProcess when using WhatIf" {
New-AzDoExtension @params -WhatIf -Verbose 4>&1 | Out-String | Should -BeLike "*Calling Invoke-AzDoRestMethod with {*"
}

It "Installs AzDo extension when ExtensionId and ExtensionPublisherId are provided and returns null or empty" {
(New-AzDoExtension @params) | Should -BeNullOrEmpty
}

It "Installs AzDo extension when ExtensionId, ExtensionPublisherId and ExtensionVersion are provided and returns null or empty" {
$params.Add("ExtensionVersion", "0.0.1")
(New-AzDoExtension @params) | Should -BeNullOrEmpty
}

It "Throws exception when ExtensionId/PublisherName is already installed" {
Mock Invoke-AzDoRestMethod { throw "Extension already installed" }
{ New-AzDoExtension @params } | Should -Throw -Because "Extension already installed"
}


}

Describe "Remove-AzDoExtension" -Tag Local {
BeforeAll {
Mock Invoke-AzDoRestMethod { $null }
$params.Remove("ExtensionVersion")
}

It "It provides users with feedback via ShouldProcess when using WhatIf" {
Remove-AzDoExtension @params -ExtensionId "vss-testextension" -ExtensionPublisherid "pesterpublisher" -WhatIf -Verbose 4>&1 | Out-String | Should -BeLike "*Calling Invoke-AzDoRestMethod with {*"
}

It "Removes AzDo extension when ExtensionId and ExtensionPublisherId are provided and returns null or empty" {
(New-AzDoExtension @params -ExtensionId "vss-testextension" -ExtensionPublisherid "pesterpublisher") | Should -BeNullOrEmpty
}
}
}
Loading