Skip to content
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
69 changes: 3 additions & 66 deletions Actions/Deliver/Deliver.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -102,72 +102,9 @@ foreach ($thisProject in $sortedProjectList) {
New-Item $artifactsFolder -ItemType Directory | Out-Null
$artifactsFolderCreated = $true
}
if ($artifacts -eq '.artifacts') {
# Artifacts from this build have been downloaded
}
elseif ($artifacts -eq "current" -or $artifacts -eq "prerelease" -or $artifacts -eq "draft") {
# project is the project name as used in release asset names
$project = [Uri]::EscapeDataString($project.Replace(' ', '.')).Replace('%', '')

# latest released version
$releases = GetReleases -token $token -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY
if ($artifacts -eq "current") {
$release = $releases | Where-Object { -not ($_.prerelease -or $_.draft) } | Select-Object -First 1
}
elseif ($artifacts -eq "prerelease") {
$release = $releases | Where-Object { -not ($_.draft) } | Select-Object -First 1
}
elseif ($artifacts -eq "draft") {
$release = $releases | Select-Object -First 1
}
if (!($release)) {
throw "Unable to locate $artifacts release"
}
foreach ($mask in $atypes.Split(',')) {
$artifactFile = DownloadRelease -token $token -projects $project -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -release $release -path $artifactsFolder -mask $mask
Write-Host "'$artifactFile'"
if (!$artifactFile -or !(Test-Path $artifactFile)) {
if ($mask -eq 'Apps') {
throw "Artifact $artifacts was not found on any release. Make sure that the artifact files exist and files are not corrupted."
}
}
else {
if ($artifactFile -notlike '*.zip') {
throw "Downloaded artifact is not a .zip file"
}
Expand-Archive -Path $artifactFile -DestinationPath ($artifactFile.SubString(0, $artifactFile.Length - 4))
Remove-Item $artifactFile -Force
}
}
}
else {
$atypes.Split(',') | ForEach-Object {
$atype = $_
$allArtifacts = GetArtifacts -token $token -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -mask $atype -projects $project -version $artifacts -branch $ENV:GITHUB_REF_NAME
if ($allArtifacts) {
$allArtifacts | ForEach-Object {
$artifactFile = DownloadArtifact -token $token -artifact $_ -path $artifactsFolder
Write-Host $artifactFile
if (!(Test-Path $artifactFile)) {
throw "Unable to download artifact $($_.name)"
}
if ($artifactFile -notlike '*.zip') {
throw "Downloaded artifact is not a .zip file"
}
Expand-Archive -Path $artifactFile -DestinationPath ($artifactFile.SubString(0, $artifactFile.Length - 4))
Remove-Item $artifactFile -Force
}
}
else {
if ($atype -eq "Apps") {
throw "ERROR: Could not find any $atype artifacts for projects $projects, version $artifacts"
}
else {
Write-Host "WARNING: Could not find any $atype artifacts for projects $projects, version $artifacts"
}
}
}
}
# Download the artifacts to deliver. Note that the project name returned is the name used in the
# downloaded artifact folder names (release assets use a slightly different naming than build artifacts)
$project = Get-ArtifactsForDelivery -token $token -artifacts $artifacts -artifactsFolder $artifactsFolder -project $project -atypes $atypes -branch $ENV:GITHUB_REF_NAME
}

Write-Host "Project '$project'"
Expand Down
139 changes: 138 additions & 1 deletion Actions/Deliver/Deliver.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,141 @@ function Get-ProjectsInDeliveryOrder {
return $sortedProjectList
}

Export-ModuleMember -Function Get-ProjectsInDeliveryOrder
<#
.SYNOPSIS
Downloads the artifacts to deliver for a single project into the artifacts folder

.DESCRIPTION
Downloads the artifacts matching the requested version into the artifacts folder.
The version can be a release indicator (current, prerelease or draft), latest or a version number.
If 'current' is requested and the repository doesn't contain any releases, the artifacts from the
latest build are used instead - the same fallback as the one used when deploying to an environment.

.PARAMETER Token
The GitHub token used for accessing releases and artifacts

.PARAMETER Artifacts
The version of the artifacts to download (current, prerelease, draft, latest or a version number)

.PARAMETER ArtifactsFolder
The folder in which the artifacts are downloaded and unpacked

.PARAMETER Project
The name of the project (as used in artifact names)

.PARAMETER Atypes
Comma separated list of artifact types to download (f.ex. Apps,Dependencies,TestApps)

.PARAMETER Branch
The branch from which to get build artifacts

.OUTPUTS
The project name as used in the names of the downloaded artifact folders
#>
function Get-ArtifactsForDelivery {
Param(
[Parameter(Mandatory = $false)]
[string] $Token,

[Parameter(Mandatory = $true)]
[string] $Artifacts,

[Parameter(Mandatory = $true)]
[string] $ArtifactsFolder,

[Parameter(Mandatory = $true)]
[string] $Project,

[Parameter(Mandatory = $true)]
[string] $Atypes,

[Parameter(Mandatory = $true)]
[string] $Branch
)

$searchArtifacts = $false
$searchArtifactsVersion = $Artifacts
if ($Artifacts -eq '.artifacts') {
# Artifacts from this build have already been downloaded
}
elseif ($Artifacts -eq "current" -or $Artifacts -eq "prerelease" -or $Artifacts -eq "draft") {
# latest released version
$releases = GetReleases -token $Token -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY
if ($releases) {
if ($Artifacts -eq "current") {
$release = $releases | Where-Object { -not ($_.prerelease -or $_.draft) } | Select-Object -First 1
}
elseif ($Artifacts -eq "prerelease") {
$release = $releases | Where-Object { -not ($_.draft) } | Select-Object -First 1
}
else {
$release = $releases | Select-Object -First 1
}
if (!($release)) {
throw "Unable to locate $Artifacts release"
}
# project is the project name as used in release asset names
$Project = [Uri]::EscapeDataString($Project.Replace(' ', '.')).Replace('%', '')
foreach ($mask in $Atypes.Split(',')) {
$artifactFile = DownloadRelease -token $Token -projects $Project -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -release $release -path $ArtifactsFolder -mask $mask
Write-Host "'$artifactFile'"
if (!$artifactFile -or !(Test-Path $artifactFile)) {
if ($mask -eq 'Apps') {
throw "Artifact $Artifacts was not found on any release. Make sure that the artifact files exist and files are not corrupted."
}
}
else {
if ($artifactFile -notlike '*.zip') {
throw "Downloaded artifact is not a .zip file"
}
Expand-Archive -Path $artifactFile -DestinationPath ($artifactFile.SubString(0, $artifactFile.Length - 4))
Remove-Item $artifactFile -Force
}
}
}
elseif ($Artifacts -eq "current") {
# No releases exist in the repository - fall back to the artifacts from the latest build
Write-Host "::Warning::Current release was specified, but no releases were found. Searching for latest build artifacts instead."
$searchArtifactsVersion = "latest"
$searchArtifacts = $true
Comment on lines +150 to +154
}
else {
throw "Artifact $Artifacts was not found on any release."
}
}
else {
$searchArtifacts = $true
}

if ($searchArtifacts) {
foreach ($atype in $Atypes.Split(',')) {
$allArtifacts = @(GetArtifacts -token $Token -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -mask $atype -projects $Project -version $searchArtifactsVersion -branch $Branch)
if ($allArtifacts) {
foreach ($artifact in $allArtifacts) {
$artifactFile = DownloadArtifact -token $Token -artifact $artifact -path $ArtifactsFolder
Write-Host $artifactFile
if (!(Test-Path $artifactFile)) {
throw "Unable to download artifact $($artifact.name)"
}
if ($artifactFile -notlike '*.zip') {
throw "Downloaded artifact is not a .zip file"
}
Expand-Archive -Path $artifactFile -DestinationPath ($artifactFile.SubString(0, $artifactFile.Length - 4))
Remove-Item $artifactFile -Force
}
}
else {
if ($atype -eq "Apps") {
throw "ERROR: Could not find any $atype artifacts for project $Project, version $searchArtifactsVersion"
}
else {
Write-Host "WARNING: Could not find any $atype artifacts for project $Project, version $searchArtifactsVersion"
}
}
}
}

return $Project
}

Export-ModuleMember -Function Get-ProjectsInDeliveryOrder, Get-ArtifactsForDelivery
1 change: 1 addition & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ As part of this, the warning comparison now also parses the raw AL compiler outp
- Issue 2267 - `AppSourceCop.json` is now created for test apps when `enableCodeAnalyzersOnTestApps` is true.
- Issue 2320 - Deliver to NuGet of release workflow failing with app and test app
- Issue 2337 - Incremental builds: skipped projects re-publish Default-mode apps into ALL buildMode-specific artifacts
- Issue 2281 - Publish To AppSource (and other delivery targets) now falls back to the artifacts from the latest build when `current` is specified and the repository doesn't contain any releases - the same fallback as the one already used when deploying to an environment.

### Valid SARIF URIs for file paths containing spaces

Expand Down
109 changes: 109 additions & 0 deletions Tests/Deliver.Module.Test.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,112 @@ Describe "Deliver Module - Get-ProjectsInDeliveryOrder Tests" {
Remove-Item $baseFolder -Force -Recurse -ErrorAction SilentlyContinue
}
}

Describe "Deliver Module - Get-ArtifactsForDelivery Tests" {
BeforeAll {
. (Join-Path -Path $PSScriptRoot -ChildPath "../Actions/AL-Go-Helper.ps1" -Resolve)
DownloadAndImportBcContainerHelper -baseFolder $([System.IO.Path]::GetTempPath())

Import-Module (Join-Path $PSScriptRoot "../Actions/Deliver/Deliver.psm1" -Resolve) -DisableNameChecking -Scope Global

$ENV:GITHUB_API_URL = 'https://api.github.com'
$ENV:GITHUB_REPOSITORY = 'myOrg/myRepo'
}

BeforeEach {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'artifactsFolder', Justification = 'False positive.')]
$artifactsFolder = (New-Item -ItemType Directory -Path (Join-Path $([System.IO.Path]::GetTempPath()) $([System.IO.Path]::GetRandomFileName()))).FullName

# Do not unpack/delete anything - the artifacts are never really downloaded in these tests
Mock Test-Path { return $true } -ModuleName Deliver
Mock Expand-Archive { } -ModuleName Deliver
Mock Remove-Item { } -ModuleName Deliver
Mock DownloadRelease { return (Join-Path $artifactsFolder 'MyProject-main-Apps-1.0.0.0.zip') } -ModuleName Deliver
Mock DownloadArtifact { return (Join-Path $artifactsFolder 'MyProject-main-Apps-1.0.0.0.zip') } -ModuleName Deliver
Mock GetArtifacts { return @([PSCustomObject]@{ Name = 'MyProject-main-Apps-1.0.0.0' }) } -ModuleName Deliver
}

It 'does nothing when the artifacts have already been downloaded' {
Mock GetReleases { throw 'GetReleases should not be called' } -ModuleName Deliver

Get-ArtifactsForDelivery -token 'token' -artifacts '.artifacts' -artifactsFolder $artifactsFolder -project 'MyProject' -atypes 'Apps' -branch 'main' | Should -BeExactly 'MyProject'

Should -Invoke GetArtifacts -ModuleName Deliver -Times 0 -Exactly
Should -Invoke DownloadRelease -ModuleName Deliver -Times 0 -Exactly
}

It 'downloads artifacts from the current release when releases exist' {
Mock GetReleases {
return @([PSCustomObject]@{ tag_name = '1.0.0'; prerelease = $false; draft = $false })
} -ModuleName Deliver

Get-ArtifactsForDelivery -token 'token' -artifacts 'current' -artifactsFolder $artifactsFolder -project 'MyProject' -atypes 'Apps,Dependencies' -branch 'main' | Should -BeExactly 'MyProject'

Should -Invoke DownloadRelease -ModuleName Deliver -Times 2 -Exactly
Should -Invoke GetArtifacts -ModuleName Deliver -Times 0 -Exactly
}

It 'falls back to the latest build artifacts when current is specified and no releases exist' {
Mock GetReleases { return @() } -ModuleName Deliver

Get-ArtifactsForDelivery -token 'token' -artifacts 'current' -artifactsFolder $artifactsFolder -project 'MyProject' -atypes 'Apps,Dependencies' -branch 'main' | Should -BeExactly 'MyProject'

Should -Invoke DownloadRelease -ModuleName Deliver -Times 0 -Exactly
Should -Invoke GetArtifacts -ModuleName Deliver -Times 2 -Exactly
Should -Invoke GetArtifacts -ModuleName Deliver -Times 1 -Exactly -ParameterFilter { $version -eq 'latest' -and $mask -eq 'Apps' -and $branch -eq 'main' }
Should -Invoke DownloadArtifact -ModuleName Deliver -Times 2 -Exactly
}

It 'throws when prerelease is specified and no releases exist' {
Mock GetReleases { return @() } -ModuleName Deliver

{ Get-ArtifactsForDelivery -token 'token' -artifacts 'prerelease' -artifactsFolder $artifactsFolder -project 'MyProject' -atypes 'Apps' -branch 'main' } | Should -Throw '*was not found on any release*'

Should -Invoke GetArtifacts -ModuleName Deliver -Times 0 -Exactly
}

It 'throws when releases exist, but none of them match the requested version' {
Mock GetReleases {
return @([PSCustomObject]@{ tag_name = '1.0.0-beta'; prerelease = $true; draft = $false })
} -ModuleName Deliver

{ Get-ArtifactsForDelivery -token 'token' -artifacts 'current' -artifactsFolder $artifactsFolder -project 'MyProject' -atypes 'Apps' -branch 'main' } | Should -Throw '*Unable to locate current release*'

Should -Invoke GetArtifacts -ModuleName Deliver -Times 0 -Exactly
}

It 'searches for build artifacts when a version number is specified' {
Mock GetReleases { throw 'GetReleases should not be called' } -ModuleName Deliver

Get-ArtifactsForDelivery -token 'token' -artifacts '1.0.0.0' -artifactsFolder $artifactsFolder -project 'MyProject' -atypes 'Apps' -branch 'main' | Should -BeExactly 'MyProject'

Should -Invoke GetArtifacts -ModuleName Deliver -Times 1 -Exactly -ParameterFilter { $version -eq '1.0.0.0' }
}

It 'throws when no Apps artifacts are found' {
Mock GetReleases { return @() } -ModuleName Deliver
Mock GetArtifacts { return @() } -ModuleName Deliver

{ Get-ArtifactsForDelivery -token 'token' -artifacts 'current' -artifactsFolder $artifactsFolder -project 'MyProject' -atypes 'Apps' -branch 'main' } | Should -Throw '*Could not find any Apps artifacts*'
}

It 'uses the release asset naming convention for the project when downloading releases' {
Mock GetReleases {
return @([PSCustomObject]@{ tag_name = '1.0.0'; prerelease = $false; draft = $false })
} -ModuleName Deliver

Get-ArtifactsForDelivery -token 'token' -artifacts 'current' -artifactsFolder $artifactsFolder -project 'My Project' -atypes 'Apps' -branch 'main' | Should -BeExactly 'My.Project'
}

It 'uses the build artifact naming convention for the project when falling back to the latest build' {
Mock GetReleases { return @() } -ModuleName Deliver

Get-ArtifactsForDelivery -token 'token' -artifacts 'current' -artifactsFolder $artifactsFolder -project 'My Project' -atypes 'Apps' -branch 'main' | Should -BeExactly 'My Project'

Should -Invoke GetArtifacts -ModuleName Deliver -Times 1 -Exactly -ParameterFilter { $projects -eq 'My Project' }
}

AfterEach {
Remove-Item $artifactsFolder -Force -Recurse -ErrorAction SilentlyContinue
}
}
Loading