From e0080d16e411c834560386730d300b6f0610f91f Mon Sep 17 00:00:00 2001 From: bcbuild-github-agent <137281497+bcbuild-github-agent@users.noreply.github.com> Date: Wed, 21 Aug 2024 11:13:25 +0200 Subject: [PATCH] Deploying AL-Go from main (a2c4efcc730e9b264f74cb321e7c2193c8c96b8d) to main (#62) Deploying AL-Go from main (a2c4efcc730e9b264f74cb321e7c2193c8c96b8d) to main Co-authored-by: microsoft --- AL-Go-Helper.ps1 | 116 ++- AddExistingApp/AddExistingApp.ps1 | 222 +++-- AddExistingApp/README.md | 5 +- AddExistingApp/action.yaml | 14 +- AnalyzeTests/AnalyzeTests.ps1 | 72 +- AnalyzeTests/README.md | 9 +- AnalyzeTests/TestResultAnalyzer.ps1 | 377 +++++++-- AnalyzeTests/action.yaml | 22 +- BuildPowerPlatform/README.md | 4 + BuildPowerPlatform/action.yaml | 13 +- BuildReferenceDocumentation/README.md | 4 + BuildReferenceDocumentation/action.yaml | 7 +- CalculateArtifactNames/README.md | 5 + CalculateArtifactNames/action.yaml | 7 +- .../CheckForUpdates.HelperFunctions.ps1 | 32 +- CheckForUpdates/CheckForUpdates.ps1 | 33 +- CheckForUpdates/README.md | 4 + CheckForUpdates/action.yaml | 7 +- CreateApp/CreateApp.ps1 | 12 - CreateApp/README.md | 5 +- CreateApp/action.yaml | 14 +- .../CreateDevelopmentEnvironment.ps1 | 45 +- CreateDevelopmentEnvironment/README.md | 5 +- CreateDevelopmentEnvironment/action.yaml | 14 +- CreateReleaseNotes/CreateReleaseNotes.ps1 | 85 +- CreateReleaseNotes/README.md | 6 +- CreateReleaseNotes/action.yaml | 14 +- Deliver/Deliver.ps1 | 797 +++++++++--------- Deliver/README.md | 5 +- Deliver/action.yaml | 14 +- Deploy/Deploy.ps1 | 48 +- Deploy/README.md | 5 +- Deploy/action.yaml | 14 +- DeployPowerPlatform/README.md | 4 + DeployPowerPlatform/action.yaml | 18 +- DetermineArtifactUrl/DetermineArtifactUrl.ps1 | 57 +- DetermineArtifactUrl/README.md | 5 +- DetermineArtifactUrl/action.yaml | 14 +- .../DetermineDeliveryTargets.ps1 | 6 +- DetermineDeliveryTargets/README.md | 4 + DetermineDeliveryTargets/action.yaml | 7 +- .../DetermineDeploymentEnvironments.ps1 | 17 +- DetermineDeploymentEnvironments/README.md | 6 +- DetermineDeploymentEnvironments/action.yaml | 7 +- .../DetermineProjectsToBuild.Action.ps1 | 106 +-- DetermineProjectsToBuild/README.md | 6 +- DetermineProjectsToBuild/action.yaml | 14 +- DownloadProjectDependencies/README.md | 5 + DownloadProjectDependencies/action.yaml | 7 +- DumpWorkflowInfo/README.md | 4 + DumpWorkflowInfo/action.yaml | 7 +- GetArtifactsForDeployment/README.md | 6 + GetArtifactsForDeployment/action.yaml | 7 +- .../IncrementVersionNumber.ps1 | 152 ++-- .../IncrementVersionNumber.psm1 | 36 +- IncrementVersionNumber/README.md | 7 +- IncrementVersionNumber/action.yaml | 14 +- Invoke-AlGoAction.ps1 | 30 + Packages.json | 6 +- PipelineCleanup/PipelineCleanup.ps1 | 29 +- PipelineCleanup/README.md | 5 +- PipelineCleanup/action.yaml | 14 +- PullPowerPlatformChanges/README.md | 5 +- PullPowerPlatformChanges/action.yaml | 23 +- .../PullRequestStatusCheck.ps1 | 2 +- PullRequestStatusCheck/README.md | 4 + PullRequestStatusCheck/action.yaml | 7 +- README.md | 2 + ReadPowerPlatformSettings/README.md | 5 + ReadPowerPlatformSettings/action.yaml | 7 +- ReadSecrets/README.md | 9 +- ReadSecrets/ReadSecrets.ps1 | 12 + ReadSecrets/ReadSecretsHelper.psm1 | 139 ++- ReadSecrets/action.yaml | 7 +- ReadSettings/README.md | 7 +- ReadSettings/action.yaml | 7 +- RunPipeline/README.md | 10 +- RunPipeline/RunPipeline.ps1 | 12 - RunPipeline/action.yaml | 14 +- SECURITY.md | 16 +- Sign/README.md | 7 +- Sign/Sign.ps1 | 102 ++- Sign/Sign.psm1 | 84 +- Sign/action.yaml | 16 +- TelemetryHelper.psm1 | 253 ++++-- Troubleshooting/README.md | 4 + Troubleshooting/action.yaml | 7 +- VerifyPRChanges/README.md | 4 + VerifyPRChanges/action.yaml | 7 +- WorkflowInitialize/README.md | 7 +- WorkflowInitialize/WorkflowInitialize.ps1 | 93 +- WorkflowInitialize/action.yaml | 16 +- WorkflowPostProcess/README.md | 7 +- WorkflowPostProcess/WorkflowPostProcess.ps1 | 116 ++- WorkflowPostProcess/action.yaml | 23 +- 95 files changed, 1981 insertions(+), 1689 deletions(-) create mode 100644 Invoke-AlGoAction.ps1 diff --git a/AL-Go-Helper.ps1 b/AL-Go-Helper.ps1 index 08900f6..2c10f1c 100644 --- a/AL-Go-Helper.ps1 +++ b/AL-Go-Helper.ps1 @@ -19,7 +19,6 @@ $defaultCICDPushBranches = @( 'main', 'release/*', 'feature/*' ) $defaultCICDPullRequestBranches = @( 'main' ) $runningLocal = $local.IsPresent $defaultBcContainerHelperVersion = "latest" # Must be double quotes. Will be replaced by BcContainerHelperVersion if necessary in the deploy step - ex. "https://github.com/organization/navcontainerhelper/archive/refs/heads/branch.zip" -$microsoftTelemetryConnectionString = "InstrumentationKey=84bd9223-67d4-4378-8590-9e4a46023be2;IngestionEndpoint=https://westeurope-1.in.applicationinsights.azure.com/" $notSecretProperties = @("Scopes","TenantId","BlobName","ContainerName","StorageAccountName","ServerUrl","ppUserName") $runAlPipelineOverrides = @( @@ -631,13 +630,19 @@ function ReadSettings { "cacheImageName" = "my" "cacheKeepDays" = 3 "alwaysBuildAllProjects" = $false - "microsoftTelemetryConnectionString" = $microsoftTelemetryConnectionString + "microsoftTelemetryConnectionString" = "InstrumentationKey=cd2cc63e-0f37-4968-b99a-532411a314b8;IngestionEndpoint=https://northeurope-2.in.applicationinsights.azure.com/" "partnerTelemetryConnectionString" = "" "sendExtendedTelemetryToMicrosoft" = $false "environments" = @() "buildModes" = @() "useCompilerFolder" = $false "pullRequestTrigger" = "pull_request_target" + "bcptThresholds" = [ordered]@{ + "DurationWarning" = 10 + "DurationError" = 25 + "NumberOfSqlStmtsWarning" = 5 + "NumberOfSqlStmtsError" = 10 + } "fullBuildPatterns" = @() "excludeEnvironments" = @() "alDoc" = [ordered]@{ @@ -1263,21 +1268,25 @@ function GetProjectFolders { $projectFolders } -function installModules { +function InstallModule { Param( - [String[]] $modules + [String] $name, + [System.Version] $minimumVersion = $null ) - $modules | ForEach-Object { - if (-not (get-installedmodule -Name $_ -ErrorAction SilentlyContinue)) { - Write-Host "Installing module $_" - Install-Module $_ -Force | Out-Null - } + if ($null -eq $minimumVersion) { + $minimumVersion = [System.Version](GetPackageVersion -packageName $name) } - $modules | ForEach-Object { - Write-Host "Importing module $_" - Import-Module $_ -DisableNameChecking -WarningAction SilentlyContinue | Out-Null + $module = Get-Module -name $name -ListAvailable | Select-Object -First 1 + if ($module -and $module.Version -ge $minimumVersion) { + Write-Host "Module $name is available in version $($module.Version)" + } + else { + Write-Host "Installing module $name (minimum version $minimumVersion)" + Install-Module -Name $name -MinimumVersion "$minimumVersion" -Force | Out-Null } + Write-Host "Importing module $name (minimum version $minimumVersion)" + Import-Module -Name $name -MinimumVersion $minimumVersion -DisableNameChecking -WarningAction SilentlyContinue | Out-Null } function CloneIntoNewFolder { @@ -1323,6 +1332,7 @@ function CommitFromNewFolder { Param( [string] $serverUrl, [string] $commitMessage, + [string] $body = $commitMessage, [string] $branch ) @@ -1351,7 +1361,7 @@ function CommitFromNewFolder { } invoke-git push -u $serverUrl $branch try { - invoke-gh pr create --fill --head $branch --repo $env:GITHUB_REPOSITORY --base $ENV:GITHUB_REF_NAME + invoke-gh pr create --fill --head $branch --repo $env:GITHUB_REPOSITORY --base $ENV:GITHUB_REF_NAME --body "$body" } catch { OutputError("GitHub actions are not allowed to create Pull Requests (see GitHub Organization or Repository Actions Settings). You can create the PR manually by navigating to $($env:GITHUB_SERVER_URL)/$($env:GITHUB_REPOSITORY)/tree/$branch") @@ -1657,7 +1667,7 @@ function CreateDevEnv { if (($settings.keyVaultName) -and -not ($bcAuthContext)) { Write-Host "Reading Key Vault $($settings.keyVaultName)" - installModules -modules @('Az.KeyVault') + InstallAzModuleIfNeeded -name 'Az.KeyVault' if ($kind -eq "local") { $LicenseFileSecret = Get-AzKeyVaultSecret -VaultName $settings.keyVaultName -Name $settings.licenseFileUrlSecretName @@ -2350,14 +2360,86 @@ function GetProjectsFromRepository { return @(GetMatchingProjects -projects $projects -selectProjects $selectProjects) } -function Get-PackageVersion($PackageName) { +function GetPackageVersion($packageName) { $alGoPackages = Get-Content -Path "$PSScriptRoot\Packages.json" | ConvertFrom-Json # Check if the package is in the list of packages - if ($alGoPackages.PSobject.Properties.name -match $PackageName) { - return $alGoPackages.$PackageName + if ($alGoPackages.PSobject.Properties.name -eq $PackageName) { + return $alGoPackages."$PackageName" } else { throw "Package $PackageName is not in the list of packages" } } + +function InstallAzModuleIfNeeded { + Param( + [string] $name, + [System.version] $minimumVersion = $null + ) + + if ($null -eq $minimumVersion) { + $minimumVersion = [System.Version](GetPackageVersion -packageName $name) + } + $azModule = Get-Module -Name $name + if ($azModule -and $azModule.Version -ge $minimumVersion) { + # Already installed + return + } + # GitHub hosted Linux runners have AZ PowerShell module saved in /usr/share/powershell/Modules/Az.* + if ($isWindows) { + # GitHub hosted Windows Runners have AzureRm PowerShell modules installed (deprecated) + # GitHub hosted Windows Runners have AZ PowerShell module saved in C:\Modules\az_* + # Remove AzureRm modules from PSModulePath and add AZ modules + if (Test-Path 'C:\Modules\az_*') { + $azModulesPath = Get-ChildItem 'C:\Modules\az_*' | Where-Object { $_.PSIsContainer } + if ($azModulesPath) { + Write-Host "Adding AZ module path: $($azModulesPath.FullName)" + $ENV:PSModulePath = "$($azModulesPath.FullName);$(("$ENV:PSModulePath".Split(';') | Where-Object { $_ -notlike 'C:\\Modules\Azure*' }) -join ';')" + } + } + } + InstallModule -name $name -minimumVersion $minimumVersion +} + +$script:AzConnected = $false + +function ConnectAz { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'GitHub Secrets come in as plain text')] + param( + [PsCustomObject] $azureCredentials + ) + if ($script:AzConnected) { + return + } + InstallAzModuleIfNeeded -name 'Az.KeyVault' + try { + Clear-AzContext -Scope Process + Clear-AzContext -Scope CurrentUser -Force -ErrorAction SilentlyContinue + if ($azureCredentials.PSObject.Properties.Name -eq 'ClientSecret' -and $azureCredentials.ClientSecret) { + Write-Host "Connecting to Azure using clientId and clientSecret." + $credential = New-Object pscredential -ArgumentList $azureCredentials.ClientId, (ConvertTo-SecureString -string $azureCredentials.ClientSecret -AsPlainText -Force) + Connect-AzAccount -ServicePrincipal -Tenant $azureCredentials.TenantId -Credential $credential -WarningAction SilentlyContinue | Out-Null + } + else { + try { + Write-Host "Query federated token" + $result = Invoke-RestMethod -Method GET -UseBasicParsing -Headers @{ "Authorization" = "bearer $ENV:ACTIONS_ID_TOKEN_REQUEST_TOKEN"; "Accept" = "application/vnd.github+json" } -Uri "$ENV:ACTIONS_ID_TOKEN_REQUEST_URL&audience=api://AzureADTokenExchange" + } + catch { + throw "Unable to get federated token, maybe id_token: write permissions are missing. Error was $($_.Exception.Message)" + } + Write-Host "Connecting to Azure using clientId and federated token." + Connect-AzAccount -ApplicationId $azureCredentials.ClientId -Tenant $azureCredentials.TenantId -FederatedToken $result.value -WarningAction SilentlyContinue | Out-Null + } + if ($azureCredentials.PSObject.Properties.Name -eq 'SubscriptionId' -and $azureCredentials.SubscriptionId) { + Write-Host "Selecting subscription $($azureCredentials.SubscriptionId)" + Set-AzContext -SubscriptionId $azureCredentials.SubscriptionId -Tenant $azureCredentials.TenantId -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Out-Null + } + $script:AzConnected = $true + Write-Host "Successfully connected to Azure" + } + catch { + throw "Error trying to authenticate to Azure. Error was $($_.Exception.Message)" + } +} diff --git a/AddExistingApp/AddExistingApp.ps1 b/AddExistingApp/AddExistingApp.ps1 index 15f0d72..318a991 100644 --- a/AddExistingApp/AddExistingApp.ps1 +++ b/AddExistingApp/AddExistingApp.ps1 @@ -3,8 +3,6 @@ Param( [string] $actor, [Parameter(HelpMessage = "The GitHub token running the action", Mandatory = $false)] [string] $token, - [Parameter(HelpMessage = "Specifies the parent telemetry scope for the telemetry signal", Mandatory = $false)] - [string] $parentTelemetryScopeJson = '7b7d', [Parameter(HelpMessage = "Project name if the repository is setup for multiple projects", Mandatory = $false)] [string] $project = '.', [Parameter(HelpMessage = "Direct Download Url of .app or .zip file", Mandatory = $true)] @@ -78,149 +76,135 @@ function expandfile { } } -$telemetryScope = $null +. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) +$serverUrl, $branch = CloneIntoNewFolder -actor $actor -token $token -updateBranch $updateBranch -DirectCommit $directCommit -newBranchPrefix 'add-existing-app' +$baseFolder = (Get-Location).path +DownloadAndImportBcContainerHelper -baseFolder $baseFolder -try { - . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) - $serverUrl, $branch = CloneIntoNewFolder -actor $actor -token $token -updateBranch $updateBranch -DirectCommit $directCommit -newBranchPrefix 'add-existing-app' - $baseFolder = (Get-Location).path - DownloadAndImportBcContainerHelper -baseFolder $baseFolder - import-module (Join-Path -path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) - $telemetryScope = CreateScope -eventId 'DO0070' -parentTelemetryScopeJson $parentTelemetryScopeJson - - $type = "PTE" - Write-Host "Reading $RepoSettingsFile" - $settingsJson = Get-Content $RepoSettingsFile -Encoding UTF8 | ConvertFrom-Json - if ($settingsJson.PSObject.Properties.Name -eq "type") { - $type = $settingsJson.type - } +$type = "PTE" +Write-Host "Reading $RepoSettingsFile" +$settingsJson = Get-Content $RepoSettingsFile -Encoding UTF8 | ConvertFrom-Json +if ($settingsJson.PSObject.Properties.Name -eq "type") { + $type = $settingsJson.type +} - CheckAndCreateProjectFolder -project $project - $projectFolder = (Get-Location).path +CheckAndCreateProjectFolder -project $project +$projectFolder = (Get-Location).path - $appNames = @() - getfiles -url $url | ForEach-Object { - $appFolder = $_ - "?Content_Types?.xml", "MediaIdListing.xml", "navigation.xml", "NavxManifest.xml", "DocComments.xml", "SymbolReference.json" | ForEach-Object { - Remove-Item (Join-Path $appFolder $_) -Force -ErrorAction SilentlyContinue - } - $appJson = Get-Content (Join-Path $appFolder "app.json") -Encoding UTF8 | ConvertFrom-Json - $appNames += @($appJson.Name) +$appNames = @() +getfiles -url $url | ForEach-Object { + $appFolder = $_ + "?Content_Types?.xml", "MediaIdListing.xml", "navigation.xml", "NavxManifest.xml", "DocComments.xml", "SymbolReference.json" | ForEach-Object { + Remove-Item (Join-Path $appFolder $_) -Force -ErrorAction SilentlyContinue + } + $appJson = Get-Content (Join-Path $appFolder "app.json") -Encoding UTF8 | ConvertFrom-Json + $appNames += @($appJson.Name) - $ranges = @() - if ($appJson.PSObject.Properties.Name -eq "idRanges") { - $ranges += $appJson.idRanges - } - if ($appJson.PSObject.Properties.Name -eq "idRange") { - $ranges += @($appJson.idRange) - } + $ranges = @() + if ($appJson.PSObject.Properties.Name -eq "idRanges") { + $ranges += $appJson.idRanges + } + if ($appJson.PSObject.Properties.Name -eq "idRange") { + $ranges += @($appJson.idRange) + } - # Determine whether the app is PTE or AppSource App based on one of the id ranges (the first) - if ($ranges[0].from -lt 100000 -and $ranges[0].to -lt 100000) { - $ttype = "PTE" - } - else { - $ttype = "AppSource App" - } + # Determine whether the app is PTE or AppSource App based on one of the id ranges (the first) + if ($ranges[0].from -lt 100000 -and $ranges[0].to -lt 100000) { + $ttype = "PTE" + } + else { + $ttype = "AppSource App" + } - if ($appJson.PSObject.Properties.Name -eq "dependencies") { - foreach($dependency in $appJson.dependencies) { - if ($dependency.PSObject.Properties.Name -eq "AppId") { - $id = $dependency.AppId - } - else { - $id = $dependency.Id - } - if ($testRunnerApps.Contains($id)) { - $ttype = "Test App" - } + if ($appJson.PSObject.Properties.Name -eq "dependencies") { + foreach($dependency in $appJson.dependencies) { + if ($dependency.PSObject.Properties.Name -eq "AppId") { + $id = $dependency.AppId + } + else { + $id = $dependency.Id + } + if ($testRunnerApps.Contains($id)) { + $ttype = "Test App" } } + } - if ($ttype -ne "Test App") { - foreach($appName in (Get-ChildItem -Path $appFolder -Filter "*.al" -Recurse).FullName) { - $alContent = (Get-Content -Path $appName -Encoding UTF8) -join "`n" - if ($alContent -like "*codeunit*subtype*=*test*[test]*") { - $ttype = "Test App" - } + if ($ttype -ne "Test App") { + foreach($appName in (Get-ChildItem -Path $appFolder -Filter "*.al" -Recurse).FullName) { + $alContent = (Get-Content -Path $appName -Encoding UTF8) -join "`n" + if ($alContent -like "*codeunit*subtype*=*test*[test]*") { + $ttype = "Test App" } } + } - if ($ttype -ne "Test App" -and $ttype -ne $type) { - OutputWarning -message "According to settings, repository is for apps of type $type. The app you are adding seams to be of type $ttype" - } + if ($ttype -ne "Test App" -and $ttype -ne $type) { + OutputWarning -message "According to settings, repository is for apps of type $type. The app you are adding seams to be of type $ttype" + } - $appFolders = Get-ChildItem -Path $appFolder | Where-Object { $_.PSIsContainer -and (Test-Path (Join-Path $_.FullName 'app.json')) } - if (-not $appFolders) { - $appFolders = @($appFolder) - # TODO: What to do about the über app.json - another workspace? another setting? - } + $appFolders = Get-ChildItem -Path $appFolder | Where-Object { $_.PSIsContainer -and (Test-Path (Join-Path $_.FullName 'app.json')) } + if (-not $appFolders) { + $appFolders = @($appFolder) + # TODO: What to do about the über app.json - another workspace? another setting? + } - $orgfolderName = $appJson.name.Split([System.IO.Path]::getInvalidFileNameChars()) -join "" - $folderName = GetUniqueFolderName -baseFolder $projectFolder -folderName $orgfolderName - if ($folderName -ne $orgfolderName) { - OutputWarning -message "$orgFolderName already exists as a folder in the repo, using $folderName instead" - } + $orgfolderName = $appJson.name.Split([System.IO.Path]::getInvalidFileNameChars()) -join "" + $folderName = GetUniqueFolderName -baseFolder $projectFolder -folderName $orgfolderName + if ($folderName -ne $orgfolderName) { + OutputWarning -message "$orgFolderName already exists as a folder in the repo, using $folderName instead" + } - Move-Item -Path $appFolder -Destination $projectFolder -Force - Rename-Item -Path ([System.IO.Path]::GetFileName($appFolder)) -NewName $folderName - $appFolder = Join-Path $projectFolder $folderName + Move-Item -Path $appFolder -Destination $projectFolder -Force + Rename-Item -Path ([System.IO.Path]::GetFileName($appFolder)) -NewName $folderName + $appFolder = Join-Path $projectFolder $folderName - Get-ChildItem $appFolder -Filter '*.*' -Recurse | ForEach-Object { - if ($_.Name.Contains('%20')) { - Rename-Item -Path $_.FullName -NewName $_.Name.Replace('%20', ' ') - } + Get-ChildItem $appFolder -Filter '*.*' -Recurse | ForEach-Object { + if ($_.Name.Contains('%20')) { + Rename-Item -Path $_.FullName -NewName $_.Name.Replace('%20', ' ') } + } - $appFolders | ForEach-Object { - # Modify .AL-Go\settings.json - try { - $settingsJsonFile = Join-Path $projectFolder $ALGoSettingsFile - $SettingsJson = Get-Content $settingsJsonFile -Encoding UTF8 | ConvertFrom-Json - if (@($settingsJson.appFolders) + @($settingsJson.testFolders)) { - if ($ttype -eq "Test App") { - if ($SettingsJson.testFolders -notcontains $foldername) { - $SettingsJson.testFolders += @($folderName) - } + $appFolders | ForEach-Object { + # Modify .AL-Go\settings.json + try { + $settingsJsonFile = Join-Path $projectFolder $ALGoSettingsFile + $SettingsJson = Get-Content $settingsJsonFile -Encoding UTF8 | ConvertFrom-Json + if (@($settingsJson.appFolders) + @($settingsJson.testFolders)) { + if ($ttype -eq "Test App") { + if ($SettingsJson.testFolders -notcontains $foldername) { + $SettingsJson.testFolders += @($folderName) } - else { - if ($SettingsJson.appFolders -notcontains $foldername) { - $SettingsJson.appFolders += @($folderName) - } + } + else { + if ($SettingsJson.appFolders -notcontains $foldername) { + $SettingsJson.appFolders += @($folderName) } - $SettingsJson | Set-JsonContentLF -Path $settingsJsonFile } + $SettingsJson | Set-JsonContentLF -Path $settingsJsonFile } - catch { - throw "$ALGoSettingsFile is malformed. Error: $($_.Exception.Message)" - } + } + catch { + throw "$ALGoSettingsFile is malformed. Error: $($_.Exception.Message)" + } - # Modify workspace - Get-ChildItem -Path $projectFolder -Filter "*.code-workspace" | ForEach-Object { - try { - $workspaceFileName = $_.Name - $workspaceFile = $_.FullName - $workspace = Get-Content $workspaceFile -Encoding UTF8 | ConvertFrom-Json - if (-not ($workspace.folders | Where-Object { $_.Path -eq $foldername })) { - $workspace.folders += @(@{ "path" = $foldername }) - } - $workspace | Set-JsonContentLF -Path $workspaceFile - } - catch { - throw "$workspaceFileName is malformed.$([environment]::Newline) $($_.Exception.Message)" + # Modify workspace + Get-ChildItem -Path $projectFolder -Filter "*.code-workspace" | ForEach-Object { + try { + $workspaceFileName = $_.Name + $workspaceFile = $_.FullName + $workspace = Get-Content $workspaceFile -Encoding UTF8 | ConvertFrom-Json + if (-not ($workspace.folders | Where-Object { $_.Path -eq $foldername })) { + $workspace.folders += @(@{ "path" = $foldername }) } + $workspace | Set-JsonContentLF -Path $workspaceFile + } + catch { + throw "$workspaceFileName is malformed.$([environment]::Newline) $($_.Exception.Message)" } } } - Set-Location $baseFolder - CommitFromNewFolder -serverUrl $serverUrl -commitMessage "Add existing apps ($($appNames -join ', '))" -branch $branch | Out-Null - - TrackTrace -telemetryScope $telemetryScope -} -catch { - if (Get-Module BcContainerHelper) { - TrackException -telemetryScope $telemetryScope -errorRecord $_ - } - throw } +Set-Location $baseFolder +CommitFromNewFolder -serverUrl $serverUrl -commitMessage "Add existing apps ($($appNames -join ', '))" -branch $branch | Out-Null diff --git a/AddExistingApp/README.md b/AddExistingApp/README.md index 3983101..112a10a 100644 --- a/AddExistingApp/README.md +++ b/AddExistingApp/README.md @@ -1,22 +1,25 @@ # Add existing app + Add an existing app to an AL-Go for GitHub repository ## INPUT ### ENV variables + none ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | | actor | | The GitHub actor running the action | github.actor | | token | | The GitHub token running the action | github.token | -| parentTelemetryScopeJson | | Specifies the parent telemetry scope for the telemetry signal | {} | | project | | Project name if the repository is setup for multiple projects | . | | url | Yes | Direct Download Url of .app or .zip file to add to the repository | | | updateBranch | | Which branch should the app be added to | github.ref_name | | directCommit | | true if the action should create a direct commit against the branch or false to create a Pull Request | false | ## OUTPUT + none diff --git a/AddExistingApp/action.yaml b/AddExistingApp/action.yaml index f320f7f..e0fde0f 100644 --- a/AddExistingApp/action.yaml +++ b/AddExistingApp/action.yaml @@ -16,10 +16,6 @@ inputs: description: The GitHub token running the action required: false default: ${{ github.token }} - parentTelemetryScopeJson: - description: Specifies the parent telemetry scope for the telemetry signal - required: false - default: '7b7d' project: description: Project name if the repository is setup for multiple projects required: false @@ -43,19 +39,13 @@ runs: env: _actor: ${{ inputs.actor }} _token: ${{ inputs.token }} - _parentTelemetryScopeJson: ${{ inputs.parentTelemetryScopeJson }} _project: ${{ inputs.project }} _url: ${{ inputs.url }} _updateBranch: ${{ inputs.updateBranch }} _directCommit: ${{ inputs.directCommit }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { - ${{ github.action_path }}/AddExistingApp.ps1 -actor $ENV:_actor -token $ENV:_token -parentTelemetryScopeJson $ENV:_parentTelemetryScopeJson -project $ENV:_project -url $ENV:_url -updateBranch $ENV:_updateBranch -directCommit ($ENV:_directCommit -eq 'true') - } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "AddExistingApp" -Action { + ${{ github.action_path }}/AddExistingApp.ps1 -actor $ENV:_actor -token $ENV:_token -project $ENV:_project -url $ENV:_url -updateBranch $ENV:_updateBranch -directCommit ($ENV:_directCommit -eq 'true') } branding: icon: terminal diff --git a/AnalyzeTests/AnalyzeTests.ps1 b/AnalyzeTests/AnalyzeTests.ps1 index 316574e..ebd0172 100644 --- a/AnalyzeTests/AnalyzeTests.ps1 +++ b/AnalyzeTests/AnalyzeTests.ps1 @@ -1,49 +1,45 @@ Param( - [Parameter(HelpMessage = "Specifies the parent telemetry scope for the telemetry signal", Mandatory = $false)] - [string] $parentTelemetryScopeJson = '7b7d', [Parameter(HelpMessage = "Project to analyze", Mandatory = $false)] - [string] $project + [string] $project = '.' ) -$telemetryScope = $null +. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) -try { - . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) - DownloadAndImportBcContainerHelper +. (Join-Path -Path $PSScriptRoot 'TestResultAnalyzer.ps1') - import-module (Join-Path -path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) - $telemetryScope = CreateScope -eventId 'DO0082' -parentTelemetryScopeJson $parentTelemetryScopeJson +$testResultsFile = Join-Path $ENV:GITHUB_WORKSPACE "$project\TestResults.xml" +$testResultsSummaryMD, $testResultsfailuresMD, $testResultsFailuresSummaryMD = GetTestResultSummaryMD -testResultsFile $testResultsFile - . (Join-Path -Path $PSScriptRoot 'TestResultAnalyzer.ps1') +$settings = $env:Settings | ConvertFrom-Json +$bcptTestResultsFile = Join-Path $ENV:GITHUB_WORKSPACE "$project\bcptTestResults.json" +$bcptBaseLineFile = Join-Path $ENV:GITHUB_WORKSPACE "$project\bcptBaseLine.json" +$bcptThresholdsFile = Join-Path $ENV:GITHUB_WORKSPACE "$project\bcptThresholds.json" +$bcptSummaryMD = GetBcptSummaryMD ` + -bcptTestResultsFile $bcptTestResultsFile ` + -baseLinePath $bcptBaseLineFile ` + -thresholdsPath $bcptThresholdsFile ` + -bcptThresholds ($settings.bcptThresholds | ConvertTo-HashTable) - $testResultsFile = Join-Path $ENV:GITHUB_WORKSPACE "$project\TestResults.xml" - if (Test-Path $testResultsFile) { - $testResults = [xml](Get-Content "$project\TestResults.xml") - $testResultSummary = GetTestResultSummary -testResults $testResults -includeFailures 50 - - Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "TestResultMD=$testResultSummary" - Write-Host "TestResultMD=$testResultSummary" - - Add-Content -path $ENV:GITHUB_STEP_SUMMARY -value "$($testResultSummary.Replace("\n","`n"))`n" - } - else { - Write-Host "Test results not found" - } - - $bcptTestResultsFile = Join-Path $ENV:GITHUB_WORKSPACE "$project\BCPTTestResults.json" - if (Test-Path $bcptTestResultsFile) { - # TODO Display BCPT Test Results - } - else { - #Add-Content -path $ENV:GITHUB_STEP_SUMMARY -value "*BCPT test results not found*`n`n" - } - - TrackTrace -telemetryScope $telemetryScope +# If summary fits, we will display it in the GitHub summary +if ($testResultsSummaryMD.Length -gt 65000) { + # If Test results summary is too long, we will not display it in the GitHub summary, instead we will display a message to download the test results + $testResultsSummaryMD = "Test results summary size exceeds GitHub summary capacity. Download **TestResults** artifact to see details." +} +# If summary AND BCPT summary fits, we will display both in the GitHub summary +if ($testResultsSummaryMD.Length + $bcptSummaryMD.Length -gt 65000) { + # If Combined Test Results and BCPT summary exceeds GitHub summary capacity, we will not display the BCPT summary + $bcptSummaryMD = "Performance test results summary size exceeds GitHub summary capacity. Download **BcptTestResults** artifact to see details." +} +# If summary AND BCPT summary AND failures summary fits, we will display all in the GitHub summary +if ($testResultsSummaryMD.Length + $testResultsfailuresMD.Length + $bcptSummaryMD.Length -gt 65000) { + # If Combined Test Results, failures and BCPT summary exceeds GitHub summary capacity, we will not display the failures details, only the failures summary + $testResultsfailuresMD = $testResultsFailuresSummaryMD } -catch { - if (Get-Module BcContainerHelper) { - TrackException -telemetryScope $telemetryScope -errorRecord $_ - } - throw +Add-Content -Encoding UTF8 -path $ENV:GITHUB_STEP_SUMMARY -value "## Test results`n`n" +Add-Content -Encoding UTF8 -path $ENV:GITHUB_STEP_SUMMARY -value "$($testResultsSummaryMD.Replace("\n","`n"))`n`n" +Add-Content -Encoding UTF8 -path $ENV:GITHUB_STEP_SUMMARY -value "$($testResultsfailuresMD.Replace("\n","`n"))`n`n" +if ($bcptSummaryMD) { + Add-Content -Encoding UTF8 -path $ENV:GITHUB_STEP_SUMMARY -value "## Performance test results`n`n" + Add-Content -Encoding UTF8 -path $ENV:GITHUB_STEP_SUMMARY -value "$($bcptSummaryMD.Replace("\n","`n"))`n`n" } diff --git a/AnalyzeTests/README.md b/AnalyzeTests/README.md index 0e5dbd1..9dccef1 100644 --- a/AnalyzeTests/README.md +++ b/AnalyzeTests/README.md @@ -1,27 +1,32 @@ # Analyze Tests + Analyze results of tests from the RunPipeline action ## INPUT ### ENV variables + none ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | -| parentTelemetryScopeJson | | Specifies the parent telemetry scope for the telemetry signal | {} | | project | Yes | Name of project to analyze or . if the repository is setup for single project | | ## OUTPUT ### ENV variables + none ### OUTPUT variables + | Name | Description | | :-- | :-- | -| TestResultMD | MarkDown of the test result with \n instead of line breaks | +| TestResultMD | MarkDown of the test result with \\n instead of line breaks | ### SUMMARY + This function will set the test result markdown in the GITHUB_STEP_SUMMARY section diff --git a/AnalyzeTests/TestResultAnalyzer.ps1 b/AnalyzeTests/TestResultAnalyzer.ps1 index b4eda78..f00e5a8 100644 --- a/AnalyzeTests/TestResultAnalyzer.ps1 +++ b/AnalyzeTests/TestResultAnalyzer.ps1 @@ -1,107 +1,324 @@ -function GetTestResultSummary { +$statusOK = " :heavy_check_mark:" +$statusWarning = " :warning:" +$statusError = " :x:" +$statusSkipped = " :question:" + +# Build MarkDown of TestResults file +# This function will not fail if the file does not exist or if any test errors are found +# TestResults is in JUnit format +# Returns both a summary part and a failures part +function GetTestResultSummaryMD { Param( - [xml] $testResults, - [int] $includeFailures + [string] $testResultsFile ) - $totalTests = 0 - $totalTime = 0.0 - $totalFailed = 0 - $totalSkipped = 0 - $failuresIncluded = 0 $summarySb = [System.Text.StringBuilder]::new() $failuresSb = [System.Text.StringBuilder]::new() - if ($testResults.testsuites) { - $appNames = @($testResults.testsuites.testsuite | ForEach-Object { $_.Properties.property | Where-Object { $_.Name -eq "appName" } | ForEach-Object { $_.Value } } | Select-Object -Unique) - if (-not $appNames) { - $appNames = @($testResults.testsuites.testsuite | ForEach-Object { $_.Properties.property | Where-Object { $_.Name -eq "extensionId" } | ForEach-Object { $_.Value } } | Select-Object -Unique) - } - foreach($testsuite in $testResults.testsuites.testsuite) { - $totalTests += $testsuite.Tests - $totalTime += [decimal]::Parse($testsuite.time, [System.Globalization.CultureInfo]::InvariantCulture) - $totalFailed += $testsuite.failures - $totalSkipped += $testsuite.skipped - } - Write-Host "$($appNames.Count) TestApps, $totalTests tests, $totalFailed failed, $totalSkipped skipped, $totalTime seconds" - $summarySb.Append('|Test app|Tests|Passed|Failed|Skipped|Time|\n|:---|---:|---:|---:|---:|---:|\n') | Out-Null - foreach($appName in $appNames) { - $appTests = 0 - $appTime = 0.0 - $appFailed = 0 - $appSkipped = 0 - $suites = $testResults.testsuites.testsuite | where-Object { $_.Properties.property | Where-Object { $_.Value -eq $appName } } - foreach($suite in $suites) { - $appTests += [int]$suite.tests - $appFailed += [int]$suite.failures - $appSkipped += [int]$suite.skipped - $appTime += [decimal]::Parse($suite.time, [System.Globalization.CultureInfo]::InvariantCulture) - } - $appPassed = $appTests-$appFailed-$appSkipped - Write-Host "- $appName, $appTests tests, $appPassed passed, $appFailed failed, $appSkipped skipped, $appTime seconds" - $summarySb.Append("|$appName|$appTests|") | Out-Null - if ($appPassed -gt 0) { - $summarySb.Append("$($appPassed) :white_check_mark:") | Out-Null - } - $summarySb.Append("|") | Out-Null - if ($appFailed -gt 0) { - $summarySb.Append("$($appFailed) :x:") | Out-Null + if (Test-Path -Path $testResultsFile -PathType Leaf) { + $testResults = [xml](Get-Content -path $testResultsFile -Encoding UTF8) + $totalTests = 0 + $totalTime = 0.0 + $totalFailed = 0 + $totalSkipped = 0 + if ($testResults.testsuites) { + $appNames = @($testResults.testsuites.testsuite | ForEach-Object { $_.Properties.property | Where-Object { $_.Name -eq "appName" } | ForEach-Object { $_.Value } } | Select-Object -Unique) + if (-not $appNames) { + $appNames = @($testResults.testsuites.testsuite | ForEach-Object { $_.Properties.property | Where-Object { $_.Name -eq "extensionId" } | ForEach-Object { $_.Value } } | Select-Object -Unique) } - $summarySb.Append("|") | Out-Null - if ($appSkipped -gt 0) { - $summarySb.Append("$($appSkipped) :white_circle:") | Out-Null + foreach($testsuite in $testResults.testsuites.testsuite) { + $totalTests += $testsuite.Tests + $totalTime += [decimal]::Parse($testsuite.time, [System.Globalization.CultureInfo]::InvariantCulture) + $totalFailed += $testsuite.failures + $totalSkipped += $testsuite.skipped } - $summarySb.Append("|$($appTime)s|\n") | Out-Null - if ($appFailed -gt 0) { - $failuresSb.Append("
$appName, $appTests tests, $appPassed passed, $appFailed failed, $appSkipped skipped, $appTime seconds\n") | Out-Null + Write-Host "$($appNames.Count) TestApps, $totalTests tests, $totalFailed failed, $totalSkipped skipped, $totalTime seconds" + $summarySb.Append('|Test app|Tests|Passed|Failed|Skipped|Time|\n|:---|---:|---:|---:|---:|---:|\n') | Out-Null + foreach($appName in $appNames) { + $appTests = 0 + $appTime = 0.0 + $appFailed = 0 + $appSkipped = 0 + $suites = $testResults.testsuites.testsuite | where-Object { $_.Properties.property | Where-Object { ($_.Name -eq 'appName' -or $_.Name -eq 'extensionId') -and $_.Value -eq $appName } } foreach($suite in $suites) { - Write-Host " - $($suite.name), $($suite.tests) tests, $($suite.failures) failed, $($suite.skipped) skipped, $($suite.time) seconds" - if ($suite.failures -gt 0 -and $failuresSb.Length -lt 32000 -and $includeFailures -gt $failuresIncluded) { - $failuresSb.Append("
$($suite.name), $($suite.tests) tests, $($suite.failures) failed, $($suite.skipped) skipped, $($suite.time) seconds") | Out-Null - foreach($testcase in $suite.testcase) { - if ($testcase.ChildNodes.Count -gt 0) { - Write-Host " - $($testcase.name), Failure, $($testcase.time) seconds" - $failuresSb.Append("
$($testcase.name), Failure") | Out-Null - foreach($failure in $testcase.ChildNodes) { - Write-Host " - Error: $($failure.message)" - Write-Host " Stacktrace:" - Write-Host " $($failure."#text".Trim().Replace("`n","`n "))" - $failuresSb.Append("      Error: $($failure.message)
") | Out-Null - $failuresSb.Append("      Stack trace
") | Out-Null - $failuresSb.Append("      $($failure."#text".Trim().Replace("`n","
      "))

") | Out-Null + $appTests += [int]$suite.tests + $appFailed += [int]$suite.failures + $appSkipped += [int]$suite.skipped + $appTime += [decimal]::Parse($suite.time, [System.Globalization.CultureInfo]::InvariantCulture) + } + $appPassed = $appTests-$appFailed-$appSkipped + Write-Host "- $appName, $appTests tests, $appPassed passed, $appFailed failed, $appSkipped skipped, $appTime seconds" + $summarySb.Append("|$appName|$appTests|") | Out-Null + if ($appPassed -gt 0) { + $summarySb.Append("$($appPassed)$statusOK") | Out-Null + } + $summarySb.Append("|") | Out-Null + if ($appFailed -gt 0) { + $summarySb.Append("$($appFailed)$statusError") | Out-Null + } + $summarySb.Append("|") | Out-Null + if ($appSkipped -gt 0) { + $summarySb.Append("$($appSkipped)$statusSkipped") | Out-Null + } + $summarySb.Append("|$($appTime)s|\n") | Out-Null + if ($appFailed -gt 0) { + $failuresSb.Append("
$appName, $appTests tests, $appPassed passed, $appFailed failed, $appSkipped skipped, $appTime seconds\n") | Out-Null + foreach($suite in $suites) { + Write-Host " - $($suite.name), $($suite.tests) tests, $($suite.failures) failed, $($suite.skipped) skipped, $($suite.time) seconds" + if ($suite.failures -gt 0 -and $failuresSb.Length -lt 32000) { + $failuresSb.Append("
$($suite.name), $($suite.tests) tests, $($suite.failures) failed, $($suite.skipped) skipped, $($suite.time) seconds") | Out-Null + foreach($testcase in $suite.testcase) { + if ($testcase.ChildNodes.Count -gt 0) { + Write-Host " - $($testcase.name), Failure, $($testcase.time) seconds" + $failuresSb.Append("
$($testcase.name), Failure") | Out-Null + foreach($failure in $testcase.ChildNodes) { + Write-Host " - Error: $($failure.message)" + Write-Host " Stacktrace:" + Write-Host " $($failure."#text".Trim().Replace("`n","`n "))" + $failuresSb.Append("      Error: $($failure.message)
") | Out-Null + $failuresSb.Append("      Stack trace
") | Out-Null + $failuresSb.Append("      $($failure."#text".Trim().Replace("`n","
      "))

") | Out-Null + } + $failuresSb.Append("
") | Out-Null } - $failuresSb.Append("
") | Out-Null } + $failuresSb.Append("
") | Out-Null } - $failuresSb.Append("
") | Out-Null - $failuresIncluded++ } + $failuresSb.Append("
") | Out-Null } - $failuresSb.Append("
") | Out-Null } } - } - if ($totalFailed -gt 0) { - if ($totalFailed -gt $failuresIncluded) { - $failuresSb.Insert(0,"
$totalFailed failing tests (showing the first $failuresIncluded here, download test results to see all)") | Out-Null + if ($totalFailed -gt 0) { + $failuresSummaryMD = "$totalFailed failing tests, download test results to see details" + $failuresSb.Insert(0,"
$failuresSummaryMD") | Out-Null + $failuresSb.Append("
") | Out-Null } else { - $failuresSb.Insert(0,"
$totalFailed failing tests") | Out-Null + $failuresSummaryMD = "No test failures" + $failuresSb.Append($failuresSummaryMD) | Out-Null } - $failuresSb.Append("
") | Out-Null - if (($summarySb.Length + $failuresSb.Length) -lt 65000) { - $summarySb.Append("\n\n$($failuresSb.ToString())") | Out-Null + } + else { + $summarySb.Append("No test results found") | Out-Null + $failuresSummaryMD = '' + } + $summarySb.ToString() + $failuresSb.ToString() + $failuresSummaryMD +} + +function ReadBcptFile { + Param( + [string] $bcptTestResultsFile + ) + + if ((-not $bcptTestResultsFile) -or (-not (Test-Path -Path $bcptTestResultsFile -PathType Leaf))) { + return $null + } + + # Read BCPT file + $bcptResult = Get-Content -Path $bcptTestResultsFile -Encoding UTF8 | ConvertFrom-Json + $suites = [ordered]@{} + # Sort by bcptCode, codeunitID, operation + foreach($measure in $bcptResult) { + $bcptCode = $measure.bcptCode + $codeunitID = $measure.codeunitID + $codeunitName = $measure.codeunitName + $operation = $measure.operation + + # Create Suite if it doesn't exist + if(-not $suites.Contains($bcptCode)) { + $suites."$bcptCode" = [ordered]@{} } - else { - $summarySb.Append("\n\n$totalFailed failing tests. Download test results to see all") | Out-Null + # Create Codeunit under Suite if it doesn't exist + if (-not $suites."$bcptCode".Contains("$codeunitID")) { + $suites."$bcptCode"."$codeunitID" = @{ + "codeunitName" = $codeunitName + "operations" = [ordered]@{} + } } + # Create Operation under Codeunit if it doesn't exist + if (-not $suites."$bcptCode"."$codeunitID"."operations".Contains($operation)) { + $suites."$bcptCode"."$codeunitID"."operations"."$operation" = @{ + "measurements" = @() + } + } + # Add measurement to measurements under operation + $suites."$bcptCode"."$codeunitID"."operations"."$operation".measurements += @(@{ + "durationMin" = $measure.durationMin + "numberOfSQLStmts" = $measure.numberOfSQLStmts + }) + } + $suites +} + +function GetBcptSummaryMD { + Param( + [string] $bcptTestResultsFile, + [string] $baseLinePath = '', + [string] $thresholdsPath = '', + [int] $skipMeasurements = 0, + [hashtable] $bcptThresholds = $null + ) + + $bcpt = ReadBcptFile -bcptTestResultsFile $bcptTestResultsFile + if (-not $bcpt) { + return '' + } + $baseLine = ReadBcptFile -bcptTestResultsFile $baseLinePath + if ($baseLine) { + if ($null -eq $bcptThresholds) { + throw "Thresholds must be provided when comparing to a baseline" + } + # Override thresholds if thresholds file exists + if ($thresholdsPath -and (Test-Path -path $thresholdsPath)) { + Write-Host "Reading thresholds from $thresholdsPath" + $thresholds = Get-Content -Path $thresholdsPath -Encoding UTF8 | ConvertFrom-Json + foreach($threshold in 'durationWarning', 'durationError', 'numberOfSqlStmtsWarning', 'numberOfSqlStmtsError') { + if ($thresholds.PSObject.Properties.Name -eq $threshold) { + $bcptThresholds."$threshold" = $thresholds."$threshold" + } + } + } + Write-Host "Using thresholds:" + Write-Host "- DurationWarning: $($bcptThresholds.durationWarning)" + Write-Host "- DurationError: $($bcptThresholds.durationError)" + Write-Host "- NumberOfSqlStmtsWarning: $($bcptThresholds.numberOfSqlStmtsWarning)" + Write-Host "- NumberOfSqlStmtsError: $($bcptThresholds.numberOfSqlStmtsError)" + } + + $summarySb = [System.Text.StringBuilder]::new() + if ($baseLine) { + $summarySb.Append("|BCPT Suite|Codeunit ID|Codeunit Name|Operation|Status|Duration (ms)|Duration base (ms)|Duration diff (ms)|Duration diff|SQL Stmts|SQL Stmts base|SQL Stmts diff|SQL Stmts diff|\n") | Out-Null + $summarySb.Append("|:---------|:----------|:------------|:--------|:----:|------------:|-----------------:|-----------------:|------------:|--------:|-------------:|-------------:|-------------:|\n") | Out-Null } else { - $summarySb.Append("\n\nNo test failures") | Out-Null + $summarySb.Append("|BCPT Suite|Codeunit ID|Codeunit Name|Operation|Duration (ms)|SQL Stmts|\n") | Out-Null + $summarySb.Append("|:---------|:----------|:------------|:--------|------------:|--------:|\n") | Out-Null + } + + $lastSuiteName = '' + $lastCodeunitID = '' + $lastCodeunitName = '' + $lastOperationName = '' + + # calculate statistics on measurements, skipping the $skipMeasurements longest measurements + foreach($suiteName in $bcpt.Keys) { + $suite = $bcpt."$suiteName" + foreach($codeUnitID in $suite.Keys) { + $codeunit = $suite."$codeunitID" + $codeUnitName = $codeunit.codeunitName + foreach($operationName in $codeunit."operations".Keys) { + $operation = $codeunit."operations"."$operationName" + # Get measurements to use for statistics + $measurements = @($operation."measurements" | Sort-Object -Descending { $_.durationMin } | Select-Object -Skip $skipMeasurements) + # Calculate statistics and store them in the operation + $durationMin = ($measurements | ForEach-Object { $_.durationMin } | Measure-Object -Minimum).Minimum + $numberOfSQLStmts = ($measurements | ForEach-Object { $_.numberOfSQLStmts } | Measure-Object -Minimum).Minimum + + $baseLineFound = $true + try { + $baseLineMeasurements = @($baseLine."$suiteName"."$codeUnitID"."operations"."$operationName"."measurements" | Sort-Object -Descending { $_.durationMin } | Select-Object -Skip $skipMeasurements) + if ($baseLineMeasurements.Count -eq 0) { + throw "No base line measurements" + } + $baseDurationMin = ($baseLineMeasurements | ForEach-Object { $_.durationMin } | Measure-Object -Minimum).Minimum + $diffDurationMin = $durationMin-$baseDurationMin + $baseNumberOfSQLStmts = ($baseLineMeasurements | ForEach-Object { $_.numberOfSQLStmts } | Measure-Object -Minimum).Minimum + $diffNumberOfSQLStmts = $numberOfSQLStmts-$baseNumberOfSQLStmts + } + catch { + $baseLineFound = $false + $baseDurationMin = $durationMin + $diffDurationMin = 0 + $baseNumberOfSQLStmts = $numberOfSQLStmts + $diffNumberOfSQLStmts = 0 + } + + $pctDurationMin = ($durationMin-$baseDurationMin)*100/$baseDurationMin + $durationMinStr = "$($durationMin.ToString("#"))|" + $baseDurationMinStr = "$($baseDurationMin.ToString("#"))|" + $diffDurationMinStr = "$($diffDurationMin.ToString("+#;-#;0"))|$($pctDurationMin.ToString('+#;-#;0'))%|" + + $pctNumberOfSQLStmts = ($numberOfSQLStmts-$baseNumberOfSQLStmts)*100/$baseNumberOfSQLStmts + $numberOfSQLStmtsStr = "$($numberOfSQLStmts.ToString("#"))|" + $baseNumberOfSQLStmtsStr = "$($baseNumberOfSQLStmts.ToString("#"))|" + $diffNumberOfSQLStmtsStr = "$($diffNumberOfSQLStmts.ToString("+#;-#;0"))|$($pctNumberOfSQLStmts.ToString('+#;-#;0'))%|" + + $thisOperationName = ''; if ($operationName -ne $lastOperationName) { $thisOperationName = $operationName } + $thisCodeunitName = ''; if ($codeunitName -ne $lastCodeunitName) { $thisCodeunitName = $codeunitName; $thisOperationName = $operationName } + $thisCodeunitID = ''; if ($codeunitID -ne $lastCodeunitID) { $thisCodeunitID = $codeunitID; $thisOperationName = $operationName } + $thisSuiteName = ''; if ($suiteName -ne $lastSuiteName) { $thisSuiteName = $suiteName; $thisOperationName = $operationName } + + if (!$baseLine) { + # No baseline provided + $statusStr = '' + $baseDurationMinStr = '' + $diffDurationMinStr = '' + $baseNumberOfSQLStmtsStr = '' + $diffNumberOfSQLStmtsStr = '' + } + else { + if (!$baseLineFound) { + # Baseline provided, but not found for this operation + $statusStr = $statusSkipped + $baseDurationMinStr = 'N/A|' + $diffDurationMinStr = '||' + $baseNumberOfSQLStmtsStr = 'N/A|' + $diffNumberOfSQLStmtsStr = '||' + } + else { + $statusStr = $statusOK + if ($pctDurationMin -ge $bcptThresholds.durationError) { + $statusStr = $statusError + if ($thisCodeunitName) { + # Only give errors and warnings on top level operation + OutputError -message "$operationName in $($suiteName):$codeUnitID degrades $($pctDurationMin.ToString('N0'))%, which exceeds the error threshold of $($bcptThresholds.durationError)% for duration" + } + } + if ($pctNumberOfSQLStmts -ge $bcptThresholds.numberOfSqlStmtsError) { + $statusStr = $statusError + if ($thisCodeunitName) { + # Only give errors and warnings on top level operation + OutputError -message "$operationName in $($suiteName):$codeUnitID degrades $($pctNumberOfSQLStmts.ToString('N0'))%, which exceeds the error threshold of $($bcptThresholds.numberOfSqlStmtsError)% for number of SQL statements" + } + } + if ($statusStr -eq $statusOK) { + if ($pctDurationMin -ge $bcptThresholds.durationWarning) { + $statusStr = $statusWarning + if ($thisCodeunitName) { + # Only give errors and warnings on top level operation + OutputWarning -message "$operationName in $($suiteName):$codeUnitID degrades $($pctDurationMin.ToString('N0'))%, which exceeds the warning threshold of $($bcptThresholds.durationWarning)% for duration" + } + } + if ($pctNumberOfSQLStmts -ge $bcptThresholds.numberOfSqlStmtsWarning) { + $statusStr = $statusWarning + if ($thisCodeunitName) { + # Only give errors and warnings on top level operation + OutputWarning -message "$operationName in $($suiteName):$codeUnitID degrades $($pctNumberOfSQLStmts.ToString('N0'))%, which exceeds the warning threshold of $($bcptThresholds.numberOfSqlStmtsWarning)% for number of SQL statements" + } + } + } + } + $statusStr += '|' + } + + $summarySb.Append("|$thisSuiteName|$thisCodeunitID|$thisCodeunitName|$thisOperationName|$statusStr$durationMinStr$baseDurationMinStr$diffDurationMinStr$numberOfSQLStmtsStr$baseNumberOfSQLStmtsStr$diffNumberOfSQLStmtsStr\n") | Out-Null + + $lastSuiteName = $suiteName + $lastCodeunitID = $codeUnitID + $lastCodeunitName = $codeUnitName + $lastOperationName = $operationName + } + } } - if ($summarySb.Length -lt 65500) { - $summarySb.ToString() + + if ($baseLine) { + $summarySb.Append("\nUsed baseline provided in $([System.IO.Path]::GetFileName($baseLinePath)).") | Out-Null } else { - "$totalFailed failing tests. Download test results to see all" + $summarySb.Append("\nNo baseline provided. Copy a set of BCPT results to $([System.IO.Path]::GetFileName($baseLinePath)) in the project folder in order to establish a baseline.") | Out-Null } + + $summarySb.ToString() } diff --git a/AnalyzeTests/action.yaml b/AnalyzeTests/action.yaml index 7e4f211..4b96603 100644 --- a/AnalyzeTests/action.yaml +++ b/AnalyzeTests/action.yaml @@ -5,34 +5,20 @@ inputs: description: Shell in which you want to run the action (powershell or pwsh) required: false default: powershell - parentTelemetryScopeJson: - description: Specifies the parent telemetry scope for the telemetry signal - required: false - default: '7b7d' project: description: Project to analyze - required: true -outputs: - TestResultMD: - description: MarkDown of test result - value: ${{ steps.AnalyzeTests.outputs.TestResultMD }} + required: false + default: '.' runs: using: composite steps: - name: run shell: ${{ inputs.shell }} - id: AnalyzeTests env: - _parentTelemetryScopeJson: ${{ inputs.parentTelemetryScopeJson }} _project: ${{ inputs.project }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { - ${{ github.action_path }}/AnalyzeTests.ps1 -parentTelemetryScopeJson $ENV:_parentTelemetryScopeJson -project $ENV:_project - } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "AnalyzeTests" -Action { + ${{ github.action_path }}/AnalyzeTests.ps1 -project $ENV:_project } branding: icon: terminal diff --git a/BuildPowerPlatform/README.md b/BuildPowerPlatform/README.md index 9024460..ba0ca10 100644 --- a/BuildPowerPlatform/README.md +++ b/BuildPowerPlatform/README.md @@ -1,12 +1,15 @@ # Build Power Platform + Build the Power Platform solution ## INPUT ### ENV variables + none ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | @@ -19,4 +22,5 @@ none | appRevision | | The app revision number | | ## OUTPUT + none diff --git a/BuildPowerPlatform/action.yaml b/BuildPowerPlatform/action.yaml index 1856b6e..67cb5df 100644 --- a/BuildPowerPlatform/action.yaml +++ b/BuildPowerPlatform/action.yaml @@ -37,23 +37,18 @@ runs: - name: Install Power Platform Tools uses: microsoft/powerplatform-actions/actions-install@v1 - - name: Update Power Platform Files + - name: Update Power Platform Files shell: ${{ inputs.shell }} env: _solutionFolder: ${{ inputs.solutionFolder }} _companyId: ${{ inputs.companyId }} - _environmentName: ${{ inputs.environmentName }} + _environmentName: ${{ inputs.environmentName }} _appBuild: ${{ inputs.appBuild }} _appRevision: ${{ inputs.appRevision }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "BuildPowerPlatform" -Action { ${{ github.action_path }}/BuildPowerPlatform.ps1 -solutionFolder $ENV:_solutionFolder -companyId $ENV:_companyId -environmentName $ENV:_environmentName -appBuild $ENV:_appBuild -appRevision $ENV:_appRevision } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 - } - name: Test Pack (Not real - just workaround for https://github.com/microsoft/powerplatform-vscode/issues/412) uses: microsoft/powerplatform-actions/pack-solution@v1 @@ -74,7 +69,7 @@ runs: solution-folder: ${{ inputs.solutionFolder }} solution-type: "Unmanaged" process-canvas-apps: true - + branding: icon: terminal color: blue diff --git a/BuildReferenceDocumentation/README.md b/BuildReferenceDocumentation/README.md index bb6fea6..3bd9083 100644 --- a/BuildReferenceDocumentation/README.md +++ b/BuildReferenceDocumentation/README.md @@ -1,14 +1,17 @@ # BuildReferenceDocumentation + Build documentation using [ALDoc](https://go.microsoft.com/fwlink/?linkid=2247728) and [DocFx](https://dotnet.github.io/docfx) ## INPUT ### ENV variables + | Name | Description | | :-- | :-- | | Settings | env.Settings must be set by a prior call to the ReadSettings Action | ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | @@ -16,4 +19,5 @@ Build documentation using [ALDoc](https://go.microsoft.com/fwlink/?linkid=224772 | artifacts | Yes | The artifacts to build documentation for or a folder in which the artifacts have been downloaded | | ## OUTPUT + none diff --git a/BuildReferenceDocumentation/action.yaml b/BuildReferenceDocumentation/action.yaml index 0b8434f..690cbd2 100644 --- a/BuildReferenceDocumentation/action.yaml +++ b/BuildReferenceDocumentation/action.yaml @@ -21,14 +21,9 @@ runs: _token: ${{ inputs.token }} _artifacts: ${{ inputs.artifacts }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "BuildReferenceDocumentation" -Action { ${{ github.action_path }}/BuildReferenceDocumentation.ps1 -token $ENV:_token -artifacts $ENV:_artifacts } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 - } branding: icon: terminal color: blue diff --git a/CalculateArtifactNames/README.md b/CalculateArtifactNames/README.md index 807f557..ad611a4 100644 --- a/CalculateArtifactNames/README.md +++ b/CalculateArtifactNames/README.md @@ -1,14 +1,17 @@ # Calculate Artifact Names + Calculate Artifact Names for AL-Go workflows ## INPUT ### ENV variables + | Name | Description | | :-- | :-- | | Settings | env.Settings must be set by a prior call to the ReadSettings Action | ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | @@ -19,9 +22,11 @@ Calculate Artifact Names for AL-Go workflows ## OUTPUT ### ENV variables + none ### OUTPUT variables + | Name | Description | | :-- | :-- | | ThisBuildAppsArtifactsName | Artifact name for apps being built in the current workflow run | diff --git a/CalculateArtifactNames/action.yaml b/CalculateArtifactNames/action.yaml index dd7e32c..500b750 100644 --- a/CalculateArtifactNames/action.yaml +++ b/CalculateArtifactNames/action.yaml @@ -63,14 +63,9 @@ runs: _buildMode: ${{ inputs.buildMode }} _suffix: ${{ inputs.suffix }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "CalculateArtifactNames" -Action { ${{ github.action_path }}/CalculateArtifactNames.ps1 -project $ENV:_project -buildMode $ENV:_buildMode -suffix $ENV:_suffix } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 - } branding: icon: terminal color: blue diff --git a/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 b/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 index d340b9f..70b8f42 100644 --- a/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 +++ b/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 @@ -28,7 +28,7 @@ function DownloadTemplateRepository { if ($downloadLatest) { # Get Branches from template repository - $response = InvokeWebRequest -Headers $headers -Uri "$apiUrl/branches" -retry + $response = InvokeWebRequest -Headers $headers -Uri "$apiUrl/branches?per_page=100" -retry $branchInfo = ($response.content | ConvertFrom-Json) | Where-Object { $_.Name -eq $branch } if (!$branchInfo) { throw "$templateUrl doesn't exist" @@ -115,6 +115,9 @@ function ModifyRunsOnAndShell { if ($repoSettings.shell -ne "powershell" -and $repoSettings.shell -ne "pwsh") { throw "The shell can only be set to powershell or pwsh" } + if ($repoSettings."runs-on" -eq "ubuntu-latest" -and $repoSettings.shell -eq "powershell") { + throw "The shell cannot be set to powershell when runs-on is ubuntu-latest. Use pwsh instead." + } Write-Host "Setting shell to $($repoSettings.shell)" $yaml.ReplaceAll('shell: powershell', "shell: $($repoSettings.shell)") } @@ -289,7 +292,18 @@ function GetWorkflowContentWithChangesFromSettings { ModifyPullRequestHandlerWorkflow -yaml $yaml -repoSettings $repoSettings } - if ($baseName -ne "UpdateGitHubGoSystemFiles" -and $baseName -ne 'Troubleshooting') { + $criticalWorkflows = @('UpdateGitHubGoSystemFiles', 'Troubleshooting') + $allowedRunners = @('windows-latest', 'ubuntu-latest') + $modifyRunsOnAndShell = $true + + # Critical workflows may only run on allowed runners (must always be able to run) + if($criticalWorkflows -contains $baseName) { + if($allowedRunners -notcontains $repoSettings."runs-on") { + $modifyRunsOnAndShell = $false + } + } + + if ($modifyRunsOnAndShell) { ModifyRunsOnAndShell -yaml $yaml -repoSettings $repoSettings } @@ -386,12 +400,20 @@ function GetSrcFolder { throw "Unknown repository type" } } - $path = Join-Path $templateFolder "*/Templates/$typePath/$srcPath" + $path = Join-Path $templateFolder "*/Templates/$typePath/.github/workflows" } else { - $path = Join-Path $templateFolder "*/$srcPath" + $path = Join-Path $templateFolder "*/.github/workflows" + } + # Due to this PowerShell bug: https://github.com/PowerShell/PowerShell/issues/6473#issuecomment-375930843 + # We need to resolve the path of a non-hidden folder (.github/workflows) + # and then get the parent folder of the parent folder of that path + $path = Resolve-Path -Path $path -ErrorAction SilentlyContinue + if (!$path) { + throw "No workflows found in the template repository" } - Resolve-Path -Path $path -ErrorAction SilentlyContinue + $path = Join-Path -Path (Split-Path -Path (Split-Path -Path $path -Parent) -Parent) -ChildPath $srcPath + return $path } function UpdateSettingsFile { diff --git a/CheckForUpdates/CheckForUpdates.ps1 b/CheckForUpdates/CheckForUpdates.ps1 index 271c1f5..98c3835 100644 --- a/CheckForUpdates/CheckForUpdates.ps1 +++ b/CheckForUpdates/CheckForUpdates.ps1 @@ -74,6 +74,7 @@ Write-Host "Template Folder: $templateFolder" $templateBranch = $templateUrl.Split('@')[1] $templateOwner = $templateUrl.Split('/')[3] +$templateInfo = "$templateOwner/$($templateUrl.Split('/')[4])" $isDirectALGo = IsDirectALGo -templateUrl $templateUrl if (-not $isDirectALGo) { @@ -97,7 +98,7 @@ if (-not $isDirectALGo) { # - All PowerShell scripts in .AL-Go folders (all projects) $checkfiles = @( @{ 'dstPath' = Join-Path '.github' 'workflows'; 'srcPath' = Join-Path '.github' 'workflows'; 'pattern' = '*'; 'type' = 'workflow' }, - @{ 'dstPath' = '.github'; 'srcPath' = '.AL-Go'; 'pattern' = '*.copy.md'; 'type' = 'releasenotes' } + @{ 'dstPath' = '.github'; 'srcPath' = '.github'; 'pattern' = '*.copy.md'; 'type' = 'releasenotes' } ) # Get the list of projects in the current repository @@ -128,7 +129,7 @@ if ($repoSettings.useProjectDependencies -and $projects.Count -gt 1) { # Loop through all folders in CheckFiles and check if there are any files that needs to be updated foreach($checkfile in $checkfiles) { - Write-Host "Checking $($checkfile.srcPath)\$($checkfile.pattern)" + Write-Host "Checking $($checkfile.srcPath)/$($checkfile.pattern)" $type = $checkfile.type $srcPath = $checkfile.srcPath $dstPath = $checkfile.dstPath @@ -205,7 +206,7 @@ else { # $update set, update the files try { # If a pull request already exists with the same REF, then exit - $commitMessage = "[$updateBranch] Update AL-Go System Files - $templateSha" + $commitMessage = "[$updateBranch] Update AL-Go System Files from $templateInfo - $templateSha" $env:GH_TOKEN = $token $existingPullRequest = (gh api --paginate "/repos/$env:GITHUB_REPOSITORY/pulls?base=$updateBranch" -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" | ConvertFrom-Json) | Where-Object { $_.title -eq $commitMessage } | Select-Object -First 1 if ($existingPullRequest) { @@ -230,21 +231,17 @@ else { New-Item -Path $path -ItemType Directory | Out-Null } if (([System.IO.Path]::GetFileName($_.DstFile) -eq "RELEASENOTES.copy.md") -and (Test-Path $_.DstFile)) { + # Read the release notes of the version currently installed $oldReleaseNotes = Get-ContentLF -Path $_.DstFile - while ($oldReleaseNotes) { - $releaseNotes = $_.Content - if ($releaseNotes.indexOf($oldReleaseNotes) -gt 0) { - $releaseNotes = $releaseNotes.SubString(0, $releaseNotes.indexOf($oldReleaseNotes)) - $oldReleaseNotes = "" - } - else { - $idx = $oldReleaseNotes.IndexOf("`n## ") - if ($idx -gt 0) { - $oldReleaseNotes = $oldReleaseNotes.Substring($idx) - } - else { - $oldReleaseNotes = "" - } + # Get the release notes of the new version (for the PR body) + $releaseNotes = $_.Content + # The first line with ## vX.Y, this is the latest shipped version already installed + $version = $oldReleaseNotes.Split("`n") | Where-Object { $_ -like '## v*.*' } | Select-Object -First 1 + if ($version) { + # Only use the release notes up to the version already installed + $index = $releaseNotes.IndexOf("`n$version`n") + if ($index -ge 0) { + $releaseNotes = $releaseNotes.Substring(0,$index) } } } @@ -262,7 +259,7 @@ else { Write-Host "ReleaseNotes:" Write-Host $releaseNotes - if (!(CommitFromNewFolder -serverUrl $serverUrl -commitMessage $commitMessage -branch $branch)) { + if (!(CommitFromNewFolder -serverUrl $serverUrl -commitMessage $commitMessage -branch $branch -body $releaseNotes)) { OutputWarning "No updates available for AL-Go for GitHub." } } diff --git a/CheckForUpdates/README.md b/CheckForUpdates/README.md index d5ba568..554ca9d 100644 --- a/CheckForUpdates/README.md +++ b/CheckForUpdates/README.md @@ -1,12 +1,15 @@ # Check for updates + Check for updates to AL-Go system files and perform the update if requested ## INPUT ### ENV variables + none ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | @@ -19,4 +22,5 @@ none | directCommit | | True if the action should create a direct commit against the branch or false to create a Pull Request | false | ## OUTPUT + none diff --git a/CheckForUpdates/action.yaml b/CheckForUpdates/action.yaml index d6747d4..4b12bc6 100644 --- a/CheckForUpdates/action.yaml +++ b/CheckForUpdates/action.yaml @@ -50,14 +50,9 @@ runs: _updateBranch: ${{ inputs.updateBranch }} _directCommit: ${{ inputs.directCommit }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "CheckForUpdates" -Action { ${{ github.action_path }}/CheckForUpdates.ps1 -actor $ENV:_actor -token $ENV:_token -templateUrl $ENV:_templateUrl -downloadLatest ($ENV:_downloadLatest -eq 'true') -update $ENV:_update -updateBranch $ENV:_updateBranch -directCommit ($ENV:_directCommit -eq 'true') } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 - } branding: icon: terminal color: blue diff --git a/CreateApp/CreateApp.ps1 b/CreateApp/CreateApp.ps1 index 7171588..af00696 100644 --- a/CreateApp/CreateApp.ps1 +++ b/CreateApp/CreateApp.ps1 @@ -3,8 +3,6 @@ Param( [string] $actor, [Parameter(HelpMessage = "The GitHub token running the action", Mandatory = $false)] [string] $token, - [Parameter(HelpMessage = "Specifies the parent telemetry scope for the telemetry signal", Mandatory = $false)] - [string] $parentTelemetryScopeJson = '7b7d', [Parameter(HelpMessage = "Project name if the repository is setup for multiple projects", Mandatory = $false)] [string] $project = '.', [ValidateSet("PTE", "AppSource App" , "Test App", "Performance Test App")] @@ -26,7 +24,6 @@ Param( [bool] $directCommit ) -$telemetryScope = $null $tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) try { @@ -35,9 +32,6 @@ try { $baseFolder = (Get-Location).Path DownloadAndImportBcContainerHelper -baseFolder $baseFolder - import-module (Join-Path -path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) - $telemetryScope = CreateScope -eventId 'DO0072' -parentTelemetryScopeJson $parentTelemetryScopeJson - import-module (Join-Path -path $PSScriptRoot -ChildPath "AppHelper.psm1" -Resolve) Write-Host "Template type : $type" @@ -130,14 +124,8 @@ try { Set-Location $baseFolder CommitFromNewFolder -serverUrl $serverUrl -commitMessage "New $type ($Name)" -branch $branch | Out-Null - - TrackTrace -telemetryScope $telemetryScope - } catch { - if (Get-Module BcContainerHelper) { - TrackException -telemetryScope $telemetryScope -errorRecord $_ - } throw } finally { diff --git a/CreateApp/README.md b/CreateApp/README.md index 40dd514..d0eca2c 100644 --- a/CreateApp/README.md +++ b/CreateApp/README.md @@ -1,18 +1,20 @@ # Create a new app + Create a new app and add it to an AL-Go repository ## INPUT ### ENV variables + none ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | | actor | | The GitHub actor running the action | github.actor | | token | | The GitHub token running the action | github.token | -| parentTelemetryScopeJson | | Specifies the parent telemetry scope for the telemetry signal | {} | | project | | Project name if the repository is setup for multiple projects | . | | type | Yes | Type of app to add (PTE, AppSource App, Test App) | | | name | Yes | App Name | | @@ -24,4 +26,5 @@ none | directCommit | | true if the action should create a direct commit against the branch or false to create a Pull Request | false | ## OUTPUT + none diff --git a/CreateApp/action.yaml b/CreateApp/action.yaml index 678ea1a..df8c5e8 100644 --- a/CreateApp/action.yaml +++ b/CreateApp/action.yaml @@ -16,10 +16,6 @@ inputs: description: The GitHub token running the action required: false default: ${{ github.token }} - parentTelemetryScopeJson: - description: Specifies the parent telemetry scope for the telemetry signal - required: false - default: '7b7d' project: description: Project name if the repository is setup for multiple projects required: false @@ -60,7 +56,6 @@ runs: env: _actor: ${{ inputs.actor }} _token: ${{ inputs.token }} - _parentTelemetryScopeJson: ${{ inputs.parentTelemetryScopeJson }} _project: ${{ inputs.project }} _type: ${{ inputs.type }} _name: ${{ inputs.name }} @@ -71,13 +66,8 @@ runs: _updateBranch: ${{ inputs.updateBranch }} _directCommit: ${{ inputs.directCommit }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { - ${{ github.action_path }}/CreateApp.ps1 -actor $ENV:_actor -token $ENV:_token -parentTelemetryScopeJson $ENV:_parentTelemetryScopeJson -project $ENV:_project -type $ENV:_type -name $ENV:_name -publisher $ENV:_publisher -idrange $ENV:_idrange -sampleCode ($ENV:_sampleCode -eq 'true') -sampleSuite ($ENV:_sampleSuite -eq 'true') -updateBranch $ENV:_updateBranch -directCommit ($ENV:_directCommit -eq 'true') - } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "CreateApp" -Action { + ${{ github.action_path }}/CreateApp.ps1 -actor $ENV:_actor -token $ENV:_token -project $ENV:_project -type $ENV:_type -name $ENV:_name -publisher $ENV:_publisher -idrange $ENV:_idrange -sampleCode ($ENV:_sampleCode -eq 'true') -sampleSuite ($ENV:_sampleSuite -eq 'true') -updateBranch $ENV:_updateBranch -directCommit ($ENV:_directCommit -eq 'true') } branding: icon: terminal diff --git a/CreateDevelopmentEnvironment/CreateDevelopmentEnvironment.ps1 b/CreateDevelopmentEnvironment/CreateDevelopmentEnvironment.ps1 index 005cad8..349d4e7 100644 --- a/CreateDevelopmentEnvironment/CreateDevelopmentEnvironment.ps1 +++ b/CreateDevelopmentEnvironment/CreateDevelopmentEnvironment.ps1 @@ -4,8 +4,6 @@ Param( [string] $actor, [Parameter(HelpMessage = "The GitHub token running the action", Mandatory = $false)] [string] $token, - [Parameter(HelpMessage = "Specifies the parent telemetry scope for the telemetry signal", Mandatory = $false)] - [string] $parentTelemetryScopeJson = '7b7d', [Parameter(HelpMessage = "Name of the online environment", Mandatory = $true)] [string] $environmentName, [Parameter(HelpMessage = "Project name if the repository is setup for multiple projects", Mandatory = $false)] @@ -20,34 +18,19 @@ Param( [bool] $directCommit ) -$telemetryScope = $null +. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) +$serverUrl, $branch = CloneIntoNewFolder -actor $actor -token $token -updateBranch $updateBranch -DirectCommit $directCommit -newBranchPrefix 'create-development-environment' +$baseFolder = (Get-Location).Path +DownloadAndImportBcContainerHelper -baseFolder $baseFolder -try { - . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) - $serverUrl, $branch = CloneIntoNewFolder -actor $actor -token $token -updateBranch $updateBranch -DirectCommit $directCommit -newBranchPrefix 'create-development-environment' - $baseFolder = (Get-Location).Path - DownloadAndImportBcContainerHelper -baseFolder $baseFolder +$adminCenterApiCredentials = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($adminCenterApiCredentials)) +CreateDevEnv ` + -kind cloud ` + -caller GitHubActions ` + -environmentName $environmentName ` + -reUseExistingEnvironment:$reUseExistingEnvironment ` + -baseFolder $baseFolder ` + -project $project ` + -adminCenterApiCredentials ($adminCenterApiCredentials | ConvertFrom-Json | ConvertTo-HashTable) - import-module (Join-Path -path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) - $telemetryScope = CreateScope -eventId 'DO0073' -parentTelemetryScopeJson $parentTelemetryScopeJson - - $adminCenterApiCredentials = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($adminCenterApiCredentials)) - CreateDevEnv ` - -kind cloud ` - -caller GitHubActions ` - -environmentName $environmentName ` - -reUseExistingEnvironment:$reUseExistingEnvironment ` - -baseFolder $baseFolder ` - -project $project ` - -adminCenterApiCredentials ($adminCenterApiCredentials | ConvertFrom-Json | ConvertTo-HashTable) - - CommitFromNewFolder -serverUrl $serverUrl -commitMessage "Create a development environment $environmentName" -branch $branch | Out-Null - - TrackTrace -telemetryScope $telemetryScope -} -catch { - if (Get-Module BcContainerHelper) { - TrackException -telemetryScope $telemetryScope -errorRecord $_ - } - throw -} +CommitFromNewFolder -serverUrl $serverUrl -commitMessage "Create a development environment $environmentName" -branch $branch | Out-Null diff --git a/CreateDevelopmentEnvironment/README.md b/CreateDevelopmentEnvironment/README.md index b4b675f..9edab86 100644 --- a/CreateDevelopmentEnvironment/README.md +++ b/CreateDevelopmentEnvironment/README.md @@ -1,18 +1,20 @@ # Create Development Environment + Create an online development environment ## INPUT ### ENV variables + none ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | | actor | | The GitHub actor running the action | github.actor | | token | | The GitHub token running the action | github.token | -| parentTelemetryScopeJson | | Specifies the parent telemetry scope for the telemetry signal | {} | | environmentName | Yes | Name of the online environment to create | | project | | Project name if the repository is setup for multiple projects | . | | adminCenterApiCredentials | | ClientId/ClientSecret or Refresh token for Admin Center API authentication | | @@ -21,4 +23,5 @@ none | directCommit | | true if the action should create a direct commit against the branch or false to create a Pull Request | false | ## OUTPUT + none diff --git a/CreateDevelopmentEnvironment/action.yaml b/CreateDevelopmentEnvironment/action.yaml index 86dd7b9..b99e5d6 100644 --- a/CreateDevelopmentEnvironment/action.yaml +++ b/CreateDevelopmentEnvironment/action.yaml @@ -16,10 +16,6 @@ inputs: description: The GitHub token running the action required: false default: ${{ github.token }} - parentTelemetryScopeJson: - description: Specifies the parent telemetry scope for the telemetry signal - required: false - default: '7b7d' environmentName: description: Name of the online environment required: true @@ -51,7 +47,6 @@ runs: env: _actor: ${{ inputs.actor }} _token: ${{ inputs.token }} - _parentTelemetryScopeJson: ${{ inputs.parentTelemetryScopeJson }} _environmentName: ${{ inputs.environmentName }} _project: ${{ inputs.project }} _adminCenterApiCredentials: ${{ inputs.adminCenterApiCredentials }} @@ -59,13 +54,8 @@ runs: _updateBranch: ${{ inputs.updateBranch }} _directCommit: ${{ inputs.directCommit }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { - ${{ github.action_path }}/CreateDevelopmentEnvironment.ps1 -actor $ENV:_actor -token $ENV:_token -parentTelemetryScopeJson $ENV:_parentTelemetryScopeJson -environmentName $ENV:_environmentName -project $ENV:_project -adminCenterApiCredentials $ENV:_adminCenterApiCredentials -reUseExistingEnvironment ($ENV:_reUseExistingEnvironment -eq 'true') -updateBranch $ENV:_updateBranch -directCommit ($ENV:_directCommit -eq 'true') - } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "CreateDevelopmentEnvironment" -Action { + ${{ github.action_path }}/CreateDevelopmentEnvironment.ps1 -actor $ENV:_actor -token $ENV:_token -environmentName $ENV:_environmentName -project $ENV:_project -adminCenterApiCredentials $ENV:_adminCenterApiCredentials -reUseExistingEnvironment ($ENV:_reUseExistingEnvironment -eq 'true') -updateBranch $ENV:_updateBranch -directCommit ($ENV:_directCommit -eq 'true') } branding: icon: terminal diff --git a/CreateReleaseNotes/CreateReleaseNotes.ps1 b/CreateReleaseNotes/CreateReleaseNotes.ps1 index 883b43c..0754831 100644 --- a/CreateReleaseNotes/CreateReleaseNotes.ps1 +++ b/CreateReleaseNotes/CreateReleaseNotes.ps1 @@ -1,73 +1,56 @@ Param( [Parameter(HelpMessage = "The GitHub token running the action", Mandatory = $false)] [string] $token, - [Parameter(HelpMessage = "Specifies the parent telemetry scope for the telemetry signal", Mandatory = $false)] - [string] $parentTelemetryScopeJson = '7b7d', [Parameter(HelpMessage = "Tag name", Mandatory = $true)] [string] $tag_name, [Parameter(HelpMessage = "Last commit to include in release notes", Mandatory = $false)] [string] $target_commitish ) -$telemetryScope = $null +. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1") +DownloadAndImportBcContainerHelper -try { - . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1") - DownloadAndImportBcContainerHelper - - import-module (Join-Path -path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) - $telemetryScope = CreateScope -eventId 'DO0074' -parentTelemetryScopeJson $parentTelemetryScopeJson +Import-Module (Join-Path $PSScriptRoot '..\Github-Helper.psm1' -Resolve) - Import-Module (Join-Path $PSScriptRoot '..\Github-Helper.psm1' -Resolve) +# Check that tag is SemVer +$SemVerObj = SemVerStrToSemVerObj -semVerStr $tag_name - # Check that tag is SemVer - $SemVerObj = SemVerStrToSemVerObj -semVerStr $tag_name - - # Calculate release version - $releaseVersion = "$($SemVerObj.Prefix)$($SemVerObj.Major).$($SemVerObj.Minor)" - if ($SemVerObj.Patch -or $SemVerObj.addt0 -ne 'zzz') { - $releaseVersion += ".$($SemVerObj.Patch)" - if ($SemVerObj.addt0 -ne 'zzz') { - $releaseVersion += "-$($SemVerObj.addt0)" - 1..4 | ForEach-Object { - if ($SemVerObj."addt$($_)" -ne 'zzz') { - $releaseVersion += ".$($SemVerObj."addt$($_)")" - } +# Calculate release version +$releaseVersion = "$($SemVerObj.Prefix)$($SemVerObj.Major).$($SemVerObj.Minor)" +if ($SemVerObj.Patch -or $SemVerObj.addt0 -ne 'zzz') { + $releaseVersion += ".$($SemVerObj.Patch)" + if ($SemVerObj.addt0 -ne 'zzz') { + $releaseVersion += "-$($SemVerObj.addt0)" + 1..4 | ForEach-Object { + if ($SemVerObj."addt$($_)" -ne 'zzz') { + $releaseVersion += ".$($SemVerObj."addt$($_)")" } } } - Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "releaseVersion=$releaseVersion" - Write-Host "releaseVersion=$releaseVersion" - - $latestRelease = GetLatestRelease -token $token -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -ref $ENV:GITHUB_REF_NAME - if ($latestRelease -and $latestRelease.PSobject.Properties.name -eq "target_commitish") { - if ($latestRelease.target_commitish -eq $target_commitish) { - throw "The latest release is based on the same commit as this release is targetting." - } - } +} +Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "releaseVersion=$releaseVersion" +Write-Host "releaseVersion=$releaseVersion" - $latestReleaseTag = "" - if ($latestRelease -and $latestRelease.PSobject.Properties.name -eq "tag_name") { - $latestReleaseTag = $latestRelease.tag_name +$latestRelease = GetLatestRelease -token $token -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -ref $ENV:GITHUB_REF_NAME +if ($latestRelease -and $latestRelease.PSobject.Properties.name -eq "target_commitish") { + if ($latestRelease.target_commitish -eq $target_commitish) { + throw "The latest release is based on the same commit as this release is targetting." } +} - try { - $releaseNotes = GetReleaseNotes -token $token -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -tag_name $tag_name -previous_tag_name $latestReleaseTag -target_commitish $target_commitish | ConvertFrom-Json - $releaseNotes = $releaseNotes.body -replace '%','%25' -replace '\n','%0A' -replace '\r','%0D' # supports a multiline text - } - catch { - OutputWarning -message "Couldn't create release notes.$([environment]::Newline)Error: $($_.Exception.Message)$([environment]::Newline)Stacktrace: $($_.scriptStackTrace)" - OutputWarning -message "You can modify the release note from the release page later." - $releaseNotes = "" - } - Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "releaseNotes=$releaseNotes" - Write-Host "releaseNotes=$releaseNotes" +$latestReleaseTag = "" +if ($latestRelease -and $latestRelease.PSobject.Properties.name -eq "tag_name") { + $latestReleaseTag = $latestRelease.tag_name +} - TrackTrace -telemetryScope $telemetryScope +try { + $releaseNotes = GetReleaseNotes -token $token -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -tag_name $tag_name -previous_tag_name $latestReleaseTag -target_commitish $target_commitish | ConvertFrom-Json + $releaseNotes = $releaseNotes.body -replace '%','%25' -replace '\n','%0A' -replace '\r','%0D' # supports a multiline text } catch { - if (Get-Module BcContainerHelper) { - TrackException -telemetryScope $telemetryScope -errorRecord $_ - } - throw + OutputWarning -message "Couldn't create release notes.$([environment]::Newline)Error: $($_.Exception.Message)$([environment]::Newline)Stacktrace: $($_.scriptStackTrace)" + OutputWarning -message "You can modify the release note from the release page later." + $releaseNotes = "" } +Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "releaseNotes=$releaseNotes" +Write-Host "releaseNotes=$releaseNotes" diff --git a/CreateReleaseNotes/README.md b/CreateReleaseNotes/README.md index 72c82f3..e2d7cf8 100644 --- a/CreateReleaseNotes/README.md +++ b/CreateReleaseNotes/README.md @@ -1,26 +1,30 @@ # Creates release notes + Creates release notes for a release, based on a given tag and the tag from the latest release ## INPUT ### ENV variables + none ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | | token | | The GitHub token running the action | github.token | -| parentTelemetryScopeJson | | Specifies the parent telemetry scope for the telemetry signal | {} | | tag_name | Yes | This release tag name | | | target_commitish | | Last commit to include in release notes | Latest | ## OUTPUT ### ENV variables + none ### OUTPUT variables + | Name | Description | | :-- | :-- | | ReleaseVersion | The release version | diff --git a/CreateReleaseNotes/action.yaml b/CreateReleaseNotes/action.yaml index 826c583..427d353 100644 --- a/CreateReleaseNotes/action.yaml +++ b/CreateReleaseNotes/action.yaml @@ -12,10 +12,6 @@ inputs: description: The GitHub token running the action required: false default: ${{ github.token }} - parentTelemetryScopeJson: - description: Specifies the parent telemetry scope for the telemetry signal - required: false - default: '7b7d' tag_name: description: Tag name required: true @@ -38,17 +34,11 @@ runs: id: createreleasenotes env: _token: ${{ inputs.token }} - _parentTelemetryScopeJson: ${{ inputs.parentTelemetryScopeJson }} _tag_name: ${{ inputs.tag_name }} _target_commitish: ${{ inputs.target_commitish }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { - ${{ github.action_path }}/CreateReleaseNotes.ps1 -token $ENV:_token -parentTelemetryScopeJson $ENV:_parentTelemetryScopeJson -tag_name $ENV:_tag_name -target_commitish $ENV:_target_commitish - } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "CreateReleaseNotes" -Action { + ${{ github.action_path }}/CreateReleaseNotes.ps1 -token $ENV:_token -tag_name $ENV:_tag_name -target_commitish $ENV:_target_commitish } branding: icon: terminal diff --git a/Deliver/Deliver.ps1 b/Deliver/Deliver.ps1 index 3dd4948..1680ed0 100644 --- a/Deliver/Deliver.ps1 +++ b/Deliver/Deliver.ps1 @@ -3,8 +3,6 @@ Param( [string] $actor, [Parameter(HelpMessage = "The GitHub token running the action", Mandatory = $false)] [string] $token, - [Parameter(HelpMessage = "Specifies the parent telemetry scope for the telemetry signal", Mandatory = $false)] - [string] $parentTelemetryScopeJson = '7b7d', [Parameter(HelpMessage = "Projects to deliver (default is all)", Mandatory = $false)] [string] $projects = "*", [Parameter(HelpMessage = "Delivery target (AppSource or Storage)", Mandatory = $true)] @@ -12,7 +10,7 @@ Param( [Parameter(HelpMessage = "The artifacts to deliver or a folder in which the artifacts have been downloaded", Mandatory = $true)] [string] $artifacts, [Parameter(HelpMessage = "Type of delivery (CD or Release)", Mandatory = $false)] - [ValidateSet('CD','Release')] + [ValidateSet('CD', 'Release')] [string] $type = "CD", [Parameter(HelpMessage = "Types of artifacts to deliver (Apps,Dependencies,TestApps)", Mandatory = $false)] [string] $atypes = "Apps,Dependencies,TestApps", @@ -20,485 +18,476 @@ Param( [bool] $goLive ) -$telemetryScope = $null +function ConnectAzStorageAccount { + Param( + [PSCustomObject] $storageAccountCredentials + ) -function EnsureAzStorageModule() { - if (get-command New-AzStorageContext -ErrorAction SilentlyContinue) { - Write-Host "Using Az.Storage PowerShell module" + $azStorageContext = $null + if ($storageAccountCredentials.PSObject.Properties.Name -eq 'sastoken') { + try { + Write-Host "Creating AzStorageContext based on StorageAccountName and sastoken" + $azStorageContext = New-AzStorageContext -StorageAccountName $storageAccountCredentials.StorageAccountName -SasToken $storageAccountCredentials.sastoken + } + catch { + throw "Unable to create AzStorageContext based on StorageAccountName and sastoken. Error was: $($_.Exception.Message)" + } } - else { - $azureStorageModule = Get-Module -name 'Azure.Storage' -ListAvailable | Select-Object -First 1 - if ($azureStorageModule) { - Write-Host "Azure.Storage Module is available in version $($azureStorageModule.Version)" - Write-Host "Using Azure.Storage version $($azureStorageModule.Version)" - Import-Module 'Azure.Storage' -DisableNameChecking -WarningAction SilentlyContinue | Out-Null - Set-Alias -Name New-AzStorageContext -Value New-AzureStorageContext -Scope Script - Set-Alias -Name Get-AzStorageContainer -Value Get-AzureStorageContainer -Scope Script - Set-Alias -Name New-AzStorageContainer -Value New-AzureStorageContainer -Scope Script - Set-Alias -Name Set-AzStorageBlobContent -Value Set-AzureStorageBlobContent -Scope Script + elseif ($storageAccountCredentials.PSObject.Properties.Name -eq 'StorageAccountKey') { + try { + Write-Host "Creating AzStorageContext based on StorageAccountName and StorageAccountKey" + $azStorageContext = New-AzStorageContext -StorageAccountName $storageAccountCredentials.StorageAccountName -StorageAccountKey $storageAccountCredentials.StorageAccountKey } - else { - Write-Host "Installing and importing Az.Storage." - Install-Module 'Az.Storage' -Force - Import-Module 'Az.Storage' -DisableNameChecking -WarningAction SilentlyContinue | Out-Null + catch { + throw "Unable to create AzStorageContext based on StorageAccountName and StorageAccountKey. Error was: $($_.Exception.Message)" } } + elseif (($storageAccountCredentials.PSObject.Properties.Name -eq 'clientID') -and ($storageAccountCredentials.PSObject.Properties.Name -eq 'tenantID')) { + try { + InstallAzModuleIfNeeded -name 'Az.Accounts' + ConnectAz -azureCredentials $storageAccountCredentials + Write-Host "Creating AzStorageContext based on StorageAccountName and managed identity/app registration" + $azStorageContext = New-AzStorageContext -StorageAccountName $storageAccountCredentials.StorageAccountName -UseConnectedAccount + } + catch { + throw "Unable to create AzStorageContext based on StorageAccountName and managed identity. Error was: $($_.Exception.Message)" + } + } + else { + throw "Insufficient information in StorageContext secret. See https://aka.ms/algosettings#storagecontext for details" + } + return $azStorageContext } -try { - . (Join-Path -Path $PSScriptRoot -ChildPath "../AL-Go-Helper.ps1" -Resolve) - DownloadAndImportBcContainerHelper +. (Join-Path -Path $PSScriptRoot -ChildPath "../AL-Go-Helper.ps1" -Resolve) +DownloadAndImportBcContainerHelper - import-module (Join-Path -path $PSScriptRoot -ChildPath "../TelemetryHelper.psm1" -Resolve) - $telemetryScope = CreateScope -eventId 'DO0081' -parentTelemetryScopeJson $parentTelemetryScopeJson +$refname = "$ENV:GITHUB_REF_NAME".Replace('/', '_') - $refname = "$ENV:GITHUB_REF_NAME".Replace('/','_') +$artifacts = $artifacts.Replace('/', ([System.IO.Path]::DirectorySeparatorChar)).Replace('\', ([System.IO.Path]::DirectorySeparatorChar)) - $artifacts = $artifacts.Replace('/',([System.IO.Path]::DirectorySeparatorChar)).Replace('\',([System.IO.Path]::DirectorySeparatorChar)) - - $baseFolder = $ENV:GITHUB_WORKSPACE - $settings = ReadSettings -baseFolder $baseFolder - $projectList = @(GetProjectsFromRepository -baseFolder $baseFolder -projectsFromSettings $settings.projects -selectProjects $projects) - if ($deliveryTarget -eq "AppSource") { - $atypes = "Apps,Dependencies" +$baseFolder = $ENV:GITHUB_WORKSPACE +$settings = ReadSettings -baseFolder $baseFolder +$projectList = @(GetProjectsFromRepository -baseFolder $baseFolder -projectsFromSettings $settings.projects -selectProjects $projects) +if ($deliveryTarget -eq "AppSource") { + $atypes = "Apps,Dependencies" +} +Write-Host "Artifacts $artifacts" +Write-Host "Projects:" +$projectList | Out-Host + +$secrets = $env:Secrets | ConvertFrom-Json +foreach ($thisProject in $projectList) { + # $project should be the project part of the artifact name generated from the build + if ($thisProject -and ($thisProject -ne '.')) { + $project = $thisProject.Replace('\', '_').Replace('/', '_') } - Write-Host "Artifacts $artifacts" - Write-Host "Projects:" - $projectList | Out-Host + else { + $project = $settings.repoName + } + # projectName is the project name stripped for special characters + $projectName = $project -replace "[^a-z0-9]", "-" + Write-Host "ProjectName '$projectName'" - $secrets = $env:Secrets | ConvertFrom-Json - foreach($thisProject in $projectList) { - # $project should be the project part of the artifact name generated from the build - if ($thisProject -and ($thisProject -ne '.')) { - $project = $thisProject.Replace('\','_').Replace('/','_') + if ($artifacts -like "$($baseFolder)*") { + $artifactsFolder = $artifacts + } + else { + $artifactsFolder = Join-Path $baseFolder ".artifacts" + $artifactsFolderCreated = $false + if (!(Test-Path $artifactsFolder)) { + New-Item $artifactsFolder -ItemType Directory | Out-Null + $artifactsFolderCreated = $true } - else { - $project = $settings.repoName + if ($artifacts -eq '.artifacts') { + # Artifacts from this build have been downloaded } - # projectName is the project name stripped for special characters - $projectName = $project -replace "[^a-z0-9]", "-" - Write-Host "ProjectName '$projectName'" - - if ($artifacts -like "$($baseFolder)*") { - $artifactsFolder = $artifacts - } - else { - $artifactsFolder = Join-Path $baseFolder ".artifacts" - $artifactsFolderCreated = $false - if (!(Test-Path $artifactsFolder)) { - 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 + 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." + } } - if (!($release)) { - throw "Unable to locate $artifacts release" + 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 } - 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 { + $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)" } - } - else { if ($artifactFile -notlike '*.zip') { throw "Downloaded artifact is not a .zip file" } - Expand-Archive -Path $artifactFile -DestinationPath ($artifactFile.SubString(0,$artifactFile.Length-4)) + 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 { - 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" - } + Write-Host "WARNING: Could not find any $atype artifacts for projects $projects, version $artifacts" } } } } + } + + Write-Host "Project '$project'" + Write-Host "Artifacts:" + Get-ChildItem -Path $artifactsFolder | ForEach-Object { + Write-Host "- $($_.Name)" + } - Write-Host "Project '$project'" - Write-Host "Artifacts:" - Get-ChildItem -Path $artifactsFolder | ForEach-Object { - Write-Host "- $($_.Name)" + # Check if there is a custom script to run for the delivery target + $customScript = Join-Path $baseFolder ".github/DeliverTo$deliveryTarget.ps1" + + if (Test-Path $customScript -PathType Leaf) { + Write-Host "Found custom script $customScript for delivery target $deliveryTarget" + + $projectSettings = ReadSettings -baseFolder $baseFolder -project $thisProject + $projectSettings = AnalyzeRepo -settings $projectSettings -baseFolder $baseFolder -project $thisProject -doNotCheckArtifactSetting -doNotIssueWarnings + $parameters = @{ + "Project" = $thisProject + "ProjectName" = $projectName + "type" = $type + "Context" = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets."$($deliveryTarget)Context")) + "RepoSettings" = $settings + "ProjectSettings" = $projectSettings } + #Calculate the folders per artifact type - # Check if there is a custom script to run for the delivery target - $customScript = Join-Path $baseFolder ".github/DeliverTo$deliveryTarget.ps1" + #Calculate the folders per artifact type + 'Apps', 'TestApps', 'Dependencies' | ForEach-Object { + $artifactType = $_ + $singleArtifactFilter = "$project-$refname-$artifactType-*.*.*.*"; - if (Test-Path $customScript -PathType Leaf) { - Write-Host "Found custom script $customScript for delivery target $deliveryTarget" + # Get the folder holding the artifacts from the standard build + $artifactFolder = @(Get-ChildItem -Path (Join-Path $artifactsFolder $singleArtifactFilter) -Directory) - $projectSettings = ReadSettings -baseFolder $baseFolder -project $thisProject - $projectSettings = AnalyzeRepo -settings $projectSettings -baseFolder $baseFolder -project $thisProject -doNotCheckArtifactSetting -doNotIssueWarnings - $parameters = @{ - "Project" = $thisProject - "ProjectName" = $projectName - "type" = $type - "Context" = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets."$($deliveryTarget)Context")) - "RepoSettings" = $settings - "ProjectSettings" = $projectSettings + # Verify that there is an apps folder + if ($artifactFolder.Count -eq 0 -and $artifactType -eq "Apps") { + throw "Internal error - unable to locate apps folder" } - #Calculate the folders per artifact type - - #Calculate the folders per artifact type - 'Apps', 'TestApps', 'Dependencies' | ForEach-Object { - $artifactType = $_ - $singleArtifactFilter = "$project-$refname-$artifactType-*.*.*.*"; - - # Get the folder holding the artifacts from the standard build - $artifactFolder = @(Get-ChildItem -Path (Join-Path $artifactsFolder $singleArtifactFilter) -Directory) - # Verify that there is an apps folder - if ($artifactFolder.Count -eq 0 -and $artifactType -eq "Apps") { - throw "Internal error - unable to locate apps folder" - } - - # Verify that there is only at most one artifact folder for the standard build - if ($artifactFolder.Count -gt 1) { - $artifactFolder | Out-Host - throw "Internal error - multiple $artifactType folders located" - } + # Verify that there is only at most one artifact folder for the standard build + if ($artifactFolder.Count -gt 1) { + $artifactFolder | Out-Host + throw "Internal error - multiple $artifactType folders located" + } - # Add the artifact folder to the parameters - if ($artifactFolder.Count -ne 0) { - $parameters[$artifactType.ToLowerInvariant() + "Folder"] = $artifactFolder[0].FullName - } + # Add the artifact folder to the parameters + if ($artifactFolder.Count -ne 0) { + $parameters[$artifactType.ToLowerInvariant() + "Folder"] = $artifactFolder[0].FullName + } - # Get the folders holding the artifacts from all build modes - $multipleArtifactFilter = "$project-$refname-*$artifactType-*.*.*.*"; - $artifactFolders = @(Get-ChildItem -Path (Join-Path $artifactsFolder $multipleArtifactFilter) -Directory) - if ($artifactFolders.Count -gt 0) { - $parameters[$artifactType.ToLowerInvariant() + "Folders"] = $artifactFolders.FullName - } + # Get the folders holding the artifacts from all build modes + $multipleArtifactFilter = "$project-$refname-*$artifactType-*.*.*.*"; + $artifactFolders = @(Get-ChildItem -Path (Join-Path $artifactsFolder $multipleArtifactFilter) -Directory) + if ($artifactFolders.Count -gt 0) { + $parameters[$artifactType.ToLowerInvariant() + "Folders"] = $artifactFolders.FullName } + } - Write-Host "Calling custom script: $customScript" - . $customScript -parameters $parameters - } - elseif ($deliveryTarget -eq "GitHubPackages") { - $githubPackagesCredential = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets.githubPackagesContext)) | ConvertFrom-Json - 'Apps' | ForEach-Object { - $folder = @(Get-ChildItem -Path (Join-Path $artifactsFolder "$project-$refname-$($_)-*.*.*.*") | Where-Object { $_.PSIsContainer }) - if ($folder.Count -gt 1) { - $folder | Out-Host - throw "Internal error - multiple $_ folders located" - } - elseif ($folder.Count -eq 1) { - Get-Item -Path (Join-Path $folder[0] "*.app") | ForEach-Object { - $parameters = @{ - "gitHubRepository" = "$ENV:GITHUB_SERVER_URL/$ENV:GITHUB_REPOSITORY" - "includeNuGetDependencies" = $true - "dependencyIdTemplate" = "AL-Go-{id}" - "packageId" = "AL-Go-{id}" - } - $parameters.appFiles = $_.FullName - $package = New-BcNuGetPackage @parameters - Push-BcNuGetPackage -nuGetServerUrl $gitHubPackagesCredential.serverUrl -nuGetToken $gitHubPackagesCredential.token -bcNuGetPackage $package + Write-Host "Calling custom script: $customScript" + . $customScript -parameters $parameters + } + elseif ($deliveryTarget -eq "GitHubPackages") { + $githubPackagesCredential = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets.githubPackagesContext)) | ConvertFrom-Json + 'Apps' | ForEach-Object { + $folder = @(Get-ChildItem -Path (Join-Path $artifactsFolder "$project-$refname-$($_)-*.*.*.*") | Where-Object { $_.PSIsContainer }) + if ($folder.Count -gt 1) { + $folder | Out-Host + throw "Internal error - multiple $_ folders located" + } + elseif ($folder.Count -eq 1) { + Get-Item -Path (Join-Path $folder[0] "*.app") | ForEach-Object { + $parameters = @{ + "gitHubRepository" = "$ENV:GITHUB_SERVER_URL/$ENV:GITHUB_REPOSITORY" + "includeNuGetDependencies" = $true + "dependencyIdTemplate" = "AL-Go-{id}" + "packageId" = "AL-Go-{id}" } + $parameters.appFiles = $_.FullName + $package = New-BcNuGetPackage @parameters + Push-BcNuGetPackage -nuGetServerUrl $gitHubPackagesCredential.serverUrl -nuGetToken $gitHubPackagesCredential.token -bcNuGetPackage $package } } } - elseif ($deliveryTarget -eq "NuGet") { - try { - $nuGetAccount = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets.nuGetContext)) | ConvertFrom-Json | ConvertTo-HashTable - $nuGetServerUrl = $nuGetAccount.ServerUrl - $nuGetToken = $nuGetAccount.Token - Write-Host "NuGetContext secret OK" - } - catch { - throw "NuGetContext secret is malformed. Needs to be formatted as Json, containing serverUrl and token as a minimum." - } - $appsfolder = @(Get-ChildItem -Path (Join-Path $artifactsFolder "$project-$refname-Apps-*.*.*.*") | Where-Object { $_.PSIsContainer }) - if ($appsFolder.Count -eq 0) { - throw "Internal error - unable to locate apps folder" - } - elseif ($appsFolder.Count -gt 1) { - $appsFolder | Out-Host - throw "Internal error - multiple apps folders located" - } - $testAppsFolder = @(Get-ChildItem -Path (Join-Path $artifactsFolder "$project-$refname-TestApps-*.*.*.*") | Where-Object { $_.PSIsContainer }) - if ($testAppsFolder.Count -gt 1) { - $testAppsFolder | Out-Host - throw "Internal error - multiple testApps folders located" - } - $dependenciesFolder = @(Get-ChildItem -Path (Join-Path $artifactsFolder "$project-$refname-Dependencies-*.*.*.*") | Where-Object { $_.PSIsContainer }) - if ($dependenciesFolder.Count -gt 1) { - $dependenciesFolder | Out-Host - throw "Internal error - multiple dependencies folders located" - } + } + elseif ($deliveryTarget -eq "NuGet") { + try { + $nuGetAccount = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets.nuGetContext)) | ConvertFrom-Json | ConvertTo-HashTable + $nuGetServerUrl = $nuGetAccount.ServerUrl + $nuGetToken = $nuGetAccount.Token + Write-Host "NuGetContext secret OK" + } + catch { + throw "NuGetContext secret is malformed. Needs to be formatted as Json, containing serverUrl and token as a minimum." + } + $appsfolder = @(Get-ChildItem -Path (Join-Path $artifactsFolder "$project-$refname-Apps-*.*.*.*") | Where-Object { $_.PSIsContainer }) + if ($appsFolder.Count -eq 0) { + throw "Internal error - unable to locate apps folder" + } + elseif ($appsFolder.Count -gt 1) { + $appsFolder | Out-Host + throw "Internal error - multiple apps folders located" + } + $testAppsFolder = @(Get-ChildItem -Path (Join-Path $artifactsFolder "$project-$refname-TestApps-*.*.*.*") | Where-Object { $_.PSIsContainer }) + if ($testAppsFolder.Count -gt 1) { + $testAppsFolder | Out-Host + throw "Internal error - multiple testApps folders located" + } + $dependenciesFolder = @(Get-ChildItem -Path (Join-Path $artifactsFolder "$project-$refname-Dependencies-*.*.*.*") | Where-Object { $_.PSIsContainer }) + if ($dependenciesFolder.Count -gt 1) { + $dependenciesFolder | Out-Host + throw "Internal error - multiple dependencies folders located" + } - $parameters = @{ - "gitHubRepository" = "$ENV:GITHUB_SERVER_URL/$ENV:GITHUB_REPOSITORY" - } - $parameters.appFiles = @(Get-Item -Path (Join-Path $appsFolder[0] "*.app") | ForEach-Object { $_.FullName }) - if ($testAppsFolder.Count -gt 0) { - $parameters.testAppFiles = @(Get-Item -Path (Join-Path $testAppsFolder[0] "*.app") | ForEach-Object { $_.FullName }) - } - if ($dependenciesFolder.Count -gt 0) { - $parameters.dependencyAppFiles = @(Get-Item -Path (Join-Path $dependenciesFolder[0] "*.app") | ForEach-Object { $_.FullName }) - } - if ($nuGetAccount.Keys -contains 'PackageName') { - $parameters.packageId = $nuGetAccount.PackageName.replace('{project}',$projectName).replace('{owner}',$ENV:GITHUB_REPOSITORY_OWNER).replace('{repo}',$settings.repoName) + $parameters = @{ + "gitHubRepository" = "$ENV:GITHUB_SERVER_URL/$ENV:GITHUB_REPOSITORY" + } + $parameters.appFiles = @(Get-Item -Path (Join-Path $appsFolder[0] "*.app") | ForEach-Object { $_.FullName }) + if ($testAppsFolder.Count -gt 0) { + $parameters.testAppFiles = @(Get-Item -Path (Join-Path $testAppsFolder[0] "*.app") | ForEach-Object { $_.FullName }) + } + if ($dependenciesFolder.Count -gt 0) { + $parameters.dependencyAppFiles = @(Get-Item -Path (Join-Path $dependenciesFolder[0] "*.app") | ForEach-Object { $_.FullName }) + } + if ($nuGetAccount.Keys -contains 'PackageName') { + $parameters.packageId = $nuGetAccount.PackageName.replace('{project}', $projectName).replace('{owner}', $ENV:GITHUB_REPOSITORY_OWNER).replace('{repo}', $settings.repoName) + } + else { + if ($thisProject -and ($thisProject -eq '.')) { + $parameters.packageId = "$($ENV:GITHUB_REPOSITORY_OWNER)-$($settings.repoName)" } else { - if ($thisProject -and ($thisProject -eq '.')) { - $parameters.packageId = "$($ENV:GITHUB_REPOSITORY_OWNER)-$($settings.repoName)" + $parameters.packageId = "$($ENV:GITHUB_REPOSITORY_OWNER)-$($settings.repoName)-$ProjectName" + } + } + if ($type -eq 'CD') { + $parameters.packageId += "-preview" + } + $parameters.packageVersion = [System.Version]$appsFolder[0].Name.SubString($appsFolder[0].Name.IndexOf("-Apps-") + 6) + if ($nuGetAccount.Keys -contains 'PackageTitle') { + $parameters.packageTitle = $nuGetAccount.PackageTitle + } + else { + $parameters.packageTitle = $parameters.packageId + } + if ($nuGetAccount.Keys -contains 'PackageDescription') { + $parameters.packageDescription = $nuGetAccount.PackageDescription + } + else { + $parameters.packageDescription = $parameters.packageTitle + } + if ($nuGetAccount.Keys -contains 'PackageAuthors') { + $parameters.packageAuthors = $nuGetAccount.PackageAuthors + } + else { + $parameters.packageAuthors = $actor + } + $package = New-BcNuGetPackage @parameters + Push-BcNuGetPackage -nuGetServerUrl $nuGetServerUrl -nuGetToken $nuGetToken -bcNuGetPackage $package + } + elseif ($deliveryTarget -eq "Storage") { + InstallAzModuleIfNeeded -name 'Az.Storage' + try { + $storageAccountCredentials = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets.storageContext)) | ConvertFrom-Json + $storageAccountCredentials.StorageAccountName | Out-Null + $storageContainerName = $storageAccountCredentials.ContainerName.ToLowerInvariant().replace('{project}', $projectName).replace('{branch}', $refname).ToLowerInvariant() + $storageBlobName = $storageAccountCredentials.BlobName.ToLowerInvariant() + } + catch { + throw "StorageContext secret is malformed. Needs to be formatted as Json, containing StorageAccountName, containerName, blobName.`nError was: $($_.Exception.Message)" + } + $azStorageContext = ConnectAzStorageAccount -storageAccountCredentials $storageAccountCredentials + Write-Host "Storage Container Name is $storageContainerName" + Write-Host "Storage Blob Name is $storageBlobName" + + $containerExists = $true + try { + Get-AzStorageContainer -Context $azStorageContext -name $storageContainerName | Out-Null + } + catch { + $containerExists = $false + } + + if (-not $containerExists -and $settings.Contains('DeliverToStorage') -and $settings."DeliverToStorage".Contains('CreateContainerIfNotExist') -and $settings."DeliverToStorage"."CreateContainerIfNotExist" -eq $true) { + Write-Host "Container $storageContainerName does not exist. Creating..." + New-AzStorageContainer -Context $azStorageContext -Name $storageContainerName | Out-Null + } + + Write-Host "Delivering to $storageContainerName in $($storageAccountCredentials.StorageAccountName)" + $atypes.Split(',') | ForEach-Object { + $atype = $_ + Write-Host "Looking for: $project-$refname-$atype-*.*.*.*" + $artfolder = @(Get-ChildItem -Path (Join-Path $artifactsFolder "$project-$refname-$atype-*.*.*.*") | Where-Object { $_.PSIsContainer }) + if ($artFolder.Count -eq 0) { + if ($atype -eq "Apps") { + throw "Error - unable to locate apps" } else { - $parameters.packageId = "$($ENV:GITHUB_REPOSITORY_OWNER)-$($settings.repoName)-$ProjectName" + Write-Host "WARNING: Unable to locate $atype" } } - if ($type -eq 'CD') { - $parameters.packageId += "-preview" - } - $parameters.packageVersion = [System.Version]$appsFolder[0].Name.SubString($appsFolder[0].Name.IndexOf("-Apps-")+6) - if ($nuGetAccount.Keys -contains 'PackageTitle') { - $parameters.packageTitle = $nuGetAccount.PackageTitle - } - else { - $parameters.packageTitle = $parameters.packageId - } - if ($nuGetAccount.Keys -contains 'PackageDescription') { - $parameters.packageDescription = $nuGetAccount.PackageDescription - } - else { - $parameters.packageDescription = $parameters.packageTitle - } - if ($nuGetAccount.Keys -contains 'PackageAuthors') { - $parameters.packageAuthors = $nuGetAccount.PackageAuthors + elseif ($artFolder.Count -gt 1) { + $artFolder | Out-Host + throw "Internal error - multiple $atype folders located" } else { - $parameters.packageAuthors = $actor - } - $package = New-BcNuGetPackage @parameters - Push-BcNuGetPackage -nuGetServerUrl $nuGetServerUrl -nuGetToken $nuGetToken -bcNuGetPackage $package - } - elseif ($deliveryTarget -eq "Storage") { - EnsureAzStorageModule - try { - $storageAccount = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets.storageContext)) | ConvertFrom-Json | ConvertTo-HashTable - # Check that containerName and blobName are present - $storageAccount.containerName | Out-Null - $storageAccount.blobName | Out-Null - } - catch { - throw "StorageContext secret is malformed. Needs to be formatted as Json, containing StorageAccountName, containerName, blobName and sastoken or storageAccountKey.`nError was: $($_.Exception.Message)" - } - if ($storageAccount.Keys -contains 'sastoken') { - try { - $azStorageContext = New-AzStorageContext -StorageAccountName $storageAccount.StorageAccountName -SasToken $storageAccount.sastoken - } - catch { - throw "Unable to create AzStorageContext based on StorageAccountName and sastoken.`nError was: $($_.Exception.Message)" + $artfolder = $artfolder[0].FullName + $version = $artfolder.SubString($artfolder.IndexOf("-$refname-$atype-") + "-$refname-$atype-".Length) + Write-Host $artfolder + $versions = @("$version-preview", "preview") + if ($type -eq "Release") { + $versions += @($version, "latest") } - } - else { + $tempFile = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::newguid().ToString()).zip" try { - $azStorageContext = New-AzStorageContext -StorageAccountName $storageAccount.StorageAccountName -StorageAccountKey $storageAccount.StorageAccountKey + Write-Host "Compressing" + Compress-Archive -Path (Join-Path $artfolder '*') -DestinationPath $tempFile -Force + $versions | ForEach-Object { + $version = $_ + $blob = $storageBlobName.replace('{project}', $projectName).replace('{branch}', $refname).replace('{version}', $version).replace('{type}', $atype).ToLowerInvariant() + Write-Host "Delivering $blob" + Set-AzStorageBlobContent -Context $azStorageContext -Container $storageContainerName -File $tempFile -blob $blob -Force | Out-Null + } } - catch { - throw "Unable to create AzStorageContext based on StorageAccountName and StorageAccountKey.`nError was: $($_.Exception.Message)" + finally { + Remove-Item $tempFile -Force -ErrorAction SilentlyContinue } } - - $storageContainerName = $storageAccount.ContainerName.ToLowerInvariant().replace('{project}',$projectName).replace('{branch}',$refname).ToLowerInvariant() - $storageBlobName = $storageAccount.BlobName.ToLowerInvariant() - Write-Host "Storage Container Name is $storageContainerName" - Write-Host "Storage Blob Name is $storageBlobName" - - $containerExists = $true - try { - Get-AzStorageContainer -Context $azStorageContext -name $storageContainerName | Out-Null + } + } + elseif ($deliveryTarget -eq "AppSource") { + $projectSettings = ReadSettings -baseFolder $baseFolder -project $thisProject + $projectSettings = AnalyzeRepo -settings $projectSettings -baseFolder $baseFolder -project $thisProject -doNotCheckArtifactSetting -doNotIssueWarnings + # Use old settings and issue warnings + 'continuousDelivery', 'mainAppFolder', 'productId' | ForEach-Object { + if ($projectSettings.Keys -contains "AppSource$_") { + OutputWarning "Using AppSource$_ in $thisProject/.AL-Go/settings.json is deprecated. Use deliverToAppSource.$_ instead. If both values are defined, the value in AppSource$_ is used (even if it is deprecated)." + $projectSettings.deliverToAppSource."$_" = $projectSettings."AppSource$_" } - catch { - $containerExists = $false + } + # if type is Release, we only get here with the projects that needs to be delivered to AppSource + # if type is CD, we get here for all projects, but should only deliver to AppSource if AppSourceContinuousDelivery is set to true + if ($type -eq 'Release' -or $projectSettings.deliverToAppSource.continuousDelivery) { + # AppSource submission requires the Az.Storage module + InstallAzModuleIfNeeded -name 'Az.Storage' + $appSourceContext = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets.appSourceContext)) | ConvertFrom-Json | ConvertTo-HashTable + if (!$appSourceContext) { + throw "appSourceContext secret is missing" } + $authContext = New-BcAuthContext @appSourceContext - if (-not $containerExists -and $settings.Contains('DeliverToStorage') -and $settings."DeliverToStorage".Contains('CreateContainerIfNotExist') -and $settings."DeliverToStorage"."CreateContainerIfNotExist" -eq $true) { - Write-Host "Container $storageContainerName does not exist. Creating..." - New-AzStorageContainer -Context $azStorageContext -Name $storageContainerName | Out-Null + if ($projectSettings.deliverToAppSource.MainAppFolder) { + $AppSourceMainAppFolder = $projectSettings.deliverToAppSource.MainAppFolder } - - Write-Host "Delivering to $storageContainerName in $($storageAccount.StorageAccountName)" - $atypes.Split(',') | ForEach-Object { - $atype = $_ - Write-Host "Looking for: $project-$refname-$atype-*.*.*.*" - $artfolder = @(Get-ChildItem -Path (Join-Path $artifactsFolder "$project-$refname-$atype-*.*.*.*") | Where-Object { $_.PSIsContainer }) - if ($artFolder.Count -eq 0) { - if ($atype -eq "Apps") { - throw "Error - unable to locate apps" - } - else { - Write-Host "WARNING: Unable to locate $atype" - } - } - elseif ($artFolder.Count -gt 1) { - $artFolder | Out-Host - throw "Internal error - multiple $atype folders located" + else { + try { + $AppSourceMainAppFolder = $projectSettings.appFolders[0] } - else { - $artfolder = $artfolder[0].FullName - $version = $artfolder.SubString($artfolder.IndexOf("-$refname-$atype-")+"-$refname-$atype-".Length) - Write-Host $artfolder - $versions = @("$version-preview","preview") - if ($type -eq "Release") { - $versions += @($version,"latest") - } - $tempFile = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::newguid().ToString()).zip" - try { - Write-Host "Compressing" - Compress-Archive -Path (Join-Path $artfolder '*') -DestinationPath $tempFile -Force - $versions | ForEach-Object { - $version = $_ - $blob = $storageBlobName.replace('{project}',$projectName).replace('{branch}',$refname).replace('{version}',$version).replace('{type}',$atype).ToLowerInvariant() - Write-Host "Delivering $blob" - Set-AzStorageBlobContent -Context $azStorageContext -Container $storageContainerName -File $tempFile -blob $blob -Force | Out-Null - } - } - finally { - Remove-Item $tempFile -Force -ErrorAction SilentlyContinue - } + catch { + throw "Unable to determine main App folder" } } - } - elseif ($deliveryTarget -eq "AppSource") { - $projectSettings = ReadSettings -baseFolder $baseFolder -project $thisProject - $projectSettings = AnalyzeRepo -settings $projectSettings -baseFolder $baseFolder -project $thisProject -doNotCheckArtifactSetting -doNotIssueWarnings - # Use old settings and issue warnings - 'continuousDelivery','mainAppFolder','productId' | ForEach-Object { - if ($projectSettings.Keys -contains "AppSource$_") { - OutputWarning "Using AppSource$_ in $thisProject/.AL-Go/settings.json is deprecated. Use deliverToAppSource.$_ instead. If both values are defined, the value in AppSource$_ is used (even if it is deprecated)." - $projectSettings.deliverToAppSource."$_" = $projectSettings."AppSource$_" - } + if (!$projectSettings.deliverToAppSource.ProductId) { + throw "deliverToAppSource.ProductId needs to be specified in $thisProject/.AL-Go/settings.json in order to deliver to AppSource" } - # if type is Release, we only get here with the projects that needs to be delivered to AppSource - # if type is CD, we get here for all projects, but should only deliver to AppSource if AppSourceContinuousDelivery is set to true - if ($type -eq 'Release' -or $projectSettings.deliverToAppSource.continuousDelivery) { - EnsureAzStorageModule - $appSourceContext = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets.appSourceContext)) | ConvertFrom-Json | ConvertTo-HashTable - if (!$appSourceContext) { - throw "appSourceContext secret is missing" - } - $authContext = New-BcAuthContext @appSourceContext + Write-Host "AppSource MainAppFolder $AppSourceMainAppFolder" - if ($projectSettings.deliverToAppSource.MainAppFolder) { - $AppSourceMainAppFolder = $projectSettings.deliverToAppSource.MainAppFolder - } - else { - try { - $AppSourceMainAppFolder = $projectSettings.appFolders[0] - } - catch { - throw "Unable to determine main App folder" - } - } - if (!$projectSettings.deliverToAppSource.ProductId) { - throw "deliverToAppSource.ProductId needs to be specified in $thisProject/.AL-Go/settings.json in order to deliver to AppSource" - } - Write-Host "AppSource MainAppFolder $AppSourceMainAppFolder" + $mainAppJson = Get-Content -Path (Join-Path $baseFolder "$thisProject/$AppSourceMainAppFolder/app.json") -Encoding UTF8 | ConvertFrom-Json + $mainAppFileName = ("$($mainAppJson.Publisher)_$($mainAppJson.Name)_".Split([System.IO.Path]::GetInvalidFileNameChars()) -join '') + "*.*.*.*.app" + $artfolder = @(Get-ChildItem -Path (Join-Path $artifactsFolder "$project-$refname-Apps-*.*.*.*") | Where-Object { $_.PSIsContainer }) + if ($artFolder.Count -eq 0) { + throw "Internal error - unable to locate apps folder" + } + if ($artFolder.Count -gt 1) { + $artFolder | Out-Host + throw "Internal error - multiple apps folders located" + } + $artfolder = $artfolder[0].FullName + $appFile = Get-ChildItem -path $artFolder | Where-Object { $_.name -like $mainAppFileName } | ForEach-Object { $_.FullName } + $libraryAppFiles = @(Get-ChildItem -path $artFolder | Where-Object { $_.name -notlike $mainAppFileName } | ForEach-Object { $_.FullName }) - $mainAppJson = Get-Content -Path (Join-Path $baseFolder "$thisProject/$AppSourceMainAppFolder/app.json") -Encoding UTF8 | ConvertFrom-Json - $mainAppFileName = ("$($mainAppJson.Publisher)_$($mainAppJson.Name)_".Split([System.IO.Path]::GetInvalidFileNameChars()) -join '') + "*.*.*.*.app" - $artfolder = @(Get-ChildItem -Path (Join-Path $artifactsFolder "$project-$refname-Apps-*.*.*.*") | Where-Object { $_.PSIsContainer }) - if ($artFolder.Count -eq 0) { - throw "Internal error - unable to locate apps folder" + $appSourceIncludeDependencies = $projectSettings.deliverToAppSource.includeDependencies + if ($appSourceIncludeDependencies -and $appSourceIncludeDependencies.count -gt 0) { + $depfolder = @(Get-ChildItem -Path (Join-Path $artifactsFolder "$project-$refname-Dependencies-*.*.*.*") | Where-Object { $_.PSIsContainer }) + if ($depFolder.Count -eq 0) { + throw "Unable to locate dependencies. You need to set generateDependencyArtifact to true in $thisProject/.AL-Go/settings.json in order to deliver dependencies to AppSource" } - if ($artFolder.Count -gt 1) { - $artFolder | Out-Host - throw "Internal error - multiple apps folders located" + if ($depFolder.Count -gt 1) { + $depFolder | Out-Host + throw "Internal error - multiple dependencies folders located" } - $artfolder = $artfolder[0].FullName - $appFile = Get-ChildItem -path $artFolder | Where-Object { $_.name -like $mainAppFileName } | ForEach-Object { $_.FullName } - $libraryAppFiles = @(Get-ChildItem -path $artFolder | Where-Object { $_.name -notlike $mainAppFileName } | ForEach-Object { $_.FullName }) - - $appSourceIncludeDependencies = $projectSettings.deliverToAppSource.includeDependencies - if ($appSourceIncludeDependencies -and $appSourceIncludeDependencies.count -gt 0) { - $depfolder = @(Get-ChildItem -Path (Join-Path $artifactsFolder "$project-$refname-Dependencies-*.*.*.*") | Where-Object { $_.PSIsContainer }) - if ($depFolder.Count -eq 0) { - throw "Unable to locate dependencies. You need to set generateDependencyArtifact to true in $thisProject/.AL-Go/settings.json in order to deliver dependencies to AppSource" - } - if ($depFolder.Count -gt 1) { - $depFolder | Out-Host - throw "Internal error - multiple dependencies folders located" - } - $depfolder = $depfolder[0].FullName - $libraryAppFiles += @(Get-ChildItem -path $depFolder | Where-Object { + $depfolder = $depfolder[0].FullName + $libraryAppFiles += @(Get-ChildItem -path $depFolder | Where-Object { $name = $_.name $appSourceIncludeDependencies | Where-Object { $name -like $_ } } | ForEach-Object { $_.FullName }) - } + } - Write-Host "Main App File:" - Write-Host "- $([System.IO.Path]::GetFileName($appFile))" - Write-Host "Library App Files:" - if ($libraryAppFiles.Count -eq 0) { - Write-Host "- None" - } - else { - $libraryAppFiles | ForEach-Object { Write-Host "- $([System.IO.Path]::GetFileName($_))" } - } - if (-not $appFile) { - throw "Unable to locate main app file ($mainAppFileName doesn't exist)" + Write-Host "Main App File:" + Write-Host "- $([System.IO.Path]::GetFileName($appFile))" + Write-Host "Library App Files:" + if ($libraryAppFiles.Count -eq 0) { + Write-Host "- None" + } + else { + $libraryAppFiles | ForEach-Object { Write-Host "- $([System.IO.Path]::GetFileName($_))" } + } + if (-not $appFile) { + throw "Unable to locate main app file ($mainAppFileName doesn't exist)" + } + Write-Host "Submitting to AppSource" + $status = New-AppSourceSubmission -authContext $authContext -productId $projectSettings.deliverToAppSource.productId -appFile $appFile -libraryAppFiles $libraryAppFiles -doNotWait -autoPromote:$goLive -Force + if ($goLive) { + if ($status.state -ne 'Published' -or ($status.substate -ne 'ReadyToPublish' -and $status.substate -ne 'InStore')) { + throw "AppSource submission failed. Status is $($status.state)/$($status.substate)" } - Write-Host "Submitting to AppSource" - New-AppSourceSubmission -authContext $authContext -productId $projectSettings.deliverToAppSource.productId -appFile $appFile -libraryAppFiles $libraryAppFiles -doNotWait -autoPromote:$goLive -Force } } - else { - throw "Internal error, no handler for $deliveryTarget" - } - - if ($artifactsFolderCreated) { - Remove-Item $artifactsFolder -Recurse -Force - } + } + else { + throw "Internal error, no handler for $deliveryTarget" } - TrackTrace -telemetryScope $telemetryScope -} -catch { - if (Get-Module BcContainerHelper) { - TrackException -telemetryScope $telemetryScope -errorRecord $_ + if ($artifactsFolderCreated) { + Remove-Item $artifactsFolder -Recurse -Force } - throw } diff --git a/Deliver/README.md b/Deliver/README.md index f6ae1ef..8f98816 100644 --- a/Deliver/README.md +++ b/Deliver/README.md @@ -1,21 +1,23 @@ # Deliver + Deliver App to deliveryTarget (AppSource, Storage, or...) ## INPUT ### ENV variables + | Name | Description | | :-- | :-- | | Settings | env.Settings must be set by a prior call to the ReadSettings Action | | Secrets | env.Secrets with delivery target context secrets must be read by a prior call to the ReadSecrets Action | ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | | actor | | The GitHub actor running the action | github.actor | | token | | The GitHub token running the action | github.token | -| parentTelemetryScopeJson | | Specifies the parent telemetry scope for the telemetry signal | {} | | projects | | Comma-separated list of projects to deliver | * | | deliveryTarget | Yes | Delivery target (AppSource, Storage, GitHubPackages,...) | | | artifacts | Yes | The artifacts to deliver or a folder in which the artifacts have been downloaded | | @@ -24,4 +26,5 @@ Deliver App to deliveryTarget (AppSource, Storage, or...) | goLive | | Only relevant for AppSource delivery type. Promote AppSource App to Go Live? | false | ## OUTPUT + none diff --git a/Deliver/action.yaml b/Deliver/action.yaml index 9f33256..ee340af 100644 --- a/Deliver/action.yaml +++ b/Deliver/action.yaml @@ -13,10 +13,6 @@ inputs: description: The GitHub token running the action required: false default: ${{ github.token }} - parentTelemetryScopeJson: - description: Specifies the parent telemetry scope for the telemetry signal - required: false - default: '7b7d' projects: description: Projects to deliver (default is all) required: false @@ -47,7 +43,6 @@ runs: env: _actor: ${{ inputs.actor }} _token: ${{ inputs.token }} - _parentTelemetryScopeJson: ${{ inputs.parentTelemetryScopeJson }} _projects: ${{ inputs.projects }} _deliveryTarget: ${{ inputs.deliveryTarget }} _artifacts: ${{ inputs.artifacts }} @@ -55,13 +50,8 @@ runs: _atypes: ${{ inputs.atypes }} _goLive: ${{ inputs.goLive }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { - ${{ github.action_path }}/Deliver.ps1 -actor $ENV:_actor -token $ENV:_token -parentTelemetryScopeJson $ENV:_parentTelemetryScopeJson -projects $ENV:_projects -deliveryTarget $ENV:_deliveryTarget -artifacts $ENV:_artifacts -type $ENV:_type -atypes $ENV:_atypes -goLive ($ENV:_goLive -eq 'true') - } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "Deliver" -Action { + ${{ github.action_path }}/Deliver.ps1 -actor $ENV:_actor -token $ENV:_token -projects $ENV:_projects -deliveryTarget $ENV:_deliveryTarget -artifacts $ENV:_artifacts -type $ENV:_type -atypes $ENV:_atypes -goLive ($ENV:_goLive -eq 'true') } branding: icon: terminal diff --git a/Deploy/Deploy.ps1 b/Deploy/Deploy.ps1 index 2a5dfca..9e70aae 100644 --- a/Deploy/Deploy.ps1 +++ b/Deploy/Deploy.ps1 @@ -1,6 +1,4 @@ Param( - [Parameter(HelpMessage = "The GitHub token running the action", Mandatory = $false)] - [string] $token, [Parameter(HelpMessage = "Name of environment to deploy to", Mandatory = $true)] [string] $environmentName, [Parameter(HelpMessage = "Path to the downloaded artifacts to deploy", Mandatory = $true)] @@ -17,18 +15,13 @@ DownloadAndImportBcContainerHelper $deploymentEnvironments = $deploymentEnvironmentsJson | ConvertFrom-Json | ConvertTo-HashTable -recurse $deploymentSettings = $deploymentEnvironments."$environmentName" +$buildMode = $deploymentSettings.buildMode +if ($null -eq $buildMode -or $buildMode -eq 'default') { + $buildMode = '' +} $envName = $environmentName.Split(' ')[0] $secrets = $env:Secrets | ConvertFrom-Json - -# Check obsolete secrets -"$($envName)-EnvironmentName","$($envName)_EnvironmentName","EnvironmentName" | ForEach-Object { - if ($secrets."$_") { - throw "The secret $_ is obsolete and should be replaced by using the EnvironmentName property in the DeployTo$envName setting in .github/AL-Go-Settings.json instead" - } -} -if ($secrets.Projects) { - throw "The secret Projects is obsolete and should be replaced by using the Projects property in the DeployTo$envName setting in .github/AL-Go-Settings.json instead" -} +$settings = $env:Settings | ConvertFrom-Json $authContext = $null foreach($secretName in "$($envName)-AuthContext","$($envName)_AuthContext","AuthContext") { @@ -54,10 +47,10 @@ if (Test-Path $artifactsFolder -PathType Container) { $project = $_.Replace('\','_').Replace('/','_') $refname = "$ENV:GITHUB_REF_NAME".Replace('/','_') Write-Host "project '$project'" - $projectApps = @((Get-ChildItem -Path $artifactsFolder -Filter "$project-$refname-Apps-*.*.*.*") | ForEach-Object { $_.FullName }) + $projectApps = @((Get-ChildItem -Path $artifactsFolder -Filter "$project-$refname-$($buildMode)Apps-*.*.*.*") | ForEach-Object { $_.FullName }) if (!($projectApps)) { if ($project -ne '*') { - throw "There are no artifacts present in $artifactsFolder matching $project-$refname-Apps-." + throw "There are no artifacts present in $artifactsFolder matching $project-$refname-$($buildMode)Apps-." } } else { @@ -110,8 +103,27 @@ else { try { $sandboxEnvironment = ($response.environmentType -eq 1) - if ($sandboxEnvironment -and !($bcAuthContext.ClientSecret)) { - # Sandbox and not S2S -> use dev endpoint (Publish-BcContainerApp) + $scope = $deploymentSettings.Scope + if ($null -eq $scope) { + if ($settings.Type -eq 'AppSource App' -or ($sandboxEnvironment -and !($bcAuthContext.ClientSecret -or $bcAuthContext.ClientAssertion))) { + # Sandbox and not S2S -> use dev endpoint (Publish-BcContainerApp) + $scope = 'Dev' + } + else { + $scope = 'PTE' + } + } + elseif (@('Dev','PTE') -notcontains $scope) { + throw "Invalid Scope $($scope). Valid values are Dev and PTE." + } + if (!$sandboxEnvironment -and $type -eq 'CD' -and !($deploymentSettings.continuousDeployment)) { + # Continuous deployment is undefined in settings - we will not deploy to production environments + Write-Host "::Warning::Ignoring environment $($deploymentSettings.EnvironmentName), which is a production environment" + } + elseif ($scope -eq 'Dev') { + if (!$sandboxEnvironment) { + throw "Scope Dev is only valid for sandbox environments" + } $parameters = @{ "bcAuthContext" = $bcAuthContext "environment" = $deploymentSettings.EnvironmentName @@ -127,10 +139,6 @@ else { Write-Host "Publishing apps using development endpoint" Publish-BcContainerApp @parameters -useDevEndpoint -checkAlreadyInstalled -excludeRuntimePackages -replacePackageId } - elseif (!$sandboxEnvironment -and $type -eq 'CD' -and !($deploymentSettings.continuousDeployment)) { - # Continuous deployment is undefined in settings - we will not deploy to production environments - Write-Host "::Warning::Ignoring environment $($deploymentSettings.EnvironmentName), which is a production environment" - } else { # Use automation API for production environments (Publish-PerTenantExtensionApps) $parameters = @{ diff --git a/Deploy/README.md b/Deploy/README.md index 099be64..c0fe51d 100644 --- a/Deploy/README.md +++ b/Deploy/README.md @@ -1,9 +1,11 @@ # Deploy + Deploy Apps to online environment ## INPUT ### ENV variables + | Name | Description | | :-- | :-- | | Settings | env.Settings must be set by a prior call to the ReadSettings Action | @@ -11,16 +13,17 @@ Deploy Apps to online environment | deviceCode | When deploying to a single environment which doesn't have an AuthContext, we will wait for the user to finalize the deviceflow with this deviceCode | ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | -| token | | The GitHub token running the action | github.token | | environmentName | Yes | Name of environment to deploy to | | artifactsFolder | Yes | Path to the downloaded artifacts to deploy | | | type | | Type of delivery (CD or Release) | CD | | deploymentEnvironmentsJson | Yes | The settings for all Deployment Environments | | ## OUTPUT + | Name | Description | | :-- | :-- | | environmentUrl | The URL for the environment. This URL is presented in the Deploy Step in summary under the environment name | diff --git a/Deploy/action.yaml b/Deploy/action.yaml index 238cf7c..c0d591e 100644 --- a/Deploy/action.yaml +++ b/Deploy/action.yaml @@ -5,10 +5,6 @@ inputs: description: Shell in which you want to run the action (powershell or pwsh) required: false default: powershell - token: - description: The GitHub token running the action - required: false - default: ${{ github.token }} environmentName: description: Name of environment to deploy to required: true @@ -33,19 +29,13 @@ runs: shell: ${{ inputs.shell }} id: Deploy env: - _token: ${{ inputs.token }} _environmentName: ${{ inputs.environmentName }} _artifactsFolder: ${{ inputs.artifactsFolder }} _type: ${{ inputs.type }} _deploymentEnvironmentsJson: ${{ inputs.deploymentEnvironmentsJson }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { - ${{ github.action_path }}/Deploy.ps1 -token $ENV:_token -environmentName $ENV:_environmentName -artifactsFolder $ENV:_artifactsFolder -type $ENV:_type -deploymentEnvironmentsJson $ENV:_deploymentEnvironmentsJson - } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "Deploy" -Action { + ${{ github.action_path }}/Deploy.ps1 -environmentName $ENV:_environmentName -artifactsFolder $ENV:_artifactsFolder -type $ENV:_type -deploymentEnvironmentsJson $ENV:_deploymentEnvironmentsJson } branding: icon: terminal diff --git a/DeployPowerPlatform/README.md b/DeployPowerPlatform/README.md index 37958ae..25ed241 100644 --- a/DeployPowerPlatform/README.md +++ b/DeployPowerPlatform/README.md @@ -1,15 +1,18 @@ # Deploy Power Platform + Deploy the Power Platform solution from the artifacts folder ## INPUT ### ENV variables + | Name | Description | | :-- | :-- | | Settings | env.Settings must be set by a prior call to the ReadSettings Action | | Secrets | env.Secrets with delivery target context secrets must be read by a prior call to the ReadSecrets Action | ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | @@ -21,6 +24,7 @@ Deploy the Power Platform solution from the artifacts folder Either artifactsFolder or solutionFolder needs to be specified ## OUTPUT + | Name | Description | | :-- | :-- | | environmentUrl | The URL for the environment. This URL is presented in the Deploy Step in summary under the environment name | diff --git a/DeployPowerPlatform/action.yaml b/DeployPowerPlatform/action.yaml index 90a9f01..f040421 100644 --- a/DeployPowerPlatform/action.yaml +++ b/DeployPowerPlatform/action.yaml @@ -31,14 +31,9 @@ runs: actionsRepo: ${{ github.action_repository }} actionsRef: ${{ github.action_ref }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "SetActionsRepoAndRef" -SkipTelemetry -Action { ${{ github.action_path }}/../SetActionsRepoAndRef.ps1 -actionsRepo $ENV:actionsRepo -actionsRef $ENV:actionsRef } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 - } - name: Check out AL-Go Actions uses: actions/checkout@v4 @@ -52,7 +47,7 @@ runs: uses: ./_AL-Go/Actions/ReadPowerPlatformSettings with: shell: ${{ inputs.shell }} - deploymentEnvironmentsJson: ${{ inputs.deploymentEnvironmentsJson }} + deploymentEnvironmentsJson: ${{ inputs.deploymentEnvironmentsJson }} environmentName: ${{ inputs.environmentName }} - name: Determine Power Platform solution location @@ -61,15 +56,10 @@ runs: _artifactsFolder: ${{ inputs.artifactsFolder }} _solutionFolder: ${{ inputs.solutionFolder }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "DeterminePowerPlatformSolutionFolder" -Action { ${{ github.action_path }}/DeterminePowerPlatformSolutionFolder.ps1 -artifactsFolder $ENV:_artifactsFolder -solutionFolder $ENV:_solutionFolder } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 - } - + - name: Unpack solution artifact if: env.powerPlatformSolutionFilePath != '' uses: microsoft/powerplatform-actions/unpack-solution@v1 diff --git a/DetermineArtifactUrl/DetermineArtifactUrl.ps1 b/DetermineArtifactUrl/DetermineArtifactUrl.ps1 index 980277d..4da2626 100644 --- a/DetermineArtifactUrl/DetermineArtifactUrl.ps1 +++ b/DetermineArtifactUrl/DetermineArtifactUrl.ps1 @@ -1,44 +1,27 @@ Param( - [Parameter(HelpMessage = "Specifies the parent telemetry scope for the telemetry signal", Mandatory = $false)] - [string] $parentTelemetryScopeJson = '7b7d', [Parameter(HelpMessage = "Project folder", Mandatory = $false)] [string] $project = "." ) -$telemetryScope = $null +#region Action: Setup +. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) +DownloadAndImportBcContainerHelper +#endregion -try { - #region Action: Setup - . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) - DownloadAndImportBcContainerHelper - Import-Module (Join-Path -Path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) -DisableNameChecking - #endregion - - #region Action: Determine artifacts to use - $telemetryScope = CreateScope -eventId 'DO0084' -parentTelemetryScopeJson $parentTelemetryScopeJson - - $settings = $env:Settings | ConvertFrom-Json | ConvertTo-HashTable - $settings = AnalyzeRepo -settings $settings -project $project -doNotCheckArtifactSetting -doNotIssueWarnings - $artifactUrl = DetermineArtifactUrl -projectSettings $settings - $artifactCacheKey = '' - if ($settings.useCompilerFolder) { - $artifactCacheKey = $artifactUrl.Split('?')[0] - } - #endregion - - #region Action: Output - # Set output variables - Add-Content -Encoding UTF8 -Path $env:GITHUB_ENV -Value "artifact=$artifactUrl" - Write-Host "artifact=$artifactUrl" - Add-Content -Encoding UTF8 -Path $env:GITHUB_ENV -Value "artifactCacheKey=$artifactCacheKey" - Write-Host "artifactCacheKey=$artifactCacheKey" - #endregion - - TrackTrace -telemetryScope $telemetryScope -} -catch { - if (Get-Module BcContainerHelper) { - TrackException -telemetryScope $telemetryScope -errorRecord $_ - } - throw +#region Action: Determine artifacts to use +$settings = $env:Settings | ConvertFrom-Json | ConvertTo-HashTable +$settings = AnalyzeRepo -settings $settings -project $project -doNotCheckArtifactSetting -doNotIssueWarnings +$artifactUrl = DetermineArtifactUrl -projectSettings $settings +$artifactCacheKey = '' +if ($settings.useCompilerFolder) { + $artifactCacheKey = $artifactUrl.Split('?')[0] } +#endregion + +#region Action: Output +# Set output variables +Add-Content -Encoding UTF8 -Path $env:GITHUB_ENV -Value "artifact=$artifactUrl" +Write-Host "artifact=$artifactUrl" +Add-Content -Encoding UTF8 -Path $env:GITHUB_ENV -Value "artifactCacheKey=$artifactCacheKey" +Write-Host "artifactCacheKey=$artifactCacheKey" +#endregion diff --git a/DetermineArtifactUrl/README.md b/DetermineArtifactUrl/README.md index 82edf8e..955d1a7 100644 --- a/DetermineArtifactUrl/README.md +++ b/DetermineArtifactUrl/README.md @@ -1,23 +1,26 @@ # Determine artifactUrl + Determines the artifactUrl to use for a given project ## INPUT ### ENV variables + | Name | Description | | :-- | :-- | | Settings | env.Settings must be set by a prior call to the ReadSettings Action | ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | -| parentTelemetryScopeJson | | Specifies the parent telemetry scope for the telemetry signal | {} | | project | | Project folder if repository is setup for multiple projects | . | ## OUTPUT ### ENV variables + | Name | Description | | :-- | :-- | | artifact | The ArtifactUrl to use for the build | diff --git a/DetermineArtifactUrl/action.yaml b/DetermineArtifactUrl/action.yaml index 936181b..b2151ab 100644 --- a/DetermineArtifactUrl/action.yaml +++ b/DetermineArtifactUrl/action.yaml @@ -5,10 +5,6 @@ inputs: description: Shell in which you want to run the action (powershell or pwsh) required: false default: powershell - parentTelemetryScopeJson: - description: Specifies the parent telemetry scope for the telemetry signal - required: false - default: '7b7d' project: description: Project folder required: false @@ -19,16 +15,10 @@ runs: - name: run shell: ${{ inputs.shell }} env: - _parentTelemetryScopeJson: ${{ inputs.parentTelemetryScopeJson }} _project: ${{ inputs.project }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { - ${{ github.action_path }}/DetermineArtifactUrl.ps1 -parentTelemetryScopeJson $env:_parentTelemetryScopeJson -project $ENV:_project - } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "DetermineArtifactUrl" -Action { + ${{ github.action_path }}/DetermineArtifactUrl.ps1 -project $ENV:_project } branding: icon: terminal diff --git a/DetermineDeliveryTargets/DetermineDeliveryTargets.ps1 b/DetermineDeliveryTargets/DetermineDeliveryTargets.ps1 index 7832446..fcd28ed 100644 --- a/DetermineDeliveryTargets/DetermineDeliveryTargets.ps1 +++ b/DetermineDeliveryTargets/DetermineDeliveryTargets.ps1 @@ -8,7 +8,7 @@ Param( function IncludeBranch([string] $deliveryTarget) { $settingsName = "DeliverTo$deliveryTarget" if ($settings.Contains($settingsName) -and $settings."$settingsName".Contains('Branches')) { - Write-Host "- Branches defined: $($settings."$settingsName".Branches -join ', ') - " + Write-Host "- Branches defined: $($settings."$settingsName".Branches -join ', ')" return ($null -ne ($settings."$settingsName".Branches | Where-Object { $ENV:GITHUB_REF_NAME -like $_ })) } else { @@ -18,7 +18,7 @@ function IncludeBranch([string] $deliveryTarget) { } function IncludeDeliveryTarget([string] $deliveryTarget) { - Write-Host "DeliveryTarget $_ - " + Write-Host "DeliveryTarget $_" # DeliveryTarget Context Secret needs to be specified for a delivery target to be included $contextName = "$($_)Context" $secrets = $env:Secrets | ConvertFrom-Json @@ -38,7 +38,7 @@ if ($settings.type -eq "AppSource App") { ($projectsJson | ConvertFrom-Json) | ForEach-Object { $projectSettings = ReadSettings -project $_ if ($projectSettings.deliverToAppSource.ContinuousDelivery -or ($projectSettings.Contains('AppSourceContinuousDelivery') -and $projectSettings.AppSourceContinuousDelivery)) { - Write-Host "Project $_ is setup for Continuous Delivery" + Write-Host "Project $_ is setup for Continuous Delivery to AppSource" $deliveryTargets += @("AppSource") } } diff --git a/DetermineDeliveryTargets/README.md b/DetermineDeliveryTargets/README.md index 4ce95f0..138aea6 100644 --- a/DetermineDeliveryTargets/README.md +++ b/DetermineDeliveryTargets/README.md @@ -1,15 +1,18 @@ # Determine Delivery Targets + Determines the delivery targets to use for the build ## INPUT ### ENV variables + | Name | Description | | :-- | :-- | | Settings | env.Settings must be set by a prior call to the ReadSettings Action | | Secrets | env.Secrets with delivery target context secrets must be read by a prior call to the ReadSecrets Action (if checkContextSecrets is set to Y) | ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | @@ -17,6 +20,7 @@ Determines the delivery targets to use for the build | checkContextSecrets | | Determines whether to check that delivery targets have a corresponding context secret defined | true | ## OUTPUT + | Name | Description | | :-- | :-- | | deliveryTargets | Compressed JSON array containing all delivery targets to use for the build | diff --git a/DetermineDeliveryTargets/action.yaml b/DetermineDeliveryTargets/action.yaml index af8d918..6704dc7 100644 --- a/DetermineDeliveryTargets/action.yaml +++ b/DetermineDeliveryTargets/action.yaml @@ -30,14 +30,9 @@ runs: _projectsJson: ${{ inputs.projectsJson }} _checkContextSecrets: ${{ inputs.checkContextSecrets }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "DetermineDeliveryTargets" -Action { ${{ github.action_path }}/DetermineDeliveryTargets.ps1 -projectsJson $ENV:_projectsJson -checkContextSecrets ($ENV:_checkContextSecrets -eq 'true') } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 - } branding: icon: terminal color: blue diff --git a/DetermineDeploymentEnvironments/DetermineDeploymentEnvironments.ps1 b/DetermineDeploymentEnvironments/DetermineDeploymentEnvironments.ps1 index d7b66b0..7d6301b 100644 --- a/DetermineDeploymentEnvironments/DetermineDeploymentEnvironments.ps1 +++ b/DetermineDeploymentEnvironments/DetermineDeploymentEnvironments.ps1 @@ -105,6 +105,8 @@ if (!($environments)) { "BranchesFromPolicy" = @() "Projects" = '*' "SyncMode" = $null + "Scope" = $null + "buildMode" = $null "continuousDeployment" = !($getEnvironments -like '* (PROD)' -or $getEnvironments -like '* (Production)' -or $getEnvironments -like '* (FAT)' -or $getEnvironments -like '* (Final Acceptance Test)') "runs-on" = @($settings."runs-on".Split(',').Trim()) "shell" = $settings."shell" @@ -143,6 +145,8 @@ else { "BranchesFromPolicy" = @() "Projects" = '*' "SyncMode" = $null + "Scope" = $null + "buildMode" = $null "continuousDeployment" = $null "runs-on" = @($settings."runs-on".Split(',').Trim()) "shell" = $settings."shell" @@ -156,12 +160,21 @@ else { # If a DeployTo setting exists - use values from this (over the defaults) Write-Host "Setting $settingsName" $deployTo = $settings."$settingsName" - $keys = @($deploymentSettings.Keys) + $keys = @($deployTo.Keys) foreach($key in $keys) { - if ($deployTo.ContainsKey($key)) { + if ($deploymentSettings.ContainsKey($key)) { + if ($null -ne $deploymentSettings."$key" -and $null -ne $deployTo."$key" -and $deploymentSettings."$key".GetType().Name -ne $deployTo."$key".GetType().Name) { + Write-Host "::WARNING::The property $key in $settingsName is expected to be of type $($deploymentSettings."$key".GetType().Name)" + } Write-Host "Property $key = $($deployTo."$key")" $deploymentSettings."$key" = $deployTo."$key" } + else { + $deploymentSettings += @{ + "$key" = $deployTo."$key" + } + } + } if ($deploymentSettings."shell" -ne 'pwsh' -and $deploymentSettings."shell" -ne 'powershell') { throw "The shell setting in $settingsName must be either 'pwsh' or 'powershell'" diff --git a/DetermineDeploymentEnvironments/README.md b/DetermineDeploymentEnvironments/README.md index 717a6de..2a34e96 100644 --- a/DetermineDeploymentEnvironments/README.md +++ b/DetermineDeploymentEnvironments/README.md @@ -1,22 +1,26 @@ # Determine Deployment Environments + Determines the environments to be used for a build or a publish ## INPUT ### ENV variables + | Name | Description | | :-- | :-- | | Settings | env.Settings must be set by a prior call to the ReadSettings Action | | GITHUB_TOKEN | GITHUB_TOKEN must be set as an environment variable when calling this action | ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | -| getEnvironments | Yes | Specifies the pattern of the environments you want to retrieve (* for all) | | +| getEnvironments | Yes | Specifies the pattern of the environments you want to retrieve (\* for all) | | | type | Yes | Type of deployment to get environments for (CD, Publish or All) | | ## OUTPUT + | EnvironmentsMatrixJson | The Environment matrix to use for the Deploy step in compressed JSON format | | DeploymentEnvironmentsJson | Deployment Environments with settings in compressed JSON format | | EnvironmentCount | Number of Deployment Environments | diff --git a/DetermineDeploymentEnvironments/action.yaml b/DetermineDeploymentEnvironments/action.yaml index e4cef42..d913710 100644 --- a/DetermineDeploymentEnvironments/action.yaml +++ b/DetermineDeploymentEnvironments/action.yaml @@ -40,14 +40,9 @@ runs: _getEnvironments: ${{ inputs.getEnvironments }} _type: ${{ inputs.type }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "DetermineDeploymentEnvironments" -Action { ${{ github.action_path }}/DetermineDeploymentEnvironments.ps1 -getEnvironments $ENV:_getEnvironments -type $ENV:_type } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 - } branding: icon: terminal color: blue diff --git a/DetermineProjectsToBuild/DetermineProjectsToBuild.Action.ps1 b/DetermineProjectsToBuild/DetermineProjectsToBuild.Action.ps1 index c6b7360..30584db 100644 --- a/DetermineProjectsToBuild/DetermineProjectsToBuild.Action.ps1 +++ b/DetermineProjectsToBuild/DetermineProjectsToBuild.Action.ps1 @@ -4,72 +4,54 @@ Param( [Parameter(HelpMessage = "The maximum depth to build the dependency tree", Mandatory = $false)] [int] $maxBuildDepth = 0, [Parameter(HelpMessage = "The GitHub token to use to fetch the modified files", Mandatory = $true)] - [string] $token, - [Parameter(HelpMessage = "Specifies the parent telemetry scope for the telemetry signal", Mandatory = $false)] - [string] $parentTelemetryScopeJson = '7b7d' + [string] $token ) -$telemetryScope = $null +#region Action: Setup +. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) -try { - #region Action: Setup - . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) +DownloadAndImportBcContainerHelper -baseFolder $baseFolder +Import-Module (Join-Path -Path $PSScriptRoot -ChildPath "DetermineProjectsToBuild.psm1" -Resolve) -DisableNameChecking +#endregion - DownloadAndImportBcContainerHelper -baseFolder $baseFolder - Import-Module (Join-Path -Path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) -DisableNameChecking - Import-Module (Join-Path -Path $PSScriptRoot -ChildPath "DetermineProjectsToBuild.psm1" -Resolve) -DisableNameChecking - #endregion +#region Action: Determine projects to build +Write-Host "::group::Get Modified Files" +$modifiedFiles = @(Get-ModifiedFiles -token $token) +Write-Host "$($modifiedFiles.Count) modified file(s): $($modifiedFiles -join ', ')" +Write-Host "::endgroup::" - $telemetryScope = CreateScope -eventId 'DO0085' -parentTelemetryScopeJson $parentTelemetryScopeJson +Write-Host "::group::Determine Partial Build" +$buildAllProjects = Get-BuildAllProjects -modifiedFiles $modifiedFiles -baseFolder $baseFolder +Write-Host "::endgroup::" - #region Action: Determine projects to build - Write-Host "::group::Get Modified Files" - $modifiedFiles = @(Get-ModifiedFiles -token $token) - Write-Host "$($modifiedFiles.Count) modified file(s): $($modifiedFiles -join ', ')" - Write-Host "::endgroup::" - - Write-Host "::group::Determine Partial Build" - $buildAllProjects = Get-BuildAllProjects -modifiedFiles $modifiedFiles -baseFolder $baseFolder - Write-Host "::endgroup::" - - Write-Host "::group::Determine Baseline Workflow ID" - $baselineWorkflowRunId = 0 #default to 0, which means no baseline workflow run ID is set - if(-not $buildAllProjects) { - $baselineWorkflowRunId = FindLatestSuccessfulCICDRun -repository "$env:GITHUB_REPOSITORY" -branch "$env:GITHUB_BASE_REF" -token $token - } - Write-Host "::endgroup::" - - Write-Host "::group::Get Projects To Build" - $allProjects, $projectsToBuild, $projectDependencies, $buildOrder = Get-ProjectsToBuild -baseFolder $baseFolder -buildAllProjects $buildAllProjects -modifiedFiles $modifiedFiles -maxBuildDepth $maxBuildDepth - AddTelemetryProperty -telemetryScope $telemetryScope -key "projects" -value "$($allProjects -join ', ')" - Write-Host "::endgroup::" - #endregion - - #region Action: Output - $projectsJson = ConvertTo-Json $projectsToBuild -Depth 99 -Compress - $projectDependenciesJson = ConvertTo-Json $projectDependencies -Depth 99 -Compress - $buildOrderJson = ConvertTo-Json $buildOrder -Depth 99 -Compress - - # Set output variables - Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "ProjectsJson=$projectsJson" - Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "ProjectDependenciesJson=$projectDependenciesJson" - Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "BuildOrderJson=$buildOrderJson" - Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "BuildAllProjects=$([int] $buildAllProjects)" - Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "BaselineWorkflowRunId=$baselineWorkflowRunId" - - - Write-Host "ProjectsJson=$projectsJson" - Write-Host "ProjectDependenciesJson=$projectDependenciesJson" - Write-Host "BuildOrderJson=$buildOrderJson" - Write-Host "BuildAllProjects=$buildAllProjects" - Write-Host "BaselineWorkflowRunId=$baselineWorkflowRunId" - #endregion - - TrackTrace -telemetryScope $telemetryScope -} -catch { - if (Get-Module BcContainerHelper) { - TrackException -telemetryScope $telemetryScope -errorRecord $_ - } - throw +Write-Host "::group::Determine Baseline Workflow ID" +$baselineWorkflowRunId = 0 #default to 0, which means no baseline workflow run ID is set +if(-not $buildAllProjects) { + $baselineWorkflowRunId = FindLatestSuccessfulCICDRun -repository "$env:GITHUB_REPOSITORY" -branch "$env:GITHUB_BASE_REF" -token $token } +Write-Host "::endgroup::" + +Write-Host "::group::Get Projects To Build" +$allProjects, $projectsToBuild, $projectDependencies, $buildOrder = Get-ProjectsToBuild -baseFolder $baseFolder -buildAllProjects $buildAllProjects -modifiedFiles $modifiedFiles -maxBuildDepth $maxBuildDepth +Write-Host "::endgroup::" +#endregion + +#region Action: Output +$projectsJson = ConvertTo-Json $projectsToBuild -Depth 99 -Compress +$projectDependenciesJson = ConvertTo-Json $projectDependencies -Depth 99 -Compress +$buildOrderJson = ConvertTo-Json $buildOrder -Depth 99 -Compress + +# Set output variables +Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "ProjectsJson=$projectsJson" +Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "ProjectDependenciesJson=$projectDependenciesJson" +Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "BuildOrderJson=$buildOrderJson" +Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "BuildAllProjects=$([int] $buildAllProjects)" +Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "BaselineWorkflowRunId=$baselineWorkflowRunId" + + +Write-Host "ProjectsJson=$projectsJson" +Write-Host "ProjectDependenciesJson=$projectDependenciesJson" +Write-Host "BuildOrderJson=$buildOrderJson" +Write-Host "BuildAllProjects=$buildAllProjects" +Write-Host "BaselineWorkflowRunId=$baselineWorkflowRunId" +#endregion diff --git a/DetermineProjectsToBuild/README.md b/DetermineProjectsToBuild/README.md index b245266..e391cd1 100644 --- a/DetermineProjectsToBuild/README.md +++ b/DetermineProjectsToBuild/README.md @@ -1,4 +1,5 @@ # Determine projects to build + Scans for AL-Go projects and determines which one to build The action also computes build dimensions, based on the projects and the build modes for each of them @@ -6,21 +7,24 @@ The action also computes build dimensions, based on the projects and the build m ## INPUT ### ENV variables + none ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | -| parentTelemetryScopeJson | | Specifies the parent telemetry scope for the telemetry signal | {} | | maxBuildDepth | | Specifies the maximum build depth suppored by the workflow running the action | 0 | ## OUTPUT ### ENV variables + none ### OUTPUT variables + | Name | Description | | :-- | :-- | | ProjectsJson | An array of AL-Go projects in compressed JSON format | diff --git a/DetermineProjectsToBuild/action.yaml b/DetermineProjectsToBuild/action.yaml index be7e770..8f48f5d 100644 --- a/DetermineProjectsToBuild/action.yaml +++ b/DetermineProjectsToBuild/action.yaml @@ -6,10 +6,6 @@ inputs: description: Shell in which you want to run the action (powershell or pwsh) required: false default: powershell - parentTelemetryScopeJson: - description: Specifies the parent telemetry scope for the telemetry signal - required: false - default: '7b7d' maxBuildDepth: description: Specifies the maximum build depth suppored by the workflow running the action required: false @@ -37,16 +33,10 @@ runs: shell: ${{ inputs.shell }} id: determineProjectsToBuild env: - _parentTelemetryScopeJson: ${{ inputs.parentTelemetryScopeJson }} _maxBuildDepth: ${{ inputs.maxBuildDepth }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { - ${{ github.action_path }}/DetermineProjectsToBuild.Action.ps1 -baseFolder ${{ github.workspace }} -maxBuildDepth $env:_maxBuildDepth -parentTelemetryScopeJson $env:_parentTelemetryScopeJson -token ${{ github.token }} - } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "DetermineProjectsToBuild" -Action { + ${{ github.action_path }}/DetermineProjectsToBuild.Action.ps1 -baseFolder ${{ github.workspace }} -maxBuildDepth $env:_maxBuildDepth -token ${{ github.token }} } branding: icon: terminal diff --git a/DownloadProjectDependencies/README.md b/DownloadProjectDependencies/README.md index 24774fb..4d4b3bb 100644 --- a/DownloadProjectDependencies/README.md +++ b/DownloadProjectDependencies/README.md @@ -1,4 +1,5 @@ # 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 @@ -6,12 +7,14 @@ The action constructs arrays of paths to .app files, that are dependencies of th ## INPUT ### ENV variables + | Name | Description | | :-- | :-- | | Settings | env.Settings must be set by a prior call to the ReadSettings Action | | Secrets | env.Secrets must be read by a prior call to the ReadSecrets Action with appDependencyProbingPathsSecrets in getSecrets | ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | @@ -23,6 +26,7 @@ The action constructs arrays of paths to .app files, that are dependencies of th ## OUTPUT ### ENV variables + | Name | Description | | :-- | :-- | | appFolders | A JSON-formatted array of appFolders | @@ -30,6 +34,7 @@ The action constructs arrays of paths to .app files, that are dependencies of th | bcptTestFolders | A JSON-formatted array of bcptTestFolders | ### OUTPUT variables + | Name | Description | | :-- | :-- | | DownloadedApps | A JSON-formatted list of paths to .app files, that dependencies of the apps | diff --git a/DownloadProjectDependencies/action.yaml b/DownloadProjectDependencies/action.yaml index d21fbcd..51cf139 100644 --- a/DownloadProjectDependencies/action.yaml +++ b/DownloadProjectDependencies/action.yaml @@ -46,14 +46,9 @@ runs: _gitHubToken: ${{ github.token }} _baselineWorkflowRunId: ${{ inputs.baselineWorkflowRunId }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "DownloadProjectDependencies" -Action { ${{ github.action_path }}/DownloadProjectDependencies.Action.ps1 -project $ENV:_project -buildMode $ENV:_buildMode -projectsDependenciesJson $ENV:_projectsDependenciesJson -baselineWorkflowRunId $ENV:_baselineWorkflowRunId -baseFolder $ENV:_baseFolder -destinationPath $ENV:_destinationPath -token $ENV:_gitHubToken } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))" - exit 1 - } branding: icon: terminal color: blue diff --git a/DumpWorkflowInfo/README.md b/DumpWorkflowInfo/README.md index 83a023e..275eefc 100644 --- a/DumpWorkflowInfo/README.md +++ b/DumpWorkflowInfo/README.md @@ -1,15 +1,19 @@ # Dump Workflow Info + Dump workflow info ## INPUT ### ENV variables + none ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | ## OUTPUT + none diff --git a/DumpWorkflowInfo/action.yaml b/DumpWorkflowInfo/action.yaml index 414cfcd..4ffbd58 100644 --- a/DumpWorkflowInfo/action.yaml +++ b/DumpWorkflowInfo/action.yaml @@ -11,14 +11,9 @@ runs: - name: run shell: ${{ inputs.shell }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "DumpWorkflowInfo" -Action { ${{ github.action_path }}/DumpWorkflowInfo.ps1 } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 - } branding: icon: terminal color: blue diff --git a/GetArtifactsForDeployment/README.md b/GetArtifactsForDeployment/README.md index 34eb675..40d153e 100644 --- a/GetArtifactsForDeployment/README.md +++ b/GetArtifactsForDeployment/README.md @@ -1,12 +1,15 @@ # Get artifacts for deployment + Download artifacts for deployment ## INPUT ### ENV variables + none ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | @@ -15,10 +18,13 @@ none | artifactsFolder | Yes | Folder in which the artifacts will be downloaded | | ## OUTPUT + none ### ENV variables + none ### OUTPUT variables + none diff --git a/GetArtifactsForDeployment/action.yaml b/GetArtifactsForDeployment/action.yaml index 4230f72..67e1578 100644 --- a/GetArtifactsForDeployment/action.yaml +++ b/GetArtifactsForDeployment/action.yaml @@ -25,14 +25,9 @@ runs: _artifactsVersion: ${{ inputs.artifactsVersion }} _artifactsFolder: ${{ inputs.artifactsFolder }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "GetArtifactsForDeployment" -Action { ${{ github.action_path }}/GetArtifactsForDeployment.ps1 -token $ENV:_token -artifactsVersion $ENV:_artifactsVersion -artifactsFolder $ENV:_artifactsFolder } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 - } branding: icon: terminal color: blue diff --git a/IncrementVersionNumber/IncrementVersionNumber.ps1 b/IncrementVersionNumber/IncrementVersionNumber.ps1 index a2d4f0c..45cc6e0 100644 --- a/IncrementVersionNumber/IncrementVersionNumber.ps1 +++ b/IncrementVersionNumber/IncrementVersionNumber.ps1 @@ -3,8 +3,6 @@ Param( [string] $actor, [Parameter(HelpMessage = "The GitHub token running the action", Mandatory = $false)] [string] $token, - [Parameter(HelpMessage = "Specifies the parent telemetry scope for the telemetry signal", Mandatory = $false)] - [string] $parentTelemetryScopeJson = '7b7d', [Parameter(HelpMessage = "List of project names if the repository is setup for multiple projects (* for all projects)", Mandatory = $false)] [string] $projects = '*', [Parameter(HelpMessage = "The version to update to. Use Major.Minor for absolute change, use +1 to bump to the next major version, use +0.1 to bump to the next minor version", Mandatory = $true)] @@ -15,97 +13,99 @@ Param( [bool] $directCommit ) -$telemetryScope = $null +. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) +Import-Module (Join-Path -path $PSScriptRoot -ChildPath "IncrementVersionNumber.psm1" -Resolve) -try { - . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) - Import-Module (Join-Path -path $PSScriptRoot -ChildPath "IncrementVersionNumber.psm1" -Resolve) +$serverUrl, $branch = CloneIntoNewFolder -actor $actor -token $token -updateBranch $updateBranch -DirectCommit $directCommit -newBranchPrefix 'increment-version-number' +$baseFolder = (Get-Location).path +DownloadAndImportBcContainerHelper -baseFolder $baseFolder - $serverUrl, $branch = CloneIntoNewFolder -actor $actor -token $token -updateBranch $updateBranch -DirectCommit $directCommit -newBranchPrefix 'increment-version-number' - $baseFolder = (Get-Location).path - DownloadAndImportBcContainerHelper -baseFolder $baseFolder - - Import-Module (Join-Path -path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) - $telemetryScope = CreateScope -eventId 'DO0076' -parentTelemetryScopeJson $parentTelemetryScopeJson - - $settings = $env:Settings | ConvertFrom-Json - if ($versionNumber.StartsWith('+')) { - # Handle incremental version number - $allowedIncrementalVersionNumbers = @('+1', '+0.1') - if (-not $allowedIncrementalVersionNumbers.Contains($versionNumber)) { - throw "Incremental version number $versionNumber is not allowed. Allowed incremental version numbers are: $($allowedIncrementalVersionNumbers -join ', ')" - } +$settings = $env:Settings | ConvertFrom-Json +if ($versionNumber.StartsWith('+')) { + # Handle incremental version number + $allowedIncrementalVersionNumbers = @('+1', '+0.1') + if (-not $allowedIncrementalVersionNumbers.Contains($versionNumber)) { + throw "Incremental version number $versionNumber is not allowed. Allowed incremental version numbers are: $($allowedIncrementalVersionNumbers -join ', ')" } - else { - # Handle absolute version number - $versionNumberFormat = '^\d+\.\d+$' # Major.Minor - if (-not ($versionNumber -match $versionNumberFormat)) { - throw "Version number $versionNumber is not in the correct format. The version number must be in the format Major.Minor (e.g. 1.0 or 1.2)" - } +} +else { + # Handle absolute version number + $versionNumberFormat = '^\d+\.\d+$' # Major.Minor + if (-not ($versionNumber -match $versionNumberFormat)) { + throw "Version number $versionNumber is not in the correct format. The version number must be in the format Major.Minor (e.g. 1.0 or 1.2)" } +} - # Collect all projects (AL and PowerPlatform Solution) - $projectList = @(GetProjectsFromRepository -baseFolder $baseFolder -projectsFromSettings $settings.projects -selectProjects $projects) - $PPprojects = @(GetMatchingProjects -projects @($settings.powerPlatformSolutionFolder) -selectProjects $projects) - if ($projectList.Count -eq 0 -and $PPproject.Count -eq 0) { - throw "No projects matches '$selectProjects'" - } +# Collect all projects (AL and PowerPlatform Solution) +$projectList = @(GetProjectsFromRepository -baseFolder $baseFolder -projectsFromSettings $settings.projects -selectProjects $projects) +$PPprojects = @(GetMatchingProjects -projects @($settings.powerPlatformSolutionFolder) -selectProjects $projects) +if ($projectList.Count -eq 0 -and $PPprojects.Count -eq 0) { + throw "No projects matches '$projects'" +} - # Increment version number in AL Projects - if ($projectList.Count -gt 0) { - $allAppFolders = @() - foreach($project in $projectList) { - $projectPath = Join-Path $baseFolder $project - $projectSettingsPath = Join-Path $projectPath $ALGoSettingsFile # $ALGoSettingsFile is defined in AL-Go-Helper.ps1 - $settings = ReadSettings -baseFolder $baseFolder -project $project +$repositorySettingsPath = Join-Path $baseFolder $RepoSettingsFile # $RepoSettingsFile is defined in AL-Go-Helper.ps1 - # Ensure the repoVersion setting exists in the project settings. Defaults to 1.0 if it doesn't exist. - Set-VersionInSettingsFile -settingsFilePath $projectSettingsPath -settingName 'repoVersion' -newValue $settings.repoVersion -Force +# Increment version number in AL Projects +if ($projectList.Count -gt 0) { + $allAppFolders = @() + $repoVersionExistsInRepoSettings = Test-SettingExists -settingsFilePath $repositorySettingsPath -settingName 'repoVersion' + $repoVersionInRepoSettingsWasUpdated = $false + foreach ($project in $projectList) { + $projectPath = Join-Path $baseFolder $project + $projectSettingsPath = Join-Path $projectPath $ALGoSettingsFile # $ALGoSettingsFile is defined in AL-Go-Helper.ps1 - # Set repoVersion in project settings according to the versionNumber parameter + if (Test-SettingExists -settingsFilePath $projectSettingsPath -settingName 'repoVersion') { + # If 'repoVersion' exists in the project settings, update it there Set-VersionInSettingsFile -settingsFilePath $projectSettingsPath -settingName 'repoVersion' -newValue $versionNumber - - # Resolve project folders to get all app folders that contain an app.json file - $projectSettings = ReadSettings -baseFolder $baseFolder -project $project - ResolveProjectFolders -baseFolder $baseFolder -project $project -projectSettings ([ref] $projectSettings) - - # Set version in app manifests (app.json files) - Set-VersionInAppManifests -projectPath $projectPath -projectSettings $projectSettings -newValue $versionNumber - - # Collect all project's app folders - $allAppFolders += $projectSettings.appFolders | ForEach-Object { Join-Path $projectPath $_ -Resolve } - $allAppFolders += $projectSettings.testFolders | ForEach-Object { Join-Path $projectPath $_ -Resolve } - $allAppFolders += $projectSettings.bcptTestFolders | ForEach-Object { Join-Path $projectPath $_ -Resolve } } - - # Set dependencies in app manifests - if($allAppFolders.Count -eq 0) { - Write-Host "No App folders found for projects $projects" + elseif ($repoVersionExistsInRepoSettings) { + # If 'repoVersion' is not found in project settings but it exists in repo settings, update it there instead + if (-not $repoVersionInRepoSettingsWasUpdated) { + Write-Host "Setting 'repoVersion' not found in $projectSettingsPath. Updating it on repo level instead" + Set-VersionInSettingsFile -settingsFilePath $repositorySettingsPath -settingName 'repoVersion' -newValue $versionNumber + $repoVersionInRepoSettingsWasUpdated = $true + } } else { - # Set dependencies in app manifests - Set-DependenciesVersionInAppManifests -appFolders $allAppFolders + # If 'repoVersion' is neither found in project settings nor in repo settings, force create it in project settings + # Ensure the repoVersion setting exists in the project settings. Defaults to 1.0 if it doesn't exist. + $settings = ReadSettings -baseFolder $baseFolder -project $project + Set-VersionInSettingsFile -settingsFilePath $projectSettingsPath -settingName 'repoVersion' -newValue $settings.repoVersion -Force + Set-VersionInSettingsFile -settingsFilePath $projectSettingsPath -settingName 'repoVersion' -newValue $versionNumber } - } - # Increment version number in PowerPlatform Solution - foreach($PPproject in $PPprojects) { - $projectPath = Join-Path $baseFolder $PPproject - Set-PowerPlatformSolutionVersion -powerPlatformSolutionPath $projectPath -newValue $versionNumber - } + # Resolve project folders to get all app folders that contain an app.json file + $projectSettings = ReadSettings -baseFolder $baseFolder -project $project + ResolveProjectFolders -baseFolder $baseFolder -project $project -projectSettings ([ref] $projectSettings) + + # Set version in app manifests (app.json files) + Set-VersionInAppManifests -projectPath $projectPath -projectSettings $projectSettings -newValue $versionNumber - $commitMessage = "New Version number $versionNumber" - if ($versionNumber.StartsWith('+')) { - $commitMessage = "Incremented Version number by $versionNumber" + # Collect all project's app folders + $allAppFolders += $projectSettings.appFolders | ForEach-Object { Join-Path $projectPath $_ -Resolve } + $allAppFolders += $projectSettings.testFolders | ForEach-Object { Join-Path $projectPath $_ -Resolve } + $allAppFolders += $projectSettings.bcptTestFolders | ForEach-Object { Join-Path $projectPath $_ -Resolve } } - CommitFromNewFolder -serverUrl $serverUrl -commitMessage $commitMessage -branch $branch | Out-Null + # Set dependencies in app manifests + if ($allAppFolders.Count -eq 0) { + Write-Host "No App folders found for projects $projects" + } + else { + # Set dependencies in app manifests + Set-DependenciesVersionInAppManifests -appFolders $allAppFolders + } +} - TrackTrace -telemetryScope $telemetryScope +# Increment version number in PowerPlatform Solution +foreach ($PPproject in $PPprojects) { + $projectPath = Join-Path $baseFolder $PPproject + Set-PowerPlatformSolutionVersion -powerPlatformSolutionPath $projectPath -newValue $versionNumber } -catch { - if (Get-Module BcContainerHelper) { - TrackException -telemetryScope $telemetryScope -errorRecord $_ - } - throw + +$commitMessage = "New Version number $versionNumber" +if ($versionNumber.StartsWith('+')) { + $commitMessage = "Incremented Version number by $versionNumber" } + +CommitFromNewFolder -serverUrl $serverUrl -commitMessage $commitMessage -branch $branch | Out-Null diff --git a/IncrementVersionNumber/IncrementVersionNumber.psm1 b/IncrementVersionNumber/IncrementVersionNumber.psm1 index 1e4578c..0a0bf77 100644 --- a/IncrementVersionNumber/IncrementVersionNumber.psm1 +++ b/IncrementVersionNumber/IncrementVersionNumber.psm1 @@ -124,6 +124,40 @@ function Set-VersionInSettingsFile { $settingsJson | Set-JsonContentLF -Path $settingsFilePath } +<# + .Synopsis + Checks if a setting exists in a settings file. + .Description + Checks if a setting exists in a settings file. + .Parameter settingsFilePath + Path to a JSON file containing the settings. + .Parameter settingName + Name of the setting to check. +#> +function Test-SettingExists { + param( + [Parameter(Mandatory = $true)] + [string] $settingsFilePath, + [Parameter(Mandatory = $true)] + [string] $settingName + ) + + if (-not (Test-Path $settingsFilePath)) { + throw "Settings file ($settingsFilePath) not found." + } + + Write-Host "Reading settings from $settingsFilePath" + try { + $settingsJson = Get-Content $settingsFilePath -Encoding UTF8 -Raw | ConvertFrom-Json + } + catch { + throw "Settings file ($settingsFilePath) is malformed: $_" + } + + $settingExists = [bool] ($settingsJson.PSObject.Properties.Name -eq $settingName) + return $settingExists +} + <# .Synopsis Changes the version number of a project. @@ -258,4 +292,4 @@ function Set-PowerPlatformSolutionVersion { } } -Export-ModuleMember -Function Set-VersionInSettingsFile, Set-VersionInAppManifests, Set-DependenciesVersionInAppManifests, Set-PowerPlatformSolutionVersion +Export-ModuleMember -Function Set-VersionInSettingsFile, Set-VersionInAppManifests, Set-DependenciesVersionInAppManifests, Set-PowerPlatformSolutionVersion, Test-SettingExists diff --git a/IncrementVersionNumber/README.md b/IncrementVersionNumber/README.md index cdfcaaf..017ff24 100644 --- a/IncrementVersionNumber/README.md +++ b/IncrementVersionNumber/README.md @@ -1,24 +1,27 @@ # Increment version number + Increment version number in AL-Go repository ## INPUT ### ENV variables + | Name | Description | | :-- | :-- | | Settings | env.Settings must be set by a prior call to the ReadSettings Action | ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | | actor | | The GitHub actor running the action | github.actor | | token | | The GitHub token running the action | github.token | -| parentTelemetryScopeJson | | Specifies the parent telemetry scope for the telemetry signal | {} | -| projects | | List of project names if the repository is setup for multiple projects (* for all projects) | * | +| projects | | List of project names if the repository is setup for multiple projects (\* for all projects) | * | | versionNumber | Yes | The version to update to. Use Major.Minor for absolute change, use +1 to bump to the next major version, use +0.1 to bump to the next minor version | | | updateBranch | | Which branch should the app be added to | github.ref_name | | directCommit | | true if the action should create a direct commit against the branch or false to create a Pull Request | false | ## OUTPUT + none diff --git a/IncrementVersionNumber/action.yaml b/IncrementVersionNumber/action.yaml index 58866ff..888dd12 100644 --- a/IncrementVersionNumber/action.yaml +++ b/IncrementVersionNumber/action.yaml @@ -16,10 +16,6 @@ inputs: description: The GitHub token running the action required: false default: ${{ github.token }} - parentTelemetryScopeJson: - description: Specifies the parent telemetry scope for the telemetry signal - required: false - default: '7b7d' projects: description: List of project names if the repository is setup for multiple projects (* for all projects) required: false @@ -43,19 +39,13 @@ runs: env: _actor: ${{ inputs.actor }} _token: ${{ inputs.token }} - _parentTelemetryScopeJson: ${{ inputs.parentTelemetryScopeJson }} _projects: ${{ inputs.projects }} _versionNumber: ${{ inputs.versionNumber }} _updateBranch: ${{ inputs.updateBranch }} _directCommit: ${{ inputs.directCommit }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { - ${{ github.action_path }}/IncrementVersionNumber.ps1 -actor $ENV:_actor -token $ENV:_token -parentTelemetryScopeJson $ENV:_parentTelemetryScopeJson -projects $ENV:_projects -versionNumber $ENV:_versionNumber -updateBranch $ENV:_updateBranch -directCommit ($ENV:_directCommit -eq 'true') - } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "IncrementVersionNumber" -Action { + ${{ github.action_path }}/IncrementVersionNumber.ps1 -actor $ENV:_actor -token $ENV:_token -projects $ENV:_projects -versionNumber $ENV:_versionNumber -updateBranch $ENV:_updateBranch -directCommit ($ENV:_directCommit -eq 'true') } branding: icon: terminal diff --git a/Invoke-AlGoAction.ps1 b/Invoke-AlGoAction.ps1 new file mode 100644 index 0000000..7cfd29c --- /dev/null +++ b/Invoke-AlGoAction.ps1 @@ -0,0 +1,30 @@ +param( + [Parameter(Mandatory = $true)] + [string] $ActionName, + [Parameter(Mandatory = $true)] + [scriptblock]$Action, + [Parameter(Mandatory = $false)] + [switch]$SkipTelemetry +) + +$errorActionPreference = "Stop" +$progressPreference = "SilentlyContinue" +Set-StrictMode -Version 2.0 + +Import-Module (Join-Path -Path $PSScriptRoot -ChildPath "TelemetryHelper.psm1" -Resolve) + +try { + Invoke-Command -ScriptBlock $Action + + if (-not $SkipTelemetry) { + Trace-Information -ActionName $ActionName + } +} +catch { + if (-not $SkipTelemetry) { + Trace-Exception -ActionName $ActionName -ErrorRecord $_ + } + + Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; + exit 1 +} diff --git a/Packages.json b/Packages.json index 62f0680..3eeff0d 100644 --- a/Packages.json +++ b/Packages.json @@ -1,3 +1,7 @@ { - "sign": "0.9.1-beta.24123.2" + "sign": "0.9.1-beta.24123.2", + "Microsoft.ApplicationInsights": "2.20.0", + "Az.Accounts": "2.15.1", + "Az.Storage": "6.1.1", + "Az.KeyVault": "5.2.0" } diff --git a/PipelineCleanup/PipelineCleanup.ps1 b/PipelineCleanup/PipelineCleanup.ps1 index 7eeca36..2485174 100644 --- a/PipelineCleanup/PipelineCleanup.ps1 +++ b/PipelineCleanup/PipelineCleanup.ps1 @@ -1,29 +1,12 @@ Param( [Parameter(HelpMessage = "Project folder", Mandatory = $false)] - [string] $project = ".", - [Parameter(HelpMessage = "Specifies the parent telemetry scope for the telemetry signal", Mandatory = $false)] - [string] $parentTelemetryScopeJson = '7b7d' + [string] $project = "." ) -$telemetryScope = $null +. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) +DownloadAndImportBcContainerHelper -try { - . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) - DownloadAndImportBcContainerHelper +if ($project -eq ".") { $project = "" } - import-module (Join-Path -path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) - $telemetryScope = CreateScope -eventId 'DO0077' -parentTelemetryScopeJson $parentTelemetryScopeJson - - if ($project -eq ".") { $project = "" } - - $containerName = GetContainerName($project) - Remove-Bccontainer $containerName - - TrackTrace -telemetryScope $telemetryScope -} -catch { - if (Get-Module BcContainerHelper) { - TrackException -telemetryScope $telemetryScope -errorRecord $_ - } - throw -} +$containerName = GetContainerName($project) +Remove-Bccontainer $containerName diff --git a/PipelineCleanup/README.md b/PipelineCleanup/README.md index cc43a11..b9e525f 100644 --- a/PipelineCleanup/README.md +++ b/PipelineCleanup/README.md @@ -1,17 +1,20 @@ # Pipeline Cleanup + Perform cleanup after running pipeline in AL-Go repository ## INPUT ### ENV variables + none ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | -| parentTelemetryScopeJson | | Specifies the parent telemetry scope for the telemetry signal | {} | | project | | Project name if the repository is setup for multiple projects | . | ## OUTPUT + none diff --git a/PipelineCleanup/action.yaml b/PipelineCleanup/action.yaml index fb085fd..411d7bd 100644 --- a/PipelineCleanup/action.yaml +++ b/PipelineCleanup/action.yaml @@ -9,10 +9,6 @@ inputs: description: Project folder required: false default: '.' - parentTelemetryScopeJson: - description: Specifies the parent telemetry scope for the telemetry signal - required: false - default: '7b7d' runs: using: composite steps: @@ -20,15 +16,9 @@ runs: shell: ${{ inputs.shell }} env: _project: ${{ inputs.project }} - _parentTelemetryScopeJson: ${{ inputs.parentTelemetryScopeJson }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { - ${{ github.action_path }}/PipelineCleanup.ps1 -project $ENV:_project -parentTelemetryScopeJson $ENV:_parentTelemetryScopeJson - } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "PipelineCleanup" -Action { + ${{ github.action_path }}/PipelineCleanup.ps1 -project $ENV:_project } branding: icon: terminal diff --git a/PullPowerPlatformChanges/README.md b/PullPowerPlatformChanges/README.md index 5bfa3a1..4230494 100644 --- a/PullPowerPlatformChanges/README.md +++ b/PullPowerPlatformChanges/README.md @@ -1,15 +1,18 @@ # Pull Power Platform Changes + Pull the Power Platform solution from the specified Power Platform environment ## INPUT ### ENV variables + | Name | Description | | :-- | :-- | | Settings | env.Settings must be set by a prior call to the ReadSettings Action | | Secrets | env.Secrets with delivery target context secrets must be read by a prior call to the ReadSecrets Action | ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | @@ -21,10 +24,10 @@ Pull the Power Platform solution from the specified Power Platform environment | updateBranch | | The branch to update | github.ref_name | | directCommit | | true if the action should create a direct commit against the branch or false to create a Pull Request | false | - Either artifactsFolder or solutionFolder needs to be specified ## OUTPUT + | Name | Description | | :-- | :-- | | environmentUrl | The URL for the environment. This URL is presented in the Deploy Step in summary under the environment name | diff --git a/PullPowerPlatformChanges/action.yaml b/PullPowerPlatformChanges/action.yaml index 38f0600..dc8118d 100644 --- a/PullPowerPlatformChanges/action.yaml +++ b/PullPowerPlatformChanges/action.yaml @@ -43,14 +43,9 @@ runs: actionsRepo: ${{ github.action_repository }} actionsRef: ${{ github.action_ref }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "SetActionsRepoAndRef" -SkipTelemetry -Action { ${{ github.action_path }}/../SetActionsRepoAndRef.ps1 -actionsRepo $ENV:actionsRepo -actionsRef $ENV:actionsRef } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 - } - name: Check out AL-Go Actions uses: actions/checkout@v4 @@ -64,7 +59,7 @@ runs: uses: ./_AL-Go/Actions/ReadPowerPlatformSettings with: shell: ${{ inputs.shell }} - deploymentEnvironmentsJson: ${{ inputs.deploymentEnvironmentsJson }} + deploymentEnvironmentsJson: ${{ inputs.deploymentEnvironmentsJson }} environmentName: ${{ inputs.environmentName }} - name: Set up new branch for changes @@ -75,14 +70,9 @@ runs: _updateBranch: ${{ inputs.updateBranch }} _directCommit: ${{ inputs.directCommit }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "GitCloneReponsitory" -Action { ${{ github.action_path }}/GitCloneReponsitory.ps1 -actor $ENV:_actor -token $ENV:_token -updateBranch $ENV:_updateBranch -directCommit ($ENV:_directCommit -eq 'true') } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 - } - name: Export Solution (username) if: steps.ReadPowerPlatformSettings.outputs.ppUserName != '' @@ -128,14 +118,9 @@ runs: _solutionFolder: ${{ inputs.solutionFolder }} _environmentName: ${{ inputs.environmentName }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "GitCommitChanges" -Action { ${{ github.action_path }}/GitCommitChanges.ps1 -Actor $ENV:_actor -Token $ENV:_token -PowerPlatformSolutionName $ENV:_solutionFolder -EnvironmentName $ENV:_environmentName -Location $ENV:clonedRepoPath -ServerUrl $ENV:serverUrl -GitHubBranch $ENV:gitHubBranch } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 - } branding: icon: terminal diff --git a/PullRequestStatusCheck/PullRequestStatusCheck.ps1 b/PullRequestStatusCheck/PullRequestStatusCheck.ps1 index a5a77e0..e97b91f 100644 --- a/PullRequestStatusCheck/PullRequestStatusCheck.ps1 +++ b/PullRequestStatusCheck/PullRequestStatusCheck.ps1 @@ -8,7 +8,7 @@ function PullRequestStatusCheck() ) Write-Host "Checking PR Build status for run $RunId in repository $Repository" - $workflowJobs = gh api /repos/$Repository/actions/runs/$RunId/jobs -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" | ConvertFrom-Json + $workflowJobs = gh api /repos/$Repository/actions/runs/$RunId/jobs --paginate -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" | ConvertFrom-Json $failedJobs = $workflowJobs.jobs | Where-Object { $_.conclusion -eq "failure" } if ($failedJobs) { diff --git a/PullRequestStatusCheck/README.md b/PullRequestStatusCheck/README.md index 0da5d82..cb761b2 100644 --- a/PullRequestStatusCheck/README.md +++ b/PullRequestStatusCheck/README.md @@ -1,17 +1,21 @@ # Pull Request Status Check + Check the status of a pull request build and fail the build if any jobs have failed. ## INPUT ### ENV variables + | Name | Description | | :-- | :-- | | GITHUB_TOKEN | GITHUB_TOKEN must be set as an environment variable when calling this action | ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | ## OUTPUT + none diff --git a/PullRequestStatusCheck/action.yaml b/PullRequestStatusCheck/action.yaml index e579d73..859b0b3 100644 --- a/PullRequestStatusCheck/action.yaml +++ b/PullRequestStatusCheck/action.yaml @@ -11,14 +11,9 @@ runs: - name: run shell: ${{ inputs.shell }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "PullRequestStatusCheck" -Action { ${{ github.action_path }}/PullRequestStatusCheck.ps1 } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 - } branding: icon: terminal color: blue diff --git a/README.md b/README.md index c2d11a4..22c434b 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # AL-Go Actions + This repository contains a set of GitHub actions used by the AL-Go for GitHub templates. Please go to https://aka.ms/AL-Go to learn more. ## Actions + | Name | Description | | :-- | :-- | | [Add existing app](AddExistingApp) | Add an existing app to an AL-Go for GitHub repository | diff --git a/ReadPowerPlatformSettings/README.md b/ReadPowerPlatformSettings/README.md index 199bb0d..c5f7324 100644 --- a/ReadPowerPlatformSettings/README.md +++ b/ReadPowerPlatformSettings/README.md @@ -1,15 +1,18 @@ # Read Power Platform Settings + Read settings for Power Platform deployment from settings and secrets ## INPUT ### ENV variables + | Name | Description | | :-- | :-- | | Settings | env.Settings must be set by a prior call to the ReadSettings Action | | Secrets | env.Secrets with delivery target context secrets must be read by a prior call to the ReadSecrets Action | ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | @@ -19,9 +22,11 @@ Read settings for Power Platform deployment from settings and secrets ## OUTPUT ### ENV variables + none ### OUTPUT variables + | Name | Description | | :-- | :-- | | ppEnvironmentUrl | Power Platform Environment URL | diff --git a/ReadPowerPlatformSettings/action.yaml b/ReadPowerPlatformSettings/action.yaml index 5b5b0a5..bbfabe9 100644 --- a/ReadPowerPlatformSettings/action.yaml +++ b/ReadPowerPlatformSettings/action.yaml @@ -46,14 +46,9 @@ runs: _deploymentEnvironmentsJson: ${{ inputs.deploymentEnvironmentsJson }} _environmentName: ${{ inputs.environmentName }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "ReadPowerPlatformSettings" -Action { ${{ github.action_path }}/ReadPowerPlatformSettings.ps1 -deploymentEnvironmentsJson $ENV:_deploymentEnvironmentsJson -environmentName $ENV:_environmentName } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 - } branding: icon: terminal color: blue diff --git a/ReadSecrets/README.md b/ReadSecrets/README.md index 4dfddd8..f2aff30 100644 --- a/ReadSecrets/README.md +++ b/ReadSecrets/README.md @@ -1,18 +1,21 @@ # Read secrets + Read secrets from GitHub secrets or Azure Keyvault for AL-Go workflows The secrets read and added to the output are the secrets specified in the getSecrets parameter Additionally, the secrets specified by the authToken secret in AppDependencyProbingPaths are read if appDependencyProbingPathsSecrets is specified in getSecrets All secrets included in the Secrets output are Base64 encoded to avoid issues with national characters -Secrets, which name is preceded by an asterisk (*) are encrypted and Base64 encoded +Secrets, which name is preceded by an asterisk (\*) are encrypted and Base64 encoded ## INPUT ### ENV variables + | Name | Description | | :-- | :-- | | Settings | env.Settings must be set by a prior call to the ReadSettings Action | ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | @@ -23,10 +26,12 @@ Secrets, which name is preceded by an asterisk (*) are encrypted and Base64 enco ## OUTPUT ### ENV variables + none ### OUTPUT variables + | Name | Description | | :-- | :-- | -| Secrets | A compressed json construct with all requested secrets base64 encoded. Secrets preceded by an asterisk (*) are encrypted before base64 encoding. The secret value + the base64 value of the secret value are masked in the log | +| Secrets | A compressed json construct with all requested secrets base64 encoded. Secrets preceded by an asterisk (\*) are encrypted before base64 encoding. The secret value + the base64 value of the secret value are masked in the log | | TokenForPush | The token to use when workflows are pushing changes (either directly, or via pull requests). This is either the GITHUB_TOKEN or the GhTokenWorkflow secret (based on the env variable useGhTokenWorkflowForPush) | diff --git a/ReadSecrets/ReadSecrets.ps1 b/ReadSecrets/ReadSecrets.ps1 index d417dbd..d0008fb 100644 --- a/ReadSecrets/ReadSecrets.ps1 +++ b/ReadSecrets/ReadSecrets.ps1 @@ -100,6 +100,18 @@ try { MaskValue -key "$($secretName).$($keyName)" -value "$($json."$keyName")" } } + if ($json.ContainsKey('clientID') -and !($json.ContainsKey('clientSecret') -or $json.ContainsKey('refreshToken'))) { + try { + Write-Host "Query federated token" + $result = Invoke-RestMethod -Method GET -UseBasicParsing -Headers @{ "Authorization" = "bearer $ENV:ACTIONS_ID_TOKEN_REQUEST_TOKEN"; "Accept" = "application/vnd.github+json" } -Uri "$ENV:ACTIONS_ID_TOKEN_REQUEST_URL&audience=api://AzureADTokenExchange" + $json += @{ "clientAssertion" = $result.value } + $secretValue = $json | ConvertTo-Json -Compress + MaskValue -key "$secretName with federated token" -value $secretValue + } + catch { + throw "$SecretName doesn't contain any ClientSecret and AL-Go is unable to acquire an ID_TOKEN. Error was $($_.Exception.Message)" + } + } } $base64value = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($secretValue)) $outSecrets += @{ "$secretsProperty" = $base64value } diff --git a/ReadSecrets/ReadSecretsHelper.psm1 b/ReadSecrets/ReadSecretsHelper.psm1 index f7b5e88..4f7b480 100644 --- a/ReadSecrets/ReadSecretsHelper.psm1 +++ b/ReadSecrets/ReadSecretsHelper.psm1 @@ -4,13 +4,26 @@ Param( ) $script:gitHubSecrets = $_gitHubSecrets | ConvertFrom-Json -$script:keyvaultConnectionExists = $false -$script:azureRm210 = $false -$script:isKeyvaultSet = $script:gitHubSecrets.PSObject.Properties.Name -eq "AZURE_CREDENTIALS" $script:escchars = @(' ','!','\"','#','$','%','\u0026','\u0027','(',')','*','+',',','-','.','/','0','1','2','3','4','5','6','7','8','9',':',';','\u003c','=','\u003e','?','@','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','[','\\',']','^','_',[char]96,'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','{','|','}','~') -function IsKeyVaultSet { - return $script:isKeyvaultSet +. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) + +function GetAzureCredentialsSecretName { + $settings = $env:Settings | ConvertFrom-Json + if ($settings.PSObject.Properties.Name -eq "AZURE_CREDENTIALSSecretName") { + return $settings.AZURE_CREDENTIALSSecretName + } + else { + return "AZURE_CREDENTIALS" + } +} + +function GetAzureCredentials { + $secretName = GetAzureCredentialsSecretName + if ($script:gitHubSecrets.PSObject.Properties.Name -eq $secretName) { + return $script:gitHubSecrets."$secretName" + } + return $null } function MaskValue { @@ -71,23 +84,23 @@ function GetGithubSecret { function GetKeyVaultCredentials { $creds = $null - if ($script:isKeyvaultSet) { - $jsonStr = $script:gitHubSecrets.AZURE_CREDENTIALS + $jsonStr = GetAzureCredentials + if ($jsonStr) { if ($jsonStr -contains "`n" -or $jsonStr -contains "`r") { - throw "Secret AZURE_CREDENTIALS cannot contain line breaks, needs to be formatted as compressed JSON (no line breaks)" + throw "Secret for Azure KeyVault Connection ($(GetAzureCredentialsSecretName)) cannot contain line breaks, needs to be formatted as compressed JSON (no line breaks)" } try { $creds = $jsonStr | ConvertFrom-Json - # Mask ClientSecret - MaskValue -key 'clientSecret' -value $creds.ClientSecret - $creds.ClientSecret = ConvertTo-SecureString $creds.ClientSecret -AsPlainText -Force + if ($creds.PSObject.Properties.Name -eq 'ClientSecret' -and $creds.ClientSecret) { + # Mask ClientSecret + MaskValue -key 'ClientSecret' -value $creds.ClientSecret + } # Check thet $creds contains the needed properties $creds.ClientId | Out-Null - $creds.subscriptionId | Out-Null $creds.TenantId | Out-Null } catch { - throw "Secret AZURE_CREDENTIALS is wrongly formatted. Needs to be formatted as compressed JSON (no line breaks) and contain at least the properties: clientId, clientSecret, tenantId and subscriptionId." + throw "Secret for Azure KeyVault Connection ($(GetAzureCredentialsSecretName)) is wrongly formatted. Needs to be formatted as compressed JSON (no line breaks) and contain at least the properties: clientId, clientSecret, tenantId and subscriptionId." } $keyVaultNameExists = $creds.PSObject.Properties.Name -eq 'keyVaultName' $settings = $env:Settings | ConvertFrom-Json @@ -105,76 +118,17 @@ function GetKeyVaultCredentials { $creds = $null } } - return $creds -} - -function InstallKeyVaultModuleIfNeeded { - if ($isWindows -and (Test-Path 'C:\Modules\az_*')) { - $azModulesPath = Get-ChildItem 'C:\Modules\az_*' | Where-Object { $_.PSIsContainer } - if ($azModulesPath) { - Write-Host $azModulesPath.FullName - $ENV:PSModulePath = "$($azModulesPath.FullName);$(("$ENV:PSModulePath".Split(';') | Where-Object { $_ -notlike 'C:\\Modules\Azure*' }) -join ';')" - } - } - - $azKeyVaultModule = Get-Module -name 'Az.KeyVault' -ListAvailable | Select-Object -First 1 - if ($azKeyVaultModule) { - Write-Host "Az.KeyVault Module is available in version $($azKeyVaultModule.Version)" - Write-Host "Using Az.KeyVault version $($azKeyVaultModule.Version)" - Import-Module 'Az.KeyVault' -DisableNameChecking -WarningAction SilentlyContinue | Out-Null - } - else { - $AzKeyVaultModule = Get-InstalledModule -Name 'Az.KeyVault' -ErrorAction SilentlyContinue - if ($AzKeyVaultModule) { - Write-Host "Az.KeyVault version $($AzKeyVaultModule.Version) is installed" - Import-Module 'Az.KeyVault' -DisableNameChecking -WarningAction SilentlyContinue - } - else { - $azureRmKeyVaultModule = Get-Module -name 'AzureRm.KeyVault' -ListAvailable | Select-Object -First 1 - if ($azureRmKeyVaultModule) { Write-Host "AzureRm.KeyVault Module is available in version $($azureRmKeyVaultModule.Version)" } - $azureRmProfileModule = Get-Module -name 'AzureRm.Profile' -ListAvailable | Select-Object -First 1 - if ($azureRmProfileModule) { Write-Host "AzureRm.Profile Module is available in version $($azureRmProfileModule.Version)" } - if ($azureRmKeyVaultModule -and $azureRmProfileModule -and "$($azureRmKeyVaultModule.Version)" -eq "2.1.0" -and "$($azureRmProfileModule.Version)" -eq "2.1.0") { - Write-Host "Using AzureRM version 2.1.0" - $script:azureRm210 = $true - $azureRmKeyVaultModule | Import-Module -WarningAction SilentlyContinue - $azureRmProfileModule | Import-Module -WarningAction SilentlyContinue - Disable-AzureRmDataCollection -WarningAction SilentlyContinue - } - else { - Write-Host "Installing and importing Az.KeyVault." - Install-Module 'Az.KeyVault' -Force - Import-Module 'Az.KeyVault' -DisableNameChecking -WarningAction SilentlyContinue | Out-Null - } - } - } -} - -function ConnectAzureKeyVault { - param( - [string] $subscriptionId, - [string] $tenantId, - [string] $clientId, - [SecureString] $clientSecret - ) - try { - $credential = New-Object PSCredential -argumentList $clientId, $clientSecret - if ($script:azureRm210) { - Add-AzureRmAccount -ServicePrincipal -Tenant $tenantId -Credential $credential -WarningAction SilentlyContinue | Out-Null - Set-AzureRmContext -SubscriptionId $subscriptionId -Tenant $tenantId -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Out-Null + if ($creds) { + try { + # check that we have access to get secrets from the keyvault by trying to get a dummy secret + GetKeyVaultSecret -secretName 'algodummysecret' -keyVaultCredentials $creds -encrypted | Out-Null } - else { - Clear-AzContext -Scope Process - Clear-AzContext -Scope CurrentUser -Force -ErrorAction SilentlyContinue - Connect-AzAccount -ServicePrincipal -Tenant $tenantId -Credential $credential -WarningAction SilentlyContinue | Out-Null - Set-AzContext -SubscriptionId $subscriptionId -Tenant $tenantId -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Out-Null + catch { + Write-Host "Unable to get secrets from Azure Key Vault. Error was $($_.Exception.Message). Using Github secrets instead." + $creds = $null } - $script:keyvaultConnectionExists = $true - Write-Host "Successfully connected to Azure Key Vault." - } - catch { - throw "Error trying to authenticate to Azure using Az. Error was $($_.Exception.Message)" } + return $creds } function GetKeyVaultSecret { @@ -183,15 +137,11 @@ function GetKeyVaultSecret { [PsCustomObject] $keyVaultCredentials, [switch] $encrypted ) - - if (-not $script:isKeyvaultSet) { + if ($null -eq $keyVaultCredentials) { return $null } - if (-not $script:keyvaultConnectionExists) { - InstallKeyVaultModuleIfNeeded - ConnectAzureKeyVault -subscriptionId $keyVaultCredentials.subscriptionId -tenantId $keyVaultCredentials.tenantId -clientId $keyVaultCredentials.clientId -clientSecret $keyVaultCredentials.clientSecret - } + ConnectAz -azureCredentials $keyVaultCredentials $secretSplit = $secretName.Split('=') $envVar = $secretSplit[0] @@ -199,13 +149,22 @@ function GetKeyVaultSecret { if ($secretSplit.Count -gt 1) { $secret = $secretSplit[1] } + if ($secret.Contains('_')) { + # Secret name contains a '_', which is not allowed in Key Vault secret names + return $null + } $value = $null - if ($script:azureRm210) { - $keyVaultSecret = Get-AzureKeyVaultSecret -VaultName $keyVaultCredentials.keyVaultName -Name $secret -ErrorAction SilentlyContinue + try { + $keyVaultSecret = Get-AzKeyVaultSecret -VaultName $keyVaultCredentials.keyVaultName -Name $secret } - else { - $keyVaultSecret = Get-AzKeyVaultSecret -VaultName $keyVaultCredentials.keyVaultName -Name $secret -ErrorAction SilentlyContinue + catch { + if ($keyVaultCredentials.PSObject.Properties.Name -eq 'ClientSecret') { + throw "Error trying to get secrets from Azure Key Vault. Error was $($_.Exception.Message)" + } + else { + throw "Error trying to get secrets from Azure Key Vault, maybe your Key Vault isn't setup for role based access control?. Error was $($_.Exception.Message)" + } } if ($keyVaultSecret) { if ($encrypted) { diff --git a/ReadSecrets/action.yaml b/ReadSecrets/action.yaml index a4343b2..0626e4c 100644 --- a/ReadSecrets/action.yaml +++ b/ReadSecrets/action.yaml @@ -32,14 +32,9 @@ runs: _getSecrets: ${{ inputs.getSecrets }} _useGhTokenWorkflowForPush: ${{ inputs.useGhTokenWorkflowForPush }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "ReadSecrets" -Action { ${{ github.action_path }}/ReadSecrets.ps1 -gitHubSecrets '${{ inputs.gitHubSecrets }}' -getSecrets $ENV:_getSecrets -useGhTokenWorkflowForPush $ENV:_useGhTokenWorkflowForPush } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 - } branding: icon: terminal color: blue diff --git a/ReadSettings/README.md b/ReadSettings/README.md index 5f76261..d947db0 100644 --- a/ReadSettings/README.md +++ b/ReadSettings/README.md @@ -1,12 +1,15 @@ # Read settings + Read settings for AL-Go workflows ## INPUT ### ENV variables + none ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | @@ -16,14 +19,16 @@ none ## OUTPUT ### ENV variables + | Name | Description | | :-- | :-- | | Settings | A compressed JSON structure with ALL AL-Go settings, independent of the get parameter. If project was not specified, this will only include repository settings. | -> [!NOTE] +> \[!NOTE\] > This method creates individual environment variables for every setting specified in the get parameter. ### OUTPUT variables + | Name | Description | | :-- | :-- | | GitHubRunnerJson | GitHubRunner in compressed Json format | diff --git a/ReadSettings/action.yaml b/ReadSettings/action.yaml index 1f02309..63dfc8e 100644 --- a/ReadSettings/action.yaml +++ b/ReadSettings/action.yaml @@ -30,14 +30,9 @@ runs: _project: ${{ inputs.project }} _get: ${{ inputs.get }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "ReadSettings" -Action { ${{ github.action_path }}/ReadSettings.ps1 -project $ENV:_project -get $ENV:_get } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 - } branding: icon: terminal color: blue diff --git a/RunPipeline/README.md b/RunPipeline/README.md index bd1955e..ceeb216 100644 --- a/RunPipeline/README.md +++ b/RunPipeline/README.md @@ -1,32 +1,36 @@ # Run pipeline + Run pipeline in AL-Go repository ## INPUT ### ENV variables + | Name | Description | | :-- | :-- | | Settings | env.Settings must be set by a prior call to the ReadSettings Action | | Secrets | env.Secrets with licenseFileUrl, codeSignCertificateUrl, codeSignCertificatePassword, keyVaultCertificateUrl, keyVaultCertificatePassword, keyVaultClientId, gitHubPackagesContext, applicationInsightsConnectionString must be read by a prior call to the ReadSecets Action | ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | | token | | The GitHub token running the action | github.token | -| parentTelemetryScopeJson | | Specifies the parent telemetry scope for the telemetry signal | {} | | artifact | | ArtifactUrl to use for the build | settings.artifact | | project | | Project name if the repository is setup for multiple projects | . | | buildMode | | Specifies a mode to use for the build steps | Default | -| installAppsJson | | A JSON-formatted list of apps to install | [] | -| installTestAppsJson | | A JSON-formatted list of test apps to install | [] | +| installAppsJson | | A JSON-formatted list of apps to install | \[\] | +| installTestAppsJson | | A JSON-formatted list of test apps to install | \[\] | ## OUTPUT ## ENV variables + | Name | Description | | :-- | :-- | | containerName | Container name of a container used during build | ## OUTPUT variables + none diff --git a/RunPipeline/RunPipeline.ps1 b/RunPipeline/RunPipeline.ps1 index e943fe8..d1b6980 100644 --- a/RunPipeline/RunPipeline.ps1 +++ b/RunPipeline/RunPipeline.ps1 @@ -1,8 +1,6 @@ Param( [Parameter(HelpMessage = "The GitHub token running the action", Mandatory = $false)] [string] $token, - [Parameter(HelpMessage = "Specifies the parent telemetry scope for the telemetry signal", Mandatory = $false)] - [string] $parentTelemetryScopeJson = '7b7d', [Parameter(HelpMessage = "ArtifactUrl to use for the build", Mandatory = $false)] [string] $artifact = "", [Parameter(HelpMessage = "Project folder", Mandatory = $false)] @@ -15,7 +13,6 @@ Param( [string] $installTestAppsJson = '[]' ) -$telemetryScope = $null $containerBaseFolder = $null $projectPath = $null @@ -23,9 +20,6 @@ try { . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) DownloadAndImportBcContainerHelper - import-module (Join-Path -path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) - $telemetryScope = CreateScope -eventId 'DO0080' -parentTelemetryScopeJson $parentTelemetryScopeJson - if ($isWindows) { # Pull docker image in the background $genericImageName = Get-BestGenericImageName @@ -400,7 +394,6 @@ try { -uninstallRemovedApps if ($containerBaseFolder) { - Write-Host "Copy artifacts and build output back from build container" $destFolder = Join-Path $ENV:GITHUB_WORKSPACE $project Copy-Item -Path (Join-Path $projectPath ".buildartifacts") -Destination $destFolder -Recurse -Force @@ -410,13 +403,8 @@ try { Copy-Item -Path $buildOutputFile -Destination $destFolder -Force -ErrorAction SilentlyContinue Copy-Item -Path $containerEventLogFile -Destination $destFolder -Force -ErrorAction SilentlyContinue } - - TrackTrace -telemetryScope $telemetryScope } catch { - if (Get-Module BcContainerHelper) { - TrackException -telemetryScope $telemetryScope -errorRecord $_ - } throw } finally { diff --git a/RunPipeline/action.yaml b/RunPipeline/action.yaml index 65448da..5e137d4 100644 --- a/RunPipeline/action.yaml +++ b/RunPipeline/action.yaml @@ -9,10 +9,6 @@ inputs: description: The GitHub token running the action required: false default: ${{ github.token }} - parentTelemetryScopeJson: - description: Specifies the parent telemetry scope for the telemetry signal - required: false - default: '7b7d' artifact: description: ArtifactUrl to use for the build required: false @@ -40,20 +36,14 @@ runs: shell: ${{ inputs.shell }} env: _token: ${{ inputs.token }} - _parentTelemetryScopeJson: ${{ inputs.parentTelemetryScopeJson }} _artifact: ${{ inputs.artifact }} _project: ${{ inputs.project }} _buildMode: ${{ inputs.buildMode }} _installAppsJson: ${{ inputs.installAppsJson }} _installTestAppsJson: ${{ inputs.installTestAppsJson }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { - ${{ github.action_path }}/RunPipeline.ps1 -token $ENV:_token -parentTelemetryScopeJson $ENV:_parentTelemetryScopeJson -artifact $ENV:_artifact -project $ENV:_project -buildMode $ENV:_buildMode -installAppsJson $ENV:_installAppsJson -installTestAppsJson $ENV:_installTestAppsJson - } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "RunPipeline" -Action { + ${{ github.action_path }}/RunPipeline.ps1 -token $ENV:_token -artifact $ENV:_artifact -project $ENV:_project -buildMode $ENV:_buildMode -installAppsJson $ENV:_installAppsJson -installTestAppsJson $ENV:_installTestAppsJson } branding: icon: terminal diff --git a/SECURITY.md b/SECURITY.md index a050f36..9657262 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,7 +4,7 @@ Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). -If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](), please report it to us as described below. ## Reporting Security Issues @@ -18,13 +18,13 @@ You should receive a response within 24 hours. If for some reason you do not, pl Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: - * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) - * Full paths of source file(s) related to the manifestation of the issue - * The location of the affected source code (tag/branch/commit or direct URL) - * Any special configuration required to reproduce the issue - * Step-by-step instructions to reproduce the issue - * Proof-of-concept or exploit code (if possible) - * Impact of the issue, including how an attacker might exploit the issue +- Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) +- Full paths of source file(s) related to the manifestation of the issue +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- Step-by-step instructions to reproduce the issue +- Proof-of-concept or exploit code (if possible) +- Impact of the issue, including how an attacker might exploit the issue This information will help us triage your report more quickly. diff --git a/Sign/README.md b/Sign/README.md index 2554b78..1927863 100644 --- a/Sign/README.md +++ b/Sign/README.md @@ -1,22 +1,25 @@ # Sign + Sign apps with a certificate stored in Azure Key Vault ## INPUT ### ENV variables + | Name | Description | | :-- | :-- | | Settings | env.Settings must be set by a prior call to the ReadSettings Action | ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | -| parentTelemetryScopeJson | | Specifies the parent telemetry scope for the telemetry signal | {} | -| azureCredentialsJson | Yes | Azure Credentials secret | | +| azureCredentialsJson | Yes | Azure Credentials secret (Base 64 encoded) | | | timestampService | | The URI of the timestamp server | http://timestamp.digicert.com | | digestAlgorithm | | The digest algorithm to use for signing and timestamping | SHA256 | | pathToFiles | Yes | The path to the files to be signed | ## OUTPUT + none diff --git a/Sign/Sign.ps1 b/Sign/Sign.ps1 index 3627e16..4cfe162 100644 --- a/Sign/Sign.ps1 +++ b/Sign/Sign.ps1 @@ -1,71 +1,65 @@ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '', Justification = 'GitHub Secrets are transferred as plain text')] param( - [Parameter(HelpMessage = "Azure Credentials secret", Mandatory = $true)] + [Parameter(HelpMessage = "Azure Credentials secret (Base 64 encoded)", Mandatory = $true)] [string] $AzureCredentialsJson, [Parameter(HelpMessage = "The path to the files to be signed", Mandatory = $true)] [String] $PathToFiles, [Parameter(HelpMessage = "The URI of the timestamp server", Mandatory = $false)] [string] $TimestampService = "http://timestamp.digicert.com", [Parameter(HelpMessage = "The digest algorithm to use for signing and timestamping", Mandatory = $false)] - [string] $digestAlgorithm = "sha256", - [Parameter(HelpMessage = "Specifies the parent telemetry scope for the telemetry signal", Mandatory = $false)] - [string] $ParentTelemetryScopeJson = '7b7d' + [string] $digestAlgorithm = "sha256" ) -$telemetryScope = $null +. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) +Import-Module (Join-Path -Path $PSScriptRoot -ChildPath "Sign.psm1" -Resolve) -try { - . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) - Import-Module (Join-Path -path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) - Import-Module (Join-Path -Path $PSScriptRoot -ChildPath "Sign.psm1" -Resolve) - DownloadAndImportBcContainerHelper - $telemetryScope = CreateScope -eventId 'DO0083' -parentTelemetryScopeJson $ParentTelemetryScopeJson - - $Files = Get-ChildItem -Path $PathToFiles -File | Select-Object -ExpandProperty FullName - if (-not $Files) { - Write-Host "No files to sign. Exiting." - return - } +$Files = Get-ChildItem -Path $PathToFiles -File | Select-Object -ExpandProperty FullName +if (-not $Files) { + Write-Host "No files to sign. Exiting." + return +} - Write-Host "::group::Files to be signed" - $Files | ForEach-Object { - Write-Host "- $_" - } - Write-Host "::endgroup::" +Write-Host "::group::Files to be signed" +$Files | ForEach-Object { + Write-Host "- $_" +} +Write-Host "::endgroup::" - # Get parameters for signing - $AzureCredentials = ConvertFrom-Json $AzureCredentialsJson - $settings = $env:Settings | ConvertFrom-Json - if ($settings.keyVaultName) { - $AzureKeyVaultName = $settings.keyVaultName - } - elseif ($AzureCredentials.PSobject.Properties.name -eq "keyVaultName") { - $AzureKeyVaultName = $AzureCredentials.keyVaultName - } - else { - throw "KeyVaultName is not specified in AzureCredentials nor in settings. Please specify it in one of them." - } - $description = "Signed with AL-Go for GitHub" - $descriptionUrl = "$ENV:GITHUB_SERVER_URL/$ENV:GITHUB_REPOSITORY" +# Get parameters for signing +$AzureCredentials = ConvertFrom-Json ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($AzureCredentialsJson))) +$settings = $env:Settings | ConvertFrom-Json +if ($settings.keyVaultName) { + $AzureKeyVaultName = $settings.keyVaultName +} +elseif ($AzureCredentials.PSobject.Properties.name -eq "keyVaultName") { + $AzureKeyVaultName = $AzureCredentials.keyVaultName +} +else { + throw "KeyVaultName is not specified in AzureCredentials nor in settings. Please specify it in one of them." +} - Write-Host "::group::Signing files" - Invoke-SigningTool -KeyVaultName $AzureKeyVaultName ` - -CertificateName $settings.keyVaultCodesignCertificateName ` - -ClientId $AzureCredentials.clientId ` - -ClientSecret $AzureCredentials.clientSecret ` - -TenantId $AzureCredentials.tenantId ` - -FilesToSign $PathToFiles ` - -Description $description ` - -DescriptionUrl $descriptionUrl ` - -TimestampService $TimestampService ` - -DigestAlgorithm $digestAlgorithm ` - -Verbosity "Information" - Write-Host "::endgroup::" - TrackTrace -telemetryScope $telemetryScope +$AzureCredentialParams = @{ + "ClientId" = $AzureCredentials.clientId + "TenantId" = $AzureCredentials.tenantId } -catch { - if (Get-Module BcContainerHelper) { - TrackException -telemetryScope $telemetryScope -errorRecord $_ +if ($AzureCredentials.PSobject.Properties.name -eq "clientSecret") { + $AzureCredentialParams += @{ + "ClientSecret" = $AzureCredentials.clientSecret } - throw } +InstallAzModuleIfNeeded -name 'Az.Accounts' +ConnectAz -azureCredentials $AzureCredentials + +$description = "Signed with AL-Go for GitHub" +$descriptionUrl = "$ENV:GITHUB_SERVER_URL/$ENV:GITHUB_REPOSITORY" + +Write-Host "::group::Signing files" +Invoke-SigningTool @AzureCredentialParams -KeyVaultName $AzureKeyVaultName ` + -CertificateName $settings.keyVaultCodesignCertificateName ` + -FilesToSign $PathToFiles ` + -Description $description ` + -DescriptionUrl $descriptionUrl ` + -TimestampService $TimestampService ` + -DigestAlgorithm $digestAlgorithm ` + -Verbosity "Information" +Write-Host "::endgroup::" diff --git a/Sign/Sign.psm1 b/Sign/Sign.psm1 index 7cf93a2..f7207d5 100644 --- a/Sign/Sign.psm1 +++ b/Sign/Sign.psm1 @@ -5,22 +5,22 @@ Installs the dotnet signing tool. #> function Install-SigningTool() { - . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) + . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) - # Create folder in temp directory with a unique name - $tempFolder = Join-Path -Path ([System.IO.Path]::GetTempPath()) "SigningTool-$(Get-Random)" + # Create folder in temp directory with a unique name + $tempFolder = Join-Path -Path ([System.IO.Path]::GetTempPath()) "SigningTool-$(Get-Random)" - # Get version of the signing tool - $version = Get-PackageVersion -PackageName "sign" + # Get version of the signing tool + $version = GetPackageVersion -PackageName "sign" - # Install the signing tool in the temp folder - Write-Host "Installing signing tool version $version in $tempFolder" - New-Item -ItemType Directory -Path $tempFolder | Out-Null - dotnet tool install sign --version $version --tool-path $tempFolder | Out-Null + # Install the signing tool in the temp folder + Write-Host "Installing signing tool version $version in $tempFolder" + New-Item -ItemType Directory -Path $tempFolder | Out-Null + dotnet tool install sign --version $version --tool-path $tempFolder | Out-Null - # Return the path to the signing tool - $signingTool = Join-Path -Path $tempFolder "sign.exe" -Resolve - return $signingTool + # Return the path to the signing tool + $signingTool = Join-Path -Path $tempFolder "sign.exe" -Resolve + return $signingTool } <# @@ -28,16 +28,17 @@ function Install-SigningTool() { Signs files in a given path using a certificate from Azure Key Vault. .DESCRIPTION Signs files in a given path using a certificate from Azure Key Vault. + Connection to the Azure Key Vault can be done using a service principal or a managed identity. .PARAMETER KeyVaultName The name of the Azure Key Vault where the certificate is stored. .PARAMETER CertificateName The name of the certificate in the Azure Key Vault. .PARAMETER ClientId - The client ID of the service principal used to authenticate with Azure Key Vault. + [Optional] The client ID of the service principal used to authenticate with Azure Key Vault. If not specified, managed identity will be used. .PARAMETER ClientSecret - The client secret of the service principal used to authenticate with Azure Key Vault. + [Optional] The client secret of the service principal used to authenticate with Azure Key Vault. If not specified, managed identity will be used. .PARAMETER TenantId - The tenant ID of the service principal used to authenticate with Azure Key Vault. + [Optional] The tenant ID of the service principal used to authenticate with Azure Key Vault. If not specified, managed identity will be used. .PARAMETER FilesToSign The path to the file(s) to be signed. Supports wildcards. .PARAMETER Description @@ -51,8 +52,10 @@ function Install-SigningTool() { .PARAMETER Verbosity The verbosity level of the signing tool. .EXAMPLE - Invoke-SigningTool -KeyVaultName "my-key-vault" -CertificateName "my-certificatename" -ClientId "my-client-id" -ClientSecret "my-client-secret" -TenantId "my-tenant-id" + Invoke-SigningTool -KeyVaultName "my-key-vault" -CertificateName "my-certificatename" -ClientId "my-client-id" -ClientSecret "my-client-secret" -TenantId "my-tenant-id" ` -FilesToSign "C:\path\to\files\*.app" -Description "Signed with AL-Go for GitHub" -DescriptionUrl "github.com/myorg/myrepo" + .EXAMPLE + Invoke-SigningTool -KeyVaultName "my-key-vault" -CertificateName "my-certificatename" -FilesToSign "C:\path\to\files\*.app" -Description "Signed with AL-Go for GitHub" -DescriptionUrl "github.com/myorg/myrepo" #> function Invoke-SigningTool() { param( @@ -60,11 +63,11 @@ function Invoke-SigningTool() { [string] $KeyVaultName, [Parameter(Mandatory = $true)] [string] $CertificateName, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $false)] [string] $ClientId, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $false)] [string] $ClientSecret, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $false)] [string] $TenantId, [Parameter(Mandatory = $true)] [string] $FilesToSign, @@ -83,19 +86,36 @@ function Invoke-SigningTool() { $signingToolExe = Install-SigningTool # Sign files - . $signingToolExe code azure-key-vault ` - --azure-key-vault-url "https://$KeyVaultName.vault.azure.net/" ` - --azure-key-vault-certificate $CertificateName ` - --azure-key-vault-client-id $ClientId ` - --azure-key-vault-client-secret $ClientSecret ` - --azure-key-vault-tenant-id $TenantId ` - --description $Description ` - --description-url $DescriptionUrl ` - --file-digest $DigestAlgorithm ` - --timestamp-digest $DigestAlgorithm ` - --timestamp-url $TimestampService ` - --verbosity $Verbosity ` - $FilesToSign + if ($ClientId -and $ClientSecret -and $TenantId) { + Write-Host "Invoking signing tool using clientId/clientSecret" + . $signingToolExe code azure-key-vault ` + --azure-key-vault-url "https://$KeyVaultName.vault.azure.net/" ` + --azure-key-vault-certificate $CertificateName ` + --azure-key-vault-client-id $ClientId ` + --azure-key-vault-client-secret $ClientSecret ` + --azure-key-vault-tenant-id $TenantId ` + --description $Description ` + --description-url $DescriptionUrl ` + --file-digest $DigestAlgorithm ` + --timestamp-digest $DigestAlgorithm ` + --timestamp-url $TimestampService ` + --verbosity $Verbosity ` + $FilesToSign + } + else { + Write-Host "Invoking signing tool using managed identity" + . $signingToolExe code azure-key-vault ` + --azure-key-vault-url "https://$KeyVaultName.vault.azure.net/" ` + --azure-key-vault-certificate $CertificateName ` + --azure-key-vault-managed-identity $true ` + --description $Description ` + --description-url $DescriptionUrl ` + --file-digest $DigestAlgorithm ` + --timestamp-digest $DigestAlgorithm ` + --timestamp-url $TimestampService ` + --verbosity $Verbosity ` + $FilesToSign + } } Export-ModuleMember -Function Invoke-SigningTool diff --git a/Sign/action.yaml b/Sign/action.yaml index a62d21c..01d9840 100644 --- a/Sign/action.yaml +++ b/Sign/action.yaml @@ -6,7 +6,7 @@ inputs: required: false default: powershell azureCredentialsJson: - description: Azure Credentials secret + description: Azure Credentials secret (Base 64 encoded) required: true pathToFiles: description: The path to the files to be signed @@ -19,10 +19,6 @@ inputs: description: The digest algorithm to use for signing and timestamping required: false default: SHA256 - parentTelemetryScopeJson: - description: Specifies the parent telemetry scope for the telemetry signal - required: false - default: '7b7d' runs: using: composite steps: @@ -33,15 +29,9 @@ runs: _pathToFiles: ${{ inputs.pathToFiles }} _timestampService: ${{ inputs.timestampService }} _digestAlgorithm: ${{ inputs.digestAlgorithm }} - _parentTelemetryScopeJson: ${{ inputs.parentTelemetryScopeJson }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { - ${{ github.action_path }}/Sign.ps1 -AzureCredentialsJson $ENV:_azureCredentialsJson -PathToFiles $ENV:_pathToFiles -TimestampService $ENV:_timestampService -digestAlgorithm $ENV:_digestAlgorithm -ParentTelemetryScopeJson $ENV:_parentTelemetryScopeJson - } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "Sign" -Action { + ${{ github.action_path }}/Sign.ps1 -AzureCredentialsJson $ENV:_azureCredentialsJson -PathToFiles $ENV:_pathToFiles -TimestampService $ENV:_timestampService -digestAlgorithm $ENV:_digestAlgorithm } branding: icon: terminal diff --git a/TelemetryHelper.psm1 b/TelemetryHelper.psm1 index 83feaaf..1e4e8b1 100644 --- a/TelemetryHelper.psm1 +++ b/TelemetryHelper.psm1 @@ -1,87 +1,208 @@ -$signals = @{ - "DO0070" = "AL-Go action ran: AddExistingApp" - "DO0071" = "AL-Go action ran: CheckForUpdates" - "DO0072" = "AL-Go action ran: CreateApp" - "DO0073" = "AL-Go action ran: CreateDevelopmentEnvironment" - "DO0074" = "AL-Go action ran: CreateReleaseNotes" - "DO0075" = "AL-Go action ran: Deploy" - "DO0076" = "AL-Go action ran: IncrementVersionNumber" - "DO0077" = "AL-Go action ran: PipelineCleanup" - "DO0078" = "AL-Go action ran: ReadSecrets" - "DO0079" = "AL-Go action ran: ReadSettings" - "DO0080" = "AL-Go action ran: RunPipeline" - "DO0081" = "AL-Go action ran: Deliver" - "DO0082" = "AL-Go action ran: AnalyzeTests" - "DO0083" = "AL-Go action ran: Sign" - - "DO0084" = "AL-Go action ran: DetermineArtifactUrl" - "DO0085" = "AL-Go action ran: DetermineProjectsToBuild" - - "DO0090" = "AL-Go workflow ran: AddExistingAppOrTestApp" - "DO0091" = "AL-Go workflow ran: CICD" - "DO0092" = "AL-Go workflow ran: CreateApp" - "DO0093" = "AL-Go workflow ran: CreateOnlineDevelopmentEnvironment" - "DO0094" = "AL-Go workflow ran: CreateRelease" - "DO0095" = "AL-Go workflow ran: CreateTestApp" - "DO0096" = "AL-Go workflow ran: IncrementVersionNumber" - "DO0097" = "AL-Go workflow ran: PublishToEnvironment" - "DO0098" = "AL-Go workflow ran: UpdateGitHubGoSystemFiles" - "DO0099" = "AL-Go workflow ran: NextMajor" - "DO0100" = "AL-Go workflow ran: NextMinor" - "DO0101" = "AL-Go workflow ran: Current" - "DO0102" = "AL-Go workflow ran: CreatePerformanceTestApp" - "DO0103" = "AL-Go workflow ran: PublishToAppSource" - "DO0104" = "AL-Go workflow ran: PullRequestHandler" +. (Join-Path -Path $PSScriptRoot -ChildPath ".\AL-Go-Helper.ps1" -Resolve) +Import-Module (Join-Path $PSScriptRoot '.\Github-Helper.psm1' -Resolve) + +#region Loading telemetry helper +function DownloadNugetPackage($PackageName, $PackageVersion) { + $nugetPackagePath = Join-Path "$ENV:GITHUB_WORKSPACE" "/.nuget/packages/$PackageName/$PackageVersion/" + + if (-not (Test-Path -Path $nugetPackagePath)) { + $url = "https://www.nuget.org/api/v2/package/$PackageName/$PackageVersion" + + Write-Host "Downloading Nuget package $PackageName $PackageVersion..." + New-Item -ItemType Directory -Path $nugetPackagePath | Out-Null + Invoke-WebRequest -Uri $Url -OutFile "$nugetPackagePath/$PackageName.$PackageVersion.zip" + + # Unzip the package + Expand-Archive -Path "$nugetPackagePath/$PackageName.$PackageVersion.zip" -DestinationPath "$nugetPackagePath" + + # Remove the zip file + Remove-Item -Path "$nugetPackagePath/$PackageName.$PackageVersion.zip" + } + + return $nugetPackagePath +} + +function LoadApplicationInsightsDll() { + $packagePath = DownloadNugetPackage -PackageName "Microsoft.ApplicationInsights" -PackageVersion (GetPackageVersion -PackageName "Microsoft.ApplicationInsights") + $AppInsightsDllPath = "$packagePath/lib/net46/Microsoft.ApplicationInsights.dll" + + if (-not (Test-Path -Path $AppInsightsDllPath)) { + throw "Failed to download Application Insights DLL" + } + + [Reflection.Assembly]::LoadFile($AppInsightsDllPath) | Out-Null } -Function strToHexStr { - Param( - [string] $str +function Get-ApplicationInsightsTelemetryClient($TelemetryConnectionString) +{ + # Load the Application Insights DLL + LoadApplicationInsightsDll + + $TelemetryClient = [Microsoft.ApplicationInsights.TelemetryClient]::new() + $TelemetryClient.TelemetryConfiguration.ConnectionString = $TelemetryConnectionString + + return $TelemetryClient +} +#endregion + +function AddTelemetryEvent() +{ + param( + [Parameter(Mandatory = $true)] + [String] $Message, + [Parameter(Mandatory = $false)] + [System.Collections.Generic.Dictionary[[System.String], [System.String]]] $Data = @{}, + [Parameter(Mandatory = $false)] + [ValidateSet("Information", "Error")] + [String] $Severity = 'Information' ) - $bytes = [System.Text.Encoding]::UTF8.GetBytes($str) - $hexStr = [System.Text.StringBuilder]::new($Bytes.Length * 2) - ForEach($byte in $Bytes){ - $hexStr.AppendFormat("{0:x2}", $byte) | Out-Null + + try { + # Add powershell version + Add-TelemetryProperty -Hashtable $Data -Key 'PowerShellVersion' -Value ($PSVersionTable.PSVersion.ToString()) + + $module = Get-Module BcContainerHelper + if ($module) { + $versionNoFile = Join-Path -Path (Split-Path $module.Path -Parent) -ChildPath 'Version.txt' + Add-TelemetryProperty -Hashtable $Data -Key 'BcContainerHelperVersion' -Value (Get-Content -Path $versionNoFile -Encoding UTF8) + } + + Add-TelemetryProperty -Hashtable $Data -Key 'WorkflowName' -Value $ENV:GITHUB_WORKFLOW + Add-TelemetryProperty -Hashtable $Data -Key 'RunnerOs' -Value $ENV:RUNNER_OS + Add-TelemetryProperty -Hashtable $Data -Key 'RunId' -Value $ENV:GITHUB_RUN_ID + Add-TelemetryProperty -Hashtable $Data -Key 'RunNumber' -Value $ENV:GITHUB_RUN_NUMBER + Add-TelemetryProperty -Hashtable $Data -Key 'RunAttempt' -Value $ENV:GITHUB_RUN_ATTEMPT + + ### Add GitHub Repository information + Add-TelemetryProperty -Hashtable $Data -Key 'Repository' -Value $ENV:GITHUB_REPOSITORY_ID + + $repoSettings = ReadSettings + if ($repoSettings.microsoftTelemetryConnectionString -ne '') { + Write-Host "Enabling Microsoft telemetry..." + $MicrosoftTelemetryClient = Get-ApplicationInsightsTelemetryClient -TelemetryConnectionString $repoSettings.microsoftTelemetryConnectionString + $MicrosoftTelemetryClient.TrackTrace($Message, [Microsoft.ApplicationInsights.DataContracts.SeverityLevel]::$Severity, $Data) + $MicrosoftTelemetryClient.Flush() + } + + if ($repoSettings.partnerTelemetryConnectionString -ne '') { + Write-Host "Enabling partner telemetry..." + $PartnerTelemetryClient = Get-ApplicationInsightsTelemetryClient -TelemetryConnectionString $repoSettings.partnerTelemetryConnectionString + $PartnerTelemetryClient.TrackTrace($Message, [Microsoft.ApplicationInsights.DataContracts.SeverityLevel]::$Severity, $Data) + $PartnerTelemetryClient.Flush() + } + } catch { + Write-Host "Failed to log telemetry event: $_" } - $hexStr.ToString() } -Function hexStrToStr { - Param( - [String] $hexStr +<# + .SYNOPSIS + Logs an information message to telemetry + + .DESCRIPTION + Logs an information message to telemetry + + .PARAMETER Message + The message to log to telemetry + + .PARAMETER ActionName + The name of the action to log to telemetry + + .PARAMETER AdditionalData + Additional data to log to telemetry + + .EXAMPLE + Trace-Information -Message "AL-Go action ran: $actionName" +#> +function Trace-Information() { + param( + [Parameter(ParameterSetName = 'Message', Mandatory = $true)] + [String] $Message, + [Parameter(ParameterSetName = 'ActionName', Mandatory = $true)] + [String] $ActionName, + [Parameter(Mandatory = $false)] + [System.Collections.Generic.Dictionary[[System.String], [System.String]]] $AdditionalData = @{} ) - $Bytes = [byte[]]::new($hexStr.Length / 2) - For($i=0; $i -lt $hexStr.Length; $i+=2){ - $Bytes[$i/2] = [convert]::ToByte($hexStr.Substring($i, 2), 16) + + if (-not $Message) { + $Message = "AL-Go action ran: $ActionName" } - [System.Text.Encoding]::UTF8.GetString($Bytes) + + AddTelemetryEvent -Message $Message -Severity 'Information' -Data $AdditionalData } -function CreateScope { - param ( - [string] $eventId, - [string] $parentTelemetryScopeJson = '7b7d' +<# + .SYNOPSIS + Logs an exception message to telemetry + + .DESCRIPTION + Logs an exception message to telemetry + + .PARAMETER Message + The message to log to telemetry + + .PARAMETER ActionName + The name of the action to log to telemetry + + .PARAMETER ErrorRecord + The error record to log to telemetry + + .PARAMETER AdditionalData + Additional data to log to telemetry + + .EXAMPLE + Trace-Exception -ErrorRecord $ErrorRecord +#> +function Trace-Exception() { + param( + [Parameter(ParameterSetName = 'Message', Mandatory = $true)] + [String] $Message, + [Parameter(ParameterSetName = 'ActionName', Mandatory = $true)] + [String] $ActionName, + [Parameter(Mandatory = $false)] + [System.Management.Automation.ErrorRecord] $ErrorRecord = $null, + [Parameter(Mandatory = $false)] + [System.Collections.Generic.Dictionary[[System.String], [System.String]]] $AdditionalData = @{} ) - $signalName = $signals[$eventId] - if (-not $signalName) { - throw "Invalid event id ($eventId) is enountered." + if ($ErrorRecord -ne $null) { + Add-TelemetryProperty -Hashtable $AdditionalData -Key 'ErrorMessage' -Value $ErrorRecord.Exception.Message } - if ($parentTelemetryScopeJson -and $parentTelemetryScopeJson -ne '7b7d') { - RegisterTelemetryScope (hexStrToStr -hexStr $parentTelemetryScopeJson) | Out-Null + if (-not $Message) { + $Message = "AL-Go action failed: $ActionName" } + AddTelemetryEvent -Message $Message -Severity 'Error' -Data $AdditionalData +} - $telemetryScope = InitTelemetryScope -name $signalName -eventId $eventId -parameterValues @() -includeParameters @() +<# + .SYNOPSIS + Adds a key-value pair to a hashtable if the key does not already exist - return $telemetryScope -} + .DESCRIPTION + Adds a key-value pair to a hashtable if the key does not already exist + + .PARAMETER Hashtable + The hashtable to add the key-value pair to -function GetHash { + .PARAMETER Key + The key to add to the hashtable + + .PARAMETER Value + The value to add to the hashtable + + .EXAMPLE + Add-TelemetryProperty -Hashtable $AdditionalData -Key 'RepoType' -Value 'PTE' +#> +function Add-TelemetryProperty() { param( - [string] $str + [System.Collections.Generic.Dictionary[[System.String], [System.String]]] $Hashtable, + [String] $Key, + [String] $Value ) - - $stream = [IO.MemoryStream]::new([Text.Encoding]::UTF8.GetBytes($str)) - (Get-FileHash -InputStream $stream -Algorithm SHA256).Hash + if (-not $Hashtable.ContainsKey($Key) -and ($Value -ne '')) { + $Hashtable.Add($Key, $Value) + } } + +Export-ModuleMember -Function Trace-Information, Trace-Exception, Add-TelemetryProperty diff --git a/Troubleshooting/README.md b/Troubleshooting/README.md index a69acbc..3a048a4 100644 --- a/Troubleshooting/README.md +++ b/Troubleshooting/README.md @@ -1,12 +1,15 @@ # Troubleshooting + Run troubleshooting on repository ## INPUT ### ENV variables + none ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | @@ -14,4 +17,5 @@ none | displayNameOfSecrets | Yes | Display the names (not the values) of secrets available to the repository | | ## OUTPUT + none diff --git a/Troubleshooting/action.yaml b/Troubleshooting/action.yaml index c491ea4..5fc6b62 100644 --- a/Troubleshooting/action.yaml +++ b/Troubleshooting/action.yaml @@ -19,14 +19,9 @@ runs: env: _displayNameOfSecrets: ${{ inputs.displayNameOfSecrets }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "Troubleshooting" -Action { ${{ github.action_path }}/Troubleshooting.ps1 -gitHubSecrets '${{ inputs.gitHubSecrets }}' -displayNameOfSecrets ($ENV:_displayNameOfSecrets -eq 'true') } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 - } branding: icon: terminal color: blue diff --git a/VerifyPRChanges/README.md b/VerifyPRChanges/README.md index e01438c..e71bf42 100644 --- a/VerifyPRChanges/README.md +++ b/VerifyPRChanges/README.md @@ -1,12 +1,15 @@ # Verify Pull Request changes + Verify Pull Request Changes for AL-Go workflows ## INPUT ### ENV variables + none ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | @@ -15,4 +18,5 @@ none | pullRequestId | | The id of the pull request | github.event.pull_request.number | ## OUTPUT + none diff --git a/VerifyPRChanges/action.yaml b/VerifyPRChanges/action.yaml index af1cb8b..871513c 100644 --- a/VerifyPRChanges/action.yaml +++ b/VerifyPRChanges/action.yaml @@ -27,14 +27,9 @@ runs: _prBaseRepository: ${{ inputs.prBaseRepository }} _pullRequestId: ${{ inputs.pullRequestId }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "VerifyPRChanges" -Action { ${{ github.action_path }}/VerifyPRChanges.ps1 -token $ENV:_token -prBaseRepository $ENV:_prBaseRepository -pullRequestId $ENV:_pullRequestId } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 - } branding: icon: terminal color: blue diff --git a/WorkflowInitialize/README.md b/WorkflowInitialize/README.md index 182c85c..d3e6b88 100644 --- a/WorkflowInitialize/README.md +++ b/WorkflowInitialize/README.md @@ -1,26 +1,29 @@ # Initialize workflow + Initialize a workflow ## INPUT ### ENV variables + none ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | -| eventId | Yes | The event id of the initiating workflow | | | actionsRepo | No | The repository of the action | github.action_repository | | actionsRef | No | The ref of the action | github.action_ref | ## OUTPUT ### ENV variables + none ### OUTPUT variables + | Name | Description | | :-- | :-- | -| correlationId | A correlation Id for the workflow | | telemetryScopeJson | A telemetryScope that covers the workflow | diff --git a/WorkflowInitialize/WorkflowInitialize.ps1 b/WorkflowInitialize/WorkflowInitialize.ps1 index 6231908..5629ea4 100644 --- a/WorkflowInitialize/WorkflowInitialize.ps1 +++ b/WorkflowInitialize/WorkflowInitialize.ps1 @@ -1,82 +1,37 @@ Param( - [Parameter(HelpMessage = "The event id of the initiating workflow", Mandatory = $true)] - [string] $eventId, [Parameter(HelpMessage = "The repository of the action", Mandatory = $false)] [string] $actionsRepo, [Parameter(HelpMessage = "The ref of the action", Mandatory = $false)] [string] $actionsRef ) -$telemetryScope = $null +. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) +. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-TestRepoHelper.ps1" -Resolve) -try { - . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) - . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-TestRepoHelper.ps1" -Resolve) - - if ($actionsRepo -eq 'microsoft/AL-Go-Actions') { - Write-Host "Using AL-Go for GitHub $actionsRef" - $verstr = $actionsRef - } - elseif ($actionsRepo -eq 'microsoft/AL-Go') { - Write-Host "Using AL-Go for GitHub Preview ($actionsRef)" - $verstr = "p" - } - else { - Write-Host "Using direct AL-Go development ($($actionsRepo)@$actionsRef)" - $verstr = "d" - } - - Write-Big -str "a$verstr" - - TestALGoRepository - - DownloadAndImportBcContainerHelper +if ($actionsRepo -eq 'microsoft/AL-Go-Actions') { + Write-Host "Using AL-Go for GitHub $actionsRef" + $verstr = $actionsRef +} +elseif ($actionsRepo -eq 'microsoft/AL-Go') { + Write-Host "Using AL-Go for GitHub Preview ($actionsRef)" + $verstr = "p" +} +else { + Write-Host "Using direct AL-Go development ($($actionsRepo)@$actionsRef)" + $verstr = "d" +} - TestRunnerPrerequisites +Write-Big -str "a$verstr" - import-module (Join-Path -path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) - $telemetryScope = CreateScope -eventId $eventId - if ($telemetryScope) { - $repoSettings = Get-Content -Path (Join-Path $ENV:GITHUB_WORKSPACE '.github/AL-Go-Settings.json') -Raw -Encoding UTF8 | ConvertFrom-Json | ConvertTo-HashTable - $type = 'PTE' - if ($repoSettings.Keys -contains 'type') { - $type = $repoSettings.type - } - $templateUrl = 'Not set' - if ($repoSettings.Keys -contains 'templateUrl') { - $templateUrl = $repoSettings.templateUrl - } - if ($verstr -eq "d") { - $verstr = "Developer/Private" - } - elseif ($verstr -eq "p") { - $verstr = "Preview" - } - AddTelemetryProperty -telemetryScope $telemetryScope -key "ALGoVersion" -value $verstr - AddTelemetryProperty -telemetryScope $telemetryScope -key "type" -value $type - AddTelemetryProperty -telemetryScope $telemetryScope -key "templateUrl" -value $templateUrl - AddTelemetryProperty -telemetryScope $telemetryScope -key "repository" -value $ENV:GITHUB_REPOSITORY - AddTelemetryProperty -telemetryScope $telemetryScope -key "runAttempt" -value $ENV:GITHUB_RUN_ATTEMPT - AddTelemetryProperty -telemetryScope $telemetryScope -key "runNumber" -value $ENV:GITHUB_RUN_NUMBER - AddTelemetryProperty -telemetryScope $telemetryScope -key "runId" -value $ENV:GITHUB_RUN_ID +# Test the AL-Go repository is set up correctly +TestALGoRepository - $scopeJson = strToHexStr -str ($telemetryScope | ConvertTo-Json -Compress) - $correlationId = ($telemetryScope.CorrelationId).ToString() - } - else { - $scopeJson = '7b7d' - $correlationId = [guid]::Empty.ToString() - } +# Test the prerequisites for the test runner +TestRunnerPrerequisites - Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "telemetryScopeJson=$scopeJson" - Write-Host "telemetryScopeJson=$scopeJson" +# Create a json object that contains an entry for the workflowstarttime +$scopeJson = @{ + "workflowStartTime" = [DateTime]::UtcNow +} | ConvertTo-Json -Compress - Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "correlationId=$correlationId" - Write-Host "correlationId=$correlationId" -} -catch { - if (Get-Module BcContainerHelper) { - TrackException -telemetryScope $telemetryScope -errorRecord $_ - } - throw -} +Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "telemetryScopeJson=$scopeJson" diff --git a/WorkflowInitialize/action.yaml b/WorkflowInitialize/action.yaml index 3bcb93a..0e3c122 100644 --- a/WorkflowInitialize/action.yaml +++ b/WorkflowInitialize/action.yaml @@ -5,9 +5,6 @@ inputs: description: Shell in which you want to run the action (powershell or pwsh) required: false default: powershell - eventId: - description: The event id of the initiating workflow - required: true actionsRepo: description: The repository of the action required: false @@ -17,9 +14,6 @@ inputs: required: false default: ${{ github.action_ref }} outputs: - correlationId: - description: A correlation Id for the workflow - value: ${{ steps.workflowinitialize.outputs.correlationId }} telemetryScopeJson: description: A telemetryScope that covers the workflow value: ${{ steps.workflowinitialize.outputs.telemetryScopeJson }} @@ -30,17 +24,11 @@ runs: shell: ${{ inputs.shell }} id: workflowinitialize env: - _eventId: ${{ inputs.eventId }} _actionsRepo: ${{ inputs.actionsRepo }} _actionsRef: ${{ inputs.actionsRef }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { - ${{ github.action_path }}/WorkflowInitialize.ps1 -eventId $ENV:_eventId -actionsRepo $ENV:_actionsRepo -actionsRef $ENV:_actionsRef - } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "WorkflowInitialize" -Action { + ${{ github.action_path }}/WorkflowInitialize.ps1 -actionsRepo $ENV:_actionsRepo -actionsRef $ENV:_actionsRef } branding: icon: terminal diff --git a/WorkflowPostProcess/README.md b/WorkflowPostProcess/README.md index bcfc3ab..ae66baf 100644 --- a/WorkflowPostProcess/README.md +++ b/WorkflowPostProcess/README.md @@ -1,18 +1,21 @@ # PostProcess action + Finalize a workflow ## INPUT ### ENV variables + none ### Parameters + | Name | Required | Description | Default value | | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | -| eventId | Yes | The event id of the initiating workflow | | | telemetryScopeJson | | Telemetry scope generated during the workflow initialization | {} | - +| currentJobContext | | The current job context | '' | ## OUTPUT + none diff --git a/WorkflowPostProcess/WorkflowPostProcess.ps1 b/WorkflowPostProcess/WorkflowPostProcess.ps1 index ddb5bb2..2c6704b 100644 --- a/WorkflowPostProcess/WorkflowPostProcess.ps1 +++ b/WorkflowPostProcess/WorkflowPostProcess.ps1 @@ -1,26 +1,110 @@ Param( - [Parameter(HelpMessage = "The event Id of the initiating workflow", Mandatory = $true)] - [string] $eventId, [Parameter(HelpMessage = "Telemetry scope generated during the workflow initialization", Mandatory = $false)] - [string] $telemetryScopeJson = '7b7d' + [string] $telemetryScopeJson = '', + [Parameter(HelpMessage = "The current job context", Mandatory = $false)] + [string] $currentJobContext = '', + [Parameter(HelpMessage = "The ref of the action", Mandatory = $false)] + [string] $actionsRef ) -$telemetryScope = $null +function GetWorkflowConclusion($JobContext) { + # Check the conclusion for the current job + if ($JobContext -ne '') { + $jobContext = $JobContext | ConvertFrom-Json + if ($jobContext.status -eq 'failure') { + return "Failure" + } + if ($jobContext.status -eq 'timed_out') { + return "TimedOut" + } + if ($jobContext.status -eq 'cancelled') { + return "Cancelled" + } + } -try { - . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) - DownloadAndImportBcContainerHelper - import-module (Join-Path -path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) + # Check the conclusion for the past jobs in the workflow + $workflowJobs = gh api /repos/$ENV:GITHUB_REPOSITORY/actions/runs/$ENV:GITHUB_RUN_ID/jobs --paginate -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" | ConvertFrom-Json + if ($null -ne $workflowJobs) { + $failedJobs = $workflowJobs.jobs | Where-Object { $_.conclusion -eq "failure" } + if ($null -ne $failedJobs) { + return "Failure" + } + $timedOutJobs = $workflowJobs.jobs | Where-Object { $_.conclusion -eq "timed_out" } + if ($null -ne $timedOutJobs) { + return "TimedOut" + } + $cancelledJobs = $workflowJobs.jobs | Where-Object { $_.conclusion -eq "cancelled" } + if ($null -ne $cancelledJobs) { + return "Cancelled" + } + } - Write-Host "Post-processing workflow $eventId" - if ($telemetryScopeJson -and $telemetryScopeJson -ne '7b7d') { - $telemetryScope = RegisterTelemetryScope (hexStrToStr -hexStr $telemetryScopeJson) - TrackTrace -telemetryScope $telemetryScope + return "Success" +} + +function GetAlGoVersion($ActionRef) { + if ($ENV:GITHUB_REPOSITORY -eq "microsoft/AL-Go") { + return "Preview" + } elseif($ENV:GITHUB_REPOSITORY -notlike "microsoft/*") { + return "Developer/Private" + } else { + return $ActionRef } } -catch { - if (Get-Module BcContainerHelper) { - TrackException -telemetryScope $telemetryScope -errorRecord $_ + +function LogWorkflowEnd($TelemetryScopeJson, $JobContext, $AlGoVersion) { + [System.Collections.Generic.Dictionary[[System.String], [System.String]]] $AdditionalData = @{} + $telemetryScope = $null + if ($TelemetryScopeJson -ne '') { + $telemetryScope = $TelemetryScopeJson | ConvertFrom-Json + } + + # Get the workflow conclusion + $workflowConclusion = GetWorkflowConclusion -JobContext $JobContext + Add-TelemetryProperty -Hashtable $AdditionalData -Key 'WorkflowConclusion' -Value $workflowConclusion + + # Calculate the workflow duration using the github api + if ($telemetryScope -and ($null -ne $telemetryScope.workflowStartTime)) { + Write-Host "Calculating workflow duration..." + $workflowTiming= [DateTime]::UtcNow.Subtract([DateTime]::Parse($telemetryScope.workflowStartTime)).TotalSeconds + Add-TelemetryProperty -Hashtable $AdditionalData -Key 'WorkflowDuration' -Value $workflowTiming + } + + # Log additional telemetry from AL-Go settings + $alGoSettingsPath = "$ENV:GITHUB_WORKSPACE/.github/AL-Go-Settings.json" + if (Test-Path -Path $alGoSettingsPath) { + $repoSettings = Get-Content -Path $alGoSettingsPath -Raw -Encoding UTF8 | ConvertFrom-Json + + if ($repoSettings.PSObject.Properties.Name -contains 'type') { + Add-TelemetryProperty -Hashtable $AdditionalData -Key 'RepoType' -Value $repoSettings.type + } + + if ($repoSettings.PSObject.Properties.Name -contains 'githubRunner') { + Add-TelemetryProperty -Hashtable $AdditionalData -Key 'GitHubRunner' -Value $repoSettings.githubRunner + } + + if ($repoSettings.PSObject.Properties.Name -contains 'runs-on') { + Add-TelemetryProperty -Hashtable $AdditionalData -Key 'RunsOn' -Value $repoSettings.'runs-on' + } } - throw + + if ($AlGoVersion -ne '') { + Add-TelemetryProperty -Hashtable $AdditionalData -Key 'ALGoVersion' -Value $AlGoVersion + } + + if ($workflowConclusion -in @("Failure", "TimedOut")) { + Trace-Exception -Message "AL-Go workflow failed: $($ENV:GITHUB_WORKFLOW.Trim())" -AdditionalData $AdditionalData + } else { + Trace-Information -Message "AL-Go workflow ran: $($ENV:GITHUB_WORKFLOW.Trim())" -AdditionalData $AdditionalData + } +} + +Import-Module (Join-Path -Path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) + +try { + LogWorkflowEnd -TelemetryScopeJson $telemetryScopeJson -JobContext $currentJobContext -AlGoVersion (GetAlGoVersion -ActionRef $actionsRef) +} catch { + # Log the exception to telemetry but don't fail the action if gathering telemetry fails + Write-Host "::Warning::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; + Trace-Exception -ActionName "WorkflowPostProcess" -ErrorRecord $_ } diff --git a/WorkflowPostProcess/action.yaml b/WorkflowPostProcess/action.yaml index d1a30a3..84816e8 100644 --- a/WorkflowPostProcess/action.yaml +++ b/WorkflowPostProcess/action.yaml @@ -5,29 +5,30 @@ inputs: description: Shell in which you want to run the action (powershell or pwsh) required: false default: powershell - eventId: - description: The event Id of the initiating workflow - required: true telemetryScopeJson: description: Telemetry scope generated during the workflow initialization required: false default: '7b7d' + currentJobContext: + description: The current job context + required: false + default: '' + actionsRef: + description: The ref of the action + required: false + default: ${{ github.action_ref }} runs: using: composite steps: - name: run shell: ${{ inputs.shell }} env: - _eventId: ${{ inputs.eventId }} _telemetryScopeJson: ${{ inputs.telemetryScopeJson }} + _currentJobContext: ${{ inputs.currentJobContext }} + _actionsRef: ${{ inputs.actionsRef }} run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { - ${{ github.action_path }}/WorkflowPostProcess.ps1 -eventId $ENV:_eventId -telemetryScopeJson $ENV:_telemetryScopeJson - } - catch { - Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; - exit 1 + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "WorkflowPostProcess" -Action { + ${{ github.action_path }}/WorkflowPostProcess.ps1 -telemetryScopeJson $ENV:_telemetryScopeJson -currentJobContext $ENV:_currentJobContext -actionsRef $ENV:_actionsRef } branding: icon: terminal