diff --git a/docs/preview/features/powershell/azure-devops.md b/docs/preview/features/powershell/azure-devops.md index 35fd0082..b495ff30 100644 --- a/docs/preview/features/powershell/azure-devops.md +++ b/docs/preview/features/powershell/azure-devops.md @@ -10,6 +10,7 @@ This module provides the following capabilities: - [Setting a variable in an Azure DevOps pipeline](#setting-a-variable-in-an-azure-devops-pipeline) - [Setting ARM outputs to Azure DevOps variable group](#setting-arm-outputs-to-azure-devops-variable-group) - [Setting ARM outputs to Azure DevOps pipeline variables](#setting-arm-outputs-to-azure-devops-pipeline-variables) +- [Save Azure DevOps build](#save-azure-devops-build) ## Installation @@ -105,3 +106,20 @@ PS> Set-AzDevOpsArmOutputsToPipelineVariables -ArmOutputsEnvironmentVariableName # The pipeline variable my-variable will be updated to value my-value, so it can be used in subsequent tasks of the current job. # ##vso[task.setvariable variable=my-variable]my-value ``` + +## Save Azure DevOps build + +Saves/retains a specific Azure DevOps pipeline run. + +| Parameter | Mandatory | Description | +| --------------- | --------- | ---------------------------------------------------------------------------| +| `ProjectId` | yes | The Id of the Project where the build that must be retained can be found | +| `BuildId` | yes | The Id of the build that must be retained | + +**Example** + +```powershell +PS> Save-AzDevOpsBuild -ProjectId $(System.TeamProjectId) -BuildId $(Build.BuildId) +# The variables $(System.TeamProjectId) and $(Build.BuildId) are predefined Azure DevOps variables +# Information on them can be found here: https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml +``` \ No newline at end of file diff --git a/src/Arcus.Scripting.DevOps/Arcus.Scripting.DevOps.psd1 b/src/Arcus.Scripting.DevOps/Arcus.Scripting.DevOps.psd1 index b6af9a06..fff64231 100644 Binary files a/src/Arcus.Scripting.DevOps/Arcus.Scripting.DevOps.psd1 and b/src/Arcus.Scripting.DevOps/Arcus.Scripting.DevOps.psd1 differ diff --git a/src/Arcus.Scripting.DevOps/Arcus.Scripting.DevOps.psm1 b/src/Arcus.Scripting.DevOps/Arcus.Scripting.DevOps.psm1 index f3b5d062..bf51fa00 100644 --- a/src/Arcus.Scripting.DevOps/Arcus.Scripting.DevOps.psm1 +++ b/src/Arcus.Scripting.DevOps/Arcus.Scripting.DevOps.psm1 @@ -72,4 +72,31 @@ function Set-AzDevOpsArmOutputsToPipelineVariables { . $PSScriptRoot\Scripts\Set-AzDevOpsArmOutputs.ps1 -ArmOutputsEnvironmentVariableName $ArmOutputsEnvironmentVariableName -UpdateVariablesForCurrentJob } -Export-ModuleMember -Function Set-AzDevOpsArmOutputsToPipelineVariables \ No newline at end of file +Export-ModuleMember -Function Set-AzDevOpsArmOutputsToPipelineVariables + +<# + .Synopsis + Indicates that the specified DevOps pipeline-run must be retained indefinetely. + + .Description + Indicates that the specified DevOps pipeline-run must be retained indefinetely. + + .Parameter ProjectId + The Id of the Project in Azure DevOps to which the build that must be retained, belongs to. + (You can use the predefined variable $(System.TeamProjectId) in an Azure DevOps pipeline). + + .Parameter BuildId + The Id of the Build that must be retained. + (You can use the predefined variable $(Build.BuildId) in an Azure DevOps pipeline). + +#> +function Save-AzDevOpsBuild { + param( + [Parameter(Mandatory = $true)][string] $ProjectId = $(throw "ProjectId is required"), + [Parameter(Mandatory = $true)][string] $BuildId = $(throw "BuildId is required") + ) + + . $PSScriptRoot\Scripts\Save-AzDevOpsBuild.ps1 -ProjectId $ProjectId -BuildId $BuildId +} + +Export-ModuleMember -Function Save-AzDevOpsBuild \ No newline at end of file diff --git a/src/Arcus.Scripting.DevOps/Arcus.Scripting.DevOps.pssproj b/src/Arcus.Scripting.DevOps/Arcus.Scripting.DevOps.pssproj index 435afd35..1ba027ce 100644 --- a/src/Arcus.Scripting.DevOps/Arcus.Scripting.DevOps.pssproj +++ b/src/Arcus.Scripting.DevOps/Arcus.Scripting.DevOps.pssproj @@ -33,6 +33,7 @@ + diff --git a/src/Arcus.Scripting.DevOps/Scripts/Save-AzDevOpsBuild.ps1 b/src/Arcus.Scripting.DevOps/Scripts/Save-AzDevOpsBuild.ps1 new file mode 100644 index 00000000..de4dc042 --- /dev/null +++ b/src/Arcus.Scripting.DevOps/Scripts/Save-AzDevOpsBuild.ps1 @@ -0,0 +1,28 @@ +param( + [Parameter(Mandatory = $true)][string] $ProjectId = $(throw "ProjectId is required"), + [Parameter(Mandatory = $true)][string] $BuildId = $(throw "BuildId is required") +) + +$retentionPayload = @{ + keepforever='true' +} + +$requestBody = $retentionPayload | ConvertTo-Json -Depth 1 -Compress + +$collectionUri = $env:SYSTEM_COLLECTIONURI + +if( $collectionUri.EndsWith('/') -eq $False ){ + $collectionUri = $collectionUri + '/' +} + +$requestUri = "$collectionUri" + "$ProjectId/_apis/build/builds/" + $BuildId + "?api-version=6.0" + +Write-Verbose "Saving AzDevOps build with buildid $BuildId in project $ProjectId by posting $requestBody to $requestUri" + +$response = Invoke-WebRequest -Uri $requestUri -Method Patch -Body $requestBody -ContentType "application/json" -Headers @{ Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN" } + +if( $response.StatusCode -ne 200 ) { + Throw "Unable to retain build indefinetely. API request returned statuscode $($response.StatusCode)" +} + +exit 0 diff --git a/src/Arcus.Scripting.Tests.Unit/Arcus.Scripting.DevOps.tests.ps1 b/src/Arcus.Scripting.Tests.Unit/Arcus.Scripting.DevOps.tests.ps1 index 70313a47..ecc9441b 100644 --- a/src/Arcus.Scripting.Tests.Unit/Arcus.Scripting.DevOps.tests.ps1 +++ b/src/Arcus.Scripting.Tests.Unit/Arcus.Scripting.DevOps.tests.ps1 @@ -1,7 +1,7 @@ Describe "Arcus" { Context "Azure DevOps" { InModuleScope Arcus.Scripting.DevOps { - It "Seting DevOps variable should write to host" { + It "Setting DevOps variable should write to host" { # Arrange Mock Write-Host { $Object | Should -Be "#vso[task.setvariable variable=test] value" } -Verifiable @@ -120,6 +120,76 @@ Assert-VerifiableMock Assert-MockCalled Write-Host } - } + It "Save-AzDevOpsBuild fails when API call does not return success-code" { + # Arrange + $env:SYSTEM_COLLECTIONURI = "https://dev.azure.com/myorganization/" + $env:ACCESS_TOKEN = "mocking accesstoken" + $projectId = "abc123" + $buildId = 128 + + Mock Invoke-WebRequest { + $statusCode = 400 + $response = New-Object System.Net.Http.HttpResponseMessage $statusCode + return $response + } -ModuleName Arcus.Scripting.DevOps + + # Act and Assert + { Save-AzDevOpsBuild -ProjectId $projectId -BuildId $buildId } | Should -Throw + } + It "Save-AzDevOpsBuild succeeds when API call does return success-code" { + # Arrange + $env:SYSTEM_COLLECTIONURI = "https://dev.azure.com/myorganization/" + $env:ACCESS_TOKEN = "mocking accesstoken" + $projectId = "abc123" + $buildId = 128 + + Mock Invoke-WebRequest { + $statusCode = 200 + $response = New-Object System.Net.Http.HttpResponseMessage $statusCode + return $response + } -ModuleName Arcus.Scripting.DevOps + + # Act and Assert + { Save-AzDevOpsBuild -ProjectId $projectId -BuildId $buildId } | Should -Not -Throw + } + It "Save-AzDevOpsBuild correctly builds API endpoint when CollectionUri has trailing slash" { + # Arrange + $env:SYSTEM_COLLECTIONURI = "https://dev.azure.com/myorganization/" + $env:ACCESS_TOKEN = "mocking accesstoken" + $projectId = "abc123" + $buildId = 128 + + Mock Invoke-WebRequest { + $statusCode = 200 + $response = New-Object System.Net.Http.HttpResponseMessage $statusCode + return $response + } -ModuleName Arcus.Scripting.DevOps + + # Act + Save-AzDevOpsBuild -ProjectId $projectId -BuildId $buildId + + # Assert + Should -Invoke -CommandName Invoke-WebRequest -Times 1 -ParameterFilter { $Uri -Like "https://dev.azure.com/myorganization/$projectId/*" } + } + It "Save-AzDevOpsBuild correctly builds API endpoint when CollectionUri does not have trailing slash" { + # Arrange + $env:SYSTEM_COLLECTIONURI = "https://dev.azure.com/myorganization" + $env:ACCESS_TOKEN = "mocking accesstoken" + $projectId = "abc123" + $buildId = 128 + + Mock Invoke-WebRequest { + $statusCode = 200 + $response = New-Object System.Net.Http.HttpResponseMessage $statusCode + return $response + } -ModuleName Arcus.Scripting.DevOps + + # Act + Save-AzDevOpsBuild -ProjectId $projectId -BuildId $buildId + + # Assert + Should -Invoke -CommandName Invoke-WebRequest -Times 1 -ParameterFilter { $Uri -Like "https://dev.azure.com/myorganization/$projectId/*" } + } + } } }