diff --git a/Actions/AL-Go-Helper.ps1 b/Actions/AL-Go-Helper.ps1 index 3ceeeb10a..0ab8ddbdc 100644 --- a/Actions/AL-Go-Helper.ps1 +++ b/Actions/AL-Go-Helper.ps1 @@ -1562,7 +1562,7 @@ function CreateDevEnv { New-Object -Type PSObject -Property $_ } }) - Get-dependencies -probingPathsJson $repo.appDependencyProbingPaths -saveToPath $dependenciesFolder -api_url 'https://api.github.com' | ForEach-Object { + Get-Dependencies -probingPathsJson $repo.appDependencyProbingPaths -saveToPath $dependenciesFolder -api_url 'https://api.github.com' | ForEach-Object { if ($_.startswith('(')) { $installTestApps += $_ } diff --git a/Actions/DownloadProjectDependencies/DownloadProjectDependencies.Action.ps1 b/Actions/DownloadProjectDependencies/DownloadProjectDependencies.Action.ps1 new file mode 100644 index 000000000..d0267599e --- /dev/null +++ b/Actions/DownloadProjectDependencies/DownloadProjectDependencies.Action.ps1 @@ -0,0 +1,128 @@ +Param( + [Parameter(HelpMessage = "The project for which to download dependencies", Mandatory = $true)] + [string] $project, + [string] $baseFolder, + [string] $buildMode = 'Default', + [string] $projectsDependenciesJson, + [string] $destinationPath, + [string] $token +) + +function DownloadDependenciesFromProbingPaths($baseFolder, $project, $destinationPath) { + $projectSettings = ReadSettings -baseFolder $baseFolder -project $project + + $probingPaths = $projectSettings.appDependencyProbingPaths | ConvertFrom-Json + + $downloadedDependencies = @() + if ($probingPaths) { + $downloadedDependencies += Get-Dependencies -probingPathsJson $repo.appDependencyProbingPaths -saveToPath $destinationPath | Where-Object { $_ } + } + + return $downloadedDependencies +} + +function DownloadDependenciesFromCurrentBuild($baseFolder, $project, $projectsDependencies, $buildMode, $destinationPath) { + Write-Host "Downloading dependencies for project '$project'" + + $dependencyProjects = @() + if ($projectsDependencies.Keys -contains $project) { + $dependencyProjects = @($projectsDependencies."$project") + } + + Write-Host "Dependency projects: $($dependencyProjects -join ', ')" + + # For each dependency project, calculate the corresponding probing path + $dependeciesProbingPaths = @($dependencyProjects | ForEach-Object { + $dependencyProject = $_ + + Write-Host "Reading settings for project '$dependencyProject'" + $dependencyProjectSettings = ReadSettings -baseFolder $baseFolder -project $dependencyProject + + $dependencyBuildMode = $buildMode + if(!($dependencyProjectSettings.buildModes -contains $dependencyBuildMode)) { + # Download the default build mode if the specified build mode is not supported for the dependency project + Write-Host "Build mode '$dependencyBuildMode' is not supported for project '$dependencyProject'. Using the default build mode." + $dependencyBuildMode = 'Default'; + } + + $currentBranch = $ENV:GITHUB_REF_NAME + + $baseBranch = $ENV:GITHUB_BASE_REF_NAME + # $ENV:GITHUB_BASE_REF_NAME is specified only for pull requests, so if it is not specified, use the current branch + if(!$baseBranch) { + $baseBranch = $currentBranch + } + + return @{ + "release_status" = "thisBuild" + "version" = "latest" + "buildMode" = $dependencyBuildMode + "projects" = $dependencyProject + "repo" = "$ENV:GITHUB_SERVER_URL/$ENV:GITHUB_REPOSITORY" + "branch" = $currentBranch + "baseBranch" = $baseBranch + "authTokenSecret" = $token + } + }) + + # For each probing path, download the dependencies + $downloadedDependencies = @() + $dependeciesProbingPaths | ForEach-Object { + $probingPath = $_ + + $buildMode = $probingPath.buildMode + $project = $probingPath.projects + $branch = $probingPath.branch + $baseBranch = $probingPath.baseBranch + + Write-Host "Downloading dependencies for project '$project'. BuildMode: $buildMode, Branch: $branch, Base Branch: $baseBranch" + + $dependency = Get-Dependencies -probingPathsJson $probingPath -saveToPath $destinationPath | Where-Object { $_ } + $downloadedDependencies += $dependency + } + + return $downloadedDependencies +} + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version 2.0 + +. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) + +Write-Host "Downloading dependencies for project '$project'. BuildMode: $buildMode, Base Folder: $baseFolder, Destination Path: $destinationPath" + +$downloadedDependencies = @() + +Write-Host "::group::Downloading project dependencies from current build" +$projectsDependencies = $projectsDependenciesJson | ConvertFrom-Json | ConvertTo-HashTable +$downloadedDependencies += DownloadDependenciesFromCurrentBuild -baseFolder $baseFolder -project $project -projectsDependencies $projectsDependencies -buildMode $buildMode -destinationPath $destinationPath +Write-Host "::endgroup::" + +Write-Host "::group::Downloading project dependencies from probing paths" +$downloadedDependencies += DownloadDependenciesFromProbingPaths -baseFolder $baseFolder -project $project -destinationPath $destinationPath +Write-Host "::endgroup::" + +Write-Host "Downloaded dependencies: $($downloadedDependencies -join ', ')" + +$downloadedApps = @() +$downloadedTestApps = @() + +# Split the downloaded dependencies into apps and test apps +$downloadedDependencies | ForEach-Object { + # naming convention: app, (testapp) + if ($_.startswith('(')) { + $DownloadedTestApps += $_ + } + else { + $DownloadedApps += $_ + } +} + +Write-Host "Downloaded dependencies apps: $($DownloadedApps -join ', ')" +Write-Host "Downloaded dependencies test apps: $($DownloadedTestApps -join ', ')" + +$DownloadedAppsJson = ConvertTo-Json $DownloadedApps -Depth 99 -Compress +$DownloadedTestAppsJson = ConvertTo-Json $DownloadedTestApps -Depth 99 -Compress + +Add-Content -Path $env:GITHUB_OUTPUT -Value "DownloadedApps=$DownloadedAppsJson" +Add-Content -Path $env:GITHUB_OUTPUT -Value "DownloadedTestApps=$DownloadedTestAppsJson" \ No newline at end of file diff --git a/Actions/DownloadProjectDependencies/README.md b/Actions/DownloadProjectDependencies/README.md new file mode 100644 index 000000000..d311fcb4d --- /dev/null +++ b/Actions/DownloadProjectDependencies/README.md @@ -0,0 +1,22 @@ +# Download project dependencies +Downloads artifacts from AL-Go projects, that are dependencies of a given AL-Go project. + +The action constructs arrays of paths to .app files, that are dependencies of the apps in an AL-Go project. + +## Parameters +### project +The AL-Go project for which to download dependencies + +### buildMode +The build mode to use to downloaded to most appropriate dependencies. +If a dependency project isn't built in the provided build mode, then the artifacts from the default mode will be used. + +### projectsDependenciesJson +A JSON-formatted object that maps a project to an array of its dependencies. + +## Outputs +### DownloadedApps: +A JSON-formatted list of paths to .app files, that dependencies of the apps. + +### DownloadedTestApps: +A JSON-formatted list of paths to .app files, that dependencies of the test apps. diff --git a/Actions/DownloadProjectDependencies/action.yaml b/Actions/DownloadProjectDependencies/action.yaml new file mode 100644 index 000000000..0c3c1915e --- /dev/null +++ b/Actions/DownloadProjectDependencies/action.yaml @@ -0,0 +1,53 @@ +name: Download Project Dependencies +author: Microsoft Corporation +description: Downloads the dependencies of an AL-Go project +inputs: + shell: + description: Shell in which you want to run the action (powershell or pwsh) + required: false + default: powershell + project: + description: Project for which to download dependencies + required: true + buildMode: + description: Build mode used when building the artifacts + required: true + projectsDependenciesJson: + description: A JSON object that matches eash project with its dependency projects + required: true +outputs: + DownloadedApps: + description: A JSON-formatted array of paths to .app files of the apps that were downloaded + value: ${{ steps.DownloadDependencies.outputs.DownloadedApps }} + DownloadedTestApps: + description: A JSON-formatted array of paths to .app files of the test apps that were downloaded + value: ${{ steps.DownloadDependencies.outputs.DownloadedTestApps }} +runs: + using: composite + steps: + - name: Download artifacts from current build + uses: actions/download-artifact@v3 + with: + path: ${{ github.workspace }}/.dependencies + + - name: Download project dependencies + shell: ${{ inputs.shell }} + id: DownloadDependencies + env: + _project: ${{ inputs.project }} + _buildMode: ${{ inputs.buildMode }} + _projectsDependenciesJson: ${{ inputs.projectsDependenciesJson }} + _baseFolder: ${{ github.workspace }} + _destinationPath: ${{ github.workspace }}/.dependencies + _gitHubToken: ${{ github.token }} + run: + try { + ${{ github.action_path }}/DownloadProjectDependencies.Action.ps1 -project $ENV:_project -buildMode $ENV:_buildMode -projectsDependenciesJson $ENV:_projectsDependenciesJson -baseFolder $ENV:_baseFolder -destinationPath $ENV:_destinationPath -token $ENV:_gitHubToken + } + catch { + Write-Host "::Error::Unexpected error when running action ($($_.Exception.Message.Replace("`r",'').Replace("`n",' ')))" + exit 1 + } +branding: + icon: terminal + color: blue diff --git a/Actions/Github-Helper.psm1 b/Actions/Github-Helper.psm1 index e074ab13e..38203a05a 100644 --- a/Actions/Github-Helper.psm1 +++ b/Actions/Github-Helper.psm1 @@ -107,7 +107,7 @@ function InvokeWebRequest { } } -function Get-dependencies { +function Get-Dependencies { Param( $probingPathsJson, [string] $api_url = $ENV:GITHUB_API_URL, @@ -121,10 +121,17 @@ function Get-dependencies { $downloadedList = @() 'Apps','TestApps' | ForEach-Object { $mask = $_ - Write-Host "Locating all $mask artifacts from probing paths" $probingPathsJson | ForEach-Object { $dependency = $_ $projects = $dependency.projects + $buildMode = $dependency.buildMode + + # change the mask to include the build mode + if($buildMode -ne "Default") { + $mask = "$buildMode$mask" + } + + Write-Host "Locating $mask artifacts for projects: $projects" if ($dependency.release_status -eq "thisBuild") { $missingProjects = @() @@ -146,14 +153,14 @@ function Get-dependencies { Write-Host "$($_.FullName) found from previous job" } } - elseif ($mask -ne 'TestApps') { + elseif ($mask -notlike '*TestApps') { Write-Host "$_ not built, downloading from artifacts" $missingProjects += @($_) } } if ($missingProjects) { $dependency.release_status = 'latestBuild' - $dependency.branch = "main" + $dependency.branch = $dependency.baseBranch $dependency.projects = $missingProjects -join "," } } diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index 51ddd80ab..dbe346eff 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -7,15 +7,17 @@ Param( [string] $parentTelemetryScopeJson = '7b7d', [Parameter(HelpMessage = "Project folder", Mandatory = $false)] [string] $project = "", - [Parameter(HelpMessage = "Project Dependencies in compressed Json format", Mandatory = $false)] - [string] $projectDependenciesJson = "", [Parameter(HelpMessage = "Settings from repository in compressed Json format", Mandatory = $false)] [string] $settingsJson = '{"appBuild":"", "appRevision":""}', [Parameter(HelpMessage = "Secrets from repository in compressed Json format", Mandatory = $false)] [string] $secretsJson = '{"insiderSasToken":"","licenseFileUrl":"","codeSignCertificateUrl":"","codeSignCertificatePassword":"","keyVaultCertificateUrl":"","keyVaultCertificatePassword":"","keyVaultClientId":"","storageContext":"","applicationInsightsConnectionString":""}', [Parameter(HelpMessage = "Specifies a mode to use for the build steps", Mandatory = $false)] [ValidateSet('Default', 'Translated', 'Clean')] - [string] $buildMode = 'Default' + [string] $buildMode = 'Default', + [Parameter(HelpMessage = "A JSON-formatted list of apps to install", Mandatory = $false)] + [string] $installAppsJson = '[]', + [Parameter(HelpMessage = "A JSON-formatted list of test apps to install", Mandatory = $false)] + [string] $installTestAppsJson = '[]' ) $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 @@ -132,53 +134,8 @@ try { $installApps = $repo.installApps $installTestApps = $repo.installTestApps - Write-Host "Project: $project" - if ($project -and $repo.useProjectDependencies -and $projectDependenciesJson -ne "") { - Write-Host "Using project dependencies: $projectDependenciesJson" - - $projectDependencies = $projectDependenciesJson | ConvertFrom-Json | ConvertTo-HashTable - if ($projectDependencies.Keys -contains $project) { - $projects = @($projectDependencies."$project") -join "," - } - else { - $projects = '' - } - if ($projects) { - Write-Host "Project dependencies: $projects" - $thisBuildProbingPaths = @(@{ - "release_status" = "thisBuild" - "version" = "latest" - "projects" = $projects - "repo" = "$ENV:GITHUB_SERVER_URL/$ENV:GITHUB_REPOSITORY" - "branch" = $ENV:GITHUB_REF_NAME - "authTokenSecret" = $token - }) - Get-dependencies -probingPathsJson $thisBuildProbingPaths | where-Object { $_ } | ForEach-Object { - if ($_.startswith('(')) { - $installTestApps += $_ - } - else { - $installApps += $_ - } - } - } - else { - Write-Host "No project dependencies" - } - } - - if ($repo.appDependencyProbingPaths) { - Write-Host "::group::Downloading dependencies" - Get-dependencies -probingPathsJson $repo.appDependencyProbingPaths | ForEach-Object { - if ($_.startswith('(')) { - $installTestApps += $_ - } - else { - $installApps += $_ - } - } - Write-Host "::endgroup::" - } + $installApps += $installAppsJson | ConvertFrom-Json + $installTestApps += $installTestAppsJson | ConvertFrom-Json # Analyze app.json version dependencies before launching pipeline diff --git a/Actions/RunPipeline/action.yaml b/Actions/RunPipeline/action.yaml index b856a0572..77ced37a5 100644 --- a/Actions/RunPipeline/action.yaml +++ b/Actions/RunPipeline/action.yaml @@ -21,10 +21,6 @@ inputs: description: Project folder required: false default: '.' - projectDependenciesJson: - description: Project Dependencies in compressed Json format - required: false - default: '' settingsJson: description: Settings from repository in compressed Json format required: false @@ -37,6 +33,14 @@ inputs: description: Specifies a mode to use for the build steps required: false default: 'Default' + installAppsJson: + description: A JSON-formatted list of apps to install + required: false + default: '[]' + installTestAppsJson: + description: A JSON-formatted list of test apps to install + required: false + default: '[]' runs: using: composite steps: @@ -47,11 +51,12 @@ runs: _token: ${{ inputs.token }} _parentTelemetryScopeJson: ${{ inputs.parentTelemetryScopeJson }} _project: ${{ inputs.project }} - _projectDependenciesJson: ${{ inputs.projectDependenciesJson }} _settingsJson: ${{ inputs.settingsJson }} _secretsJson: ${{ inputs.secretsJson }} _buildMode: ${{ inputs.buildMode }} - run: try { ${{ github.action_path }}/RunPipeline.ps1 -actor $ENV:_actor -token $ENV:_token -parentTelemetryScopeJson $ENV:_parentTelemetryScopeJson -project $ENV:_project -projectDependenciesJson $ENV:_projectDependenciesJson -settingsJson $ENV:_settingsJson -secretsJson $ENV:_secretsJson -buildMode $ENV:_buildMode } catch { Write-Host "::Error::Unexpected error when running action ($($_.Exception.Message.Replace("`r",'').Replace("`n",' ')))"; exit 1 } + _installAppsJson: ${{ inputs.installAppsJson }} + _installTestAppsJson: ${{ inputs.installTestAppsJson }} + run: try { ${{ github.action_path }}/RunPipeline.ps1 -actor $ENV:_actor -token $ENV:_token -parentTelemetryScopeJson $ENV:_parentTelemetryScopeJson -project $ENV:_project -settingsJson $ENV:_settingsJson -secretsJson $ENV:_secretsJson -buildMode $ENV:_buildMode -installAppsJson $ENV:_installAppsJson -installTestAppsJson $ENV:_installTestAppsJson } catch { Write-Host "::Error::Unexpected error when running action ($($_.Exception.Message.Replace("`r",'').Replace("`n",' ')))"; exit 1 } branding: icon: terminal color: blue diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 1e33a6b83..d3958c1f8 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -15,9 +15,6 @@ Create Online Development environment workflow failed in AppSource template unle Create Online Development environment workflow didn't have a project parameter and only worked for single project repositories Create Online Development environment workflow didn't work if runs-on was set to Linux -### New Settings -- `keyVaultCodesignCertificateName`: With this setting you can delegate the codesigning to an Azure Key Vault. This can be useful if your certificate has to be stored in a Hardware Security Module - ### Issue 555 AL-Go contains several workflows, which create a Pull Request or pushes code directly. All (except Update AL-Go System Files) earlier used the GITHUB_TOKEN to create the PR or commit. @@ -25,6 +22,12 @@ The problem using GITHUB_TOKEN is that is doesn't trigger a pull request build o This is by design: https://docs.github.com/en/actions/using-workflows/triggering-a-workflow#triggering-a-workflow-from-a-workflow Now, you can set the checkbox called Use GhTokenWorkflow to allowing you to use the GhTokenWorkflow instead of the GITHUB_TOKEN - making sure that workflows are triggered +### New Settings +- `keyVaultCodesignCertificateName`: With this setting you can delegate the codesigning to an Azure Key Vault. This can be useful if your certificate has to be stored in a Hardware Security Module + +### New Actions +- `DownloadProjectDependencies`: Downloads the dependency apps for a given project and build mode. + ## v3.1 ### Issues diff --git a/Templates/AppSource App/.github/workflows/_BuildALGoProject.yaml b/Templates/AppSource App/.github/workflows/_BuildALGoProject.yaml index 0f43bc491..2315db6b4 100644 --- a/Templates/AppSource App/.github/workflows/_BuildALGoProject.yaml +++ b/Templates/AppSource App/.github/workflows/_BuildALGoProject.yaml @@ -76,12 +76,6 @@ jobs: with: ref: ${{ inputs.checkoutRef }} lfs: true - - - name: Download thisbuild artifacts - if: inputs.publishThisBuildArtifacts - uses: actions/download-artifact@v3 - with: - path: '.dependencies' - name: Read settings uses: microsoft/AL-Go-Actions/ReadSettings@main @@ -117,6 +111,15 @@ jobs: path: .artifactcache key: ${{ steps.determineArtifactUrl.outputs.ArtifactCacheKey }} + - name: Download Project Dependencies + id: DownloadProjectDependencies + uses: microsoft/AL-Go-Actions/DownloadProjectDependencies@main + with: + shell: ${{ inputs.shell }} + project: ${{ inputs.project }} + buildMode: ${{ inputs.buildMode }} + projectsDependenciesJson: ${{ inputs.projectDependenciesJson }} + - name: Run pipeline id: RunPipeline uses: microsoft/AL-Go-Actions/RunPipeline@main @@ -126,10 +129,11 @@ jobs: shell: ${{ inputs.shell }} parentTelemetryScopeJson: ${{ inputs.parentTelemetryScopeJson }} project: ${{ inputs.project }} - projectDependenciesJson: ${{ inputs.projectDependenciesJson }} settingsJson: ${{ env.Settings }} secretsJson: ${{ env.RepoSecrets }} buildMode: ${{ inputs.buildMode }} + installAppsJson: ${{ steps.DownloadProjectDependencies.outputs.DownloadedApps }} + installTestAppsJson: ${{ steps.DownloadProjectDependencies.outputs.DownloadedTestApps }} - name: Sign if: inputs.signArtifacts && env.doNotSignApps == 'False' && env.keyVaultCodesignCertificateName != '' diff --git a/Templates/Per Tenant Extension/.github/workflows/_BuildALGoProject.yaml b/Templates/Per Tenant Extension/.github/workflows/_BuildALGoProject.yaml index 0f43bc491..2315db6b4 100644 --- a/Templates/Per Tenant Extension/.github/workflows/_BuildALGoProject.yaml +++ b/Templates/Per Tenant Extension/.github/workflows/_BuildALGoProject.yaml @@ -76,12 +76,6 @@ jobs: with: ref: ${{ inputs.checkoutRef }} lfs: true - - - name: Download thisbuild artifacts - if: inputs.publishThisBuildArtifacts - uses: actions/download-artifact@v3 - with: - path: '.dependencies' - name: Read settings uses: microsoft/AL-Go-Actions/ReadSettings@main @@ -117,6 +111,15 @@ jobs: path: .artifactcache key: ${{ steps.determineArtifactUrl.outputs.ArtifactCacheKey }} + - name: Download Project Dependencies + id: DownloadProjectDependencies + uses: microsoft/AL-Go-Actions/DownloadProjectDependencies@main + with: + shell: ${{ inputs.shell }} + project: ${{ inputs.project }} + buildMode: ${{ inputs.buildMode }} + projectsDependenciesJson: ${{ inputs.projectDependenciesJson }} + - name: Run pipeline id: RunPipeline uses: microsoft/AL-Go-Actions/RunPipeline@main @@ -126,10 +129,11 @@ jobs: shell: ${{ inputs.shell }} parentTelemetryScopeJson: ${{ inputs.parentTelemetryScopeJson }} project: ${{ inputs.project }} - projectDependenciesJson: ${{ inputs.projectDependenciesJson }} settingsJson: ${{ env.Settings }} secretsJson: ${{ env.RepoSecrets }} buildMode: ${{ inputs.buildMode }} + installAppsJson: ${{ steps.DownloadProjectDependencies.outputs.DownloadedApps }} + installTestAppsJson: ${{ steps.DownloadProjectDependencies.outputs.DownloadedTestApps }} - name: Sign if: inputs.signArtifacts && env.doNotSignApps == 'False' && env.keyVaultCodesignCertificateName != ''