Compare commits

..

1 Commits

Author SHA1 Message Date
Maxim Lobanov
71775b6820 Merge pull request #11 from actions/main
Integrate main back to master to disable scheduling
2020-07-15 09:12:19 +03:00
26 changed files with 285 additions and 747 deletions

1
.github/CODEOWNERS vendored
View File

@@ -1 +0,0 @@
* @actions/virtual-environments-owners

View File

@@ -1,37 +0,0 @@
name: $(date:yyyyMMdd)$(rev:.r)
trigger: none
pr: none
schedules:
- cron: "0 8 * * Thu"
displayName: Daily build
branches:
include:
- main
always: true
variables:
PoolName: 'Azure Pipelines'
VmImage: 'ubuntu-18.04'
stages:
- stage: Find_New_Versions
dependsOn: []
jobs:
- job: Find_New_Versions
pool:
name: $(PoolName)
vmImage: $(VmImage)
steps:
- template: /azure-pipelines/templates/get-tool-versions-steps.yml
- stage: Check_New_Versions
dependsOn: Find_New_Versions
jobs:
- job: Check_New_Versions
pool:
name: $(PoolName)
vmImage: $(VmImage)
variables:
ToolVersions: $[ stageDependencies.Find_New_Versions.Find_New_Versions.outputs['Get_versions.TOOL_VERSIONS'] ]
steps:
- template: /azure-pipelines/templates/check-versions.yml

View File

@@ -20,38 +20,25 @@ variables:
VmImage: 'ubuntu-18.04'
stages:
- stage: Find_New_Versions
- stage: Get_New_Versions
dependsOn: []
jobs:
- job: Find_New_Versions
- job: Get_Tool_Versions
pool:
name: $(PoolName)
vmImage: $(VmImage)
steps:
- template: /azure-pipelines/templates/get-tool-versions-steps.yml
- stage: Check_New_Versions
dependsOn: Find_New_Versions
jobs:
- job: Check_New_Versions
pool:
name: $(PoolName)
vmImage: $(VmImage)
variables:
ToolVersions: $[ stageDependencies.Find_New_Versions.Find_New_Versions.outputs['Get_versions.TOOL_VERSIONS'] ]
steps:
- template: /azure-pipelines/templates/check-versions.yml
- stage: Trigger_Builds
dependsOn: [Find_New_Versions, Check_New_Versions]
condition: and(succeeded(), ne(variables['WORKFLOW_FILE_NAME'], ''))
dependsOn: Get_New_Versions
jobs:
- deployment: Run_Builds
pool:
name: $(PoolName)
vmImage: $(VmImage)
variables:
ToolVersions: $[ stageDependencies.Find_New_Versions.Find_New_Versions.outputs['Get_versions.TOOL_VERSIONS'] ]
ToolVersions: $[ stageDependencies.Get_New_Versions.Get_Tool_Versions.outputs['Get_versions.TOOL_VERSIONS'] ]
timeoutInMinutes: 180
environment: 'Get Available Tools Versions - Publishing Approval'
strategy:

View File

@@ -1,49 +0,0 @@
steps:
- task: PowerShell@2
displayName: Check Versions
condition: and(succeeded(), eq(variables.ToolVersions, ''))
inputs:
TargetType: inline
script: |
Write-Host "No new versions were found"
Import-Module "./azure-devops/azure-devops-api.ps1"
$azureDevOpsApi = Get-AzureDevOpsApi -TeamFoundationCollectionUri $(System.TeamFoundationCollectionUri) `
-ProjectName $(System.TeamProject) `
-AccessToken $(System.AccessToken)
$AzureDevOpsApi.UpdateBuildStatus($(Build.BuildId), 'Cancelling') | Out-Null
Start-Sleep -Seconds 60
- task: PowerShell@2
displayName: 'Set PIPELINE_URL variable'
inputs:
TargetType: inline
script: |
$ToolName = "$(TOOL_NAME)"
if ($ToolName -in @("Python", "Xamarin")) {
$PipelineUrl = " "
} else {
$PipelineUrl = "$(System.TeamFoundationCollectionUri)$(System.TeamProject)/_build/results?buildId=$(Build.BuildId)"
}
Write-Host "##vso[task.setvariable variable=PIPELINE_URL]$PipelineUrl"
- task: PowerShell@2
displayName: 'Change build name'
inputs:
TargetType: inline
script: |
$newBuildName = "[FOUND] $(Build.BuildNumber)"
Write-Host "##vso[build.updatebuildnumber]$newBuildName"
- task: PowerShell@2
displayName: 'Send Slack notification'
inputs:
targetType: filePath
filePath: './get-new-tool-versions/send-slack-notification.ps1'
arguments: |
-Url "$(SLACK_CHANNEL_URL)" `
-ToolName "$(TOOL_NAME)" `
-ToolVersion "$(ToolVersions)" `
-PipelineUrl "$(PIPELINE_URL)" `
-ImageUrl "$(IMAGE_URL)"

View File

@@ -6,4 +6,42 @@ steps:
targetType: filePath
filePath: './get-new-tool-versions/get-new-tool-versions.ps1'
arguments: |
-ToolName "$(TOOL_NAME)"
-DistURL "$(DIST_URL)" `
-ManifestLink "$(MANIFEST_URL)" `
-VersionFilterToInclude $(INCLUDE_FILTER) `
-VersionFilterToExclude $(EXCLUDE_FILTER)
- task: PowerShell@2
displayName: 'Cancel build'
condition: and(succeeded(), eq(variables['Get_versions.TOOL_VERSIONS'], ''))
inputs:
TargetType: inline
script: |
Import-Module "./azure-devops/azure-devops-api.ps1"
$azureDevOpsApi = Get-AzureDevOpsApi -TeamFoundationCollectionUri $(System.TeamFoundationCollectionUri) `
-ProjectName $(System.TeamProject) `
-AccessToken $(System.AccessToken)
$AzureDevOpsApi.UpdateBuildStatus($(Build.BuildId), 'Cancelling') | Out-Null
- task: PowerShell@2
displayName: 'Set env variable'
condition: and(succeeded(), ne(variables['Get_versions.TOOL_VERSIONS'], ''))
inputs:
TargetType: inline
script: |
$PipelineUrl = "$(System.TeamFoundationCollectionUri)$(System.TeamProject)/_build/results?buildId=$(Build.BuildId)"
Write-Output "##vso[task.setvariable variable=PIPELINE_URL]$PipelineUrl"
- task: PowerShell@2
displayName: 'Send Slack notification'
condition: and(succeeded(), ne(variables['Get_versions.TOOL_VERSIONS'], ''))
inputs:
targetType: filePath
filePath: './get-new-tool-versions/send-slack-notification.ps1'
arguments: |
-Url "$(SLACK_CHANNEL_URL)" `
-ToolName "$(TOOL_NAME)" `
-ToolVersion "$(Get_versions.TOOL_VERSIONS)" `
-PipelineUrl "$(PIPELINE_URL)" `
-ImageUrl "$(IMAGE_URL)"

View File

@@ -1,15 +1,29 @@
steps:
- checkout: self
- task: PowerShell@2
displayName: 'Get source version'
inputs:
TargetType: inline
script: |
$url = "https://api.github.com/repos/$(REPOSITORY)/commits/$(BRANCH)"
$commit = Invoke-RestMethod -Uri $url -Method "GET"
Write-Output "##vso[task.setvariable variable=COMMIT_SHA]$($commit.sha)"
- task: PowerShell@2
displayName: 'Run builds'
inputs:
targetType: filePath
filePath: './github/run-ci-builds.ps1'
filePath: './azure-devops/run-ci-builds.ps1'
arguments: |
-RepositoryFullName $(REPOSITORY_FULL_NAME) `
-AccessToken $(GITHUB_TOKEN) `
-WorkflowFileName $(WORKFLOW_FILE_NAME) `
-WorkflowDispatchRef $(DISPATCH_REF) `
-TeamFoundationCollectionUri $(System.TeamFoundationCollectionUri) `
-AzureDevOpsProjectName $(System.TeamProject) `
-AzureDevOpsAccessToken $(System.AccessToken) `
-SourceBranch $(BRANCH) `
-DefinitionId $(DEFINITION_ID) `
-SourceVersion $(COMMIT_SHA) `
-ManifestLink $(MANIFEST_URL) `
-WaitForBuilds $(WAIT_FOR_BUILDS) `
-ToolVersions "$(ToolVersions)" `
-PublishReleases $(PUPLISH_RELEASES)
-RetryIntervalSec $(INTERVAL_SEC) `
-RetryCount $(RETRY_COUNT)

View File

@@ -3,11 +3,6 @@ param (
)
$targetPath = $env:AGENT_TOOLSDIRECTORY
if ([string]::IsNullOrEmpty($targetPath)) {
# GitHub Windows images don't have `AGENT_TOOLSDIRECTORY` variable
$targetPath = $env:RUNNER_TOOL_CACHE
}
if ($ToolName) {
$targetPath = Join-Path $targetPath $ToolName
}

View File

@@ -83,26 +83,3 @@ function IsNixPlatform {
return ($Platform -match "macos") -or ($Platform -match "darwin") -or ($Platform -match "ubuntu") -or ($Platform -match "linux")
}
<#
.SYNOPSIS
Get root directory of selected tool
#>
function GetToolDirectory {
param(
[Parameter(Mandatory=$true)]
[String]$ToolName,
[Parameter(Mandatory=$true)]
[String]$Version,
[Parameter(Mandatory=$true)]
[String]$Architecture
)
$targetPath = $env:AGENT_TOOLSDIRECTORY
if ([string]::IsNullOrEmpty($targetPath)) {
# GitHub Windows images don't have `AGENT_TOOLSDIRECTORY` variable
$targetPath = $env:RUNNER_TOOL_CACHE
}
$ToolcachePath = Join-Path -Path $targetPath -ChildPath $ToolName
$ToolcacheVersionPath = Join-Path -Path $ToolcachePath -ChildPath $Version
return Join-Path $ToolcacheVersionPath $Architecture
}

View File

@@ -0,0 +1,93 @@
#Requires -Modules Pester
Import-Module (Join-Path $PSScriptRoot "helpers.psm1") -Force
Describe "Validate-FiltersFormat" {
It "Filter with word" {
{ Validate-FiltersFormat -Filters @("1two.2") } | Should -Throw "Invalid filter format"
}
It "Filter with non-word character" {
{ Validate-FiltersFormat -Filters @("1,.2") } | Should -Throw "Invalid filter format"
}
It "Valid filters" {
{ Validate-FiltersFormat -Filters @("*", "1", "1.*", "1.2", "1.2.*") } | Should -Not -Throw "Invalid filter format"
}
}
Describe "Format-Versions" {
It "Clean versions" {
$actualOutput = Format-Versions -Versions @("14.2.0", "1.14.0")
$expectedOutput = @("14.2.0", "1.14.0")
$actualOutput | Should -Be $expectedOutput
}
It "Versions with prefixes" {
$actualOutput = Format-Versions -Versions @("v14.2.0", "go1.14.0")
$expectedOutput = @("14.2.0", "1.14.0")
$actualOutput | Should -Be $expectedOutput
}
It "Skip beta and rc versions" {
$actualOutput = Format-Versions -Versions @("14.2.0-beta", "v1.14.0-rc-1")
$expectedOutput = @()
$actualOutput | Should -Be $expectedOutput
}
It "Short version" {
$actualOutput = Format-Versions -Versions @("14.2", "v2.0")
$expectedOutput = @("14.2.0", "2.0.0")
$actualOutput | Should -Be $expectedOutput
}
It "Skip versions with 1 digit" {
$actualOutput = Format-Versions -Versions @("14", "v2")
$expectedOutput = @()
$actualOutput | Should -Be $expectedOutput
}
}
Describe "Select-VersionsByFilter" {
$inputVersions = @("8.2.1", "9.3.3", "10.0.2", "10.0.3", "10.5.6", "12.4.3", "12.5.1", "14.2.0")
It "Include filter only" {
$includeFilters = @("8.*", "14.*")
$excludeFilters = @()
$actualOutput = Select-VersionsByFilter -Versions $inputVersions -IncludeFilters $includeFilters -ExcludeFilters $excludeFilters
$expectedOutput = @("8.2.1", "14.2.0")
$actualOutput | Should -Be $expectedOutput
}
It "Include and exclude filters" {
$includeFilters = @("10.*", "12.*")
$excludeFilters = @("10.0.*", "12.4.3")
$actualOutput = Select-VersionsByFilter -Versions $inputVersions -IncludeFilters $includeFilters -ExcludeFilters $excludeFilters
$expectedOutput = @("10.5.6", "12.5.1")
$actualOutput | Should -Be $expectedOutput
}
It "Exclude filter only" {
$includeFilters = @()
$excludeFilters = @("10.*", "12.*")
$actualOutput = Select-VersionsByFilter -Versions $inputVersions -IncludeFilters $includeFilters -ExcludeFilters $excludeFilters
$expectedOutput = @("8.2.1", "9.3.3", "14.2.0")
$actualOutput | Should -Be $expectedOutput
}
It "Include and exclude filters are empty" {
$actualOutput = Select-VersionsByFilter -Versions $inputVersions
$expectedOutput = @("8.2.1", "9.3.3", "10.0.2", "10.0.3", "10.5.6", "12.4.3", "12.5.1", "14.2.0")
$actualOutput | Should -Be $expectedOutput
}
}
Describe "Skip-ExistingVersions" {
It "Substract versions correctly" {
$distInput = @("14.2.0", "14.3.0", "14.4.0", "14.4.1")
$manifestInput = @("12.0.0", "14.2.0", "14.4.0")
$actualOutput = Skip-ExistingVersions -VersionsFromDist $distInput -VersionsFromManifest $manifestInput
$expectedOutput = @("14.3.0", "14.4.1")
$actualOutput | Should -Be $expectedOutput
}
}

View File

@@ -2,34 +2,79 @@
.SYNOPSIS
Check and return list of new available tool versions
.PARAMETER ToolName
Required parameter. The name of tool for which parser is available (Node, Go, Python, Xamarin)
.PARAMETER DistURL
Required parameter. Link to the json file included all available tool versions
.PARAMETER ManifestLink
Required parameter. Link to the the version-manifest.json file
.PARAMETER VersionFilterToInclude
Optional parameter. List of filters to include particular versions
.PARAMETER VersionFilterToExclude
Optional parameter. List of filters to exclude particular versions
.PARAMETER RetryIntervalSec
Optional parameter. Retry interval in seconds
.PARAMETER RetryCount
Optional parameter. Retry count
#>
param (
[Parameter(Mandatory)] [string] $ToolName
[Parameter(Mandatory)] [string] $DistURL,
[Parameter(Mandatory)] [string] $ManifestLink,
[string[]] $VersionFilterToInclude,
[string[]] $VersionFilterToExclude,
[UInt32] $RetryIntervalSec = 60,
[UInt32] $RetryCount = 3
)
Import-Module "$PSScriptRoot/parsers/parsers-factory.psm1"
Import-Module (Join-Path $PSScriptRoot "helpers.psm1")
$ToolVersionParser = Get-ToolVersionsParser -ToolName $ToolName
$VersionsFromDist = $ToolVersionParser.GetAvailableVersions()
$VersionsFromManifest = $ToolVersionParser.GetUploadedVersions()
function Get-VersionsByUrl {
param (
[Parameter(Mandatory)] [string] $ToolPackagesUrl,
[Parameter(Mandatory)] [UInt32] $RetryIntervalSec,
[Parameter(Mandatory)] [UInt32] $RetryCount
)
$joinChars = ", "
if ($ToolName -eq "Xamarin") {
$VersionsToBuild = $VersionsFromDist | Where-Object { $VersionsFromManifest[$_.name] -notcontains $_.version } | ForEach-Object {[string]::Empty} {
'{0,-15} : {1}' -f $_.name, $_.version
}
$joinChars = "\n\t"
} else {
$VersionsToBuild = $VersionsFromDist | Where-Object { $VersionsFromManifest -notcontains $_ }
$packages = Invoke-RestMethod $ToolPackagesUrl -MaximumRetryCount $RetryCount -RetryIntervalSec $RetryIntervalSec
return $packages.version
}
if ($VersionsToBuild) {
$availableVersions = $VersionsToBuild -join $joinChars
Write-Host "The following versions are available to build:`n${availableVersions}"
Write-Host "##vso[task.setvariable variable=TOOL_VERSIONS;isOutput=true]${availableVersions}"
if ($VersionFilterToInclude) {
Validate-FiltersFormat -Filters $VersionFilterToInclude
}
if ($VersionFilterToExclude) {
Validate-FiltersFormat -Filters $VersionFilterToExclude
}
Write-Host "Get the packages list from $DistURL"
$versionsFromDist = Get-VersionsByUrl -ToolPackagesUrl $DistURL `
-RetryIntervalSec $RetryIntervalSec `
-RetryCount $RetryCount
Write-Host "Get the packages list from $ManifestLink"
[Version[]] $versionsFromManifest = Get-VersionsByUrl -ToolPackagesUrl $ManifestLink `
-RetryIntervalSec $RetryIntervalSec `
-RetryCount $RetryCount
[Version[]] $formattedVersions = Format-Versions -Versions $versionsFromDist
$formattedVersions = Select-VersionsByFilter -Versions $formattedVersions `
-IncludeFilters $VersionFilterToInclude `
-ExcludeFilters $VersionFilterToExclude
if (-not $formattedVersions) {
Write-Host "Couldn't find available versions with current filters"
exit 1
}
$versionsToBuild = Skip-ExistingVersions -VersionsFromManifest $versionsFromManifest `
-VersionsFromDist $formattedVersions
if ($versionsToBuild) {
$availableVersions = $versionsToBuild -join ","
$toolVersions = $availableVersions.Replace(",",", ")
Write-Host "The following versions are available to build:`n$toolVersions"
Write-Output "##vso[task.setvariable variable=TOOL_VERSIONS;isOutput=true]$toolVersions"
} else {
Write-Host "There aren't versions to build"
}

View File

@@ -1,3 +1,17 @@
function Validate-FiltersFormat {
param (
[Parameter(Mandatory)] [string[]] $Filters
)
foreach($filter in $Filters) {
$filter.Split('.') | ForEach-Object {
if (($_ -notmatch '^\d+$') -and ($_ -ne '*')) {
throw "Invalid filter format - $filter"
}
}
}
}
function Format-Versions {
param (
[Parameter(Mandatory)] [string[]] $Versions

View File

@@ -1,31 +0,0 @@
class BaseVersionsParser {
[Int32]$ApiRetryCount = 3
[Int32]$ApiRetryIntervalSeconds = 60
[SemVer[]] GetAvailableVersions() {
$allVersionsRaw = $this.ParseAllAvailableVersions()
$allVersions = $allVersionsRaw | ForEach-Object { $this.FormatVersion($_) }
$filteredVersions = $allVersions | Where-Object { $this.ShouldIncludeVersion($_) }
return $filteredVersions
}
[SemVer[]] GetUploadedVersions() {
throw "Method is not implemented in base class"
}
hidden [SemVer[]] ParseAllAvailableVersions() {
throw "Method is not implemented in base class"
}
hidden [SemVer] FormatVersion([string]$VersionSpec) {
throw "Method is not implemented in base class"
}
hidden [bool] ShouldIncludeVersion([SemVer]$Version) {
throw "Method is not implemented in base class"
}
hidden [string] BuildGitHubFileUrl($OrganizationName, $RepositoryName, $BranchName, $FilePath) {
return "https://raw.githubusercontent.com/${OrganizationName}/${RepositoryName}/${BranchName}/${FilePath}"
}
}

View File

@@ -1,30 +0,0 @@
using module "./base-parser.psm1"
class GoVersionsParser: BaseVersionsParser {
[SemVer[]] GetUploadedVersions() {
$url = $this.BuildGitHubFileUrl("actions", "go-versions", "main", "versions-manifest.json")
$releases = Invoke-RestMethod $url -MaximumRetryCount $this.ApiRetryCount -RetryIntervalSec $this.ApiRetryIntervalSeconds
return $releases.version
}
hidden [string[]] ParseAllAvailableVersions() {
$url = "https://golang.org/dl/?mode=json&include=all"
$releases = Invoke-RestMethod $url -MaximumRetryCount $this.ApiRetryCount -RetryIntervalSec $this.ApiRetryIntervalSeconds
return $releases.version
}
hidden [SemVer] FormatVersion([string]$VersionSpec) {
$cleanVersion = $VersionSpec -replace "^go", ""
$semanticVersion = $cleanVersion -replace '(\d+\.\d+\.?\d*?)((?:alpha|beta|rc))(\d*)', '$1-$2.$3'
return [SemVer]$semanticVersion
}
hidden [bool] ShouldIncludeVersion([SemVer]$Version) {
if ($Version.PreReleaseLabel) {
return $false
}
# For Go, we include all versions greater than 1.12
return $Version -gt [SemVer]"1.12.0"
}
}

View File

@@ -1,30 +0,0 @@
using module "./base-parser.psm1"
class NodeVersionsParser: BaseVersionsParser {
[SemVer[]] GetUploadedVersions() {
$url = $this.BuildGitHubFileUrl("actions", "node-versions", "main", "versions-manifest.json")
$releases = Invoke-RestMethod $url -MaximumRetryCount $this.ApiRetryCount -RetryIntervalSec $this.ApiRetryIntervalSeconds
return $releases.version
}
hidden [string[]] ParseAllAvailableVersions() {
$url = "https://nodejs.org/dist/index.json"
$releases = Invoke-RestMethod $url -MaximumRetryCount $this.ApiRetryCount -RetryIntervalSec $this.ApiRetryIntervalSeconds
return $releases.version
}
hidden [SemVer] FormatVersion([string]$VersionSpec) {
$cleanVersion = $VersionSpec -replace "^v", ""
return [SemVer]$cleanVersion
}
hidden [bool] ShouldIncludeVersion([SemVer]$Version) {
if ($Version.Major -lt 8) {
return $false
}
# For Node.JS, we should include all LTS versions (all even-numbered releases)
# https://nodejs.org/en/about/releases/
return $Version.Major % 2 -eq 0
}
}

View File

@@ -1,21 +0,0 @@
using module "./node-parser.psm1"
using module "./go-parser.psm1"
using module "./python-parser.psm1"
using module "./xamarin-parser.psm1"
function Get-ToolVersionsParser {
param(
[Parameter(Mandatory)]
[string]$ToolName
)
switch ($ToolName) {
"Node" { return [NodeVersionsParser]::New() }
"Go" { return [GoVersionsParser]::New() }
"Python" { return [PythonVersionsParser]::New() }
"Xamarin" { return [XamarinversionsParser]::New() }
Default {
throw "Unknown tool name"
}
}
}

View File

@@ -1,53 +0,0 @@
using module "./base-parser.psm1"
class PythonVersionsParser: BaseVersionsParser {
[SemVer[]] GetUploadedVersions() {
$url = $this.BuildGitHubFileUrl("actions", "python-versions", "main", "versions-manifest.json")
$releases = Invoke-RestMethod $url -MaximumRetryCount $this.ApiRetryCount -RetryIntervalSec $this.ApiRetryIntervalSeconds
return $releases.version
}
hidden [string[]] ParseAllAvailableVersions() {
$stableVersionsUrl = "https://www.python.org/ftp/python"
$stableVersionsHtmlRaw = Invoke-WebRequest $stableVersionsUrl -MaximumRetryCount $this.ApiRetryCount -RetryIntervalSec $this.ApiRetryIntervalSeconds
$stableVersionsList = $stableVersionsHtmlRaw.Links.href | Where-Object {
$parsed = $null
return $_.EndsWith("/") -and [SemVer]::TryParse($_.Replace("/", ""), [ref]$parsed)
}
return $stableVersionsList | ForEach-Object {
$subVersionsUrl = "${stableVersionsUrl}/${_}"
$subVersionsHtmlRaw = Invoke-WebRequest $subVersionsUrl -MaximumRetryCount $this.ApiRetryCount -RetryIntervalSec $this.ApiRetryIntervalSeconds
return $subVersionsHtmlRaw.Links.href | ForEach-Object {
if ($_ -match "^Python-(\d+\.\d+\.\d+[a-z]{0,2}\d*)\.tgz$") {
return $Matches[1]
}
}
}
}
hidden [SemVer] FormatVersion([string]$VersionSpec) {
$VersionSpec -match "^(\d+)\.(\d+)\.(\d+)([a-z]{1,2})?(\d+)?$"
if ($Matches.Count -gt 4) {
$VersionLabel = "{0}.{1}" -f $this.ConvertPythonLabel($Matches[4]), $Matches[5]
return [SemVer]::new($Matches[1], $Matches[2], $Matches[3], $VersionLabel)
}
return [SemVer]::new($Matches[1], $Matches[2], $Matches[3])
}
hidden [string] ConvertPythonLabel([string]$Label) {
switch ($Label) {
"a" { return "alpha" }
"b" { return "beta" }
}
return $Label
}
[bool] ShouldIncludeVersion([SemVer]$Version) {
# For Python, we include all versions greater than 3.9.0
return $Version -gt [SemVer]"3.9.0"
}
}

View File

@@ -1,30 +0,0 @@
using module "./base-parser.psm1"
class XamarinVersionsParser: BaseVersionsParser {
[PSCustomObject] GetAvailableVersions() {
$allVersions = $this.ParseAllAvailableVersions()
return $allVersions
}
[hashtable] GetUploadedVersions() {
$url = $this.BuildGitHubFileUrl("actions", "virtual-environments", "main", "images/macos/toolsets/toolset-11.json")
$releases = Invoke-RestMethod $url -MaximumRetryCount $this.ApiRetryCount -RetryIntervalSec $this.ApiRetryIntervalSeconds
$xamarin = $releases.xamarin
$xamarinReleases = @{
'Mono Framework' = $xamarin.'mono-versions'
'Xamarin.Android' = $xamarin.'android-versions'
'Xamarin.iOS' = $xamarin.'ios-versions'
'Xamarin.Mac' = $xamarin.'mac-versions'
}
return $xamarinReleases
}
hidden [PSCustomObject] ParseAllAvailableVersions() {
$url = "http://aka.ms/manifest/stable"
$filteredProducts = @('Mono Framework', 'Xamarin.Android', 'Xamarin.iOS', 'Xamarin.Mac')
$releases = Invoke-RestMethod $url -MaximumRetryCount $this.ApiRetryCount -RetryIntervalSec $this.ApiRetryIntervalSeconds
$items = $releases.items
$products = $items | Where-Object {$_.name -in $filteredProducts} | Sort-Object name | Select-Object name, version
return $products
}
}

View File

@@ -27,7 +27,10 @@ param(
[ValidateNotNullOrEmpty()]
[System.String]$ToolVersion,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[System.String]$PipelineUrl,
[System.String]$ImageUrl = 'https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png'
)
@@ -35,14 +38,7 @@ param(
Import-Module $PSScriptRoot/helpers.psm1 -DisableNameChecking
# Create JSON body
if ($toolName -eq "Xamarin") {
$text = "The following versions of '$toolName' are available, consider adding them to toolset: $toolVersion"
} else {
$text = "The following versions of '$toolName' are available to upload: $toolVersion"
}
if (-not ([string]::IsNullOrWhiteSpace($PipelineUrl))) {
$text += "\nLink to the pipeline: $pipelineUrl"
}
$text = "The following versions of '$toolName' are available to upload: $toolVersion\nLink to the pipeline: $pipelineUrl"
$jsonBodyMessage = @"
{
"blocks": [

View File

@@ -2,8 +2,10 @@
.SYNOPSIS
Create commit with all unstaged changes in repository and create pull-request
.PARAMETER RepositoryFullName
Required parameter. The owner and repository name. For example, 'actions/versions-package-tools'
.PARAMETER RepositoryOwner
Required parameter. The organization which tool repository belongs
.PARAMETER RepositoryName
Optional parameter. The name of tool repository
.PARAMETER AccessToken
Required parameter. PAT Token to authorize
.PARAMETER BranchName
@@ -16,7 +18,8 @@ Required parameter. The title of pull-request
Required parameter. The description of pull-request
#>
param (
[Parameter(Mandatory)] [string] $RepositoryFullName,
[Parameter(Mandatory)] [string] $RepositoryOwner,
[Parameter(Mandatory)] [string] $RepositoryName,
[Parameter(Mandatory)] [string] $AccessToken,
[Parameter(Mandatory)] [string] $BranchName,
[Parameter(Mandatory)] [string] $CommitMessage,
@@ -43,11 +46,11 @@ function Update-PullRequest {
$updatedPullRequest = $GitHubApi.UpdatePullRequest($Title, $Body, $BranchName, $PullRequest.number)
if (($null -eq $updatedPullRequest) -or ($null -eq $updatedPullRequest.html_url)) {
Write-Host "Unexpected error occurs while updating pull request."
if (($updatedPullRequest -eq $null) -or ($updatedPullRequest.html_url -eq $null)) {
Write-Host "##vso[task.logissue type=error;] Unexpected error occurs while updating pull request."
exit 1
}
Write-host "Pull request updated: $($updatedPullRequest.html_url)"
Write-host "##[section] Pull request updated: $($updatedPullRequest.html_url)"
}
function Create-PullRequest {
@@ -64,12 +67,12 @@ function Create-PullRequest {
$createdPullRequest = $GitHubApi.CreateNewPullRequest($Title, $Body, $BranchName)
if (($null -eq $createdPullRequest) -or ($null -eq $createdPullRequest.html_url)) {
Write-Host "Unexpected error occurs while creating pull request."
if (($createdPullRequest -eq $null) -or ($createdPullRequest.html_url -eq $null)) {
Write-Host "##vso[task.logissue type=error;] Unexpected error occurs while creating pull request."
exit 1
}
Write-host "Pull request created: $($createdPullRequest.html_url)"
Write-host "##[section] Pull request created: $($createdPullRequest.html_url)"
}
Write-Host "Configure local git preferences"
@@ -84,8 +87,8 @@ Git-CommitAllChanges -Message $CommitMessage
Write-Host "Push branch: $BranchName"
Git-PushBranch -Name $BranchName -Force $true
$gitHubApi = Get-GitHubApi -RepositoryFullName $RepositoryFullName -AccessToken $AccessToken
$pullRequest = $gitHubApi.GetPullRequest($BranchName)
$gitHubApi = Get-GitHubApi -AccountName $RepositoryOwner -ProjectName $RepositoryName -AccessToken $AccessToken
$pullRequest = $gitHubApi.GetPullRequest($BranchName, $RepositoryOwner)
if ($pullRequest.Count -gt 0) {
Write-Host "Update pull request"

View File

@@ -1,86 +0,0 @@
<#
.SYNOPSIS
Trigger runs on the workflow_dispatch event to create tool release
.PARAMETER RepositoryFullName
Required parameter. The owner and repository name. For example, 'actions/versions-package-tools'
.PARAMETER AccessToken
Required parameter. PAT Token to authorize
.PARAMETER ToolVersion
Required parameter. Version of tool
.PARAMETER TagName
Required parameter. The name of the release tag
.PARAMETER ReleaseBody
Required parameter. Text describing the contents of the release
.PARAMETER EventType
Required parameter. The name of the repository dispatch event
#>
param (
[Parameter(Mandatory)] [string] $RepositoryFullName,
[Parameter(Mandatory)] [string] $AccessToken,
[Parameter(Mandatory)] [string] $ToolVersion,
[Parameter(Mandatory)] [string] $TagName,
[Parameter(Mandatory)] [string] $ReleaseBody,
[Parameter(Mandatory)] [string] $EventType,
[UInt32] $RetryIntervalSec = 10,
[UInt32] $RetryCount = 5
)
Import-Module (Join-Path $PSScriptRoot "github-api.psm1")
function Create-Release {
param (
[Parameter(Mandatory)] [object] $GitHubApi,
[Parameter(Mandatory)] [string] $ToolVersion,
[Parameter(Mandatory)] [string] $TagName,
[Parameter(Mandatory)] [string] $ReleaseBody,
[Parameter(Mandatory)] [string] $EventType
)
$eventPayload = @{
ToolVersion = $ToolVersion
TagName = $TagName
ReleaseBody = $ReleaseBody
}
Write-Host "Create '$EventType' repository dispatch event"
$GitHubApi.CreateRepositoryDispatch($EventType, $eventPayload)
}
function Validate-ReleaseAvailability {
param (
[Parameter(Mandatory)] [object] $GitHubApi,
[Parameter(Mandatory)] [string] $TagName,
[Parameter(Mandatory)] [UInt32] $RetryIntervalSec,
[Parameter(Mandatory)] [UInt32] $RetryCount
)
do {
$createdRelease = $GitHubApi.GetReleases() | Where-Object { $_.tag_name -eq $TagName }
if ($createdRelease) {
Write-Host "Release was successfully created: $($createdRelease.html_url)"
return
}
$RetryCount--
Start-Sleep -Seconds $RetryIntervalSec
} while($RetryCount -gt 0)
Write-Host "Release was not created"
exit 1
}
$gitHubApi = Get-GitHubApi -RepositoryFullName $RepositoryFullName -AccessToken $AccessToken
Create-Release -GitHubApi $gitHubApi `
-ToolVersion $ToolVersion `
-TagName $TagName `
-ReleaseBody $ReleaseBody `
-EventType $EventType
Start-Sleep -s $RetryIntervalSec
Validate-ReleaseAvailability -GitHubApi $gitHubApi `
-TagName $TagName `
-RetryIntervalSec $RetryIntervalSec `
-RetryCount $RetryCount

View File

@@ -5,8 +5,8 @@ The module that contains a bunch of methods to interact with GitHub API V3
class GitHubApi
{
[string] $BaseUrl
[string] $RepoOwner
[object] $AuthHeader
[string] $RepositoryOwner
GitHubApi(
[string] $AccountName,
@@ -15,7 +15,6 @@ class GitHubApi
) {
$this.BaseUrl = $this.BuildBaseUrl($AccountName, $ProjectName)
$this.AuthHeader = $this.BuildAuth($AccessToken)
$this.RepositoryOwner = $AccountName
}
[object] hidden BuildAuth([string]$AccessToken) {
@@ -44,9 +43,9 @@ class GitHubApi
return $this.InvokeRestMethod($url, 'Post', $null, $requestBody)
}
[object] GetPullRequest([string]$BranchName){
[object] GetPullRequest([string]$BranchName, [string]$RepositoryOwner){
$url = "pulls"
return $this.InvokeRestMethod($url, 'GET', "head=$($this.RepositoryOwner):${BranchName}&base=main", $null)
return $this.InvokeRestMethod($url, 'GET', "head=${RepositoryOwner}:$BranchName&base=main", $null)
}
[object] UpdatePullRequest([string]$Title, [string]$Body, [string]$BranchName, [string]$PullRequestNumber){
@@ -83,39 +82,6 @@ class GitHubApi
return $releases
}
[void] CreateRepositoryDispatch([string]$EventType, [object]$EventPayload) {
$url = "dispatches"
$body = @{
event_type = $EventType
client_payload = $EventPayload
} | ConvertTo-Json
$this.InvokeRestMethod($url, 'POST', $null, $body)
}
[object] GetWorkflowRuns([string]$WorkflowFileName) {
$url = "actions/workflows/$WorkflowFileName/runs"
return $this.InvokeRestMethod($url, 'GET', $null, $null)
}
[object] GetWorkflowRunJobs([string]$WorkflowRunId) {
$url = "actions/runs/$WorkflowRunId/jobs"
return $this.InvokeRestMethod($url, 'GET', $null, $null)
}
[void] CreateWorkflowDispatch([string]$WorkflowFileName, [string]$Ref, [object]$Inputs) {
$url = "actions/workflows/${WorkflowFileName}/dispatches"
$body = @{ ref = $Ref }
if ($Inputs) {
$body.inputs = $Inputs
}
$jsonBody = $body | ConvertTo-Json
$this.InvokeRestMethod($url, 'POST', $null, $jsonBody)
}
[string] hidden BuildUrl([string]$Url, [string]$RequestParams) {
if ([string]::IsNullOrEmpty($RequestParams)) {
return "$($this.BaseUrl)/$($Url)"
@@ -151,18 +117,10 @@ class GitHubApi
function Get-GitHubApi {
param (
[Parameter(ParameterSetName = 'RepositorySingle')]
[string] $RepositoryFullName,
[Parameter(ParameterSetName = 'RepositorySplitted')]
[string] $RepositoryOwner,
[Parameter(ParameterSetName = 'RepositorySplitted')]
[string] $RepositoryName,
[string] $AccountName,
[string] $ProjectName,
[string] $AccessToken
)
if ($PSCmdlet.ParameterSetName -eq "RepositorySingle") {
$RepositoryOwner, $RepositoryName = $RepositoryFullName.Split('/', 2)
}
return [GitHubApi]::New($RepositoryOwner, $RepositoryName, $AccessToken)
return [GitHubApi]::New($AccountName, $ProjectName, $AccessToken)
}

View File

@@ -1,91 +0,0 @@
<#
.SYNOPSIS
Trigger runs on the workflow_dispatch event to build and upload tool packages
.PARAMETER RepositoryFullName
Required parameter. The owner and repository name. For example, 'actions/versions-package-tools'
.PARAMETER AccessToken
Required parameter. PAT to authorize
.PARAMETER WorkflowFileName
Required parameter. The name of workflow file that will be triggered
.PARAMETER WorkflowDispatchRef
Required parameter. The reference of the workflow run. The reference can be a branch, tag, or a commit SHA.
.PARAMETER ToolVersions
Required parameter. List of tool versions to build and upload
.PARAMETER PublishReleases
Required parameter. Whether to publish releases, true or false
#>
param (
[Parameter(Mandatory)] [string] $RepositoryFullName,
[Parameter(Mandatory)] [string] $AccessToken,
[Parameter(Mandatory)] [string] $WorkflowFileName,
[Parameter(Mandatory)] [string] $WorkflowDispatchRef,
[Parameter(Mandatory)] [string] $ToolVersions,
[Parameter(Mandatory)] [string] $PublishReleases
)
Import-Module (Join-Path $PSScriptRoot "github-api.psm1")
function Get-WorkflowRunLink {
param(
[Parameter(Mandatory)] [object] $GitHubApi,
[Parameter(Mandatory)] [string] $WorkflowFileName,
[Parameter(Mandatory)] [string] $ToolVersion
)
$listWorkflowRuns = $GitHubApi.GetWorkflowRuns($WorkflowFileName).workflow_runs | Sort-Object -Property 'run_number' -Descending
foreach ($workflowRun in $listWorkflowRuns) {
$workflowRunJob = $gitHubApi.GetWorkflowRunJobs($workflowRun.id).jobs | Select-Object -First 1
if ($workflowRunJob.name -match $ToolVersion) {
return $workflowRun.html_url
}
}
return $null
}
function Queue-Builds {
param (
[Parameter(Mandatory)] [object] $GitHubApi,
[Parameter(Mandatory)] [string] $ToolVersions,
[Parameter(Mandatory)] [string] $WorkflowFileName,
[Parameter(Mandatory)] [string] $WorkflowDispatchRef,
[Parameter(Mandatory)] [string] $PublishReleases
)
$inputs = @{
PUBLISH_RELEASES = $PublishReleases
}
$ToolVersions.Split(',') | ForEach-Object {
$version = $_.Trim()
$inputs.VERSION = $version
Write-Host "Queue build for $version..."
$GitHubApi.CreateWorkflowDispatch($WorkflowFileName, $WorkflowDispatchRef, $inputs)
Start-Sleep -s 10
$workflowRunLink = Get-WorkflowRunLink -GitHubApi $GitHubApi `
-WorkflowFileName $WorkflowFileName `
-ToolVersion $version
if (-not $workflowRunLink) {
Write-Host "Could not find build for $version..."
exit 1
}
Write-Host "Link to the build: $workflowRunLink"
}
}
$gitHubApi = Get-GitHubApi -RepositoryFullName $RepositoryFullName -AccessToken $AccessToken
Write-Host "Versions to build: $ToolVersions"
Queue-Builds -GitHubApi $gitHubApi `
-ToolVersions $ToolVersions `
-WorkflowFileName $WorkflowFileName `
-WorkflowDispatchRef $WorkflowDispatchRef `
-PublishReleases $PublishReleases

View File

@@ -1,10 +1,13 @@
<#
.SYNOPSIS
Generate versions manifest based on repository releases
.DESCRIPTION
Versions manifest is needed to find the latest assets for particular version of tool
.PARAMETER RepositoryFullName
Required parameter. The owner and repository name. For example, 'actions/versions-package-tools'
.PARAMETER GitHubRepositoryOwner
Required parameter. The organization which tool repository belongs
.PARAMETER GitHubRepositoryName
Required parameter. The name of tool repository
.PARAMETER GitHubAccessToken
Required parameter. PAT Token to overcome GitHub API Rate limit
.PARAMETER OutputFile
@@ -14,7 +17,8 @@ Path to the json file with parsing configuration
#>
param (
[Parameter(Mandatory)] [string] $RepositoryFullName,
[Parameter(Mandatory)] [string] $GitHubRepositoryOwner,
[Parameter(Mandatory)] [string] $GitHubRepositoryName,
[Parameter(Mandatory)] [string] $GitHubAccessToken,
[Parameter(Mandatory)] [string] $OutputFile,
[Parameter(Mandatory)] [string] $ConfigurationFile
@@ -25,7 +29,7 @@ Import-Module (Join-Path $PSScriptRoot "manifest-utils.psm1") -Force
$configuration = Read-ConfigurationFile -Filepath $ConfigurationFile
$gitHubApi = Get-GitHubApi -RepositoryFullName $RepositoryFullName -AccessToken $GitHubAccessToken
$gitHubApi = Get-GitHubApi -AccountName $GitHubRepositoryOwner -ProjectName $GitHubRepositoryName -AccessToken $GitHubAccessToken
$releases = $gitHubApi.GetReleases()
$versionIndex = Build-VersionsManifest -Releases $releases -Configuration $configuration
$versionIndex | ConvertTo-Json -Depth 5 | Out-File $OutputFile -Encoding UTF8NoBOM -Force

View File

@@ -126,7 +126,7 @@ Describe "Build-VersionsManifest" {
[PSCustomObject]@{ version = "3.8.3"; stable = $true; release_url = "fake_html_url"; files = $expectedManifestFiles }
)
[array]$actualManifest = Build-VersionsManifest -Releases $releases -Configuration $configuration
Assert-Equivalent -Actual $actualManifest -Expected $expectedManifest
Assert-Equivalent -Actual $actualManifest -Expected $expectedManifest
}
It "take latest published release for each version" {
@@ -141,74 +141,4 @@ Describe "Build-VersionsManifest" {
[array]$actualManifest = Build-VersionsManifest -Releases $releases -Configuration $configuration
Assert-Equivalent -Actual $actualManifest -Expected $expectedManifest
}
It "build correct manifest if release includes one asset" {
$asset = @(
@{ name = "python-3.8.3-linux-16.04-x64.tar.gz"; browser_download_url = "fake_url"; }
)
$expectedManifestFile = @(
[PSCustomObject]@{ filename = "python-3.8.3-linux-16.04-x64.tar.gz"; arch = "x64"; platform = "linux"; platform_version = "16.04"; download_url = "fake_url" }
)
$releases = @(
@{ name = "3.8.3"; draft = $false; prerelease = $false; html_url = "fake_html_url"; published_at = "2020-05-06T11:43:38Z"; assets = $asset },
@{ name = "3.8.1"; draft = $false; prerelease = $false; html_url = "fake_html_url"; published_at = "2020-05-14T09:54:06Z"; assets = $assets }
)
$expectedManifest = @(
[PSCustomObject]@{ version = "3.8.3"; stable = $true; release_url = "fake_html_url"; files = $expectedManifestFile },
[PSCustomObject]@{ version = "3.8.1"; stable = $true; release_url = "fake_html_url"; files = $expectedManifestFiles }
)
[array]$actualManifest = Build-VersionsManifest -Releases $releases -Configuration $configuration
Assert-Equivalent -Actual $actualManifest -Expected $expectedManifest
}
It "set correct lts value for versions" {
$releases = @(
@{ name = "14.2.1"; draft = false; prerelease = $false; html_url = "fake_html_url"; published_at = "2020-05-14T09:54:06Z"; assets = $assets },
@{ name = "12.0.1"; draft = $false; prerelease = false; html_url = "fake_html_url"; published_at = "2020-05-06T11:45:36Z"; assets = $assets },
@{ name = "16.2.2"; draft = $false; prerelease = $false; html_url = "fake_html_url"; published_at = "2020-05-06T11:43:38Z"; assets = $assets }
)
$configuration = @{
regex = "python-\d+\.\d+\.\d+-(\w+)-([\w\.]+)?-?(x\d+)";
groups = [PSCustomObject]@{ platform = 1; platform_version = 2; arch = "x64"; }
lts_rule_expression = "@(@{ Name = '14'; Value = 'Fermium' }, @{ Name = '12'; Value = 'Erbium' })"
}
$expectedManifest = @(
[PSCustomObject]@{ version = "16.2.2"; stable = $true; release_url = "fake_html_url"; files = $expectedManifestFiles },
[PSCustomObject]@{ version = "14.2.1"; stable = $true; lts = "Fermium"; release_url = "fake_html_url"; files = $expectedManifestFiles },
[PSCustomObject]@{ version = "12.0.1"; stable = $true; lts = "Erbium"; release_url = "fake_html_url"; files = $expectedManifestFiles }
)
[array]$actualManifest = Build-VersionsManifest -Releases $releases -Configuration $configuration
Assert-Equivalent -Actual $actualManifest -Expected $expectedManifest
}
}
Describe "Get-VersionLtsStatus" {
$ltsRules = @(
@{ Name = "14"; Value = "Fermium" },
@{ Name = "12"; Value = "Erbium" },
@{ Name = "10"; Value = $true },
@{ Name = "8.3"; Value = "LTS 8.3" }
)
It "lts label is matched" {
Get-VersionLtsStatus -Version "14.2.2" -LtsRules $ltsRules | Should -Be "Fermium"
Get-VersionLtsStatus -Version "12.3.1" -LtsRules $ltsRules | Should -Be "Erbium"
Get-VersionLtsStatus -Version "10.8.1" -LtsRules $ltsRules | Should -Be $true
Get-VersionLtsStatus -Version "8.3.2" -LtsRules $ltsRules | Should -Be "LTS 8.3"
Get-VersionLtsStatus -Version "14" -LtsRules $ltsRules | Should -Be "Fermium"
}
It "lts label is not matched" {
Get-VersionLtsStatus -Version "9.1" -LtsRules $ltsRules | Should -Be $null
Get-VersionLtsStatus -Version "13.8" -LtsRules $ltsRules | Should -Be $null
Get-VersionLtsStatus -Version "5" -LtsRules $ltsRules | Should -Be $null
Get-VersionLtsStatus -Version "8.4" -LtsRules $ltsRules | Should -Be $null
Get-VersionLtsStatus -Version "142.5.1" -LtsRules $ltsRules | Should -Be $null
}
It "no rules" {
Get-VersionLtsStatus -Version "14.2.2" | Should -Be $null
Get-VersionLtsStatus -Version "12.3.1" -LtsRules $null | Should -Be $null
}
}

View File

@@ -50,7 +50,6 @@ function Build-VersionsManifest {
)
$Releases = $Releases | Sort-Object -Property "published_at" -Descending
$ltsRules = Get-LtsRules -Configuration $Configuration
$versionsHash = @{}
foreach ($release in $Releases) {
@@ -65,49 +64,15 @@ function Build-VersionsManifest {
continue
}
$ltsStatus = Get-VersionLtsStatus -Version $versionKey -LtsRules $ltsRules
$stable = $version.PreReleaseLabel ? $false : $true
[array]$releaseAssets = $release.assets | ForEach-Object { New-AssetItem -ReleaseAsset $_ -Configuration $Configuration }
$versionHash = [PSCustomObject]@{}
$versionHash | Add-Member -Name "version" -Value $versionKey -MemberType NoteProperty
$versionHash | Add-Member -Name "stable" -Value $stable -MemberType NoteProperty
if ($ltsStatus) {
$versionHash | Add-Member -Name "lts" -Value $ltsStatus -MemberType NoteProperty
}
$versionHash | Add-Member -Name "release_url" -Value $release.html_url -MemberType NoteProperty
$versionHash | Add-Member -Name "files" -Value $releaseAssets -MemberType NoteProperty
$versionsHash.Add($versionKey, $versionHash)
$versionsHash.Add($versionKey, [PSCustomObject]@{
version = $versionKey
stable = $stable
release_url = $release.html_url
files = $release.assets | ForEach-Object { New-AssetItem -ReleaseAsset $_ -Configuration $Configuration }
})
}
# Sort versions by descending
return $versionsHash.Values | Sort-Object -Property @{ Expression = { [Semver]$_.version }; Descending = $true }
}
function Get-LtsRules {
param (
[Parameter(Mandatory)][object]$Configuration
)
$ruleExpression = $Configuration."lts_rule_expression"
if ($ruleExpression) {
Invoke-Expression $ruleExpression
} else {
@()
}
}
function Get-VersionLtsStatus {
param (
[Parameter(Mandatory)][string]$Version,
[array]$LtsRules
)
foreach ($ltsRule in $LtsRules) {
if (($Version -eq $ltsRule.Name) -or ($Version.StartsWith("$($ltsRule.Name)."))) {
return $ltsRule.Value
}
}
return $null
}
}

View File

@@ -4,52 +4,30 @@ Pester extension that allows to run command and validate exit code
.EXAMPLE
"python file.py" | Should -ReturnZeroExitCode
#>
function Get-CommandResult {
Param (
[Parameter(Mandatory=$true)]
[string] $Command,
[switch] $Multiline
)
# CMD and bash trick to suppress and show error output because some commands write to stderr (for example, "python --version")
If ($IsWindows) {
$stdout = & $env:comspec /c "$Command 2>&1"
} else {
$stdout = & bash -c "$Command 2>&1"
}
$exitCode = $LASTEXITCODE
return @{
Output = If ($Multiline -eq $true) { $stdout } else { [string]$stdout }
ExitCode = $exitCode
}
}
function ShouldReturnZeroExitCode {
Param(
[String] $ActualValue,
[switch] $Negate,
[string] $Because # This parameter is unused by we need it to match Pester asserts signature
[Parameter (Mandatory = $true)] [ValidateNotNullOrEmpty()]
[String]$ActualValue,
[switch]$Negate
)
$result = Get-CommandResult $ActualValue
Write-Host "Run command '${ActualValue}'"
Invoke-Expression -Command $ActualValue | ForEach-Object { Write-Host $_ }
$actualExitCode = $LASTEXITCODE
[bool]$succeeded = $result.ExitCode -eq 0
[bool]$succeeded = $actualExitCode -eq 0
if ($Negate) { $succeeded = -not $succeeded }
if (-not $succeeded)
{
$commandOutputIndent = " " * 4
$commandOutput = ($result.Output | ForEach-Object { "${commandOutputIndent}${_}" }) -join "`n"
$failureMessage = "Command '${ActualValue}' has finished with exit code ${actualExitCode}`n${commandOutput}"
$failureMessage = "Command '${ActualValue}' has finished with exit code ${actualExitCode}"
}
return [PSCustomObject] @{
return New-Object PSObject -Property @{
Succeeded = $succeeded
FailureMessage = $failureMessage
}
}
if (Get-Command -Name Add-AssertionOperator -ErrorAction SilentlyContinue) {
Add-AssertionOperator -Name ReturnZeroExitCode -InternalName ShouldReturnZeroExitCode -Test ${function:ShouldReturnZeroExitCode}
}
Add-AssertionOperator -Name ReturnZeroExitCode `
-Test $function:ShouldReturnZeroExitCode