diff --git a/docs/preview/03-Features/powershell/azure-devops.md b/docs/preview/03-Features/powershell/azure-devops.md index 68fc702f..e8821856 100644 --- a/docs/preview/03-Features/powershell/azure-devops.md +++ b/docs/preview/03-Features/powershell/azure-devops.md @@ -175,18 +175,31 @@ Example of how to use this function in an Azure DevOps pipeline: 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 | +| 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 | +| `DaysToKeep` | no | The number of days to keep the Azure DevOps pipeline run, if not supplied the Azure DevOps pipeline run will be saved indefinitely | **Example** +Saving an Azure DevOps pipeline run indefinitely + ```powershell PS> Save-AzDevOpsBuild ` -ProjectId $(System.TeamProjectId) ` -BuildId $(Build.BuildId) -# Saved Azure DevOps build with build ID $BuildId in project $ProjectId +# Saved Azure DevOps build indefinitely with build ID $BuildId in project $ProjectId +``` + +Saving an Azure DevOps pipeline run for 10 days + +```powershell +PS> Save-AzDevOpsBuild ` +-ProjectId $(System.TeamProjectId) ` +-BuildId $(Build.BuildId) ` +-DaysToKeep 10 +# Saved Azure DevOps build for 10 days with build ID $BuildId in project $ProjectId ``` > 💡 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 diff --git a/src/Arcus.Scripting.DevOps/Arcus.Scripting.DevOps.psm1 b/src/Arcus.Scripting.DevOps/Arcus.Scripting.DevOps.psm1 index 43834480..83c26ddd 100644 --- a/src/Arcus.Scripting.DevOps/Arcus.Scripting.DevOps.psm1 +++ b/src/Arcus.Scripting.DevOps/Arcus.Scripting.DevOps.psm1 @@ -98,10 +98,11 @@ Export-ModuleMember -Function Set-AzDevOpsArmOutputsToPipelineVariables function Save-AzDevOpsBuild { param( [Parameter(Mandatory = $true)][string] $ProjectId = $(throw "ProjectId is required"), - [Parameter(Mandatory = $true)][string] $BuildId = $(throw "BuildId is required") + [Parameter(Mandatory = $true)][string] $BuildId = $(throw "BuildId is required"), + [Parameter(Mandatory = $false)][int] $DaysToKeep ) - . $PSScriptRoot\Scripts\Save-AzDevOpsBuild.ps1 -ProjectId $ProjectId -BuildId $BuildId + . $PSScriptRoot\Scripts\Save-AzDevOpsBuild.ps1 -ProjectId $ProjectId -BuildId $BuildId -DaysToKeep $DaysToKeep } Export-ModuleMember -Function Save-AzDevOpsBuild \ No newline at end of file diff --git a/src/Arcus.Scripting.DevOps/Scripts/Save-AzDevOpsBuild.ps1 b/src/Arcus.Scripting.DevOps/Scripts/Save-AzDevOpsBuild.ps1 index e303beec..3bdec58f 100644 --- a/src/Arcus.Scripting.DevOps/Scripts/Save-AzDevOpsBuild.ps1 +++ b/src/Arcus.Scripting.DevOps/Scripts/Save-AzDevOpsBuild.ps1 @@ -1,26 +1,35 @@ param( [Parameter(Mandatory = $true)][string] $ProjectId = $(throw "ProjectId is required"), - [Parameter(Mandatory = $true)][string] $BuildId = $(throw "BuildId is required") + [Parameter(Mandatory = $true)][string] $BuildId = $(throw "BuildId is required"), + [Parameter(Mandatory = $false)][int] $DaysToKeep ) -$retentionPayload = @{ - keepforever='true' +if ($DaysToKeep -eq '' -Or $DaysToKeep -eq 0) { + $daysValid = 99999 +} else { + $daysValid = $DaysToKeep } -$requestBody = $retentionPayload | ConvertTo-Json -Depth 1 -Compress +$retentionPayload = @{ daysValid = $daysValid; definitionId = $env:SYSTEM_DEFINITIONID; ownerId = "User:$env:BUILD_REQUESTEDFORID"; protectPipeline = $true; runId = $BuildId }; +$requestBody = ConvertTo-Json @($retentionPayload); $collectionUri = $env:SYSTEM_COLLECTIONURI if ($collectionUri.EndsWith('/') -eq $false) { - $collectionUri = $collectionUri + '/' + $collectionUri = $collectionUri + '/' } -$requestUri = "$collectionUri" + "$ProjectId/_apis/build/builds/" + $BuildId + "?api-version=6.0" +$urlEncodedProjectId = [uri]::EscapeDataString($ProjectId) +$requestUri = "$collectionUri" + "$urlEncodedProjectId/_apis/build/retention/leases?api-version=7.0" -Write-Verbose "Saving Azure DevOps build with build ID $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" } +Write-Verbose "Saving Azure DevOps build for $daysValid days with build ID $BuildId in project $ProjectId by posting '$requestBody' to '$requestUri'..." +$response = Invoke-WebRequest -Uri $requestUri -Method Post -Body $requestBody -ContentType "application/json" -Headers @{ Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN" } if ($response.StatusCode -ne 200) { - throw "Unable to retain Azure DevOps build indefinetely with build ID $BuildId in project $ProjectId. API request returned statuscode $($response.StatusCode)" + throw "Unable to retain Azure DevOps build with build ID $BuildId in project $ProjectId. API request returned statuscode $($response.StatusCode)" } -Write-Host "Saved Azure DevOps build with build ID $BuildId in project $ProjectId" -ForegroundColor Green \ No newline at end of file +if ($DaysToKeep -eq '') { + Write-Host "Saved Azure DevOps build indefinitely with build ID $BuildId in project $ProjectId" -ForegroundColor Green +} else { + Write-Host "Saved Azure DevOps build for $DaysToKeep days with build ID $BuildId in project $ProjectId" -ForegroundColor Green +} \ No newline at end of file diff --git a/src/Arcus.Scripting.Tests.Integration/Arcus.Scripting.DevOps.tests.ps1 b/src/Arcus.Scripting.Tests.Integration/Arcus.Scripting.DevOps.tests.ps1 index 0fedbc28..588be4d9 100644 --- a/src/Arcus.Scripting.Tests.Integration/Arcus.Scripting.DevOps.tests.ps1 +++ b/src/Arcus.Scripting.Tests.Integration/Arcus.Scripting.DevOps.tests.ps1 @@ -7,12 +7,12 @@ InModuleScope Arcus.Scripting.DevOps { & $PSScriptRoot\Connect-AzAccountFromConfig.ps1 -config $config } Context "Save Azure DevOps build" { - It "Saves the Azure DevOps build indefinetely" { + It "Saves the Azure DevOps build indefinitely" { # Arrange $projectId = $env:SYSTEM_TEAMPROJECTID $buildId = $env:BUILD_BUILDID $collectionUri = $env:SYSTEM_COLLECTIONURI - $requestUri = "$collectionUri" + "$projectId/_apis/build/builds/" + $buildId + "?api-version=6.0" + $requestUri = "$collectionUri" + "$projectId/_apis/build/builds/" + $buildId + "/leases?api-version=7.0" $headers = @{ Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN" } try { # Act @@ -21,12 +21,49 @@ InModuleScope Arcus.Scripting.DevOps { # Assert $getResponse = Invoke-WebRequest -Uri $requestUri -Method Get -Headers $headers $json = ConvertFrom-Json $getResponse.Content - $json.keepForever | Should -Be $true + foreach ($lease in $json.value) { + $lease.protectPipeline | Should -Be $true + $date = Get-Date -Year 2200 -Month 1 -Day 1 + $lease.validUntil | Should -BeGreaterThan $date + } } finally { - $retentionPayload = @{ keepforever='false' } - $requestBody = $retentionPayload | ConvertTo-Json -Compress - $patchResponse = Invoke-WebRequest -Uri $requestUri -Method Patch -Headers $headers -Body $requestBody -ContentType "application/json" - $patchResponse.StatusCode | Should -Be 200 + $getResponse = Invoke-WebRequest -Uri $requestUri -Method Get -Headers $headers + $json = ConvertFrom-Json $getResponse.Content + foreach ($lease in $json.value) { + $deleteUri = "$collectionUri" + "$projectId/_apis/build/retention/leases?ids=" + $lease.leaseId + "&api-version=7.0" + $deleteResponse = Invoke-WebRequest -Uri $deleteUri -Method Delete -Headers $headers + $deleteResponse.StatusCode | Should -Be 204 + } + } + } + It "Saves the Azure DevOps build for 10 days" { + # Arrange + $projectId = $env:SYSTEM_TEAMPROJECTID + $buildId = $env:BUILD_BUILDID + $collectionUri = $env:SYSTEM_COLLECTIONURI + $requestUri = "$collectionUri" + "$projectId/_apis/build/builds/" + $buildId + "/leases?api-version=7.0" + $headers = @{ Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN" } + try { + # Act + Save-AzDevOpsBuild -ProjectId $projectId -BuildId $buildId -DaysToKeep 10 + + # Assert + $getResponse = Invoke-WebRequest -Uri $requestUri -Method Get -Headers $headers + $json = ConvertFrom-Json $getResponse.Content + foreach ($lease in $json.value) { + $lease.protectPipeline | Should -Be $true + $expectedDate = (Get-Date).AddDays(10) + $actualDate = [DateTime]$lease.validUntil + $actualDate.ToUniversalTime().ToString("yyyy-MM-dd") | Should -Be $expectedDate.ToUniversalTime().ToString("yyyy-MM-dd") + } + } finally { + $getResponse = Invoke-WebRequest -Uri $requestUri -Method Get -Headers $headers + $json = ConvertFrom-Json $getResponse.Content + foreach ($lease in $json.value) { + $deleteUri = "$collectionUri" + "$projectId/_apis/build/retention/leases?ids=" + $lease.leaseId + "&api-version=7.0" + $deleteResponse = Invoke-WebRequest -Uri $deleteUri -Method Delete -Headers $headers + $deleteResponse.StatusCode | Should -Be 204 + } } } It "Sets the DevOps variable group description with the release name" -Skip { 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 6b423afc..e709ce12 100644 --- a/src/Arcus.Scripting.Tests.Unit/Arcus.Scripting.DevOps.tests.ps1 +++ b/src/Arcus.Scripting.Tests.Unit/Arcus.Scripting.DevOps.tests.ps1 @@ -205,7 +205,7 @@ InModuleScope 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" { + It "Save-AzDevOpsBuild indefinitely succeeds when API call does return success-code" { # Arrange $env:SYSTEM_COLLECTIONURI = "https://dev.azure.com/myorganization/" $env:ACCESS_TOKEN = "mocking accesstoken" @@ -221,6 +221,22 @@ InModuleScope Arcus.Scripting.DevOps { # Act and Assert { Save-AzDevOpsBuild -ProjectId $projectId -BuildId $buildId } | Should -Not -Throw } + It "Save-AzDevOpsBuild for 10 days 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 -DaysToKeep 10} | Should -Not -Throw + } It "Save-AzDevOpsBuild correctly builds API endpoint when CollectionUri has trailing slash" { # Arrange $env:SYSTEM_COLLECTIONURI = "https://dev.azure.com/myorganization/"