From a26f554feb8fcf96abf9d7c8c4b7d9ba89492e86 Mon Sep 17 00:00:00 2001 From: bcbuild-github-agent <137281497+bcbuild-github-agent@users.noreply.github.com> Date: Wed, 27 Sep 2023 06:27:17 -0700 Subject: [PATCH] Deploying AL-Go from main (37e4e3fac9276ad13dc998d03f7bf0fdad577d19) to main (#16) Deploying AL-Go from main (37e4e3fac9276ad13dc998d03f7bf0fdad577d19) to main Co-authored-by: microsoft --- AL-Go-Helper.ps1 | 814 ++++++++++-------- AL-Go-TestRepoHelper.ps1 | 283 +++--- AddExistingApp/AddExistingApp.ps1 | 54 +- AnalyzeTests/AnalyzeTests.ps1 | 17 +- AnalyzeTests/README.md | 2 - AnalyzeTests/TestResultAnalyzer.ps1 | 49 +- AnalyzeTests/action.yaml | 12 +- CODEOWNERS | 2 +- CheckForUpdates/CheckForUpdates.ps1 | 73 +- CheckForUpdates/yamlclass.ps1 | 4 +- CreateApp/AppHelper.psm1 | 78 +- CreateApp/CreateApp.ps1 | 23 +- .../CreateDevelopmentEnvironment.ps1 | 22 +- CreateReleaseNotes/CreateReleaseNotes.ps1 | 14 +- CreateReleaseNotes/README.md | 1 - CreateReleaseNotes/action.yaml | 7 +- Deliver/Deliver.ps1 | 55 +- Deliver/README.md | 5 +- Deploy/Deploy.ps1 | 178 ++-- Deploy/README.md | 7 +- Deploy/action.yaml | 16 +- DetermineArtifactUrl/DetermineArtifactUrl.ps1 | 27 +- .../DetermineDeliveryTargets.ps1 | 65 ++ DetermineDeliveryTargets/README.md | 25 + DetermineDeliveryTargets/action.yaml | 43 + .../DetermineDeploymentEnvironments.ps1 | 186 ++++ DetermineDeploymentEnvironments/README.md | 25 + DetermineDeploymentEnvironments/action.yaml | 47 + .../DetermineProjectsToBuild.Action.ps1 | 19 +- .../DetermineProjectsToBuild.ps1 | 95 +- DetermineProjectsToBuild/GetModifiedFiles.ps1 | 2 +- DetermineProjectsToBuild/action.yaml | 4 +- .../DownloadProjectDependencies.Action.ps1 | 86 +- DownloadProjectDependencies/README.md | 1 + Github-Helper.psm1 | 347 ++++---- .../IncrementVersionNumber.ps1 | 16 +- PipelineCleanup/PipelineCleanup.ps1 | 12 +- .../PullRequestStatusCheck.ps1 | 20 + PullRequestStatusCheck/README.md | 17 + PullRequestStatusCheck/action.yaml | 24 + README.md | 4 +- ReadSecrets/README.md | 14 +- ReadSecrets/ReadSecrets.ps1 | 140 +-- ReadSecrets/ReadSecretsHelper.psm1 | 134 +-- ReadSecrets/action.yaml | 20 +- ReadSettings/README.md | 9 - ReadSettings/ReadSettings.ps1 | 245 ++---- ReadSettings/action.yaml | 41 +- RunPipeline/README.md | 1 - RunPipeline/RunPipeline.ps1 | 127 +-- RunPipeline/action.yaml | 7 +- SECURITY.md | 2 +- SUPPORT.md | 4 +- Sign/Sign.ps1 | 35 +- Sign/Sign.psm1 | 10 +- TelemetryHelper.psm1 | 4 +- VerifyPRChanges/VerifyPRChanges.ps1 | 3 +- WorkflowInitialize/WorkflowInitialize.ps1 | 12 +- WorkflowPostProcess/WorkflowPostProcess.ps1 | 11 +- 59 files changed, 2048 insertions(+), 1552 deletions(-) create mode 100644 DetermineDeliveryTargets/DetermineDeliveryTargets.ps1 create mode 100644 DetermineDeliveryTargets/README.md create mode 100644 DetermineDeliveryTargets/action.yaml create mode 100644 DetermineDeploymentEnvironments/DetermineDeploymentEnvironments.ps1 create mode 100644 DetermineDeploymentEnvironments/README.md create mode 100644 DetermineDeploymentEnvironments/action.yaml create mode 100644 PullRequestStatusCheck/PullRequestStatusCheck.ps1 create mode 100644 PullRequestStatusCheck/README.md create mode 100644 PullRequestStatusCheck/action.yaml diff --git a/AL-Go-Helper.ps1 b/AL-Go-Helper.ps1 index 0b36cdc..72ada83 100644 --- a/AL-Go-Helper.ps1 +++ b/AL-Go-Helper.ps1 @@ -13,10 +13,12 @@ $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-S $ALGoFolderName = '.AL-Go' $ALGoSettingsFile = Join-Path '.AL-Go' 'settings.json' $RepoSettingsFile = Join-Path '.github' 'AL-Go-Settings.json' +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'defaultCICDPushBranches', Justification = 'False positive.')] $defaultCICDPushBranches = @( 'main', 'release/*', 'feature/*' ) +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'defaultCICDPullRequestBranches', Justification = 'False positive.')] $defaultCICDPullRequestBranches = @( 'main' ) $runningLocal = $local.IsPresent -$defaultBcContainerHelperVersion = "latest" # Must be double quotes. Will be replaced by BcContainerHelperVersion if necessary in the deploy step +$defaultBcContainerHelperVersion = "preview" # Must be double quotes. Will be replaced by BcContainerHelperVersion if necessary in the deploy step $microsoftTelemetryConnectionString = "InstrumentationKey=84bd9223-67d4-4378-8590-9e4a46023be2;IngestionEndpoint=https://westeurope-1.in.applicationinsights.azure.com/" $runAlPipelineOverrides = @( @@ -34,6 +36,8 @@ $runAlPipelineOverrides = @( "GetBcContainerAppRuntimePackage" "RemoveBcContainer" "InstallMissingDependencies" + "PreCompileApp" + "PostCompileApp" ) # Well known AppIds @@ -56,63 +60,95 @@ $testRunnerApps = @($permissionsMockAppId, $testRunnerAppId) + $performanceToolk $isPsCore = $PSVersionTable.PSVersion -ge "6.0.0" if ($isPsCore) { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'byteEncodingParam', Justification = 'False positive.')] $byteEncodingParam = @{ "asByteStream" = $true } + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'allowUnencryptedAuthenticationParam', Justification = 'False positive.')] $allowUnencryptedAuthenticationParam = @{ "allowUnencryptedAuthentication" = $true } } else { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'byteEncodingParam', Justification = 'False positive.')] $byteEncodingParam = @{ "Encoding" = "byte" } + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'allowUnencryptedAuthenticationParam', Justification = 'False positive.')] $allowUnencryptedAuthenticationParam = @{ } + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidAssignmentToAutomaticVariable', 'isWindows', Justification = 'Will only run on PS5')] $isWindows = $true + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidAssignmentToAutomaticVariable', 'isLinux', Justification = 'Will only run on PS5')] $isLinux = $false + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidAssignmentToAutomaticVariable', 'isMacOS', Justification = 'Will only run on PS5')] $IsMacOS = $false } # Copy a HashTable to ensure non case sensitivity (Issue #385) function Copy-HashTable() { [CmdletBinding()] + [OutputType([System.Collections.HashTable])] Param( [parameter(ValueFromPipeline)] [hashtable] $object ) - $ht = @{} - if ($object) { - $object.Keys | ForEach-Object { - $ht[$_] = $object[$_] + Process { + $ht = @{} + if ($object) { + $object.Keys | ForEach-Object { + $ht[$_] = $object[$_] + } } + $ht } - $ht } function ConvertTo-HashTable() { [CmdletBinding()] + [OutputType([System.Collections.HashTable])] Param( [parameter(ValueFromPipeline)] $object, [switch] $recurse ) - $ht = @{} - if ($object -is [System.Collections.Specialized.OrderedDictionary] -or $object -is [hashtable]) { - $object.Keys | ForEach-Object { - if ($recurse -and ($object."$_" -is [System.Collections.Specialized.OrderedDictionary] -or $object."$_" -is [hashtable] -or $object."$_" -is [PSCustomObject])) { - $ht[$_] = ConvertTo-HashTable $object."$_" -recurse + Process { + function AddValueToHashTable { + Param( + [hashtable] $ht, + [string] $name, + $value, + [switch] $recurse + ) + + if ($ht.Contains($name)) { + throw "Duplicate key $name" + } + if ($recurse -and ($value -is [System.Collections.Specialized.OrderedDictionary] -or $value -is [hashtable] -or $value -is [System.Management.Automation.PSCustomObject])) { + $ht[$name] = ConvertTo-HashTable $value -recurse + } + elseif ($recurse -and $value -is [array]) { + $ht[$name] = @($value | ForEach-Object { + if (($_ -is [System.Collections.Specialized.OrderedDictionary]) -or ($_ -is [hashtable]) -or ($_ -is [System.Management.Automation.PSCustomObject])) { + ConvertTo-HashTable $_ -recurse + } + else { + $_ + } + }) } else { - $ht[$_] = $object."$_" + $ht[$name] = $value } } - } - elseif ($object -is [PSCustomObject]) { - $object.PSObject.Properties | ForEach-Object { - if ($recurse -and ($_.Value -is [System.Collections.Specialized.OrderedDictionary] -or $_.Value -is [hashtable] -or $_.Value -is [PSCustomObject])) { - $ht[$_.Name] = ConvertTo-HashTable $_.Value -recurse + + $ht = @{} + if ($object -is [System.Collections.Specialized.OrderedDictionary] -or $object -is [hashtable]) { + foreach($key in $object.Keys) { + AddValueToHashTable -ht $ht -name $key -value $object."$key" -recurse:$recurse } - else { - $ht[$_.Name] = $_.Value + } + elseif ($object -is [System.Management.Automation.PSCustomObject]) { + foreach($property in $object.PSObject.Properties) { + AddValueToHashTable -ht $ht -name $property.Name -value $property.Value -recurse:$recurse } } + $ht } - $ht } function OutputError { @@ -187,7 +223,7 @@ function stringToInt { ) $i = 0 - if ([int]::TryParse($str.Trim(), [ref] $i)) { + if ([int]::TryParse($str.Trim(), [ref] $i)) { $i } else { @@ -226,102 +262,159 @@ function Expand-7zipArchive { } } -function DownloadAndImportBcContainerHelper { - Param( - [string] $bcContainerHelperVersion = $defaultBcContainerHelperVersion, - [string] $baseFolder = "" - ) - - $params = @{ "ExportTelemetryFunctions" = $true } - if ($baseFolder) { - $repoSettingsPath = Join-Path $baseFolder $repoSettingsFile - if (-not (Test-Path $repoSettingsPath -PathType Leaf)) { - $repoSettingsPath = Join-Path $baseFolder "..\$repoSettingsFile" - if (-not (Test-Path $repoSettingsPath -PathType Leaf)) { - $repoSettingsPath = Join-Path $baseFolder "..\..\$repoSettingsFile" - } - } - if (Test-Path $repoSettingsPath) { - $repoSettings = Get-Content $repoSettingsPath -Encoding UTF8 | ConvertFrom-Json | ConvertTo-HashTable - if ($bcContainerHelperVersion -eq "" -or $bcContainerHelperVersion -eq "latest") { - if ($repoSettings.Keys -contains "BcContainerHelperVersion") { - $bcContainerHelperVersion = $repoSettings.BcContainerHelperVersion - Write-Host "Using BcContainerHelper $bcContainerHelperVersion version" - if ($bcContainerHelperVersion -like "https://*") { - throw "Setting BcContainerHelperVersion to a URL is not allowed." - } - elseif ($bcContainerHelperVersion -ne "" -and $bcContainerHelperVersion -ne 'latest' -and $bcContainerHelperVersion -ne 'preview') { - Write-Host "::Warning::Using a specific version of BcContainerHelper is not recommended and will lead to build failures in the future. Consider removing the setting." - } - } - } - $params += @{ "bcContainerHelperConfigFile" = $repoSettingsPath } - } - } - if ($bcContainerHelperVersion -eq "") { - $bcContainerHelperVersion = "latest" - } - elseif ($bcContainerHelperVersion -eq "private") { - # Using a private BcContainerHelper version grabs a fork of BcContainerHelper with the same owner as the AL-Go actions - # The ActionsRepo below will be modified to point to actual running actions repo by the deploy mechanism, please do not change - $ActionsRepo = "microsoft/AL-Go-Actions@v3.2" - $owner = $actionsRepo.Split('/')[0] - $bcContainerHelperVersion = "https://github.com/$owner/navcontainerhelper/archive/master.zip" - } - - if ($bcContainerHelperVersion -eq "none") { - $tempName = "" +# +# Get Path to BcContainerHelper module (download if necessary) +# +# If $env:BcContainerHelperPath is set, it will be reused (ignoring the ContainerHelperVersion) +# +# ContainerHelperVersion can be: +# - preview (or dev), which will use the preview version downloaded from bccontainerhelper blob storage +# - latest, which will use the latest version downloaded from bccontainerhelper blob storage +# - a specific version, which will use the specific version downloaded from bccontainerhelper blob storage +# - none, which will use the BcContainerHelper module installed on the build agent +# - https://... - direct download url to a zip file containing the BcContainerHelper module +# +# When using direct download url, the module will be downloaded to a temp folder and will not be cached +# When using none, the module will be located in modules and used from there +# When using preview, latest or a specific version number, the module will be downloaded to a cache folder and will be reused if the same version is requested again +# This is to avoid filling up the temp folder with multiple identical versions of BcContainerHelper +# The cache folder is C:\ProgramData\BcContainerHelper on Windows and /home//.BcContainerHelper on Linux +# A Mutex will be used to ensure multiple agents aren't fighting over the same cache folder +# +# This function will set $env:BcContainerHelperPath, which is the path to the BcContainerHelper.ps1 file for reuse in subsequent calls +# +function GetBcContainerHelperPath([string] $bcContainerHelperVersion) { + if ("$env:BcContainerHelperPath" -and (Test-Path -Path $env:BcContainerHelperPath -PathType Leaf)) { + return $env:BcContainerHelperPath + } + + if ($bcContainerHelperVersion -eq 'None') { $module = Get-Module BcContainerHelper if (-not $module) { OutputError "When setting BcContainerHelperVersion to none, you need to ensure that BcContainerHelper is installed on the build agent" } - - $BcContainerHelperPath = Join-Path (Split-Path $module.Path -parent) "BcContainerHelper.ps1" -Resolve + $bcContainerHelperPath = Join-Path (Split-Path $module.Path -parent) "BcContainerHelper.ps1" -Resolve } else { - $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + if ($isWindows) { + $bcContainerHelperRootFolder = 'C:\ProgramData\BcContainerHelper' + } + else { + $myUsername = (whoami) + $bcContainerHelperRootFolder = "/home/$myUsername/.BcContainerHelper" + } + if (!(Test-Path $bcContainerHelperRootFolder)) { + New-Item -Path $bcContainerHelperRootFolder -ItemType Directory | Out-Null + } + $webclient = New-Object System.Net.WebClient if ($bcContainerHelperVersion -like "https://*") { + # Use temp space for private versions + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) Write-Host "Downloading BcContainerHelper developer version from $bcContainerHelperVersion" try { $webclient.DownloadFile($bcContainerHelperVersion, "$tempName.zip") } catch { + $tempName = Join-Path $bcContainerHelperRootFolder ([Guid]::NewGuid().ToString()) $bcContainerHelperVersion = "preview" Write-Host "Download failed, downloading BcContainerHelper $bcContainerHelperVersion version from Blob Storage" $webclient.DownloadFile("https://bccontainerhelper.blob.core.windows.net/public/$($bcContainerHelperVersion).zip", "$tempName.zip") } } - elseif ($bcContainerHelperVersion -eq "preview" -or $bcContainerHelperVersion -eq "dev") { - # For backwards compatibility, use preview when dev is specified - Write-Host "Downloading BcContainerHelper $bcContainerHelperVersion version from Blob Storage" - $webclient.DownloadFile("https://bccontainerhelper.blob.core.windows.net/public/preview.zip", "$tempName.zip") - } else { - Write-Host "Downloading BcContainerHelper $bcContainerHelperVersion version from CDN" - $webclient.DownloadFile("https://bccontainerhelper.azureedge.net/public/$($bcContainerHelperVersion).zip", "$tempName.zip") + $tempName = Join-Path $bcContainerHelperRootFolder ([Guid]::NewGuid().ToString()) + if ($bcContainerHelperVersion -eq "preview" -or $bcContainerHelperVersion -eq "dev") { + # For backwards compatibility, use preview when dev is specified + Write-Host "Downloading BcContainerHelper $bcContainerHelperVersion version from Blob Storage" + $webclient.DownloadFile("https://bccontainerhelper.blob.core.windows.net/public/preview.zip", "$tempName.zip") + } + else { + Write-Host "Downloading BcContainerHelper $bcContainerHelperVersion version from CDN" + $webclient.DownloadFile("https://bccontainerhelper.azureedge.net/public/$($bcContainerHelperVersion).zip", "$tempName.zip") + } } Expand-7zipArchive -Path "$tempName.zip" -DestinationPath $tempName - $BcContainerHelperPath = (Get-Item -Path (Join-Path $tempName "*\BcContainerHelper.ps1")).FullName + $bcContainerHelperPath = (Get-Item -Path (Join-Path $tempName "*\BcContainerHelper.ps1")).FullName Remove-Item -Path "$tempName.zip" -ErrorAction SilentlyContinue + if ($bcContainerHelperVersion -notlike "https://*") { + # Check whether the version is already available in the cache + $version = Get-Content -Encoding UTF8 -Path (Join-Path $tempName 'BcContainerHelper/Version.txt') + $cacheFolder = Join-Path $bcContainerHelperRootFolder $version + # To avoid two agents on the same machine downloading the same version at the same time, use a mutex + $buildMutexName = "DownloadAndImportBcContainerHelper" + $buildMutex = New-Object System.Threading.Mutex($false, $buildMutexName) + try { + try { + if (!$buildMutex.WaitOne(1000)) { + Write-Host "Waiting for other process loading BcContainerHelper" + $buildMutex.WaitOne() | Out-Null + Write-Host "Other process completed loading BcContainerHelper" + } + } + catch [System.Threading.AbandonedMutexException] { + Write-Host "Other process terminated abnormally" + } + if (Test-Path $cacheFolder) { + Remove-Item $tempName -Recurse -Force + } + else { + Rename-Item -Path $tempName -NewName $version + } + } + finally { + $buildMutex.ReleaseMutex() + } + $bcContainerHelperPath = Join-Path $cacheFolder "BcContainerHelper/BcContainerHelper.ps1" + } } - . $BcContainerHelperPath @params - $tempName + $env:BcContainerHelperPath = $bcContainerHelperPath + if ($ENV:GITHUB_ENV) { + Add-Content -Encoding UTF8 -Path $ENV:GITHUB_ENV "BcContainerHelperPath=$bcContainerHelperPath" + } + return $bcContainerHelperPath } -function CleanupAfterBcContainerHelper { - Param( - [string] $bcContainerHelperPath - ) +# +# Download and import the BcContainerHelper module based on repository settings +# baseFolder is the repository baseFolder +# +function DownloadAndImportBcContainerHelper([string] $baseFolder = $ENV:GITHUB_WORKSPACE) { + $params = @{ "ExportTelemetryFunctions" = $true } + $repoSettingsPath = Join-Path $baseFolder $repoSettingsFile - if ($bcContainerHelperPath) { - try { - Write-Host "Removing BcContainerHelper" - Remove-Module BcContainerHelper - Remove-Item $bcContainerHelperPath -Recurse -Force + # Default BcContainerHelper Version is hardcoded in AL-Go-Helper (replaced during AL-Go deploy) + $bcContainerHelperVersion = $defaultBcContainerHelperVersion + if (Test-Path $repoSettingsPath) { + # Read Repository Settings file (without applying organization variables, repository variables or project settings files) + # Override default BcContainerHelper version from AL-Go-Helper only if new version is specifically specified in repo settings file + $repoSettings = Get-Content $repoSettingsPath -Encoding UTF8 | ConvertFrom-Json | ConvertTo-HashTable + if ($repoSettings.Keys -contains "BcContainerHelperVersion") { + $bcContainerHelperVersion = $repoSettings.BcContainerHelperVersion + Write-Host "Using BcContainerHelper $bcContainerHelperVersion version" + if ($bcContainerHelperVersion -like "https://*") { + throw "Setting BcContainerHelperVersion to a URL in settings is not allowed. Fork the AL-Go repository and use direct AL-Go development instead." + } } - catch {} + $params += @{ "bcContainerHelperConfigFile" = $repoSettingsPath } + } + + if ($bcContainerHelperVersion -eq '') { + $bcContainerHelperVersion = "latest" } + + if ($bcContainerHelperVersion -eq 'private') { + throw "ContainerHelperVersion private is no longer supported. Use direct AL-Go development and a direct download url instead." + } + + if ($bcContainerHelperVersion -ne 'latest' -and $bcContainerHelperVersion -ne 'preview') { + Write-Host "::Warning::Using a specific version of BcContainerHelper is not recommended and will lead to build failures in the future. Consider removing the setting." + } + + $bcContainerHelperPath = GetBcContainerHelperPath -bcContainerHelperVersion $bcContainerHelperVersion + + Write-Host "Import from $bcContainerHelperPath" + . $bcContainerHelperPath @params } function MergeCustomObjectIntoOrderedDictionary { @@ -424,7 +517,7 @@ function ReadSettings { Param( [string] $path ) - + if (Test-Path $path) { try { Write-Host "Applying settings from $path" @@ -472,6 +565,7 @@ function ReadSettings { "codeSignCertificatePasswordSecretName" = "codeSignCertificatePassword" "additionalCountries" = @() "appDependencies" = @() + "projectName" = "" "appFolders" = @() "testDependencies" = @() "testFolders" = @() @@ -523,6 +617,8 @@ function ReadSettings { "buildModes" = @() "useCompilerFolder" = $false "PullRequestTrigger" = "pull_request_target" + "fullBuildPatterns" = @() + "excludeEnvironments" = @() } # Read settings from files and merge them into the settings object @@ -559,38 +655,38 @@ function ReadSettings { $settingsObjects += @($projectWorkflowSettingsObject, $userSettingsObject) } } - $settingsObjects | Where-Object { $_ } | ForEach-Object { - $settingsJson = $_ - MergeCustomObjectIntoOrderedDictionary -dst $settings -src $settingsJson - if ("$settingsJson" -ne "" -and $settingsJson.PSObject.Properties.Name -eq "ConditionalSettings") { - $settingsJson.ConditionalSettings | ForEach-Object { - $conditionalSetting = $_ - if ("$conditionalSetting" -ne "") { - $conditionMet = $true - $conditions = @() - if ($conditionalSetting.PSObject.Properties.Name -eq "branches") { - $conditionMet = $conditionMet -and ($conditionalSetting.branches | Where-Object { $branchName -like $_ }) - $conditions += @("branchName: $branchName") - } - if ($conditionalSetting.PSObject.Properties.Name -eq "repositories") { - $conditionMet = $conditionMet -and ($conditionalSetting.repositories | Where-Object { $repoName -like $_ }) - $conditions += @("repoName: $repoName") - } - if ($project -and $conditionalSetting.PSObject.Properties.Name -eq "projects") { - $conditionMet = $conditionMet -and ($conditionalSetting.projects | Where-Object { $project -like $_ }) - $conditions += @("project: $project") - } - if ($workflowName -and $conditionalSetting.PSObject.Properties.Name -eq "workflows") { - $conditionMet = $conditionMet -and ($conditionalSetting.workflows | Where-Object { $workflowName -like $_ }) - $conditions += @("workflowName: $workflowName") - } - if ($userName -and $conditionalSetting.PSObject.Properties.Name -eq "users") { - $conditionMet = $conditionMet -and ($conditionalSetting.users | Where-Object { $userName -like $_ }) - $conditions += @("userName: $userName") - } - if ($conditionMet) { - Write-Host "Applying conditional settings for $($conditions -join ", ")" - MergeCustomObjectIntoOrderedDictionary -dst $settings -src $conditionalSetting.settings + foreach($settingsJson in $settingsObjects) { + if ($settingsJson) { + MergeCustomObjectIntoOrderedDictionary -dst $settings -src $settingsJson + if ($settingsJson.PSObject.Properties.Name -eq "ConditionalSettings") { + foreach($conditionalSetting in $settingsJson.ConditionalSettings) { + if ("$conditionalSetting" -ne "") { + $conditionMet = $true + $conditions = @() + if ($conditionalSetting.PSObject.Properties.Name -eq "branches") { + $conditionMet = $conditionMet -and ($conditionalSetting.branches | Where-Object { $branchName -like $_ }) + $conditions += @("branchName: $branchName") + } + if ($conditionalSetting.PSObject.Properties.Name -eq "repositories") { + $conditionMet = $conditionMet -and ($conditionalSetting.repositories | Where-Object { $repoName -like $_ }) + $conditions += @("repoName: $repoName") + } + if ($project -and $conditionalSetting.PSObject.Properties.Name -eq "projects") { + $conditionMet = $conditionMet -and ($conditionalSetting.projects | Where-Object { $project -like $_ }) + $conditions += @("project: $project") + } + if ($workflowName -and $conditionalSetting.PSObject.Properties.Name -eq "workflows") { + $conditionMet = $conditionMet -and ($conditionalSetting.workflows | Where-Object { $workflowName -like $_ }) + $conditions += @("workflowName: $workflowName") + } + if ($userName -and $conditionalSetting.PSObject.Properties.Name -eq "users") { + $conditionMet = $conditionMet -and ($conditionalSetting.users | Where-Object { $userName -like $_ }) + $conditions += @("userName: $userName") + } + if ($conditionMet) { + Write-Host "Applying conditional settings for $($conditions -join ", ")" + MergeCustomObjectIntoOrderedDictionary -dst $settings -src $conditionalSetting.settings + } } } } @@ -634,6 +730,9 @@ function ReadSettings { if ($settings.shell -ne "powershell" -and $settings.shell -ne "pwsh") { throw "Invalid value for setting: shell: $($settings.githubRunnerShell)" } + if($settings.projectName -eq '') { + $settings.projectName = $project # Default to project path as project name + } $settings } @@ -644,8 +743,7 @@ function ExcludeUnneededApps { [hashtable] $appIdFolders ) - $folders | ForEach-Object { - $folder = $_ + foreach($folder in $folders) { if ($includeOnlyAppIds.Contains(($appIdFolders.GetEnumerator() | Where-Object { $_.Value -eq $folder }).Key)) { $folder } @@ -675,23 +773,23 @@ function ResolveProjectFolders { $isTestApp = $false $isBcptTestApp = $false - + # if an AL app has a dependency to a test app, it is a test app # if an AL app has a dependency to an app from the performance toolkit apps, it is a bcpt test app if ($appJson.PSObject.Properties.Name -eq "dependencies") { - $appJson.dependencies | ForEach-Object { - if ($_.PSObject.Properties.Name -eq "AppId") { - $id = $_.AppId + foreach($dependency in $appJson.dependencies) { + if ($dependency.PSObject.Properties.Name -eq "AppId") { + $id = $dependency.AppId } else { - $id = $_.Id + $id = $dependency.Id } # Check if the app is a test app or a bcpt app - if ($performanceToolkitApps.Contains($id)) { + if ($performanceToolkitApps.Contains($id)) { $isBcptTestApp = $true } - elseif ($testRunnerApps.Contains($id)) { + elseif ($testRunnerApps.Contains($id)) { $isTestApp = $true } } @@ -742,20 +840,16 @@ function ResolveProjectFolders { function AnalyzeRepo { Param( [hashTable] $settings, - $token, [string] $baseFolder = $ENV:GITHUB_WORKSPACE, [string] $project = '.', [string] $insiderSasToken, [switch] $doNotCheckArtifactSetting, - [switch] $doNotCheckAppDependencyProbingPaths, [switch] $doNotIssueWarnings, - [string[]] $includeOnlyAppIds, - [string] $server_url = $ENV:GITHUB_SERVER_URL, - [string] $repository = $ENV:GITHUB_REPOSITORY + [string[]] $includeOnlyAppIds ) $settings = $settings | Copy-HashTable - + if (!$runningLocal) { Write-Host "::group::Analyzing repository" } @@ -898,7 +992,7 @@ function AnalyzeRepo { } if (!$doNotCheckArtifactSetting) { - $artifactUrl = Determine-ArtifactUrl -projectSettings $settings -insiderSasToken $insiderSasToken -doNotIssueWarnings:$doNotIssueWarnings + $artifactUrl = DetermineArtifactUrl -projectSettings $settings -insiderSasToken $insiderSasToken -doNotIssueWarnings:$doNotIssueWarnings $version = $artifactUrl.Split('/')[4] Write-Host "Downloading artifacts from $($artifactUrl.Split('?')[0])" $folders = Download-Artifacts -artifactUrl $artifactUrl -includePlatform -ErrorAction SilentlyContinue @@ -958,108 +1052,132 @@ function AnalyzeRepo { Write-Host "::endgroup::" } - Write-Host "Checking project dependencies" + if (!$settings.doNotRunBcptTests -and -not $settings.bcptTestFolders) { + Write-Host "No performance test apps found in bcptTestFolders in $ALGoSettingsFile" + $settings.doNotRunBcptTests = $true + } + if (!$settings.doNotRunTests -and -not $settings.testFolders) { + if (!$doNotIssueWarnings) { OutputWarning -message "No test apps found in testFolders in $ALGoSettingsFile" } + $settings.doNotRunTests = $true + } + if (-not $settings.appFolders) { + if (!$doNotIssueWarnings) { OutputWarning -message "No apps found in appFolders in $ALGoSettingsFile" } + } - if (!$doNotCheckAppDependencyProbingPaths) { - Write-Host "Checking appDependencyProbingPaths" - if ($settings.appDependencyProbingPaths) { - $settings.appDependencyProbingPaths = @($settings.appDependencyProbingPaths | ForEach-Object { - if ($_.GetType().Name -eq "PSCustomObject") { - $_ - } - else { - New-Object -Type PSObject -Property $_ - } - }) - $settings.appDependencyProbingPaths | ForEach-Object { - $dependency = $_ - if (-not ($dependency.PsObject.Properties.name -eq "repo")) { - throw "The Setting AppDependencyProbingPaths needs to contain a repo property, pointing to the repository on which your project have a dependency" - } - if ($dependency.Repo -eq ".") { - $dependency.Repo = "$server_url/$repository" - } - elseif ($dependency.Repo -notlike "https://*") { - $dependency.Repo = "$server_url/$($dependency.Repo)" - } - if (-not ($dependency.PsObject.Properties.name -eq "Version")) { - $dependency | Add-Member -name "Version" -MemberType NoteProperty -Value "latest" - } - if (-not ($dependency.PsObject.Properties.name -eq "projects")) { - $dependency | Add-Member -name "projects" -MemberType NoteProperty -Value "*" - } - elseif ([String]::IsNullOrEmpty($dependency.projects)) { - $dependency.projects = '*' - } - if (-not ($dependency.PsObject.Properties.name -eq "release_status")) { - $dependency | Add-Member -name "release_status" -MemberType NoteProperty -Value "release" - } - if (-not ($dependency.PsObject.Properties.name -eq "branch")) { - $dependency | Add-Member -name "branch" -MemberType NoteProperty -Value "main" - } - Write-Host "Dependency to projects '$($dependency.projects)' in $($dependency.Repo)@$($dependency.branch), version $($dependency.version), release status $($dependency.release_status)" - if ($dependency.PsObject.Properties.name -eq "AuthTokenSecret") { - Write-Host "Using secret $($dependency.AuthTokenSecret) for access to repository" - $secrets = $env:Secrets | ConvertFrom-Json - # AuthTokenSecret is specified, use the value of that secret - $dependency.AuthTokenSecret = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets."$($dependency.AuthTokenSecret)")) + $settings +} + +function CheckAppDependencyProbingPaths { + Param( + [hashTable] $settings, + $token, + [string] $baseFolder = $ENV:GITHUB_WORKSPACE, + [string] $project = '.', + [string[]] $includeOnlyAppIds + ) + + Write-Host "Checking appDependencyProbingPaths" + $settings = $settings | Copy-HashTable + if ($settings.appDependencyProbingPaths) { + $settings.appDependencyProbingPaths = @($settings.appDependencyProbingPaths | ForEach-Object { + if ($_.GetType().Name -eq "PSCustomObject") { + $_ } else { - if ($token) { - Write-Host "Using GITHUB_TOKEN for access to repository" - } - else { - Write-Host "No token available, will attempt to invoke gh auth token for access to repository" - } - $dependency | Add-Member -name "AuthTokenSecret" -MemberType NoteProperty -Value $token + New-Object -Type PSObject -Property $_ } - if (-not ($dependency.PsObject.Properties.name -eq "alwaysIncludeApps")) { - $dependency | Add-Member -name "alwaysIncludeApps" -MemberType NoteProperty -Value @() + }) + foreach($dependency in $settings.appDependencyProbingPaths) { + if (-not ($dependency.PsObject.Properties.name -eq "repo")) { + throw "The Setting AppDependencyProbingPaths needs to contain a repo property, pointing to the repository on which your project have a dependency" + } + if ($dependency.Repo -eq ".") { + $dependency.Repo = "$ENV:GITHUB_SERVER_URL/$ENV:GITHUB_REPOSITORY" + } + elseif ($dependency.Repo -notlike "https://*") { + $dependency.Repo = "$ENV:GITHUB_SERVER_URL/$($dependency.Repo)" + } + if (-not ($dependency.PsObject.Properties.name -eq "Version")) { + $dependency | Add-Member -name "Version" -MemberType NoteProperty -Value "latest" + } + if (-not ($dependency.PsObject.Properties.name -eq "projects")) { + $dependency | Add-Member -name "projects" -MemberType NoteProperty -Value "*" + } + elseif ([String]::IsNullOrEmpty($dependency.projects)) { + $dependency.projects = '*' + } + if (-not ($dependency.PsObject.Properties.name -eq "release_status")) { + $dependency | Add-Member -name "release_status" -MemberType NoteProperty -Value "release" + } + if (-not ($dependency.PsObject.Properties.name -eq "branch")) { + $dependency | Add-Member -name "branch" -MemberType NoteProperty -Value "main" + } + Write-Host "Dependency to projects '$($dependency.projects)' in $($dependency.Repo)@$($dependency.branch), version $($dependency.version), release status $($dependency.release_status)" + if ($dependency.PsObject.Properties.name -eq "AuthTokenSecret") { + Write-Host "Using secret $($dependency.AuthTokenSecret) for access to repository" + if ("$env:Secrets" -eq "") { + throw "Internal error. Secrets are not available!" } - elseif ($dependency.alwaysIncludeApps -is [string]) { - $dependency.alwaysIncludeApps = $dependency.alwaysIncludeApps.Split(' ') + $secrets = $env:Secrets | ConvertFrom-Json + # AuthTokenSecret is specified, use the value of that secret + $dependency.AuthTokenSecret = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets."$($dependency.AuthTokenSecret)")) + } + else { + if ($token) { + Write-Host "Using GITHUB_TOKEN for access to repository" } - if ($dependency.alwaysIncludeApps) { - Write-Host "Always including apps: $($dependency.alwaysIncludeApps -join ", ")" + else { + Write-Host "No token available, will attempt to invoke gh auth token for access to repository" } + $dependency | Add-Member -name "AuthTokenSecret" -MemberType NoteProperty -Value $token + } + if (-not ($dependency.PsObject.Properties.name -eq "alwaysIncludeApps")) { + $dependency | Add-Member -name "alwaysIncludeApps" -MemberType NoteProperty -Value @() + } + elseif ($dependency.alwaysIncludeApps -is [string]) { + $dependency.alwaysIncludeApps = $dependency.alwaysIncludeApps.Split(' ') + } + if ($dependency.alwaysIncludeApps) { + Write-Host "Always including apps: $($dependency.alwaysIncludeApps -join ", ")" + } - if ($dependency.release_status -eq "include") { - if ($dependency.Repo -ne "$server_url/$repository") { - OutputWarning "Dependencies with release_status 'include' must be to other projects in the same repository." - } - else { - $dependency.projects.Split(',') | ForEach-Object { - if ($_ -eq '*') { - OutputWarning "Dependencies to the same repository cannot specify all projects (*)" - } - else { - $depProject = $_ - Write-Host "Identified dependency to project $depProject in the same repository" - - $dependencyIds = @( @($settings.appDependencies + $settings.testDependencies) | ForEach-Object { $_.id }) - $depSettings = ReadSettings -baseFolder $baseFolder -project $depProject -workflowName "CI/CD" - $depSettings = AnalyzeRepo -settings $depSettings -token $token -baseFolder $baseFolder -project $depProject -includeOnlyAppIds @($dependencyIds + $includeOnlyAppIds + $dependency.alwaysIncludeApps) -doNotIssueWarnings -doNotCheckArtifactSetting -server_url $server_url -repository $repository - - Push-Location $projectPath - try { - "appFolders", "testFolders", "bcptTestFolders" | ForEach-Object { - $propertyName = $_ - Write-Host "Adding folders from $depProject to $_" - $found = $false - $depSettings."$propertyName" | ForEach-Object { - $folder = Resolve-Path -Path (Join-Path $baseFolder "$depProject/$_") -Relative - if (!$settings."$propertyName".Contains($folder)) { - $settings."$propertyName" += @($folder) - $found = $true - Write-Host "- $folder" - } + if ($dependency.release_status -eq "include") { + if ($dependency.Repo -ne "$ENV:GITHUB_SERVER_URL/$ENV:GITHUB_REPOSITORY") { + OutputWarning "Dependencies with release_status 'include' must be to other projects in the same repository." + } + else { + foreach($depProject in $dependency.projects.Split(',')) { + if ($depProject -eq '*') { + OutputWarning "Dependencies to the same repository cannot specify all projects (*)" + } + else { + Write-Host "Identified dependency to project $depProject in the same repository" + + $dependencyIds = @( @($settings.appDependencies + $settings.testDependencies) | ForEach-Object { $_.id }) + $thisIncludeOnlyAppIds = @($dependencyIds + $includeOnlyAppIds + $dependency.alwaysIncludeApps) + $depSettings = ReadSettings -baseFolder $baseFolder -project $depProject -workflowName "CI/CD" + $depSettings = AnalyzeRepo -settings $depSettings -baseFolder $baseFolder -project $depProject -includeOnlyAppIds $thisIncludeOnlyAppIds -doNotCheckArtifactSetting -doNotIssueWarnings + $depSettings = CheckAppDependencyProbingPaths -settings $depSettings -token $token -baseFolder $baseFolder -project $depProject -includeOnlyAppIds $thisIncludeOnlyAppIds + + $projectPath = Join-Path $baseFolder $project -Resolve + Push-Location $projectPath + try { + foreach($propertyName in "appFolders", "testFolders", "bcptTestFolders") { + Write-Host "Adding folders from $depProject to $propertyName in $project" + $found = $false + foreach($_ in $depSettings."$propertyName") { + $folder = Resolve-Path -Path (Join-Path $baseFolder "$depProject/$_") -Relative + if (!$settings."$propertyName".Contains($folder)) { + $settings."$propertyName" += @($folder) + $found = $true + Write-Host "- $folder" } - if (!$found) { Write-Host "- No folders added" } } + if (!$found) { Write-Host "- No folders added" } } - finally { - Pop-Location - } + } + finally { + Pop-Location } } } @@ -1067,31 +1185,15 @@ function AnalyzeRepo { } } } - - if (!$settings.doNotRunBcptTests -and -not $settings.bcptTestFolders) { - if (!$doNotIssueWarnings) { OutputWarning -message "No performance test apps found in bcptTestFolders in $ALGoSettingsFile" } - $settings.doNotRunBcptTests = $true - } - if (!$settings.doNotRunTests -and -not $settings.testFolders) { - if (!$doNotIssueWarnings) { OutputWarning -message "No test apps found in testFolders in $ALGoSettingsFile" } - $settings.doNotRunTests = $true - } - if (-not $settings.appFolders) { - if (!$doNotIssueWarnings) { OutputWarning -message "No apps found in appFolders in $ALGoSettingsFile" } - } - $settings } -function Get-ProjectFolders { +function GetProjectFolders { Param( [string] $baseFolder, [string] $project, [switch] $includeALGoFolder, - [string[]] $includeOnlyAppIds, - [string] $server_url = $ENV:GITHUB_SERVER_URL, - [string] $repository = $ENV:GITHUB_REPOSITORY, - $token + [string[]] $includeOnlyAppIds ) Write-Host "Analyzing project $project" @@ -1103,7 +1205,7 @@ function Get-ProjectFolders { $projectFolders = @() $settings = ReadSettings -baseFolder $baseFolder -project $project -workflowName "CI/CD" - $settings = AnalyzeRepo -settings $settings -token $token -baseFolder $baseFolder -project $project -includeOnlyAppIds $includeOnlyAppIds -doNotIssueWarnings -doNotCheckArtifactSetting -server_url $server_url -repository $repository -doNotCheckAppDependencyProbingPaths + $settings = AnalyzeRepo -settings $settings -baseFolder $baseFolder -project $project -includeOnlyAppIds $includeOnlyAppIds -doNotIssueWarnings -doNotCheckArtifactSetting $AlGoFolderArr = @() if ($includeALGoFolder) { $AlGoFolderArr = @($ALGoFolderName) } Set-Location $baseFolder @@ -1139,7 +1241,7 @@ function installModules { Install-Module $_ -Force | Out-Null } } - $modules | ForEach-Object { + $modules | ForEach-Object { Write-Host "Importing module $_" Import-Module $_ -DisableNameChecking -WarningAction SilentlyContinue | Out-Null } @@ -1231,19 +1333,19 @@ function Select-Value { $offset = 0 $keys = @() $values = @() - - $options.GetEnumerator() | ForEach-Object { + $defaultAnswer = 0 + foreach($option in $options.GetEnumerator()) { Write-Host -ForegroundColor Yellow "$([char]($offset+97)) " -NoNewline - $keys += @($_.Key) - $values += @($_.Value) - if ($_.Key -eq $default) { - Write-Host -ForegroundColor Yellow $_.Value + $keys += @($option.Key) + $values += @($option.Value) + if ($option.Key -eq $default) { + Write-Host -ForegroundColor Yellow $option.Value $defaultAnswer = $offset } else { - Write-Host $_.Value + Write-Host $option.Value } - $offset++ + $offset++ } Write-Host $answer = -1 @@ -1297,7 +1399,6 @@ function Enter-Value { [Parameter(Mandatory = $true)] [string] $question, [switch] $doNotConvertToLower, - [switch] $previousStep, [char[]] $trimCharacters = @() ) @@ -1388,7 +1489,6 @@ function CreateDevEnv { [string] $baseFolder, [string] $project, [string] $userName = $env:Username, - [string] $bcContainerHelperPath = "", [Parameter(ParameterSetName = 'cloud')] [Hashtable] $bcAuthContext = $null, @@ -1417,11 +1517,16 @@ function CreateDevEnv { $projectFolder = Join-Path $baseFolder $project -Resolve $dependenciesFolder = Join-Path $projectFolder ".dependencies" $runAlPipelineParams = @{} - $loadBcContainerHelper = ($bcContainerHelperPath -eq "") - if ($loadBcContainerHelper) { - $BcContainerHelperPath = DownloadAndImportBcContainerHelper -baseFolder $baseFolder - } + DownloadAndImportBcContainerHelper -baseFolder $baseFolder + $removeEnvSecrets = $false try { + if ($env:Secrets) { + $secrets = $env:Secrets | ConvertFrom-Json | ConvertTo-HashTable + } + else { + $secrets = @{} + $removeEnvSecrets = $true + } if ($caller -eq "local") { $currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) if (-not $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { @@ -1437,7 +1542,7 @@ function CreateDevEnv { } if ($caller -eq "local") { $params += @{ "userName" = $userName } } $settings = ReadSettings @params - + if ($caller -eq "GitHubActions") { if ($kind -ne "cloud") { OutputError -message "Unexpected. kind=$kind, caller=$caller" @@ -1453,17 +1558,21 @@ function CreateDevEnv { $settings.appDependencyProbingPaths | ForEach-Object { if ($_.Contains("AuthTokenSecret")) { $secretName = $_.authTokenSecret - $_.Remove('authTokenSecret') + if (!$secrets.ContainsKey($secretName)) { + $secrets."$secretName" = '' + } if ($settings.keyVaultName) { $secret = Get-AzKeyVaultSecret -VaultName $settings.keyVaultName -Name $secretName - if ($secret) { $_.authTokenSecret = $secret.SecretValue | Get-PlainText } + if ($secret) { + $secrets."$secretName" = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(($secret.SecretValue | Get-PlainText))) + } } else { Write-Host "Not using Azure KeyVault, attempting to retrieve an auth token using gh auth token" $retry = $true while ($retry) { try { - $_.authTokenSecret = invoke-gh -silent -returnValue auth token + $secrets."$secretName" = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes((invoke-gh -silent -returnValue auth token))) $retry = $false } catch { @@ -1473,8 +1582,9 @@ function CreateDevEnv { } } } - } + } } + $env:Secrets = $secrets | ConvertTo-Json } if (($settings.keyVaultName) -and -not ($bcAuthContext)) { @@ -1493,12 +1603,12 @@ function CreateDevEnv { if ($settings.applicationInsightsConnectionStringSecretName) { $applicationInsightsConnectionStringSecret = Get-AzKeyVaultSecret -VaultName $settings.keyVaultName -Name $settings.applicationInsightsConnectionStringSecretName if ($applicationInsightsConnectionStringSecret) { - $runAlPipelineParams += @{ + $runAlPipelineParams += @{ "applicationInsightsConnectionString" = $applicationInsightsConnectionStringSecret.SecretValue | Get-PlainText } } } - + if ($settings.keyVaultCertificateUrlSecretName) { $KeyVaultCertificateUrlSecret = Get-AzKeyVaultSecret -VaultName $settings.keyVaultName -Name $settings.keyVaultCertificateUrlSecretName if ($KeyVaultCertificateUrlSecret) { @@ -1508,7 +1618,7 @@ function CreateDevEnv { OutputError -message "When specifying a KeyVaultCertificateUrl secret in settings, you also need to provide a KeyVaultCertificatePassword secret and a KeyVaultClientId secret" exit } - $runAlPipelineParams += @{ + $runAlPipelineParams += @{ "KeyVaultCertPfxFile" = $KeyVaultCertificateUrlSecret.SecretValue | Get-PlainText "keyVaultCertPfxPassword" = $keyVaultCertificatePasswordSecret.SecretValue "keyVaultClientId" = $keyVaultClientIdSecret.SecretValue | Get-PlainText @@ -1525,17 +1635,11 @@ function CreateDevEnv { throw "$_ is an illegal property in adminCenterApiCredentials setting" } } - if ($adminCenterApiCredentials.Keys -contains 'ClientSecret') { - $adminCenterApiCredentials.ClientSecret = ConvertTo-SecureString -String $adminCenterApiCredentials.ClientSecret -AsPlainText -Force - } } } } - $params = @{ - "settings" = $settings - "baseFolder" = $projectFolder - } + $params = @{} if ($kind -eq "local") { $params += @{ "insiderSasToken" = $insiderSasToken @@ -1546,21 +1650,22 @@ function CreateDevEnv { "doNotCheckArtifactSetting" = $true } } - $repo = AnalyzeRepo @params - if ((-not $repo.appFolders) -and (-not $repo.testFolders)) { + $settings = AnalyzeRepo -settings $settings -baseFolder $baseFolder -project $project @params + $settings = CheckAppDependencyProbingPaths -settings $settings -baseFolder $baseFolder -project $project + if ((-not $settings.appFolders) -and (-not $settings.testFolders)) { Write-Host "Repository is empty" } - if ($kind -eq "local" -and $repo.type -eq "AppSource App" ) { + if ($kind -eq "local" -and $settings.type -eq "AppSource App" ) { if ($licenseFileUrl -eq "") { OutputWarning -message "When building an AppSource App, you should create a secret called LicenseFileUrl, containing a secure URL to your license file with permission to the objects used in the app." } } - $installApps = $repo.installApps - $installTestApps = $repo.installTestApps + $installApps = $settings.installApps + $installTestApps = $settings.installTestApps - if ($repo.appDependencyProbingPaths) { + if ($settings.appDependencyProbingPaths) { Write-Host "Downloading dependencies ..." if (Test-Path $dependenciesFolder) { @@ -1570,7 +1675,7 @@ function CreateDevEnv { New-Item $dependenciesFolder -ItemType Directory | Out-Null } - $repo.appDependencyProbingPaths = @($repo.appDependencyProbingPaths | ForEach-Object { + $settings.appDependencyProbingPaths = @($settings.appDependencyProbingPaths | ForEach-Object { if ($_.GetType().Name -eq "PSCustomObject") { $_ } @@ -1578,19 +1683,19 @@ function CreateDevEnv { New-Object -Type PSObject -Property $_ } }) - Get-Dependencies -probingPathsJson $repo.appDependencyProbingPaths -saveToPath $dependenciesFolder -api_url 'https://api.github.com' | ForEach-Object { + GetDependencies -probingPathsJson $settings.appDependencyProbingPaths -saveToPath $dependenciesFolder -api_url 'https://api.github.com' | ForEach-Object { if ($_.startswith('(')) { - $installTestApps += $_ + $installTestApps += $_ } else { - $installApps += $_ + $installApps += $_ } } } - - if ($repo.versioningStrategy -eq -1) { + + if ($settings.versioningStrategy -eq -1) { if ($kind -eq "cloud") { throw "Versioningstrategy -1 cannot be used on cloud" } - $artifactVersion = [Version]$repo.artifact.Split('/')[4] + $artifactVersion = [Version]$settings.artifact.Split('/')[4] $runAlPipelineParams += @{ "appVersion" = "$($artifactVersion.Major).$($artifactVersion.Minor)" "appBuild" = $artifactVersion.Build @@ -1598,14 +1703,14 @@ function CreateDevEnv { } } else { - if (($repo.versioningStrategy -band 16) -eq 16) { + if (($settings.versioningStrategy -band 16) -eq 16) { $runAlPipelineParams += @{ - "appVersion" = $repo.repoVersion + "appVersion" = $settings.repoVersion } } $appBuild = 0 $appRevision = 0 - switch ($repo.versioningStrategy -band 15) { + switch ($settings.versioningStrategy -band 15) { 2 { # USE DATETIME $appBuild = [Int32]([DateTime]::UtcNow.ToString('yyyyMMdd')) @@ -1629,7 +1734,7 @@ function CreateDevEnv { if (Test-Path $testResultsFiles) { Remove-Item $testResultsFiles -Force } - + Set-Location $projectFolder $runAlPipelineOverrides | ForEach-Object { $scriptName = $_ @@ -1644,7 +1749,7 @@ function CreateDevEnv { if ($kind -eq "local") { $runAlPipelineParams += @{ - "artifact" = $repo.artifact.replace('{INSIDERSASTOKEN}', $insiderSasToken) + "artifact" = $settings.artifact.replace('{INSIDERSASTOKEN}', $insiderSasToken) "auth" = $auth "credential" = $credential } @@ -1664,7 +1769,7 @@ function CreateDevEnv { if ($runAlPipelineParams.Keys -contains 'NewBcContainer') { throw "Overriding NewBcContainer is not allowed when running cloud DevEnv" } - + if ($bcAuthContext) { $authContext = Renew-BcAuthContext $bcAuthContext } @@ -1687,7 +1792,7 @@ function CreateDevEnv { $baseApp = Get-BcPublishedApps -bcAuthContext $authContext -environment $environmentName | Where-Object { $_.Name -eq "Base Application" } } else { - $countryCode = $repo.country + $countryCode = $settings.country New-BcEnvironment -bcAuthContext $authContext -environment $environmentName -countryCode $countryCode -environmentType "Sandbox" | Out-Null do { Start-Sleep -Seconds 10 @@ -1695,12 +1800,12 @@ function CreateDevEnv { } while (!($baseApp)) $baseapp | Out-Host } - + $artifact = Get-BCArtifactUrl ` -country $countryCode ` -version $baseApp.Version ` -select Closest - + if ($artifact) { Write-Host "Using Artifacts: $artifact" } @@ -1716,7 +1821,7 @@ function CreateDevEnv { "updateLaunchJson" = "Cloud Sandbox ($environmentName)" } } - + "enableTaskScheduler", "assignPremiumPlan", "installTestRunner", @@ -1727,7 +1832,7 @@ function CreateDevEnv { "enableAppSourceCop", "enablePerTenantExtensionCop", "enableUICop" | ForEach-Object { - if ($repo."$_") { $runAlPipelineParams += @{ "$_" = $true } } + if ($settings."$_") { $runAlPipelineParams += @{ "$_" = $true } } } $sharedFolder = "" @@ -1736,37 +1841,37 @@ function CreateDevEnv { } Run-AlPipeline @runAlPipelineParams ` - -vsixFile $repo.vsixFile ` + -vsixFile $settings.vsixFile ` -pipelinename $workflowName ` -imageName "" ` - -memoryLimit $repo.memoryLimit ` + -memoryLimit $settings.memoryLimit ` -baseFolder $projectFolder ` -sharedFolder $sharedFolder ` -licenseFile $licenseFileUrl ` -installApps $installApps ` -installTestApps $installTestApps ` - -installOnlyReferencedApps:$repo.installOnlyReferencedApps ` - -appFolders $repo.appFolders ` - -testFolders $repo.testFolders ` + -installOnlyReferencedApps:$settings.installOnlyReferencedApps ` + -appFolders $settings.appFolders ` + -testFolders $settings.testFolders ` -testResultsFile $testResultsFile ` -testResultsFormat 'JUnit' ` - -customCodeCops $repo.customCodeCops ` + -customCodeCops $settings.customCodeCops ` -azureDevOps:($caller -eq 'AzureDevOps') ` -gitLab:($caller -eq 'GitLab') ` -gitHubActions:($caller -eq 'GitHubActions') ` - -failOn $repo.failOn ` - -treatTestFailuresAsWarnings:$repo.treatTestFailuresAsWarnings ` - -rulesetFile $repo.rulesetFile ` - -AppSourceCopMandatoryAffixes $repo.appSourceCopMandatoryAffixes ` - -obsoleteTagMinAllowedMajorMinor $repo.obsoleteTagMinAllowedMajorMinor ` + -failOn $settings.failOn ` + -treatTestFailuresAsWarnings:$settings.treatTestFailuresAsWarnings ` + -rulesetFile $settings.rulesetFile ` + -AppSourceCopMandatoryAffixes $settings.appSourceCopMandatoryAffixes ` + -obsoleteTagMinAllowedMajorMinor $settings.obsoleteTagMinAllowedMajorMinor ` -doNotRunTests ` -doNotRunBcptTests ` -useDevEndpoint ` -keepContainer } finally { - if ($loadBcContainerHelper) { - CleanupAfterBcContainerHelper -bcContainerHelperPath $bcContainerHelperPath + if ($removeEnvSecrets -and $env:Secrets) { + Remove-Item Env:\Secrets } if (Test-Path $dependenciesFolder) { Get-ChildItem -Path $dependenciesFolder -Include * -File | ForEach-Object { $_.Delete() } @@ -1843,19 +1948,17 @@ Function AnalyzeProjectDependencies { # Loop through all projects # Get all apps in the project # Get all dependencies for the apps - $projects | ForEach-Object { - $project = $_ + foreach($project in $projects) { Write-Host "- Analyzing project: $project" $projectSettings = ReadSettings -project $project -baseFolder $baseFolder ResolveProjectFolders -baseFolder $baseFolder -project $project -projectSettings ([ref] $projectSettings) - + # App folders are relative to the AL-Go project folder. Convert them to relative to the base folder Push-Location $baseFolder try { - $projectPath = Join-Path $baseFolder $project - $folders = @($projectSettings.appFolders) + @($projectSettings.testFolders) + @($projectSettings.bcptTestFolders) | ForEach-Object { - return (Resolve-Path (Join-Path $projectPath $_) -Relative) + $folders = @($projectSettings.appFolders) + @($projectSettings.testFolders) + @($projectSettings.bcptTestFolders) | ForEach-Object { + return (Resolve-Path (Join-Path $baseFolder "$project/$_") -Relative) } } finally { @@ -1892,24 +1995,24 @@ Function AnalyzeProjectDependencies { # These projects can be built in parallel and are added to the build order # The projects that are added to the build order are removed from the list of projects # The loop continues until all projects have been added to the build order - $projects | ForEach-Object { - $project = $_ + foreach($project in $projects) { Write-Host "- $project" # Find all project dependencies for the current project $dependencies = $appDependencies."$project".dependencies # Loop through all dependencies and locate the projects, containing the apps for which the current project has a dependency - $foundDependencies = @($dependencies | ForEach-Object { - $dependency = $_ - # Find the project that contains the app for which the current project has a dependency - $depProject = $projects | Where-Object { $_ -ne $project -and $appDependencies."$_".apps -contains $dependency } - # Add this project and all projects on which that project has a dependency to the list of dependencies for the current project - $depProject | ForEach-Object { - $_ - if ($projectDependencies.Value.Keys -contains $_) { - $projectDependencies.value."$_" - } + $foundDependencies = @() + foreach($dependency in $dependencies) { + # Find the project that contains the app for which the current project has a dependency + $depProjects = @($projects | Where-Object { $_ -ne $project -and $appDependencies."$_".apps -contains $dependency }) + # Add this project and all projects on which that project has a dependency to the list of dependencies for the current project + foreach($depProject in $depProjects) { + $foundDependencies += $depProject + if ($projectDependencies.Value.Keys -contains $depProject) { + $foundDependencies += $projectDependencies.value."$depProject" } - } | Select-Object -Unique) + } + } + $foundDependencies = @($foundDependencies | Select-Object -Unique) # foundDependencies now contains all projects that the current project has a dependency on # Update ref variable projectDependencies for this project if ($projectDependencies.Value.Keys -notcontains $project) { @@ -1917,12 +2020,12 @@ Function AnalyzeProjectDependencies { # Update the dependency list for that project if it contains the current project, which might lead to a changed dependency list # This is needed because we are looping through the projects in a any order $keys = @($projectDependencies.value.Keys) - $keys | ForEach-Object { - if ($projectDependencies.value."$_" -contains $project) { - $projectDeps = @( $projectDependencies.value."$_" ) - $projectDependencies.value."$_" = @( @($projectDeps + $foundDependencies) | Select-Object -Unique ) - if (Compare-Object -ReferenceObject $projectDependencies.value."$_" -differenceObject $projectDeps) { - Write-Host "Add ProjectDependencies $($foundDependencies -join ',') to $_" + foreach($key in $keys) { + if ($projectDependencies.value."$key" -contains $project) { + $projectDeps = @( $projectDependencies.value."$key" ) + $projectDependencies.value."$key" = @( @($projectDeps + $foundDependencies) | Select-Object -Unique ) + if (Compare-Object -ReferenceObject $projectDependencies.value."$key" -differenceObject $projectDeps) { + Write-Host "Add ProjectDependencies $($foundDependencies -join ',') to $key" } } } @@ -1932,14 +2035,14 @@ Function AnalyzeProjectDependencies { if ($foundDependencies) { Write-Host "Found dependencies to projects: $($foundDependencies -join ", ")" # Add project to buildAlso for this dependency to ensure that this project also gets build when the dependency is built - $foundDependencies | ForEach-Object { - if ($buildAlso.value.Keys -contains $_) { - if ($buildAlso.value."$_" -notcontains $project) { - $buildAlso.value."$_" += @( $project ) + foreach($dependency in $foundDependencies) { + if ($buildAlso.value.Keys -contains $dependency) { + if ($buildAlso.value."$dependency" -notcontains $project) { + $buildAlso.value."$dependency" += @( $project ) } } else { - $buildAlso.value."$_" = @( $project ) + $buildAlso.value."$dependency" = @( $project ) } } } @@ -1952,9 +2055,9 @@ Function AnalyzeProjectDependencies { throw "Circular project reference encountered, cannot determine build order" } Write-Host "#$no - build projects: $($thisJob -join ", ")" - + $projectsOrder += @{'projects' = $thisJob; 'projectsCount' = $thisJob.Count } - + $projects = @($projects | Where-Object { $thisJob -notcontains $_ }) $no++ } @@ -1966,7 +2069,7 @@ function GetBaseFolder { Param( [string] $folder ) - + Push-Location $folder try { $baseFolder = invoke-git rev-parse --show-toplevel -returnValue @@ -1978,7 +2081,7 @@ function GetBaseFolder { if (!$baseFolder -or !(Test-Path (Join-Path $baseFolder '.github') -PathType Container)) { throw "Cannot determine base folder from folder $folder." } - + return $baseFolder } @@ -2002,7 +2105,7 @@ function GetProject { $project } -function Determine-ArtifactUrl { +function DetermineArtifactUrl { Param( [hashtable] $projectSettings, [string] $insiderSasToken = "", @@ -2034,7 +2137,7 @@ function Determine-ArtifactUrl { } } } - + if ($artifact -like "https://*") { $artifactUrl = $artifact $storageAccount = ("$artifactUrl////".Split('/')[2]).Split('.')[0] @@ -2070,7 +2173,7 @@ function Determine-ArtifactUrl { $atArtifactUrl = Get-BCArtifactUrl -storageAccount $storageAccount -type $artifactType -country at -version "$($ver.Major).$($ver.Minor)" -select Latest -sasToken $sasToken Write-Host "Latest AT artifacts $atArtifactUrl" $latestATversion = $atArtifactUrl.Split('/')[4] - $countries = Get-BCArtifactUrl -storageAccount $storageAccount -type $artifactType -version $latestATversion -sasToken $sasToken -select All | ForEach-Object { + $countries = Get-BCArtifactUrl -storageAccount $storageAccount -type $artifactType -version $latestATversion -sasToken $sasToken -select All | ForEach-Object { $countryArtifactUrl = $_.Split('?')[0] # remove sas token $countryArtifactUrl.Split('/')[5] # get country } @@ -2089,20 +2192,21 @@ function Determine-ArtifactUrl { return $artifactUrl } -function Retry-Command { +function RetryCommand { param( [Parameter(Mandatory = $true)] [ScriptBlock]$Command, [Parameter(Mandatory = $false)] [int]$MaxRetries = 3, [Parameter(Mandatory = $false)] - [int]$RetryDelaySeconds = 5 + [int]$RetryDelaySeconds = 5, + [Object[]] $argumentList ) $retryCount = 0 while ($retryCount -lt $MaxRetries) { try { - Invoke-Command $Command + Invoke-Command $Command -ArgumentList $argumentList if ($LASTEXITCODE -ne 0) { throw "Command failed with exit code $LASTEXITCODE" } diff --git a/AL-Go-TestRepoHelper.ps1 b/AL-Go-TestRepoHelper.ps1 index 3a8cebb..05f6d1a 100644 --- a/AL-Go-TestRepoHelper.ps1 +++ b/AL-Go-TestRepoHelper.ps1 @@ -43,7 +43,7 @@ function Test-Shell { } } -function Test-Json { +function Test-SettingsJson { Param( [hashtable] $json, [string] $settingsDescription, @@ -59,7 +59,8 @@ function Test-Json { Test-Property -settingsDescription $settingsDescription -json $json -key 'templateUrl' -should } if ($type -eq 'Project') { - # Test for things that should / should not exist in a project settings file + # GitHubRunner should not be in a project settings file (only read from repo or workflow settings) + Test-Property -settingsDescription $settingsDescription -json $json -key 'githubRunner' -shouldnot } if ($type -eq 'Workflow') { # Test for things that should / should not exist in a workflow settings file @@ -71,8 +72,9 @@ function Test-Json { # templateUrl should not be in Project or Workflow settings Test-Property -settingsDescription $settingsDescription -json $json -key 'templateUrl' -maynot - # schedules and runs-on should not be in Project or Workflow settings - 'nextMajorSchedule','nextMinorSchedule','currentSchedule','githubRunner','runs-on' | ForEach-Object { + # schedules and runs-on should not be in Project or Workflow settings + # These properties are used in Update AL-Go System Files, hence they should only be in Repo settings + 'nextMajorSchedule','nextMinorSchedule','currentSchedule','runs-on' | ForEach-Object { Test-Property -settingsDescription $settingsDescription -json $json -key $_ -shouldnot } } @@ -92,7 +94,7 @@ function Test-JsonStr { try { $json = $jsonStr | ConvertFrom-Json | ConvertTo-HashTable - Test-Json -json $json -settingsDescription $settingsDescription -type:$type + Test-SettingsJson -json $json -settingsDescription $settingsDescription -type:$type } catch { throw "$($_.Exception.Message.Replace("`r",'').Replace("`n",' '))" @@ -116,9 +118,9 @@ function Test-JsonFile { function Test-ALGoRepository { Param( - [string] $baseFolder + [string] $baseFolder = $ENV:GITHUB_WORKSPACE ) - + if ($ENV:ALGoOrgSettings) { Write-Host "Checking AL-Go Org Settings variable (ALGoOrgSettings)" Test-JsonStr -jsonStr "$ENV:ALGoOrgSettings" -settingsDescription 'ALGoOrgSettings variable' -type 'Variable' @@ -149,141 +151,140 @@ function Test-ALGoRepository { } function Write-Big { -Param( - [string] $str -) -$chars = @{ -"0" = @' - ___ - / _ \ - | | | | - | | | | - | |_| | - \___/ -'@.Split("`n") -"1" = @' - __ - /_ | - | | - | | - | | - |_| -'@.Split("`n") -"2" = @' - ___ - |__ \ - ) | - / / - / /_ - |____| -'@.Split("`n") -"3" = @' - ____ - |___ \ - __) | - |__ < - ___) | - |____/ -'@.Split("`n") -"4" = @' - _ _ - | || | - | || |_ - |__ _| - | | - |_| -'@.Split("`n") -"5" = @' - _____ - | ____| - | |__ - |___ \ - ___) | - |____/ -'@.Split("`n") -"6" = @' - __ - / / - / /_ - | '_ \ - | (_) | - \___/ -'@.Split("`n") -"7" = @' - ______ - |____ | - / / - / / - / / - /_/ -'@.Split("`n") -"8" = @' - ___ - / _ \ - | (_) | - > _ < - | (_) | - \___/ -'@.Split("`n") -"9" = @' - ___ - / _ \ - | (_) | - \__, | - / / - /_/ -'@.Split("`n") -"." = @' - - - - - _ - (_) -'@.Split("`n") -"v" = @' - - - __ __ - \ \ / / - \ V / - \_(_) -'@.Split("`n") -"p" = @' - _____ _ - | __ \ (_) - | |__) | __ _____ ___ _____ __ - | ___/ '__/ _ \ \ / / |/ _ \ \ /\ / / - | | | | | __/\ V /| | __/\ V V / - |_| |_| \___| \_/ |_|\___| \_/\_/ -'@.Split("`n") -"d" = @' - _____ - | __ \ - | | | | _____ __ - | | | |/ _ \ \ / / - | |__| | __/\ V / - |_____/ \___| \_(_) -'@.Split("`n") -"a" = @' - _ _____ __ _____ _ _ _ _ _ - /\ | | / ____| / _| / ____(_) | | | | | | | - / \ | | ______| | __ ___ | |_ ___ _ __ | | __ _| |_| |__| |_ _| |__ - / /\ \ | | |______| | |_ |/ _ \ | _/ _ \| '__| | | |_ | | __| __ | | | | '_ \ - / ____ \| |____ | |__| | (_) | | || (_) | | | |__| | | |_| | | | |_| | |_) | - /_/ \_\______| \_____|\___/ |_| \___/|_| \_____|_|\__|_| |_|\__,_|_.__/ -'@.Split("`n") -} - + Param( + [string] $str + ) + $chars = @{ + "0" = @( + " ___ " + " / _ \ " + "| | | |" + "| | | |" + "| |_| |" + " \___/ " + ) + "1" = @( + " __ " + "/_ |" + " | |" + " | |" + " | |" + " |_|" + ) + "2" = @( + " ___ " + "|__ \ " + " ) |" + " / / " + " / /_ " + "|____|" + ) + "3" = @( + " ____ " + "|___ \ " + " __) |" + " |__ < " + " ___) |" + "|____/ " + ) + "4" = @( + " _ _ " + "| || | " + "| || |_ " + "|__ _|" + " | | " + " |_| " + ) + "5" = @( + " _____ " + "| ____|" + "| |__ " + "|___ \ " + " ___) |" + "|____/ " + ) + "6" = @( + " __ " + " / / " + " / /_ " + "| '_ \ " + "| (_) |" + " \___/ " + ) + "7" = @( + " ______ " + "|____ |" + " / / " + " / / " + " / / " + " /_/ " + ) + "8" = @( + " ___ " + " / _ \ " + "| (_) |" + " > _ < " + "| (_) |" + " \___/ " + ) + "9" = @( + " ___ " + " / _ \ " + "| (_) |" + " \__, |" + " / / " + " /_/ " + ) + "." = @( + " " + " " + " " + " " + " _ " + "(_)" + ) + "v" = @( + " " + " " + "__ __" + "\ \ / /" + " \ V / " + " \_(_)" + ) + "p" = @( + " _____ _ " + "| __ \ (_) " + "| |__) | __ _____ ___ _____ __" + "| ___/ '__/ _ \ \ / / |/ _ \ \ /\ / /" + "| | | | | __/\ V /| | __/\ V V / " + "|_| |_| \___| \_/ |_|\___| \_/\_/ " + ) + "d" = @( + " _____ " + "| __ \ " + "| | | | _____ __" + "| | | |/ _ \ \ / /" + "| |__| | __/\ V / " + "|_____/ \___| \_(_)" + ) + "a" = @( + " _ _____ __ _____ _ _ _ _ _ " + " /\ | | / ____| / _| / ____(_) | | | | | | | " + " / \ | | ______| | __ ___ | |_ ___ _ __ | | __ _| |_| |__| |_ _| |__ " + " / /\ \ | | |______| | |_ |/ _ \ | _/ _ \| '__| | | |_ | | __| __ | | | | '_ \ " + " / ____ \| |____ | |__| | (_) | | || (_) | | | |__| | | |_| | | | |_| | |_) | " + "/_/ \_\______| \_____|\___/ |_| \___/|_| \_____|_|\__|_| |_|\__,_|_.__/ " + ) + } -0..5 | ForEach-Object { - $line = $_ - $str.ToCharArray() | ForEach-Object { - if ($chars.Keys -contains $_) { - $ch = $chars."$_" - Write-Host -noNewline $ch[$line] + $lines = $chars."a".Count + for ($line = 0; $line -lt $lines; $line++) { + foreach ($ch in $str.ToCharArray()) { + if ($chars.Keys -contains $ch) { + $bigCh = $chars."$ch" + Write-Host -noNewline $bigCh[$line] + } } + Write-Host } - Write-Host -} } diff --git a/AddExistingApp/AddExistingApp.ps1 b/AddExistingApp/AddExistingApp.ps1 index f2209a6..5a0d31e 100644 --- a/AddExistingApp/AddExistingApp.ps1 +++ b/AddExistingApp/AddExistingApp.ps1 @@ -39,11 +39,11 @@ function expandfile { # .zip file $destinationPath = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString())" Expand-7zipArchive -path $path -destinationPath $destinationPath - + $directoryInfo = Get-ChildItem $destinationPath | Measure-Object if ($directoryInfo.count -eq 0) { throw "The file is empty or malformed." - } + } $appFolders = @() if (Test-Path (Join-Path $destinationPath 'app.json')) { @@ -71,7 +71,7 @@ function expandfile { elseif ([string]::new([char[]](Get-Content $path @byteEncodingParam -TotalCount 4)) -eq "NAVX") { $destinationPath = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString())" Extract-AppFileToFolder -appFilename $path -appFolder $destinationPath -generateAppJson - $destinationPath + $destinationPath } else { throw "The provided url cannot be extracted. The url might be wrong or the file is malformed." @@ -79,7 +79,6 @@ function expandfile { } $telemetryScope = $null -$bcContainerHelperPath = $null try { . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) @@ -89,11 +88,11 @@ try { $branch = "add-existing-app/$updateBranch/$((Get-Date).ToUniversalTime().ToString(`"yyMMddHHmmss`"))" # e.g. add-existing-app/main/210101120000 } $serverUrl = CloneIntoNewFolder -actor $actor -token $token -branch $branch - $repoBaseFolder = (Get-Location).path - $BcContainerHelperPath = DownloadAndImportBcContainerHelper -baseFolder $repoBaseFolder + $baseFolder = (Get-Location).path + DownloadAndImportBcContainerHelper -baseFolder $baseFolder import-module (Join-Path -path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) - $telemetryScope = CreateScope -eventId 'DO0070' -parentTelemetryScopeJson $parentTelemetryScopeJson + $telemetryScope = CreateScope -eventId 'DO0070' -parentTelemetryScopeJson $parentTelemetryScopeJson $type = "PTE" Write-Host "Reading $RepoSettingsFile" @@ -121,34 +120,32 @@ try { if ($appJson.PSObject.Properties.Name -eq "idRange") { $ranges += @($appJson.idRange) } - - $ttype = "" - $ranges | Select-Object -First 1 | ForEach-Object { - if ($_.from -lt 100000 -and $_.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") { - $appJson.dependencies | ForEach-Object { - if ($_.PSObject.Properties.Name -eq "AppId") { - $id = $_.AppId + foreach($dependency in $appJson.dependencies) { + if ($dependency.PSObject.Properties.Name -eq "AppId") { + $id = $dependency.AppId } else { - $id = $_.Id + $id = $dependency.Id } - if ($testRunnerApps.Contains($id)) { + if ($testRunnerApps.Contains($id)) { $ttype = "Test App" } } } if ($ttype -ne "Test App") { - Get-ChildItem -Path $appFolder -Filter "*.al" -Recurse | ForEach-Object { - $alContent = (Get-Content -Path $_.FullName -Encoding UTF8) -join "`n" + 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" } @@ -221,15 +218,14 @@ try { } } } - Set-Location $repoBaseFolder + Set-Location $baseFolder CommitFromNewFolder -serverUrl $serverUrl -commitMessage "Add existing apps ($($appNames -join ', '))" -branch $branch TrackTrace -telemetryScope $telemetryScope } catch { - TrackException -telemetryScope $telemetryScope -errorRecord $_ + if (Get-Module BcContainerHelper) { + TrackException -telemetryScope $telemetryScope -errorRecord $_ + } throw } -finally { - CleanupAfterBcContainerHelper -bcContainerHelperPath $bcContainerHelperPath -} diff --git a/AnalyzeTests/AnalyzeTests.ps1 b/AnalyzeTests/AnalyzeTests.ps1 index 17c822a..f8817d7 100644 --- a/AnalyzeTests/AnalyzeTests.ps1 +++ b/AnalyzeTests/AnalyzeTests.ps1 @@ -1,8 +1,4 @@ Param( - [Parameter(HelpMessage = "The GitHub actor running the action", Mandatory = $false)] - [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 to analyze", Mandatory = $false)] @@ -10,11 +6,10 @@ Param( ) $telemetryScope = $null -$bcContainerHelperPath = $null try { . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) - $BcContainerHelperPath = DownloadAndImportBcContainerHelper -baseFolder $ENV:GITHUB_WORKSPACE + DownloadAndImportBcContainerHelper import-module (Join-Path -path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) $telemetryScope = CreateScope -eventId 'DO0082' -parentTelemetryScopeJson $parentTelemetryScopeJson @@ -28,7 +23,7 @@ try { 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 { @@ -46,9 +41,9 @@ try { TrackTrace -telemetryScope $telemetryScope } catch { - TrackException -telemetryScope $telemetryScope -errorRecord $_ + if (Get-Module BcContainerHelper) { + TrackException -telemetryScope $telemetryScope -errorRecord $_ + } + throw } -finally { - CleanupAfterBcContainerHelper -bcContainerHelperPath $bcContainerHelperPath -} diff --git a/AnalyzeTests/README.md b/AnalyzeTests/README.md index 5ff446d..0e5dbd1 100644 --- a/AnalyzeTests/README.md +++ b/AnalyzeTests/README.md @@ -10,8 +10,6 @@ none | 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 | Yes | Name of project to analyze or . if the repository is setup for single project | | diff --git a/AnalyzeTests/TestResultAnalyzer.ps1 b/AnalyzeTests/TestResultAnalyzer.ps1 index e999de7..155bfdd 100644 --- a/AnalyzeTests/TestResultAnalyzer.ps1 +++ b/AnalyzeTests/TestResultAnalyzer.ps1 @@ -16,26 +16,25 @@ function GetTestResultSummary { if (-not $appNames) { $appNames = @($testResults.testsuites.testsuite | ForEach-Object { $_.Properties.property | Where-Object { $_.Name -eq "extensionId" } | ForEach-Object { $_.Value } } | Select-Object -Unique) } - $testResults.testsuites.testsuite | ForEach-Object { - $totalTests += $_.Tests - $totalTime += [decimal]::Parse($_.time, [System.Globalization.CultureInfo]::InvariantCulture) - $totalFailed += $_.failures - $totalSkipped += $_.skipped + 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 - $appNames | ForEach-Object { - $appName = $_ + 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 } } - $suites | ForEach-Object { - $appTests += [int]$_.tests - $appFailed += [int]$_.failures - $appSkipped += [int]$_.skipped - $appTime += [decimal]::Parse($_.time, [System.Globalization.CultureInfo]::InvariantCulture) + 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" @@ -54,21 +53,21 @@ function GetTestResultSummary { $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 - $suites | ForEach-Object { - Write-Host " - $($_.name), $($_.tests) tests, $($_.failures) failed, $($_.skipped) skipped, $($_.time) seconds" - if ($_.failures -gt 0 -and $failuresSb.Length -lt 32000) { - $failuresSb.Append("
$($_.name), $($_.tests) tests, $($_.failures) failed, $($_.skipped) skipped, $($_.time) seconds") | Out-Null - $_.testcase | ForEach-Object { - if ($_.ChildNodes.Count -gt 0) { - Write-Host " - $($_.name), Failure, $($_.time) seconds" - $failuresSb.Append("
$($_.name), Failure") | Out-Null - $_.ChildNodes | ForEach-Object { - Write-Host " - Error: $($_.message)" + 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 " $($_."#text".Trim().Replace("`n","`n "))" - $failuresSb.Append("      Error: $($_.message)
") | Out-Null + Write-Host " $($failure."#text".Trim().Replace("`n","`n "))" + $failuresSb.Append("      Error: $($failure.message)
") | Out-Null $failuresSb.Append("      Stack trace
") | Out-Null - $failuresSb.Append("      $($_."#text".Trim().Replace("`n","
      "))

") | Out-Null + $failuresSb.Append("      $($failure."#text".Trim().Replace("`n","
      "))

") | Out-Null } $failuresSb.Append("
") | Out-Null } diff --git a/AnalyzeTests/action.yaml b/AnalyzeTests/action.yaml index ff90b85..dcf5d22 100644 --- a/AnalyzeTests/action.yaml +++ b/AnalyzeTests/action.yaml @@ -5,14 +5,6 @@ inputs: description: Shell in which you want to run the action (powershell or pwsh) required: false default: powershell - actor: - description: The GitHub actor running the action - required: false - default: ${{ github.actor }} - token: - 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 @@ -31,14 +23,12 @@ runs: shell: ${{ inputs.shell }} id: AnalyzeTests env: - _actor: ${{ inputs.actor }} - _token: ${{ inputs.token }} _parentTelemetryScopeJson: ${{ inputs.parentTelemetryScopeJson }} _project: ${{ inputs.project }} run: | $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 try { - ${{ github.action_path }}/AnalyzeTests.ps1 -actor $ENV:_actor -token $ENV:_token -parentTelemetryScopeJson $ENV:_parentTelemetryScopeJson -project $ENV:_project + ${{ 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",' <- '))"; diff --git a/CODEOWNERS b/CODEOWNERS index 4c89bd5..9214b4f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1 @@ -* @microsoft/d365-bc-engineering-systems +* @microsoft/dynamics-smb-engineering-systems diff --git a/CheckForUpdates/CheckForUpdates.ps1 b/CheckForUpdates/CheckForUpdates.ps1 index 3fb49bb..7d39a0a 100644 --- a/CheckForUpdates/CheckForUpdates.ps1 +++ b/CheckForUpdates/CheckForUpdates.ps1 @@ -14,18 +14,16 @@ Param( [Parameter(HelpMessage = "Set the branch to update", Mandatory = $false)] [string] $updateBranch, [Parameter(HelpMessage = "Direct Commit (Y/N)", Mandatory = $false)] - [bool] $directCommit + [bool] $directCommit ) $telemetryScope = $null -$bcContainerHelperPath = $null try { . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) . (Join-Path -Path $PSScriptRoot -ChildPath "yamlclass.ps1") - $baseFolder = $ENV:GITHUB_WORKSPACE - $BcContainerHelperPath = DownloadAndImportBcContainerHelper -baseFolder $baseFolder + DownloadAndImportBcContainerHelper import-module (Join-Path -path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) $telemetryScope = CreateScope -eventId 'DO0071' -parentTelemetryScopeJson $parentTelemetryScopeJson @@ -92,7 +90,7 @@ try { Write-Host "Using ArchiveUrl $archiveUrl" # Download the template repository and unpack to a temp folder - $headers = @{ + $headers = @{ "Accept" = "application/vnd.github.baptiste-preview+json" "token" = $token } @@ -100,7 +98,7 @@ try { InvokeWebRequest -Headers $headers -Uri $archiveUrl -OutFile "$tempName.zip" -retry Expand-7zipArchive -Path "$tempName.zip" -DestinationPath $tempName Remove-Item -Path "$tempName.zip" - + # CheckFiles is an array of hashtables with the following properties: # dstPath: The path to the file in the current repository # srcPath: The path to the file in the template repository @@ -126,6 +124,7 @@ try { @{ "dstPath" = ".github"; "srcPath" = $srcGitHubPath; "pattern" = "*.copy.md"; "type" = "releasenotes" } ) # Get the list of projects in the current repository + $baseFolder = $ENV:GITHUB_WORKSPACE if ($repoSettings.projects) { $projects = $repoSettings.projects } @@ -158,7 +157,7 @@ try { $buildAlso = @{} $projectDependencies = @{} $projectsOrder = AnalyzeProjectDependencies -baseFolder $baseFolder -projects $projects -buildAlso ([ref]$buildAlso) -projectDependencies ([ref]$projectDependencies) - + $depth = $projectsOrder.Count Write-Host "Calculated dependency depth to be $depth" } @@ -261,7 +260,7 @@ try { if ($depth -gt 1) { # Also, duplicate the build job for each dependency depth - + $build = $yaml.Get('jobs:/Build:/') if($build) { @@ -300,7 +299,7 @@ try { $build.Replace('if:', $if) $build.Replace('needs:', "needs: [ $($needs -join ', ') ]") $build.Replace('strategy:/matrix:/include:',"include: `${{ fromJson(needs.Initialization.outputs.buildOrderJson)[$index].buildDimensions }}") - + # Last build job is called build, all other build jobs are called build1, build2, etc. if ($depth -eq $_) { $newBuild += @("Build:") @@ -329,7 +328,7 @@ try { if ($directALGo) { # If we are using the direct AL-Go repo, we need to change the owner and repo names in the workflow $lines = $srcContent.Split("`n") - + # The Original Owner and Repo in the AL-Go repository are microsoft/AL-Go-Actions, microsoft/AL-Go-PTE and microsoft/AL-Go-AppSource $originalOwnerAndRepo = @{ "actionsRepo" = "microsoft/AL-Go-Actions" @@ -426,7 +425,7 @@ try { # checkout branch to update invoke-git checkout $updateBranch - + # If $directCommit, then changes are made directly to the default branch if (!$directcommit) { # If not direct commit, create a new branch with name, relevant to the current date and base branch, and switch to it @@ -459,37 +458,34 @@ try { # Update the files # Calculate the release notes, while updating $releaseNotes = "" - try { - $updateFiles | ForEach-Object { - # Create the destination folder if it doesn't exist - $path = [System.IO.Path]::GetDirectoryName($_.DstFile) - if (-not (Test-Path -path $path -PathType Container)) { - New-Item -Path $path -ItemType Directory | Out-Null - } - if (([System.IO.Path]::GetFileName($_.DstFile) -eq "RELEASENOTES.copy.md") -and (Test-Path $_.DstFile)) { - $oldReleaseNotes = Get-ContentLF -Path $_.DstFile - while ($oldReleaseNotes) { - $releaseNotes = $_.Content - if ($releaseNotes.indexOf($oldReleaseNotes) -gt 0) { - $releaseNotes = $releaseNotes.SubString(0, $releaseNotes.indexOf($oldReleaseNotes)) - $oldReleaseNotes = "" + $updateFiles | ForEach-Object { + # Create the destination folder if it doesn't exist + $path = [System.IO.Path]::GetDirectoryName($_.DstFile) + if (-not (Test-Path -path $path -PathType Container)) { + New-Item -Path $path -ItemType Directory | Out-Null + } + if (([System.IO.Path]::GetFileName($_.DstFile) -eq "RELEASENOTES.copy.md") -and (Test-Path $_.DstFile)) { + $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 { - $idx = $oldReleaseNotes.IndexOf("`n## ") - if ($idx -gt 0) { - $oldReleaseNotes = $oldReleaseNotes.Substring($idx) - } - else { - $oldReleaseNotes = "" - } + $oldReleaseNotes = "" } } } - Write-Host "Update $($_.DstFile)" - $_.Content | Set-ContentLF -Path $_.DstFile } + Write-Host "Update $($_.DstFile)" + $_.Content | Set-ContentLF -Path $_.DstFile } - catch {} if ($releaseNotes -eq "") { $releaseNotes = "No release notes available!" } @@ -539,9 +535,8 @@ try { TrackTrace -telemetryScope $telemetryScope } catch { - TrackException -telemetryScope $telemetryScope -errorRecord $_ + if (Get-Module BcContainerHelper) { + TrackException -telemetryScope $telemetryScope -errorRecord $_ + } throw } -finally { - CleanupAfterBcContainerHelper -bcContainerHelperPath $bcContainerHelperPath -} diff --git a/CheckForUpdates/yamlclass.ps1 b/CheckForUpdates/yamlclass.ps1 index f7db997..d86d18b 100644 --- a/CheckForUpdates/yamlclass.ps1 +++ b/CheckForUpdates/yamlclass.ps1 @@ -106,8 +106,8 @@ class Yaml { $c = 0 if ($this.Find($line, [ref] $s, [ref] $c)) { $charCount = ($line.ToCharArray() | Where-Object {$_ -eq '/'} | Measure-Object).Count - [string[]] $result = @($this.content | Select-Object -Skip $s -First $c | ForEach-Object { - "$_$(" "*$charCount)".Substring(2*$charCount).TrimEnd() + [string[]] $result = @($this.content | Select-Object -Skip $s -First $c | ForEach-Object { + "$_$(" "*$charCount)".Substring(2*$charCount).TrimEnd() } ) $start.value = $s $count.value = $c diff --git a/CreateApp/AppHelper.psm1 b/CreateApp/AppHelper.psm1 index b4024d9..19221df 100644 --- a/CreateApp/AppHelper.psm1 +++ b/CreateApp/AppHelper.psm1 @@ -4,7 +4,7 @@ This module contains some useful functions for working with app manifests. . (Join-Path -path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) $here = Split-Path -Parent $MyInvocation.MyCommand.Path -$alTemplatePath = Join-Path -Path $here -ChildPath "AppTemplate" +$alTemplatePath = Join-Path -Path $here -ChildPath "AppTemplate" $validRanges = @{ @@ -14,7 +14,11 @@ $validRanges = @{ "Performance Test App" = "50000..$([int32]::MaxValue)" ; }; -function Confirm-IdRanges([string] $templateType, [string]$idrange ) { +<# +.SYNOPSIS +Check that the IdRange is valid for the template type. +#> +function ConfirmIdRanges([string] $templateType, [string]$idrange ) { $validRange = $validRanges.$templateType.Replace('..', '-').Split("-") $validStart = [int] $validRange[0] $validEnd = [int] $validRange[1] @@ -22,13 +26,13 @@ function Confirm-IdRanges([string] $templateType, [string]$idrange ) { $ids = $idrange.Replace('..', '-').Split("-") $idStart = [int] $ids[0] $idEnd = [int] $ids[1] - - if ($ids.Count -ne 2 -or ($idStart) -lt $validStart -or $idStart -gt $idEnd -or $idEnd -lt $validStart -or $idEnd -gt $validEnd -or $idStart -gt $idEnd) { + + if ($ids.Count -ne 2 -or ($idStart) -lt $validStart -or $idStart -gt $idEnd -or $idEnd -lt $validStart -or $idEnd -gt $validEnd -or $idStart -gt $idEnd) { throw "IdRange should be formatted as fromId..toId, and the Id range must be in $($validRange[0]) and $($validRange[1])" } return $ids -} +} function UpdateManifest ( @@ -39,7 +43,7 @@ function UpdateManifest [string] $version, [string[]] $idrange, [switch] $AddTestDependencies -) +) { #Modify app.json $appJson = Get-Content (Join-Path $sourceFolder "app.json") -Encoding UTF8 | ConvertFrom-Json @@ -78,7 +82,7 @@ function UpdateManifest $appJson | Set-JsonContentLF -path $appJsonFile } -function UpdateALFile +function UpdateALFile ( [string] $sourceFolder = $alTemplatePath, [string] $destinationFolder, @@ -86,7 +90,7 @@ function UpdateALFile [int] $fromId = 50100, [int] $toId = 50100, [int] $startId -) +) { $al = Get-Content -Encoding UTF8 -Raw -path (Join-Path $sourceFolder $alFileName) $fromId..$toId | ForEach-Object { @@ -100,7 +104,7 @@ function UpdateALFile .SYNOPSIS Creates a simple app. #> -function New-SampleApp +function NewSampleApp ( [string] $destinationPath, [string] $name, @@ -108,7 +112,7 @@ function New-SampleApp [string] $version, [string[]] $idrange, [bool] $sampleCode -) +) { Write-Host "Creating a new sample app in: $destinationPath" New-Item -Path $destinationPath -ItemType Directory -Force | Out-Null @@ -122,11 +126,11 @@ function New-SampleApp } -# <# -# .SYNOPSIS -# Creates a test app. -# #> -function New-SampleTestApp +<# +.SYNOPSIS +Creates a test app. +#> +function NewSampleTestApp ( [string] $destinationPath, [string] $name, @@ -134,7 +138,7 @@ function New-SampleTestApp [string] $version, [string[]] $idrange, [bool] $sampleCode -) +) { Write-Host "Creating a new test app in: $destinationPath" New-Item -Path $destinationPath -ItemType Directory -Force | Out-Null @@ -147,11 +151,11 @@ function New-SampleTestApp } } -# <# -# .SYNOPSIS -# Creates a performance test app. -# #> -function New-SamplePerformanceTestApp +<# +.SYNOPSIS +Creates a performance test app. +#> +function NewSamplePerformanceTestApp ( [string] $destinationPath, [string] $name, @@ -161,7 +165,7 @@ function New-SamplePerformanceTestApp [bool] $sampleCode, [bool] $sampleSuite, [string] $appSourceFolder -) +) { Write-Host "Creating a new performance test app in: $destinationPath" New-Item -Path $destinationPath -ItemType Directory -Force | Out-Null @@ -182,20 +186,24 @@ function New-SamplePerformanceTestApp } } -function Update-WorkSpaces +<# +.SYNOPSIS +Update workspace file +#> +function UpdateWorkspaces ( [string] $projectFolder, [string] $appName -) +) { - Get-ChildItem -Path $projectFolder -Filter "*.code-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 $appName })) { - $workspace.folders = Add-NewAppFolderToWorkspaceFolders $workspace.folders $appName + $workspace.folders = AddNewAppFolderToWorkspaceFolders $workspace.folders $appName } $workspace | Set-JsonContentLF -Path $workspaceFile } @@ -205,7 +213,11 @@ function Update-WorkSpaces } } -function Add-NewAppFolderToWorkspaceFolders +<# +.SYNOPSIS +Add new App Folder to Workspace file +#> +function AddNewAppFolderToWorkspaceFolders ( [PSCustomObject[]] $workspaceFolders, [string] $appFolder @@ -236,9 +248,9 @@ function Add-NewAppFolderToWorkspaceFolders $workspaceFolders } -Export-ModuleMember -Function New-SampleApp -Export-ModuleMember -Function New-SampleTestApp -Export-ModuleMember -Function New-SamplePerformanceTestApp -Export-ModuleMember -Function Confirm-IdRanges -Export-ModuleMember -Function Update-WorkSpaces -Export-ModuleMember -Function Add-NewAppFolderToWorkspaceFolders +Export-ModuleMember -Function NewSampleApp +Export-ModuleMember -Function NewSampleTestApp +Export-ModuleMember -Function NewSamplePerformanceTestApp +Export-ModuleMember -Function ConfirmIdRanges +Export-ModuleMember -Function UpdateWorkspaces +Export-ModuleMember -Function AddNewAppFolderToWorkspaceFolders diff --git a/CreateApp/CreateApp.ps1 b/CreateApp/CreateApp.ps1 index d356763..a9f93fe 100644 --- a/CreateApp/CreateApp.ps1 +++ b/CreateApp/CreateApp.ps1 @@ -27,7 +27,6 @@ Param( ) $telemetryScope = $null -$bcContainerHelperPath = $null $tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) try { @@ -39,11 +38,11 @@ try { } $serverUrl = CloneIntoNewFolder -actor $actor -token $token -branch $branch $baseFolder = (Get-Location).Path - $BcContainerHelperPath = DownloadAndImportBcContainerHelper -baseFolder $baseFolder + 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" @@ -56,7 +55,7 @@ try { throw "An extension name must be specified." } - $ids = Confirm-IdRanges -templateType $type -idrange $idrange + $ids = ConfirmIdRanges -templateType $type -idrange $idrange CheckAndCreateProjectFolder -project $project $projectFolder = (Get-Location).Path @@ -64,7 +63,7 @@ try { if ($type -eq "Performance Test App") { try { $settings = ReadSettings -baseFolder $baseFolder -project $project - $settings = AnalyzeRepo -settings $settings -token $token -baseFolder $baseFolder -project $project -doNotIssueWarnings -doNotCheckAppDependencyProbingPaths + $settings = AnalyzeRepo -settings $settings -baseFolder $baseFolder -project $project -doNotIssueWarnings $folders = Download-Artifacts -artifactUrl $settings.artifact -includePlatform $sampleApp = Join-Path $folders[0] "Applications.*\Microsoft_Performance Toolkit Samples_*.app" if (Test-Path $sampleApp) { @@ -76,6 +75,7 @@ try { if (!(Test-Path -Path $sampleApp)) { throw "Could not locate sample app for the Business Central version" } + Extract-AppFileToFolder -appFilename $sampleApp -generateAppJson -appFolder $tmpFolder } catch { @@ -122,16 +122,16 @@ try { } if ($type -eq "Performance Test App") { - New-SamplePerformanceTestApp -destinationPath (Join-Path $projectFolder $folderName) -name $name -publisher $publisher -version $appVersion -sampleCode $sampleCode -sampleSuite $sampleSuite -idrange $ids -appSourceFolder $tmpFolder + NewSamplePerformanceTestApp -destinationPath (Join-Path $projectFolder $folderName) -name $name -publisher $publisher -version $appVersion -sampleCode $sampleCode -sampleSuite $sampleSuite -idrange $ids -appSourceFolder $tmpFolder } elseif ($type -eq "Test App") { - New-SampleTestApp -destinationPath (Join-Path $projectFolder $folderName) -name $name -publisher $publisher -version $appVersion -sampleCode $sampleCode -idrange $ids + NewSampleTestApp -destinationPath (Join-Path $projectFolder $folderName) -name $name -publisher $publisher -version $appVersion -sampleCode $sampleCode -idrange $ids } else { - New-SampleApp -destinationPath (Join-Path $projectFolder $folderName) -name $name -publisher $publisher -version $appVersion -sampleCode $sampleCode -idrange $ids + NewSampleApp -destinationPath (Join-Path $projectFolder $folderName) -name $name -publisher $publisher -version $appVersion -sampleCode $sampleCode -idrange $ids } - Update-WorkSpaces -projectFolder $projectFolder -appName $folderName + UpdateWorkspaces -projectFolder $projectFolder -appName $folderName Set-Location $baseFolder CommitFromNewFolder -serverUrl $serverUrl -commitMessage "New $type ($Name)" -branch $branch @@ -140,11 +140,12 @@ try { } catch { - TrackException -telemetryScope $telemetryScope -errorRecord $_ + if (Get-Module BcContainerHelper) { + TrackException -telemetryScope $telemetryScope -errorRecord $_ + } throw } finally { - CleanupAfterBcContainerHelper -bcContainerHelperPath $bcContainerHelperPath if (Test-Path $tmpFolder) { Remove-Item $tmpFolder -Recurse -Force } diff --git a/CreateDevelopmentEnvironment/CreateDevelopmentEnvironment.ps1 b/CreateDevelopmentEnvironment/CreateDevelopmentEnvironment.ps1 index 38e2cb2..61f408a 100644 --- a/CreateDevelopmentEnvironment/CreateDevelopmentEnvironment.ps1 +++ b/CreateDevelopmentEnvironment/CreateDevelopmentEnvironment.ps1 @@ -1,3 +1,4 @@ +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '', Justification = 'GitHub Secrets are transferred as plain text')] Param( [Parameter(HelpMessage = "The GitHub actor running the action", Mandatory = $false)] [string] $actor, @@ -16,11 +17,10 @@ Param( [Parameter(HelpMessage = "Set the branch to update", Mandatory = $false)] [string] $updateBranch, [Parameter(HelpMessage = "Direct Commit (Y/N)", Mandatory = $false)] - [bool] $directCommit + [bool] $directCommit ) $telemetryScope = $null -$bcContainerHelperPath = $null try { . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) @@ -30,25 +30,20 @@ try { $branch = "create-development-environment/$updateBranch/$((Get-Date).ToUniversalTime().ToString(`"yyMMddHHmmss`"))" # e.g. create-development-environment/main/210101120000 } $serverUrl = CloneIntoNewFolder -actor $actor -token $token -branch $branch - $repoBaseFolder = (Get-Location).Path - $BcContainerHelperPath = DownloadAndImportBcContainerHelper -baseFolder $repoBaseFolder + $baseFolder = (Get-Location).Path + DownloadAndImportBcContainerHelper -baseFolder $baseFolder 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)) - - Write-Host "Reading $ALGoSettingsFile" - $settingsJson = Get-Content $ALGoSettingsFile -Encoding UTF8 | ConvertFrom-Json - CreateDevEnv ` -kind cloud ` -caller GitHubActions ` -environmentName $environmentName ` -reUseExistingEnvironment:$reUseExistingEnvironment ` - -baseFolder $repoBaseFolder ` + -baseFolder $baseFolder ` -project $project ` - -bcContainerHelperPath $bcContainerHelperPath ` -adminCenterApiCredentials ($adminCenterApiCredentials | ConvertFrom-Json | ConvertTo-HashTable) CommitFromNewFolder -serverUrl $serverUrl -commitMessage "Create a development environment $environmentName" -branch $branch @@ -56,9 +51,8 @@ try { TrackTrace -telemetryScope $telemetryScope } catch { - TrackException -telemetryScope $telemetryScope -errorRecord $_ + if (Get-Module BcContainerHelper) { + TrackException -telemetryScope $telemetryScope -errorRecord $_ + } throw } -finally { - CleanupAfterBcContainerHelper -bcContainerHelperPath $bcContainerHelperPath -} diff --git a/CreateReleaseNotes/CreateReleaseNotes.ps1 b/CreateReleaseNotes/CreateReleaseNotes.ps1 index 9fb650e..7866384 100644 --- a/CreateReleaseNotes/CreateReleaseNotes.ps1 +++ b/CreateReleaseNotes/CreateReleaseNotes.ps1 @@ -1,6 +1,4 @@ Param( - [Parameter(HelpMessage = "The GitHub actor running the action", Mandatory = $false)] - [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)] @@ -12,15 +10,14 @@ Param( ) $telemetryScope = $null -$bcContainerHelperPath = $null try { . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1") - $BcContainerHelperPath = DownloadAndImportBcContainerHelper -baseFolder $ENV:GITHUB_WORKSPACE + 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) # Check that tag is SemVer @@ -69,9 +66,8 @@ try { TrackTrace -telemetryScope $telemetryScope } catch { - TrackException -telemetryScope $telemetryScope -errorRecord $_ + if (Get-Module BcContainerHelper) { + TrackException -telemetryScope $telemetryScope -errorRecord $_ + } throw } -finally { - CleanupAfterBcContainerHelper -bcContainerHelperPath $bcContainerHelperPath -} diff --git a/CreateReleaseNotes/README.md b/CreateReleaseNotes/README.md index 6e34d27..50f8730 100644 --- a/CreateReleaseNotes/README.md +++ b/CreateReleaseNotes/README.md @@ -10,7 +10,6 @@ none | 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 | {} | | tag_name | Yes | This release tag name | | diff --git a/CreateReleaseNotes/action.yaml b/CreateReleaseNotes/action.yaml index a0600ed..673eea5 100644 --- a/CreateReleaseNotes/action.yaml +++ b/CreateReleaseNotes/action.yaml @@ -8,10 +8,6 @@ inputs: description: Shell in which you want to run the action (powershell or pwsh) required: false default: powershell - actor: - description: The GitHub actor running the action - required: false - default: ${{ github.actor }} token: description: The GitHub token running the action required: false @@ -41,7 +37,6 @@ runs: shell: ${{ inputs.shell }} id: createreleasenotes env: - _actor: ${{ inputs.actor }} _token: ${{ inputs.token }} _parentTelemetryScopeJson: ${{ inputs.parentTelemetryScopeJson }} _tag_name: ${{ inputs.tag_name }} @@ -49,7 +44,7 @@ runs: run: | $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 try { - ${{ github.action_path }}/CreateReleaseNotes.ps1 -actor $ENV:_actor -token $ENV:_token -parentTelemetryScopeJson $ENV:_parentTelemetryScopeJson -tag_name $ENV:_tag_name -target_commitish $ENV:_target_commitish + ${{ 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",' <- '))"; diff --git a/Deliver/Deliver.ps1 b/Deliver/Deliver.ps1 index 246c065..6d26e2f 100644 --- a/Deliver/Deliver.ps1 +++ b/Deliver/Deliver.ps1 @@ -21,7 +21,6 @@ Param( ) $telemetryScope = $null -$bcContainerHelperPath = $null function EnsureAzStorageModule() { if (get-command New-AzStorageContext -ErrorAction SilentlyContinue) { @@ -38,7 +37,7 @@ function EnsureAzStorageModule() { Set-Alias -Name Set-AzStorageBlobContent -Value Set-AzureStorageBlobContent -Scope Script } else { - Write-Host "Installing and importing Az.Storage." + Write-Host "Installing and importing Az.Storage." Install-Module 'Az.Storage' -Force Import-Module 'Az.Storage' -DisableNameChecking -WarningAction SilentlyContinue | Out-Null } @@ -46,9 +45,8 @@ function EnsureAzStorageModule() { } try { - $baseFolder = $ENV:GITHUB_WORKSPACE . (Join-Path -Path $PSScriptRoot -ChildPath "../AL-Go-Helper.ps1" -Resolve) - $BcContainerHelperPath = DownloadAndImportBcContainerHelper -baseFolder $baseFolder + DownloadAndImportBcContainerHelper import-module (Join-Path -path $PSScriptRoot -ChildPath "../TelemetryHelper.psm1" -Resolve) $telemetryScope = CreateScope -eventId 'DO0081' -parentTelemetryScopeJson $parentTelemetryScopeJson @@ -62,6 +60,7 @@ try { $artifacts = $artifacts.Replace('/',([System.IO.Path]::DirectorySeparatorChar)).Replace('\',([System.IO.Path]::DirectorySeparatorChar)) + $baseFolder = $ENV:GITHUB_WORKSPACE $settings = ReadSettings -baseFolder $baseFolder if ($settings.projects) { $projectList = $settings.projects | Where-Object { $_ -like $projects } @@ -79,21 +78,14 @@ try { throw "No projects matches the pattern '$projects'" } if ($deliveryTarget -eq "AppSource") { - $atypes = "Apps,Dependencies" + $atypes = "Apps,Dependencies" } Write-Host "Artifacts $artifacts" Write-Host "Projects:" $projectList | Out-Host - if ("$env:deliveryContext" -eq "") { - throw "$($deliveryTarget)Context is not defined, cannot deliver to $deliveryTarget" - } - $key = "$($deliveryTarget)Context" - Write-Host "Using $key" - Set-Variable -Name $key -Value $env:deliveryContext - - $projectList | ForEach-Object { - $thisProject = $_ + $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('/','_') @@ -182,19 +174,19 @@ try { 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 -doNotCheckAppDependencyProbingPaths -doNotIssueWarnings + $projectSettings = AnalyzeRepo -settings $projectSettings -baseFolder $baseFolder -project $thisProject -doNotCheckArtifactSetting -doNotIssueWarnings $parameters = @{ "Project" = $thisProject "ProjectName" = $projectName "type" = $type - "Context" = $env:deliveryContext + "Context" = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets."$($deliveryTarget)Context")) "RepoSettings" = $settings "ProjectSettings" = $projectSettings } #Calculate the folders per artifact type - + #Calculate the folders per artifact type 'Apps', 'TestApps', 'Dependencies' | ForEach-Object { $artifactType = $_ @@ -226,12 +218,12 @@ try { $parameters[$artifactType.ToLowerInvariant() + "Folders"] = $artifactFolders.FullName } } - + Write-Host "Calling custom script: $customScript" . $customScript -parameters $parameters } elseif ($deliveryTarget -eq "GitHubPackages") { - $githubPackagesCredential = $githubPackagesContext | ConvertFrom-Json + $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) { @@ -255,10 +247,10 @@ try { } elseif ($deliveryTarget -eq "NuGet") { try { - $nuGetAccount = $nuGetContext | ConvertFrom-Json | ConvertTo-HashTable + $nuGetAccount = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets.nuGetContext)) | ConvertFrom-Json | ConvertTo-HashTable $nuGetServerUrl = $nuGetAccount.ServerUrl $nuGetToken = $nuGetAccount.Token - Write-Host "NuGetContext OK" + Write-Host "NuGetContext secret OK" } catch { throw "NuGetContext secret is malformed. Needs to be formatted as Json, containing serverUrl and token as a minimum." @@ -331,7 +323,7 @@ try { elseif ($deliveryTarget -eq "Storage") { EnsureAzStorageModule try { - $storageAccount = $storageContext | ConvertFrom-Json | ConvertTo-HashTable + $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 @@ -405,13 +397,16 @@ try { } elseif ($deliveryTarget -eq "AppSource") { $projectSettings = ReadSettings -baseFolder $baseFolder -project $thisProject - $projectSettings = AnalyzeRepo -settings $projectSettings -baseFolder $baseFolder -project $thisProject -doNotCheckArtifactSetting -doNotCheckAppDependencyProbingPaths -doNotIssueWarnings + $projectSettings = AnalyzeRepo -settings $projectSettings -baseFolder $baseFolder -project $thisProject -doNotCheckArtifactSetting -doNotIssueWarnings # 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.Keys -contains 'AppSourceContinuousDelivery' -and $projectSettings.AppSourceContinuousDelivery)) { EnsureAzStorageModule - $appSourceContextHt = $appSourceContext | ConvertFrom-Json | ConvertTo-HashTable - $authContext = New-BcAuthContext @appSourceContextHt + $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 ($projectSettings.Keys -contains "AppSourceMainAppFolder") { $AppSourceMainAppFolder = $projectSettings.AppSourceMainAppFolder @@ -430,7 +425,6 @@ try { Write-Host "AppSource MainAppFolder $AppSourceMainAppFolder" $mainAppJson = Get-Content -Path (Join-Path $baseFolder "$thisProject/$AppSourceMainAppFolder/app.json") -Encoding UTF8 | ConvertFrom-Json - $mainAppVersion = [Version]$mainAppJson.Version $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) { @@ -471,9 +465,8 @@ try { TrackTrace -telemetryScope $telemetryScope } catch { - TrackException -telemetryScope $telemetryScope -errorRecord $_ + if (Get-Module BcContainerHelper) { + TrackException -telemetryScope $telemetryScope -errorRecord $_ + } throw } -finally { - CleanupAfterBcContainerHelper -bcContainerHelperPath $bcContainerHelperPath -} diff --git a/Deliver/README.md b/Deliver/README.md index 484e017..de966e2 100644 --- a/Deliver/README.md +++ b/Deliver/README.md @@ -4,7 +4,10 @@ Deliver App to deliveryTarget (AppSource, Storage, or...) ## INPUT ### ENV variables -none +| 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 | diff --git a/Deploy/Deploy.ps1 b/Deploy/Deploy.ps1 index e95cf2d..022f707 100644 --- a/Deploy/Deploy.ps1 +++ b/Deploy/Deploy.ps1 @@ -1,37 +1,58 @@ Param( - [Parameter(HelpMessage = "The GitHub actor running the action", Mandatory = $false)] - [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 deploy", Mandatory = $false)] - [string] $projects = '', [Parameter(HelpMessage = "Name of environment to deploy to", Mandatory = $true)] [string] $environmentName, [Parameter(HelpMessage = "Artifacts to deploy", Mandatory = $true)] [string] $artifacts, [Parameter(HelpMessage = "Type of deployment (CD or Publish)", Mandatory = $false)] [ValidateSet('CD','Publish')] - [string] $type = "CD" + [string] $type = "CD", + [Parameter(HelpMessage = "The settings for all Deployment Environments", Mandatory = $true)] + [string] $deploymentEnvironmentsJson ) $telemetryScope = $null -$bcContainerHelperPath = $null - -if ($projects -eq '') { - Write-Host "No projects to deploy" - exit -} try { . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) - $BcContainerHelperPath = DownloadAndImportBcContainerHelper -baseFolder $ENV:GITHUB_WORKSPACE + DownloadAndImportBcContainerHelper import-module (Join-Path -path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) $telemetryScope = CreateScope -eventId 'DO0075' -parentTelemetryScopeJson $parentTelemetryScopeJson - $EnvironmentName = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($environmentName)) + $deploymentEnvironments = $deploymentEnvironmentsJson | ConvertFrom-Json | ConvertTo-HashTable -recurse + $deploymentSettings = $deploymentEnvironments."$environmentName" + $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" + } + + $authContext = $null + foreach($secretName in "$($envName)-AuthContext","$($envName)_AuthContext","AuthContext") { + if ($secrets."$secretName") { + $authContext = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets."$secretName")) + } + } + if (-not $authContext) { + # No AuthContext secret provided, if deviceCode is present, use it - else give an error + if ($env:deviceCode) { + $authContext = "{""deviceCode"":""$($env:deviceCode)""}" + } + else { + throw "No Authentication Context found for environment ($environmentName). You must create an environment secret called AUTHCONTEXT or a repository secret called $($envName)_AUTHCONTEXT." + } + } $artifacts = $artifacts.Replace('/',([System.IO.Path]::DirectorySeparatorChar)).Replace('\',([System.IO.Path]::DirectorySeparatorChar)) @@ -45,7 +66,7 @@ try { $searchArtifacts = $false if ($artifacts -like "$($ENV:GITHUB_WORKSPACE)*") { if (Test-Path $artifacts -PathType Container) { - $projects.Split(',') | ForEach-Object { + $deploymentSettings.Projects.Split(',') | ForEach-Object { $project = $_.Replace('\','_').Replace('/','_') $refname = "$ENV:GITHUB_REF_NAME".Replace('/','_') Write-Host "project '$project'" @@ -81,8 +102,8 @@ try { } New-Item $artifactsFolder -ItemType Directory | Out-Null $artifactsFolderCreated = $true - DownloadRelease -token $token -projects $projects -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -release $release -path $artifactsFolder -mask "Apps" - DownloadRelease -token $token -projects $projects -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -release $release -path $artifactsFolder -mask "Dependencies" + DownloadRelease -token $token -projects $deploymentSettings.Projects -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -release $release -path $artifactsFolder -mask "Apps" + DownloadRelease -token $token -projects $deploymentSettings.Projects -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -release $release -path $artifactsFolder -mask "Dependencies" $apps = @((Get-ChildItem -Path $artifactsFolder) | ForEach-Object { $_.FullName }) if (!$apps) { throw "Artifact $artifacts was not found on any release. Make sure that the artifact files exist and files are not corrupted." @@ -105,9 +126,9 @@ try { if ($searchArtifacts) { New-Item $artifactsFolder -ItemType Directory | Out-Null - $baseFolderCreated = $true - $allArtifacts = @(GetArtifacts -token $token -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -mask "Apps" -projects $projects -Version $artifacts -branch "main") - $allArtifacts += @(GetArtifacts -token $token -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -mask "Dependencies" -projects $projects -Version $artifacts -branch "main") + $refname = "$ENV:GITHUB_REF_NAME".Replace('/','_') + $allArtifacts = @(GetArtifacts -token $token -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -mask "Apps" -projects $deploymentSettings.Projects -Version $artifacts -branch $refname) + $allArtifacts += @(GetArtifacts -token $token -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -mask "Dependencies" -projects $deploymentSettings.Projects -Version $artifacts -branch $refname) if ($allArtifacts) { $allArtifacts | ForEach-Object { $appFile = DownloadArtifact -token $token -artifact $_ -path $artifactsFolder @@ -118,7 +139,7 @@ try { } } else { - throw "Could not find any Apps artifacts for projects $projects, version $artifacts" + throw "Could not find any Apps artifacts for projects $($deploymentSettings.Projects), version $artifacts" } } @@ -126,58 +147,86 @@ try { $apps | Out-Host Set-Location $ENV:GITHUB_WORKSPACE - if (-not ($ENV:AuthContext)) { - throw "An environment secret for environment($environmentName) called AUTHCONTEXT containing authentication information for the environment was not found.You must create an environment secret." - } - $authContext = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($ENV:AuthContext)) - try { - $authContextParams = $authContext | ConvertFrom-Json | ConvertTo-HashTable - $bcAuthContext = New-BcAuthContext @authContextParams - if ($null -eq $bcAuthContext) { - throw "Authentication failed" - } - } catch { - throw "Authentication failed. $([environment]::Newline) $($_.exception.message)" + $customScript = Join-Path $ENV:GITHUB_WORKSPACE ".github/DeployTo$($deploymentSettings.EnvironmentType).ps1" + if (Test-Path $customScript) { + Write-Host "Executing custom deployment script $customScript" + $parameters = @{ + "type" = $type + "AuthContext" = $authContext + "Apps" = $apps + } + $deploymentSettings + . $customScript -parameters $parameters } + else { + try { + $authContextParams = $authContext | ConvertFrom-Json | ConvertTo-HashTable + $bcAuthContext = New-BcAuthContext @authContextParams + if ($null -eq $bcAuthContext) { + throw "Authentication failed" + } + } catch { + throw "Authentication failed. $([environment]::Newline) $($_.exception.message)" + } - $envName = $environmentName.Split(' ')[0] - Write-Host "$($bcContainerHelperConfig.baseUrl.TrimEnd('/'))/$($bcAuthContext.tenantId)/$envName/deployment/url" - $response = Invoke-RestMethod -UseBasicParsing -Method Get -Uri "$($bcContainerHelperConfig.baseUrl.TrimEnd('/'))/$($bcAuthContext.tenantId)/$envName/deployment/url" - if ($response.Status -eq "DoesNotExist") { - OutputError -message "Environment with name $envName does not exist in the current authorization context." - exit - } - if ($response.Status -ne "Ready") { - OutputError -message "Environment with name $envName is not ready (Status is $($response.Status))." - exit - } + Write-Host "$($bcContainerHelperConfig.baseUrl.TrimEnd('/'))/$($bcAuthContext.tenantId)/$($deploymentSettings.EnvironmentName)/deployment/url" + $response = Invoke-RestMethod -UseBasicParsing -Method Get -Uri "$($bcContainerHelperConfig.baseUrl.TrimEnd('/'))/$($bcAuthContext.tenantId)/$($deploymentSettings.EnvironmentName)/deployment/url" + if ($response.Status -eq "DoesNotExist") { + OutputError -message "Environment with name $($deploymentSettings.EnvironmentName) does not exist in the current authorization context." + exit + } + if ($response.Status -ne "Ready") { + OutputError -message "Environment with name $($deploymentSettings.EnvironmentName) is not ready (Status is $($response.Status))." + exit + } - try { - if ($response.environmentType -eq 1) { - if ($bcAuthContext.ClientSecret) { - Write-Host "Using S2S, publishing apps using automation API" - Publish-PerTenantExtensionApps -bcAuthContext $bcAuthContext -environment $envName -appFiles $apps - } - else { + try { + $sandboxEnvironment = ($response.environmentType -eq 1) + if ($sandboxEnvironment -and !($bcAuthContext.ClientSecret)) { + # Sandbox and not S2S -> use dev endpoint (Publish-BcContainerApp) + $parameters = @{ + "bcAuthContext" = $bcAuthContext + "environment" = $deploymentSettings.EnvironmentName + "appFile" = $apps + } + if ($deploymentSettings.SyncMode) { + if (@('Add','ForceSync', 'Clean', 'Development') -notcontains $deploymentSettings.SyncMode) { + throw "Invalid SyncMode $($deploymentSettings.SyncMode) when deploying using the development endpoint. Valid values are Add, ForceSync, Development and Clean." + } + Write-Host "Using $($deploymentSettings.SyncMode)" + $parameters += @{ "SyncMode" = $deploymentSettings.SyncMode } + } Write-Host "Publishing apps using development endpoint" - Publish-BcContainerApp -bcAuthContext $bcAuthContext -environment $envName -appFile $apps -useDevEndpoint -checkAlreadyInstalled -excludeRuntimePackages + Publish-BcContainerApp @parameters -useDevEndpoint -checkAlreadyInstalled -excludeRuntimePackages } - } - else { - if ($type -eq 'CD') { - Write-Host "Ignoring environment $environmentName, which is a production environment" + 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 { - # Check for AppSource App - cannot be deployed + # Use automation API for production environments (Publish-PerTenantExtensionApps) + $parameters = @{ + "bcAuthContext" = $bcAuthContext + "environment" = $deploymentSettings.EnvironmentName + "appFiles" = $apps + } + if ($deploymentSettings.SyncMode) { + if (@('Add','ForceSync') -notcontains $deploymentSettings.SyncMode) { + throw "Invalid SyncMode $($deploymentSettings.SyncMode) when deploying using the automation API. Valid values are Add and ForceSync." + } + Write-Host "Using $($deploymentSettings.SyncMode)" + $syncMode = $deploymentSettings.SyncMode + if ($syncMode -eq 'ForceSync') { $syncMode = 'Force' } + $parameters += @{ "SchemaSyncMode" = $syncMode } + } Write-Host "Publishing apps using automation API" - Publish-PerTenantExtensionApps -bcAuthContext $bcAuthContext -environment $envName -appFiles $apps + Publish-PerTenantExtensionApps @parameters } } - } - catch { - OutputError -message "Deploying to $environmentName failed.$([environment]::Newline) $($_.Exception.Message)" - exit + catch { + OutputError -message "Deploying to $environmentName failed.$([environment]::Newline) $($_.Exception.Message)" + exit + } } if ($artifactsFolderCreated) { @@ -188,9 +237,8 @@ try { } catch { - TrackException -telemetryScope $telemetryScope -errorRecord $_ + if (Get-Module BcContainerHelper) { + TrackException -telemetryScope $telemetryScope -errorRecord $_ + } throw } -finally { - CleanupAfterBcContainerHelper -bcContainerHelperPath $bcContainerHelperPath -} diff --git a/Deploy/README.md b/Deploy/README.md index ab1b525..278769c 100644 --- a/Deploy/README.md +++ b/Deploy/README.md @@ -4,13 +4,16 @@ Deploy Apps to online environment ## INPUT ### ENV variables -none +| 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 | +| 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 | -| 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 deploy. | | diff --git a/Deploy/action.yaml b/Deploy/action.yaml index dab9dbf..755d534 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 - actor: - description: The GitHub actor running the action - required: false - default: ${{ github.actor }} token: description: The GitHub token running the action required: false @@ -17,10 +13,6 @@ inputs: description: Specifies the parent telemetry scope for the telemetry signal required: false default: '7b7d' - projects: - description: Projects to deploy - required: false - default: '' environmentName: description: Name of environment to deploy to required: true @@ -31,23 +23,25 @@ inputs: description: Type of deployment (CD or Publish) required: false default: 'CD' + deploymentEnvironmentsJson: + description: The settings for all Deployment Environments + required: true runs: using: composite steps: - name: run shell: ${{ inputs.shell }} env: - _actor: ${{ inputs.actor }} _token: ${{ inputs.token }} _parentTelemetryScopeJson: ${{ inputs.parentTelemetryScopeJson }} - _projects: ${{ inputs.projects }} _environmentName: ${{ inputs.environmentName }} _artifacts: ${{ inputs.artifacts }} _type: ${{ inputs.type }} + _deploymentEnvironmentsJson: ${{ inputs.deploymentEnvironmentsJson }} run: | $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 try { - ${{ github.action_path }}/Deploy.ps1 -actor $ENV:_actor -token $ENV:_token -parentTelemetryScopeJson $ENV:_parentTelemetryScopeJson -projects $ENV:_projects -environmentName $ENV:_environmentName -artifacts $ENV:_artifacts -type $ENV:_type + ${{ github.action_path }}/Deploy.ps1 -token $ENV:_token -parentTelemetryScopeJson $ENV:_parentTelemetryScopeJson -environmentName $ENV:_environmentName -artifacts $ENV:_artifacts -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",' <- '))"; diff --git a/DetermineArtifactUrl/DetermineArtifactUrl.ps1 b/DetermineArtifactUrl/DetermineArtifactUrl.ps1 index 5cfc049..0207723 100644 --- a/DetermineArtifactUrl/DetermineArtifactUrl.ps1 +++ b/DetermineArtifactUrl/DetermineArtifactUrl.ps1 @@ -6,22 +6,27 @@ Param( ) $telemetryScope = $null -$bcContainerHelperPath = $null try { #region Action: Setup . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) - $BcContainerHelperPath = DownloadAndImportBcContainerHelper -baseFolder $ENV:GITHUB_WORKSPACE + 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 - $secrets = $env:Secrets | ConvertFrom-Json | ConvertTo-HashTable - $insiderSasToken = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets.insiderSasToken)) + + $insiderSasToken = "" + # ENV:Secrets is not set when running Pull_Request trigger + if ($env:Secrets) { + $secrets = $env:Secrets | ConvertFrom-Json | ConvertTo-HashTable + $insiderSasToken = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets.insiderSasToken)) + } + $settings = $env:Settings | ConvertFrom-Json | ConvertTo-HashTable - $settings = AnalyzeRepo -settings $settings -project $project -doNotCheckArtifactSetting -doNotCheckAppDependencyProbingPaths -doNotIssueWarnings - $artifactUrl = Determine-ArtifactUrl -projectSettings $settings -insiderSasToken $insiderSasToken + $settings = AnalyzeRepo -settings $settings -project $project -doNotCheckArtifactSetting -doNotIssueWarnings + $artifactUrl = DetermineArtifactUrl -projectSettings $settings -insiderSasToken $insiderSasToken $artifactCacheKey = '' if ($settings.useCompilerFolder) { $artifactCacheKey = $artifactUrl.Split('?')[0] @@ -39,10 +44,8 @@ try { TrackTrace -telemetryScope $telemetryScope } catch { - TrackException -telemetryScope $telemetryScope -errorRecord $_ + if (Get-Module BcContainerHelper) { + TrackException -telemetryScope $telemetryScope -errorRecord $_ + } throw } -finally { - CleanupAfterBcContainerHelper -bcContainerHelperPath $bcContainerHelperPath -} - diff --git a/DetermineDeliveryTargets/DetermineDeliveryTargets.ps1 b/DetermineDeliveryTargets/DetermineDeliveryTargets.ps1 new file mode 100644 index 0000000..8655b28 --- /dev/null +++ b/DetermineDeliveryTargets/DetermineDeliveryTargets.ps1 @@ -0,0 +1,65 @@ +Param( + [Parameter(HelpMessage = "Projects to investigate", Mandatory = $false)] + [string] $projectsJson = '["."]', + [Parameter(HelpMessage = "Check whether context secret exists", Mandatory = $false)] + [bool] $checkContextSecrets +) + +function IncludeBranch([string] $deliveryTarget) { + $settingsName = "DeliverTo$deliveryTarget" + if ($settings.Contains($settingsName) -and $settings."$settingsName".Contains('Branches')) { + Write-Host "- Branches defined: $($settings."$settingsName".Branches -join ', ') - " + return ($null -ne ($settings."$settingsName".Branches | Where-Object { $ENV:GITHUB_REF_NAME -like $_ })) + } + else { + Write-Host "- No branches defined, defaulting to main" + return ($ENV:GITHUB_REF_NAME -eq 'main') + } +} + +function IncludeDeliveryTarget([string] $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 + if (-not $secrets."$contextName") { + Write-Host "- Secret '$contextName' not found" + return $false + } + return (IncludeBranch -deliveryTarget $deliveryTarget) +} + +. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) + +$settings = $env:Settings | ConvertFrom-Json | ConvertTo-HashTable -recurse +$deliveryTargets = @('GitHubPackages','NuGet','Storage') +if ($settings.type -eq "AppSource App") { + # For multi-project repositories, we will add deliveryTarget AppSource if any project has AppSourceContinuousDelivery set to true + ($projectsJson | ConvertFrom-Json) | ForEach-Object { + $projectSettings = ReadSettings -project $_ + if ($projectSettings.Contains('AppSourceContinuousDelivery') -and $projectSettings.AppSourceContinuousDelivery) { + Write-Host "Project $_ is setup for Continuous Delivery" + $deliveryTargets += @("AppSource") + } + } +} +# Include custom delivery targets +$namePrefix = 'DeliverTo' +Get-Item -Path (Join-Path $ENV:GITHUB_WORKSPACE ".github/$($namePrefix)*.ps1") | ForEach-Object { + $deliveryTarget = [System.IO.Path]::GetFileNameWithoutExtension($_.Name.SubString($namePrefix.Length)) + $deliveryTargets += @($deliveryTarget) +} +$deliveryTargets = @($deliveryTargets | Select-Object -unique) +if ($checkContextSecrets) { + # Check all delivery targets and include only the ones needed + $deliveryTargets = @($deliveryTargets | Where-Object { IncludeDeliveryTarget -deliveryTarget $_ }) +} +$contextSecrets = @($deliveryTargets | ForEach-Object { "$($_)Context" }) + +#region Action: Output +$deliveryTargetsJson = ConvertTo-Json -InputObject $deliveryTargets -compress +Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "DeliveryTargetsJson=$deliveryTargetsJson" +Write-Host "DeliveryTargetsJson=$deliveryTargetsJson" +Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "ContextSecrets=$($contextSecrets -join ',')" +Write-Host "ContextSecrets=$($contextSecrets -join ',')" +#endregion diff --git a/DetermineDeliveryTargets/README.md b/DetermineDeliveryTargets/README.md new file mode 100644 index 0000000..7034a8a --- /dev/null +++ b/DetermineDeliveryTargets/README.md @@ -0,0 +1,25 @@ +# 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 | +| projectsJson | | Projects folder if repository is setup for multiple projects | . | +| checkContextSecrets | | Determines whether to check that delivery targets have a corresponding context secret defined | Y | + +## OUTPUT +| Name | Description | +| :-- | :-- | +| deliveryTargets | Compressed JSON array containing all delivery targets to use for the build | +| contextSecrets | A comma separated string with the names of the secrets to pass to ReadSecrets | + +### ENV variables diff --git a/DetermineDeliveryTargets/action.yaml b/DetermineDeliveryTargets/action.yaml new file mode 100644 index 0000000..90a7408 --- /dev/null +++ b/DetermineDeliveryTargets/action.yaml @@ -0,0 +1,43 @@ +name: Determine Delivery Targets +author: Microsoft Corporation +inputs: + shell: + description: Shell in which you want to run the action (powershell or pwsh) + required: false + default: powershell + projectsJson: + description: Projects to investigate + required: false + default: '["."]' + checkContextSecrets: + description: Check whether context secret exists + required: false + default: 'N' +outputs: + DeliveryTargetsJson: + description: An array of Delivery Targets in compressed JSON format + value: ${{ steps.determineDeliveryTargets.outputs.DeliveryTargetsJson }} + ContextSecrets: + description: A comma seperated list of Context Secret names used + value: ${{ steps.determineDeliveryTargets.outputs.ContextSecrets }} +runs: + using: composite + steps: + - name: run + shell: ${{ inputs.shell }} + id: determineDeliveryTargets + env: + _projectsJson: ${{ inputs.projectsJson }} + _checkContextSecrets: ${{ inputs.checkContextSecrets }} + run: | + $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + try { + ${{ github.action_path }}/DetermineDeliveryTargets.ps1 -projectsJson $ENV:_projectsJson -checkContextSecrets ($ENV:_checkContextSecrets -eq 'Y') + } + 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 new file mode 100644 index 0000000..5c11556 --- /dev/null +++ b/DetermineDeploymentEnvironments/DetermineDeploymentEnvironments.ps1 @@ -0,0 +1,186 @@ +Param( + [Parameter(HelpMessage = "Specifies the pattern of the environments you want to retreive (* for all)", Mandatory = $true)] + [string] $getEnvironments, + [Parameter(HelpMessage = "Type of deployment (CD or Publish)", Mandatory = $true)] + [ValidateSet('CD','Publish')] + [string] $type +) + +function GetGitHubEnvironments() { + $headers = GetHeader -token $env:GITHUB_TOKEN + $url = "$($ENV:GITHUB_API_URL)/repos/$($ENV:GITHUB_REPOSITORY)/environments" + try { + Write-Host "Requesting environments from GitHub" + $ghEnvironments = @((InvokeWebRequest -Headers $headers -Uri $url -ignoreErrors | ConvertFrom-Json).environments) + } + catch { + $ghEnvironments = @() + Write-Host "Failed to get environments from GitHub API - Environments are not supported in this repository" + } + $ghEnvironments +} + +function Get-BranchesFromPolicy($ghEnvironment) { + if ($ghEnvironment) { + # Environment is defined in GitHub - check protection rules + $headers = GetHeader -token $env:GITHUB_TOKEN + $branchPolicy = ($ghEnvironment.protection_rules | Where-Object { $_.type -eq "branch_policy" }) + if ($branchPolicy) { + if ($ghEnvironment.deployment_branch_policy.protected_branches) { + Write-Host "GitHub Environment $($ghEnvironment.name) only allows protected branches, getting protected branches from GitHub API" + $branchesUrl = "$($ENV:GITHUB_API_URL)/repos/$($ENV:GITHUB_REPOSITORY)/branches" + return (InvokeWebRequest -Headers $headers -Uri $branchesUrl -ignoreErrors | ConvertFrom-Json) | Where-Object { $_.protected } | ForEach-Object { $_.name } + } + elseif ($ghEnvironment.deployment_branch_policy.custom_branch_policies) { + Write-Host "GitHub Environment $($ghEnvironment.name) has custom deployment branch policies, getting branches from GitHub API" + $branchesUrl = "$($ENV:GITHUB_API_URL)/repos/$($ENV:GITHUB_REPOSITORY)/environments/$([Uri]::EscapeDataString($ghEnvironment.name))/deployment-branch-policies" + return (InvokeWebRequest -Headers $headers -Uri $branchesUrl -ignoreErrors | ConvertFrom-Json).branch_policies | ForEach-Object { $_.name } + } + } + else { + Write-Host "GitHub Environment $($ghEnvironment.name) does not have a branch policy defined" + } + } +} + +. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) + +$settings = $env:Settings | ConvertFrom-Json | ConvertTo-HashTable -recurse +Write-Host "Environment pattern to use: $getEnvironments" +$ghEnvironments = @(GetGitHubEnvironments) + +Write-Host "Reading environments from settings" +$settings.excludeEnvironments += @('github-pages') +$environments = @($ghEnvironments | ForEach-Object { $_.name }) + @($settings.environments) | Select-Object -unique | Where-Object { $settings.excludeEnvironments -notcontains $_.Split(' ')[0] -and $_.Split(' ')[0] -like $getEnvironments } + +Write-Host "Environments found: $($environments -join ', ')" + +$deploymentEnvironments = @{} +$unknownEnvironment = 0 + +if (!($environments)) { + # If no environments are defined and the user specified a single environment, use that environment + # This allows the user to specify a single environment without having to define it in the settings + if ($getenvironments -notcontains '*' -and $getenvironments -notcontains '?' -and $getenvironments -notcontains ',') { + $envName = $getEnvironments.Split(' ')[0] + $deploymentEnvironments += @{ + "$getEnvironments" = @{ + "EnvironmentType" = "SaaS" + "EnvironmentName" = $envName + "Branches" = $null + "BranchesFromPolicy" = @() + "Projects" = '*' + "SyncMode" = $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()) + } + } + $unknownEnvironment = 1 + } +} +else { + foreach($environmentName in $environments) { + Write-Host "Environment: $environmentName" + $envName = $environmentName.Split(' ')[0] + + # Check Obsolete Settings + foreach($obsoleteSetting in "$($envName)-Projects","$($envName)_Projects") { + if ($settings.Contains($obsoleteSetting)) { + throw "The setting $obsoleteSetting is obsolete and should be replaced by using the Projects property in the DeployTo$envName setting in .github/AL-Go-Settings.json instead" + } + } + + # Default Deployment settings are: + # - environment name: same + # - branches: main + # - projects: all + # - continuous deployment: only for environments not tagged with PROD or FAT + # - runs-on: same as settings."runs-on" + $deploymentSettings = @{ + "EnvironmentType" = "SaaS" + "EnvironmentName" = $envName + "Branches" = @() + "BranchesFromPolicy" = @() + "Projects" = '*' + "SyncMode" = $null + "ContinuousDeployment" = $null + "runs-on" = @($settings."runs-on".Split(',').Trim()) + } + + # Check DeployTo setting + $settingsName = "DeployTo$envName" + if ($settings.ContainsKey($settingsName)) { + # If a DeployTo setting exists - use values from this (over the defaults) + $deployTo = $settings."$settingsName" + foreach($key in 'EnvironmentType','EnvironmentName','Branches','Projects','SyncMode','ContinuousDeployment','runs-on') { + if ($deployTo.ContainsKey($key)) { + $deploymentSettings."$key" = $deployTo."$key" + } + } + } + + # Get Branch policies on GitHub Environment + $ghEnvironment = $ghEnvironments | Where-Object { $_.name -eq $environmentName } + $deploymentSettings.BranchesFromPolicy = @(Get-BranchesFromPolicy -ghEnvironment $ghEnvironment) + + # Include Environment if: + # - Type is not Continous Deployment + # - Environment is setup for Continuous Deployment (in settings) + # - Continuous Deployment is unset in settings and environment name doesn't contain PROD or FAT tags + $includeEnvironment = ($type -ne "CD" -or $deploymentSettings.ContinuousDeployment -or ($null -eq $deploymentSettings.ContinuousDeployment -and !($environmentName -like '* (PROD)' -or $environmentName -like '* (Production)' -or $environmentName -like '* (FAT)' -or $environmentName -like '* (Final Acceptance Test)'))) + + # Check branch policies and settings + if (-not $includeEnvironment) { + Write-Host "Environment $environmentName is not setup for continuous deployment" + } + else { + # Check whether any GitHub policy disallows this branch to deploy to this environment + if ($deploymentSettings.BranchesFromPolicy) { + # Check whether GITHUB_REF_NAME is allowed to deploy to this environment + $includeEnvironment = $deploymentSettings.BranchesFromPolicy | Where-Object { $ENV:GITHUB_REF_NAME -like $_ } + if ($deploymentSettings.Branches -and $includeEnvironment) { + # Branches are also defined in settings for this environment - only include branches that also exists in settings + $includeEnvironment = $deploymentSettings.Branches | Where-Object { $ENV:GITHUB_REF_NAME -like $_ } + } + } + else { + if ($deploymentSettings.Branches) { + # Branches are defined in settings for this environment - only include branches that exists in settings + $includeEnvironment = $deploymentSettings.Branches | Where-Object { $ENV:GITHUB_REF_NAME -like $_ } + } + else { + # If no branch policies are defined in GitHub nor in settings - only allow main branch to deploy + $includeEnvironment = $ENV:GITHUB_REF_NAME -eq 'main' + } + } + if (!$includeEnvironment) { + Write-Host "Environment $environmentName is not setup for deployments from branch $ENV:GITHUB_REF_NAME" + } + } + if ($includeEnvironment) { + $deploymentEnvironments += @{ "$environmentName" = $deploymentSettings } + # Dump Deployment settings for included environments + $deploymentSettings | ConvertTo-Json -Depth 99 | Out-Host + } + } +} + +# Calculate deployment matrix +$json = @{"matrix" = @{ "include" = @() }; "fail-fast" = $false } +$deploymentEnvironments.Keys | Sort-Object | ForEach-Object { + $deploymentEnvironment = $deploymentEnvironments."$_" + $json.matrix.include += @{ "environment" = $_; "os" = "$(ConvertTo-Json -InputObject $deploymentEnvironment."runs-on" -compress)" } +} +$environmentsMatrixJson = $json | ConvertTo-Json -Depth 99 -compress +Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "EnvironmentsMatrixJson=$environmentsMatrixJson" +Write-Host "EnvironmentsMatrixJson=$environmentsMatrixJson" + +$deploymentEnvironmentsJson = ConvertTo-Json -InputObject $deploymentEnvironments -Depth 99 -Compress +Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "DeploymentEnvironmentsJson=$deploymentEnvironmentsJson" +Write-Host "DeploymentEnvironmentsJson=$deploymentEnvironmentsJson" + +Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "EnvironmentCount=$($deploymentEnvironments.Keys.Count)" +Write-Host "EnvironmentCount=$($deploymentEnvironments.Keys.Count)" + +Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "UnknownEnvironment=$unknownEnvironment" +Write-Host "UnknownEnvironment=$unknownEnvironment" diff --git a/DetermineDeploymentEnvironments/README.md b/DetermineDeploymentEnvironments/README.md new file mode 100644 index 0000000..154556b --- /dev/null +++ b/DetermineDeploymentEnvironments/README.md @@ -0,0 +1,25 @@ +# 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) | | +| type | Yes | Type of deployment (CD or Publish) | | + +## 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 | +| UnknownEnvironment | Flag determining whether we try to publish to an unknown environment (invoke device code flow) | + +### ENV variables diff --git a/DetermineDeploymentEnvironments/action.yaml b/DetermineDeploymentEnvironments/action.yaml new file mode 100644 index 0000000..202df8e --- /dev/null +++ b/DetermineDeploymentEnvironments/action.yaml @@ -0,0 +1,47 @@ +name: Determine Deployment Environments +author: Microsoft Corporation +inputs: + shell: + description: Shell in which you want to run the action (powershell or pwsh) + required: false + default: powershell + getEnvironments: + description: Specifies the pattern of the environments you want to retreive (* for all) + required: true + type: + description: Type of deployment (CD or Publish) + required: true +outputs: + EnvironmentsMatrixJson: + description: The Environment matrix to use for the Deploy step in compressed JSON format + value: ${{ steps.determineDeploymentEnvironments.outputs.EnvironmentsMatrixJson }} + DeploymentEnvironmentsJson: + description: Deployment Environments with settings in compressed JSON format + value: ${{ steps.determineDeploymentEnvironments.outputs.DeploymentEnvironmentsJson }} + EnvironmentCount: + description: Number of Deployment Environments + value: ${{ steps.determineDeploymentEnvironments.outputs.EnvironmentCount }} + UnknownEnvironment: + description: Flag determining whether the environment is unknown + value: ${{ steps.determineDeploymentEnvironments.outputs.UnknownEnvironment }} +runs: + using: composite + steps: + - name: run + shell: ${{ inputs.shell }} + id: determineDeploymentEnvironments + env: + _getEnvironments: ${{ inputs.getEnvironments }} + _type: ${{ inputs.type }} + run: | + $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + try { + ${{ 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 d04ed51..b3e9cf1 100644 --- a/DetermineProjectsToBuild/DetermineProjectsToBuild.Action.ps1 +++ b/DetermineProjectsToBuild/DetermineProjectsToBuild.Action.ps1 @@ -11,15 +11,14 @@ Param( ) $telemetryScope = $null -$bcContainerHelperPath = $null try { #region Action: Setup . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) - $bcContainerHelperPath = DownloadAndImportBcContainerHelper -baseFolder $baseFolder + DownloadAndImportBcContainerHelper -baseFolder $baseFolder Import-Module (Join-Path -Path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) -DisableNameChecking #endregion - + $telemetryScope = CreateScope -eventId 'DO0085' -parentTelemetryScopeJson $parentTelemetryScopeJson #region Action: Determine projects to build @@ -32,12 +31,12 @@ try { $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 "BuildOrderJson=$buildOrderJson" + Write-Host "ProjectsJson=$projectsJson" Write-Host "ProjectDependenciesJson=$projectDependenciesJson" Write-Host "BuildOrderJson=$buildOrderJson" @@ -46,10 +45,8 @@ try { TrackTrace -telemetryScope $telemetryScope } catch { - TrackException -telemetryScope $telemetryScope -errorRecord $_ + if (Get-Module BcContainerHelper) { + TrackException -telemetryScope $telemetryScope -errorRecord $_ + } throw } -finally { - CleanupAfterBcContainerHelper -bcContainerHelperPath $bcContainerHelperPath -} - diff --git a/DetermineProjectsToBuild/DetermineProjectsToBuild.ps1 b/DetermineProjectsToBuild/DetermineProjectsToBuild.ps1 index 942d1a6..aeb2165 100644 --- a/DetermineProjectsToBuild/DetermineProjectsToBuild.ps1 +++ b/DetermineProjectsToBuild/DetermineProjectsToBuild.ps1 @@ -8,17 +8,15 @@ - project: The name of the AL-Go project - buildMode: The build mode to use for the project #> -function New-BuildDimensions( +function CreateBuildDimensions( [Parameter(HelpMessage = "A list of AL-Go projects for which to generate build dimensions")] $projects = @(), $baseFolder ) { $buildDimensions = @() - - $projects | ForEach-Object { - $project = $_ - + + foreach($project in $projects) { $projectSettings = ReadSettings -project $project -baseFolder $baseFolder $buildModes = @($projectSettings.buildModes) @@ -26,16 +24,16 @@ function New-BuildDimensions( Write-Host "No build modes found for project $project, using default build mode 'Default'." $buildModes = @('Default') } - - $buildModes | ForEach-Object { - $buildMode = $_ + + foreach($buildMode in $buildModes) { $buildDimensions += @{ project = $project + projectName = $projectSettings.projectName buildMode = $buildMode } } } - + return @(, $buildDimensions) # force array } @@ -50,39 +48,58 @@ function Get-FilteredProjectsToBuild($settings, $projects, $baseFolder, $modifie if ($settings.alwaysBuildAllProjects) { Write-Host "Building all projects because alwaysBuildAllProjects is set to true" return $projects - } + } if (!$modifiedFiles) { Write-Host "No files modified, building all projects" return $projects } - if ($modifiedFiles -like '.github/*.json') { - Write-Host "Changes to repo Settings, building all projects" - return $projects - } - + Write-Host "$($modifiedFiles.Count) modified file(s): $($modifiedFiles -join ', ')" + if ($modifiedFiles.Count -ge 250) { Write-Host "More than 250 files modified, building all projects" return $projects } - Write-Host "$($modifiedFiles.Count) modified file(s): $($modifiedFiles -join ', ')" + $fullBuildPatterns = @( Join-Path '.github' '*.json') + if($settings.fullBuildPatterns) { + $fullBuildPatterns += $settings.fullBuildPatterns + } + + #Include the base folder in the modified files + $modifiedFiles = @($modifiedFiles | ForEach-Object { return Join-Path $baseFolder $_ }) + + foreach($fullBuildFolder in $fullBuildPatterns) { + # The Join-Path is needed to make sure the path has the correct slashes + $fullBuildFolder = Join-Path $baseFolder $fullBuildFolder + + if ($modifiedFiles -like $fullBuildFolder) { + Write-Host "Changes to $fullBuildFolder, building all projects" + return $projects + } + } Write-Host "Filtering projects to build based on the modified files" - $filteredProjects = @($projects | Where-Object { - $checkProject = $_ - $buildProject = $false - if (Test-Path -Path (Join-Path $baseFolder "$checkProject/.AL-Go/settings.json")) { - $projectFolders = Get-ProjectFolders -baseFolder $baseFolder -project $checkProject -includeAlGoFolder + $filteredProjects = @() + foreach($project in $projects) + { + if (Test-Path -Path (Join-Path $baseFolder "$project/.AL-Go/settings.json")) { + $projectFolders = GetProjectFolders -baseFolder $baseFolder -project $project -includeAlGoFolder - $projectFolders | ForEach-Object { - if ($modifiedFiles -like "$_/*") { $buildProject = $true } - } + $modifiedProjectFolders = @($projectFolders | Where-Object { + $projectFolder = Join-Path $baseFolder "$_/*" + + return $($modifiedFiles -like $projectFolder) + }) + + if ($modifiedProjectFolders.Count -gt 0) { + # The project has been modified, add it to the list of projects to build + $filteredProjects += $project } - $buildProject - }) + } + } return $filteredProjects } @@ -114,15 +131,15 @@ function Get-ProjectsToBuild( $modifiedFiles = @(), [Parameter(HelpMessage = "The maximum depth to build the dependency tree", Mandatory = $false)] $maxBuildDepth = 0 -) +) { Write-Host "Determining projects to build in $baseFolder" - + Push-Location $baseFolder try { - $settings = ReadSettings -baseFolder $baseFolder -project '.' # Read AL-Go settings for the repo - + $settings = $env:Settings | ConvertFrom-Json + if ($settings.projects) { Write-Host "Projects specified in settings" $projects = $settings.projects @@ -130,28 +147,28 @@ function Get-ProjectsToBuild( else { # Get all projects that have a settings.json file $projects = @(Get-ChildItem -Path $baseFolder -Recurse -Depth 2 | Where-Object { $_.PSIsContainer -and (Test-Path (Join-Path $_.FullName ".AL-Go/settings.json") -PathType Leaf) } | ForEach-Object { $_.FullName.Substring($baseFolder.length+1) }) - + # If the repo has a settings.json file, add it to the list of projects to build if (Test-Path (Join-Path ".AL-Go" "settings.json") -PathType Leaf) { $projects += @(".") } } - + Write-Host "Found AL-Go Projects: $($projects -join ', ')" - + $projectsToBuild = @() $projectDependencies = @{} $projectsOrderToBuild = @() - + if ($projects) { $projectsToBuild += Get-FilteredProjectsToBuild -baseFolder $baseFolder -settings $settings -projects $projects -modifiedFiles $modifiedFiles - + if($settings.useProjectDependencies) { $buildAlso = @{} # Calculate the full projects order $fullProjectsOrder = AnalyzeProjectDependencies -baseFolder $baseFolder -projects $projects -buildAlso ([ref]$buildAlso) -projectDependencies ([ref]$projectDependencies) - + $projectsToBuild = @($projectsToBuild | ForEach-Object { $_; if ($buildAlso.Keys -contains $_) { $buildAlso."$_" } } | Select-Object -Unique) } else { @@ -165,7 +182,7 @@ function Get-ProjectsToBuild( if ($projectsOnDepth) { # Create build dimensions for the projects on the current depth - $buildDimensions = New-BuildDimensions -baseFolder $baseFolder -projects $projectsOnDepth + $buildDimensions = CreateBuildDimensions -baseFolder $baseFolder -projects $projectsOnDepth $projectsOrderToBuild += @{ projects = $projectsOnDepth projectsCount = $projectsOnDepth.Count @@ -174,7 +191,7 @@ function Get-ProjectsToBuild( } } } - + if ($projectsOrderToBuild.Count -eq 0) { Write-Host "Did not find any projects to add to the build order, adding default values" $projectsOrderToBuild += @{ @@ -186,7 +203,7 @@ function Get-ProjectsToBuild( Write-Host "Projects to build: $($projectsToBuild -join ', ')" if($maxBuildDepth -and ($projectsOrderToBuild.Count -gt $maxBuildDepth)) { - throw "The build depth is too deep, the maximum build depth is $maxBuildDepth. You need to run 'Update AL-Go System Files' to update the workflows" + throw "The build depth is too deep, the maximum build depth is $maxBuildDepth. You need to run 'Update AL-Go System Files' to update the workflows" } return $projects, $projectsToBuild, $projectDependencies, $projectsOrderToBuild diff --git a/DetermineProjectsToBuild/GetModifiedFiles.ps1 b/DetermineProjectsToBuild/GetModifiedFiles.ps1 index 83e0f0e..1d0fa90 100644 --- a/DetermineProjectsToBuild/GetModifiedFiles.ps1 +++ b/DetermineProjectsToBuild/GetModifiedFiles.ps1 @@ -17,7 +17,7 @@ if(-not ($ghEvent.PSObject.Properties.name -eq 'pull_request')) { $url = "$($env:GITHUB_API_URL)/repos/$($env:GITHUB_REPOSITORY)/compare/$($ghEvent.pull_request.base.sha)...$($ghEvent.pull_request.head.sha)" -$headers = @{ +$headers = @{ "Authorization" = "token $token" "Accept" = "application/vnd.github.baptiste-preview+json" } diff --git a/DetermineProjectsToBuild/action.yaml b/DetermineProjectsToBuild/action.yaml index b2807f0..312bcb0 100644 --- a/DetermineProjectsToBuild/action.yaml +++ b/DetermineProjectsToBuild/action.yaml @@ -20,7 +20,7 @@ outputs: ProjectDependenciesJson: description: An object that holds the project dependencies in compressed JSON format value: ${{ steps.determineProjectsToBuild.outputs.ProjectDependenciesJson }} - BuildOrderJson: + BuildOrderJson: description: An array of objects that determine that build order, including build dimensions value: ${{ steps.determineProjectsToBuild.outputs.BuildOrderJson }} runs: @@ -34,7 +34,7 @@ runs: _maxBuildDepth: ${{ inputs.maxBuildDepth }} run: | $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - try { + try { $changedFiles = ${{ github.action_path }}/GetModifiedFiles.ps1 -token ${{ github.token }} ${{ github.action_path }}/DetermineProjectsToBuild.Action.ps1 -baseFolder ${{ github.workspace }} -modifiedFiles $changedFiles -maxBuildDepth $env:_maxBuildDepth -parentTelemetryScopeJson $env:_parentTelemetryScopeJson } diff --git a/DownloadProjectDependencies/DownloadProjectDependencies.Action.ps1 b/DownloadProjectDependencies/DownloadProjectDependencies.Action.ps1 index af7f7a0..b1521f9 100644 --- a/DownloadProjectDependencies/DownloadProjectDependencies.Action.ps1 +++ b/DownloadProjectDependencies/DownloadProjectDependencies.Action.ps1 @@ -8,63 +8,61 @@ Param( [string] $token ) -function DownloadDependenciesFromProbingPaths($baseFolder, $project, $destinationPath) { +function DownloadDependenciesFromProbingPaths($baseFolder, $project, $destinationPath, $token) { $settings = $env:Settings | ConvertFrom-Json | ConvertTo-HashTable -recurse - $settings = AnalyzeRepo -settings $settings -token $token -baseFolder $baseFolder -project $project -doNotCheckArtifactSetting -doNotIssueWarnings + $settings = AnalyzeRepo -settings $settings -baseFolder $baseFolder -project $project -doNotCheckArtifactSetting -doNotIssueWarnings + $settings = CheckAppDependencyProbingPaths -settings $settings -token $token -baseFolder $baseFolder -project $project if ($settings.ContainsKey('appDependencyProbingPaths') -and $settings.appDependencyProbingPaths) { - return Get-Dependencies -probingPathsJson $settings.appDependencyProbingPaths -saveToPath $destinationPath | Where-Object { $_ } + return GetDependencies -probingPathsJson $settings.appDependencyProbingPaths -saveToPath $destinationPath | Where-Object { $_ } } } -function DownloadDependenciesFromCurrentBuild($baseFolder, $project, $projectsDependencies, $buildMode, $destinationPath) { +function DownloadDependenciesFromCurrentBuild($baseFolder, $project, $projectsDependencies, $buildMode, $destinationPath, $token) { Write-Host "Downloading dependencies for project '$project'" - + $dependencyProjects = @() if ($projectsDependencies.Keys -contains $project) { $dependencyProjects = @($projectsDependencies."$project") } Write-Host "Dependency projects: $($dependencyProjects -join ', ')" - + # For each dependency project, calculate the corresponding probing path - $dependeciesProbingPaths = @($dependencyProjects | ForEach-Object { - $dependencyProject = $_ - - Write-Host "Reading settings for project '$dependencyProject'" - $dependencyProjectSettings = ReadSettings -baseFolder $baseFolder -project $dependencyProject - - $dependencyBuildMode = $buildMode - if (!($dependencyProjectSettings.buildModes -contains $dependencyBuildMode)) { - # Download the default build mode if the specified build mode is not supported for the dependency project - Write-Host "Build mode '$dependencyBuildMode' is not supported for project '$dependencyProject'. Using the default build mode." - $dependencyBuildMode = 'Default'; - } - - $currentBranch = $ENV:GITHUB_REF_NAME - - $baseBranch = $ENV:GITHUB_BASE_REF_NAME - # $ENV:GITHUB_BASE_REF_NAME is specified only for pull requests, so if it is not specified, use the current branch - if (!$baseBranch) { - $baseBranch = $currentBranch - } - - return @{ - "release_status" = "thisBuild" - "version" = "latest" - "buildMode" = $dependencyBuildMode - "projects" = $dependencyProject - "repo" = "$ENV:GITHUB_SERVER_URL/$ENV:GITHUB_REPOSITORY" - "branch" = $currentBranch - "baseBranch" = $baseBranch - "authTokenSecret" = $token - } + $dependeciesProbingPaths = @() + foreach($dependencyProject in $dependencyProjects) { + Write-Host "Reading settings for project '$dependencyProject'" + $dependencyProjectSettings = ReadSettings -baseFolder $baseFolder -project $dependencyProject + + $dependencyBuildMode = $buildMode + if (!($dependencyProjectSettings.buildModes -contains $dependencyBuildMode)) { + # Download the default build mode if the specified build mode is not supported for the dependency project + Write-Host "Build mode '$dependencyBuildMode' is not supported for project '$dependencyProject'. Using the default build mode." + $dependencyBuildMode = 'Default'; + } + + $currentBranch = $ENV:GITHUB_REF_NAME + + $baseBranch = $ENV:GITHUB_BASE_REF_NAME + # $ENV:GITHUB_BASE_REF_NAME is specified only for pull requests, so if it is not specified, use the current branch + if (!$baseBranch) { + $baseBranch = $currentBranch + } + + $dependeciesProbingPaths += @(@{ + "release_status" = "thisBuild" + "version" = "latest" + "buildMode" = $dependencyBuildMode + "projects" = $dependencyProject + "repo" = "$ENV:GITHUB_SERVER_URL/$ENV:GITHUB_REPOSITORY" + "branch" = $currentBranch + "baseBranch" = $baseBranch + "authTokenSecret" = $token }) - + } + # For each probing path, download the dependencies $downloadedDependencies = @() - $dependeciesProbingPaths | ForEach-Object { - $probingPath = $_ - + foreach($probingPath in $dependeciesProbingPaths) { $buildMode = $probingPath.buildMode $project = $probingPath.projects $branch = $probingPath.branch @@ -72,7 +70,7 @@ function DownloadDependenciesFromCurrentBuild($baseFolder, $project, $projectsDe Write-Host "Downloading dependencies for project '$project'. BuildMode: $buildMode, Branch: $branch, Base Branch: $baseBranch" - $dependency = Get-Dependencies -probingPathsJson $probingPath -saveToPath $destinationPath | Where-Object { $_ } + $dependency = GetDependencies -probingPathsJson $probingPath -saveToPath $destinationPath | Where-Object { $_ } $downloadedDependencies += $dependency } @@ -87,11 +85,11 @@ $downloadedDependencies = @() Write-Host "::group::Downloading project dependencies from current build" $projectsDependencies = $projectsDependenciesJson | ConvertFrom-Json | ConvertTo-HashTable -$downloadedDependencies += DownloadDependenciesFromCurrentBuild -baseFolder $baseFolder -project $project -projectsDependencies $projectsDependencies -buildMode $buildMode -destinationPath $destinationPath +$downloadedDependencies += DownloadDependenciesFromCurrentBuild -baseFolder $baseFolder -project $project -projectsDependencies $projectsDependencies -buildMode $buildMode -destinationPath $destinationPath -token $token Write-Host "::endgroup::" Write-Host "::group::Downloading project dependencies from probing paths" -$downloadedDependencies += DownloadDependenciesFromProbingPaths -baseFolder $baseFolder -project $project -destinationPath $destinationPath +$downloadedDependencies += DownloadDependenciesFromProbingPaths -baseFolder $baseFolder -project $project -destinationPath $destinationPath -token $token Write-Host "::endgroup::" Write-Host "Downloaded dependencies: $($downloadedDependencies -join ', ')" diff --git a/DownloadProjectDependencies/README.md b/DownloadProjectDependencies/README.md index 5ab4c81..70bf977 100644 --- a/DownloadProjectDependencies/README.md +++ b/DownloadProjectDependencies/README.md @@ -9,6 +9,7 @@ The action constructs arrays of paths to .app files, that are dependencies of th | 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 | diff --git a/Github-Helper.psm1 b/Github-Helper.psm1 index 38203a0..66021e6 100644 --- a/Github-Helper.psm1 +++ b/Github-Helper.psm1 @@ -1,4 +1,5 @@ function GetExtendedErrorMessage { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingEmptyCatchBlock", "", Justification="We want to ignore errors")] Param( $errorRecord ) @@ -10,7 +11,9 @@ function GetExtendedErrorMessage { $errorDetails = $errorRecord.ErrorDetails | ConvertFrom-Json $message += " $($errorDetails.error)`n$($errorDetails.error_description)" } - catch {} + catch { + # ignore errors + } try { if ($exception -is [System.Management.Automation.MethodInvocationException]) { $exception = $exception.InnerException @@ -21,7 +24,10 @@ function GetExtendedErrorMessage { if ($webResponse.StatusDescription) { $message += "`n$($webResponse.StatusDescription)" } - } catch {} + } + catch { + # ignore errors + } $reqstream = $webResponse.GetResponseStream() $sr = new-object System.IO.StreamReader $reqstream $result = $sr.ReadToEnd() @@ -38,9 +44,13 @@ function GetExtendedErrorMessage { $message += " (ms-correlation-x = $correlationX)" } } - catch {} + catch { + # ignore errors + } + } + catch{ + # ignore errors } - catch{} $message } @@ -95,7 +105,9 @@ function InvokeWebRequest { Invoke-WebRequest @params -Uri $uri return } - catch {} + catch { + Write-Host "Retry failed as well" + } } if ($ignoreErrors.IsPresent) { Write-Host $message @@ -107,7 +119,7 @@ function InvokeWebRequest { } } -function Get-Dependencies { +function GetDependencies { Param( $probingPathsJson, [string] $api_url = $ENV:GITHUB_API_URL, @@ -119,32 +131,29 @@ function Get-Dependencies { } $downloadedList = @() - 'Apps','TestApps' | ForEach-Object { - $mask = $_ - $probingPathsJson | ForEach-Object { - $dependency = $_ + foreach($mask in 'Apps','TestApps') { + foreach($dependency in $probingPathsJson) { $projects = $dependency.projects $buildMode = $dependency.buildMode - + # change the mask to include the build mode if($buildMode -ne "Default") { $mask = "$buildMode$mask" } Write-Host "Locating $mask artifacts for projects: $projects" - + if ($dependency.release_status -eq "thisBuild") { $missingProjects = @() - $projects.Split(',') | ForEach-Object { - $project = $_ + foreach($project in $projects.Split(',')) { $project = $project.Replace('\','_').Replace('/','_') # sanitize project name - + $downloadName = Join-Path $saveToPath "thisbuild-$project-$($mask)" - + if (Test-Path $downloadName -PathType Container) { $folder = Get-Item $downloadName Get-ChildItem -Path $folder | ForEach-Object { - if ($mask -eq 'TestApps') { + if ($mask -like '*TestApps') { $downloadedList += @("($($_.FullName))") } else { @@ -154,8 +163,8 @@ function Get-Dependencies { } } elseif ($mask -notlike '*TestApps') { - Write-Host "$_ not built, downloading from artifacts" - $missingProjects += @($_) + Write-Host "$project not built, downloading from artifacts" + $missingProjects += @($project) } } if ($missingProjects) { @@ -172,7 +181,7 @@ function Get-Dependencies { $artifacts | ForEach-Object { $download = DownloadArtifact -path $saveToPath -token $dependency.authTokenSecret -artifact $_ if ($download) { - if ($mask -eq 'TestApps') { + if ($mask -like '*TestApps') { $downloadedList += @("($download)") } else { @@ -207,7 +216,7 @@ function Get-Dependencies { $download = DownloadRelease -token $dependency.authTokenSecret -projects $projects -api_url $api_url -repository $repository -path $saveToPath -release $release -mask $mask if ($download) { - if ($mask -eq 'TestApps') { + if ($mask -like '*TestApps') { $downloadedList += @("($download)") } else { @@ -221,6 +230,7 @@ function Get-Dependencies { } function CmdDo { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingEmptyCatchBlock", "", Justification="We want to ignore errors")] Param( [string] $command = "", [string] $arguments = "", @@ -234,7 +244,6 @@ function CmdDo { $oldEncoding = [Console]::OutputEncoding try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {} try { - $result = $true $pinfo = New-Object System.Diagnostics.ProcessStartInfo $pinfo.FileName = $command $pinfo.RedirectStandardError = $true @@ -264,7 +273,7 @@ function CmdDo { if ("$err" -ne "") { $message += "$err" } - + $message = $message.Trim() if ($p.ExitCode -eq 0) { @@ -296,20 +305,22 @@ function invoke-gh { [parameter(mandatory = $false, position = 1, ValueFromRemainingArguments = $true)] $remaining ) - $arguments = "$command " - $remaining | ForEach-Object { - if ("$_".IndexOf(" ") -ge 0 -or "$_".IndexOf('"') -ge 0) { - $arguments += """$($_.Replace('"','\"'))"" " + Process { + $arguments = "$command " + foreach($parameter in $remaining) { + if ("$parameter".IndexOf(" ") -ge 0 -or "$parameter".IndexOf('"') -ge 0) { + $arguments += """$($parameter.Replace('"','\"'))"" " + } + else { + $arguments += "$parameter " + } } - else { - $arguments += "$_ " + try { + cmdDo -command gh -arguments $arguments -silent:$silent -returnValue:$returnValue -inputStr $inputStr + } + catch [System.Management.Automation.MethodInvocationException] { + throw "It looks like GitHub CLI is not installed. Please install GitHub CLI from https://cli.github.com/" } - } - try { - cmdDo -command gh -arguments $arguments -silent:$silent -returnValue:$returnValue -inputStr $inputStr - } - catch [System.Management.Automation.MethodInvocationException] { - throw "It looks like GitHub CLI is not installed. Please install GitHub CLI from https://cli.github.com/" } } @@ -323,20 +334,22 @@ function invoke-git { [parameter(mandatory = $false, position = 1, ValueFromRemainingArguments = $true)] $remaining ) - $arguments = "$command " - $remaining | ForEach-Object { - if ("$_".IndexOf(" ") -ge 0 -or "$_".IndexOf('"') -ge 0) { - $arguments += """$($_.Replace('"','\"'))"" " + Process { + $arguments = "$command " + foreach($parameter in $remaining) { + if ("$parameter".IndexOf(" ") -ge 0 -or "$parameter".IndexOf('"') -ge 0) { + $arguments += """$($parameter.Replace('"','\"'))"" " + } + else { + $arguments += "$parameter " + } } - else { - $arguments += "$_ " + try { + cmdDo -command git -arguments $arguments -silent:$silent -returnValue:$returnValue -inputStr $inputStr + } + catch [System.Management.Automation.MethodInvocationException] { + throw "It looks like Git is not installed. Please install Git from https://git-scm.com/download" } - } - try { - cmdDo -command git -arguments $arguments -silent:$silent -returnValue:$returnValue -inputStr $inputStr - } - catch [System.Management.Automation.MethodInvocationException] { - throw "It looks like Git is not installed. Please install Git from https://git-scm.com/download" } } @@ -360,17 +373,20 @@ function SemVerObjToSemVerStr { [Parameter(Mandatory = $true, ValueFromPipeline = $true)] $semVerObj ) - try { - $str = "$($semVerObj.Prefix)$($semVerObj.Major).$($semVerObj.Minor).$($semVerObj.Patch)" - for ($i=0; $i -lt 5; $i++) { - $seg = $semVerObj."Addt$i" - if ($seg -eq 'zzz') { break } - if ($i -eq 0) { $str += "-$($seg)" } else { $str += ".$($seg)" } + + Process { + try { + $str = "$($semVerObj.Prefix)$($semVerObj.Major).$($semVerObj.Minor).$($semVerObj.Patch)" + for ($i=0; $i -lt 5; $i++) { + $seg = $semVerObj."Addt$i" + if ($seg -eq 'zzz') { break } + if ($i -eq 0) { $str += "-$($seg)" } else { $str += ".$($seg)" } + } + $str + } + catch { + throw "'$SemVerObj' cannot be recognized as a semantic version object (internal error)" } - $str - } - catch { - throw "'$SemVerObj' cannot be recognized as a semantic version object (internal error)" } } @@ -401,76 +417,78 @@ function SemVerStrToSemVerObj { [switch] $allowMajorMinorOnly ) - $obj = New-Object PSCustomObject - try { - # Only allowed prefix is a 'v'. - # This is supported by GitHub when sorting tags - $prefix = '' - $verstr = $semVerStr - if ($semVerStr -like 'v*') { - $prefix = 'v' - $verStr = $semVerStr.Substring(1) - } - # Next part is a version number with 2 or 3 segments - # 2 segments are allowed only if $allowMajorMinorOnly is specified - $version = [System.Version]"$($verStr.split('-')[0])" - if ($version.Revision -ne -1) { throw "not semver" } - if ($version.Build -eq -1) { - if ($allowMajorMinorOnly) { - $version = [System.Version]"$($version.Major).$($version.Minor).0" - $idx = $semVerStr.IndexOf('-') - if ($idx -eq -1) { - $semVerStr = "$semVerStr.0" + Process { + $obj = New-Object PSCustomObject + try { + # Only allowed prefix is a 'v'. + # This is supported by GitHub when sorting tags + $prefix = '' + $verstr = $semVerStr + if ($semVerStr -like 'v*') { + $prefix = 'v' + $verStr = $semVerStr.Substring(1) + } + # Next part is a version number with 2 or 3 segments + # 2 segments are allowed only if $allowMajorMinorOnly is specified + $version = [System.Version]"$($verStr.split('-')[0])" + if ($version.Revision -ne -1) { throw "not semver" } + if ($version.Build -eq -1) { + if ($allowMajorMinorOnly) { + $version = [System.Version]"$($version.Major).$($version.Minor).0" + $idx = $semVerStr.IndexOf('-') + if ($idx -eq -1) { + $semVerStr = "$semVerStr.0" + } + else { + $semVerstr = $semVerstr.insert($idx, '.0') + } } else { - $semVerstr = $semVerstr.insert($idx, '.0') + throw "not semver" } } - else { - throw "not semver" + # Add properties to the object + $obj | Add-Member -MemberType NoteProperty -Name "Prefix" -Value $prefix + $obj | Add-Member -MemberType NoteProperty -Name "Major" -Value ([int]$version.Major) + $obj | Add-Member -MemberType NoteProperty -Name "Minor" -Value ([int]$version.Minor) + $obj | Add-Member -MemberType NoteProperty -Name "Patch" -Value ([int]$version.Build) + 0..4 | ForEach-Object { + # default segments to 'zzz' for sorting of SemVer Objects to work as GitHub does + $obj | Add-Member -MemberType NoteProperty -Name "Addt$_" -Value 'zzz' } - } - # Add properties to the object - $obj | Add-Member -MemberType NoteProperty -Name "Prefix" -Value $prefix - $obj | Add-Member -MemberType NoteProperty -Name "Major" -Value ([int]$version.Major) - $obj | Add-Member -MemberType NoteProperty -Name "Minor" -Value ([int]$version.Minor) - $obj | Add-Member -MemberType NoteProperty -Name "Patch" -Value ([int]$version.Build) - 0..4 | ForEach-Object { - # default segments to 'zzz' for sorting of SemVer Objects to work as GitHub does - $obj | Add-Member -MemberType NoteProperty -Name "Addt$_" -Value 'zzz' - } - $idx = $verStr.IndexOf('-') - if ($idx -gt 0) { - $segments = $verStr.SubString($idx+1).Split('.') - if ($segments.Count -gt 5) { - throw "max. 5 segments" - } - # Add all 5 segments to the object - # If the segment is a number, it is converted to an integer - # If the segment is a string, it cannot be -ge 'zzz' (would be sorted wrongly) - 0..($segments.Count-1) | ForEach-Object { - $result = 0 - if ([int]::TryParse($segments[$_], [ref] $result)) { - $obj."Addt$_" = [int]$result + $idx = $verStr.IndexOf('-') + if ($idx -gt 0) { + $segments = $verStr.SubString($idx+1).Split('.') + if ($segments.Count -gt 5) { + throw "max. 5 segments" } - else { - if ($segments[$_] -ge 'zzz') { - throw "Unsupported segment" + # Add all 5 segments to the object + # If the segment is a number, it is converted to an integer + # If the segment is a string, it cannot be -ge 'zzz' (would be sorted wrongly) + 0..($segments.Count-1) | ForEach-Object { + $result = 0 + if ([int]::TryParse($segments[$_], [ref] $result)) { + $obj."Addt$_" = [int]$result + } + else { + if ($segments[$_] -ge 'zzz') { + throw "Unsupported segment" + } + $obj."Addt$_" = $segments[$_] } - $obj."Addt$_" = $segments[$_] } } + # Check that the object can be converted back to the original string + $newStr = SemVerObjToSemVerStr -semVerObj $obj + if ($newStr -cne $semVerStr) { + throw "Not equal" + } } - # Check that the object can be converted back to the original string - $newStr = SemVerObjToSemVerStr -semVerObj $obj - if ($newStr -cne $semVerStr) { - throw "Not equal" + catch { + throw "'$semVerStr' cannot be recognized as a semantic version string (https://semver.org)" } + $obj } - catch { - throw "'$semVerStr' cannot be recognized as a semantic version string (https://semver.org)" - } - $obj } function GetReleases { @@ -485,9 +503,9 @@ function GetReleases { if ($releases.Count -gt 1) { # Sort by SemVer tag try { - $sortedReleases = $releases.tag_name | - ForEach-Object { SemVerStrToSemVerObj -semVerStr $_ } | - Sort-Object -Property Major,Minor,Patch,Addt0,Addt1,Addt2,Addt3,Addt4 -Descending | + $sortedReleases = $releases.tag_name | + ForEach-Object { SemVerStrToSemVerObj -semVerStr $_ } | + Sort-Object -Property Major,Minor,Patch,Addt0,Addt1,Addt2,Addt3,Addt4 -Descending | ForEach-Object { SemVerObjToSemVerStr -semVerObj $_ } | ForEach-Object { $tag_name = $_ $releases | Where-Object { $_.tag_name -eq $tag_name } @@ -511,7 +529,7 @@ function GetLatestRelease { [string] $repository = $ENV:GITHUB_REPOSITORY, [string] $ref = $ENV:GITHUB_REFNAME ) - + Write-Host "Getting the latest release from $api_url/repos/$repository/releases/latest - branch $ref" # Get all releases from GitHub, sorted by SemVer tag # If any release tag is not a valid SemVer tag, use default GitHub sorting and issue a warning @@ -536,9 +554,13 @@ function GetLatestRelease { function GetHeader { param ( [string] $token, - [string] $accept = "application/vnd.github.v3+json" + [string] $accept = "application/vnd.github+json", + [string] $apiVersion = "2022-11-28" ) - $headers = @{ "Accept" = $accept } + $headers = @{ + "Accept" = $accept + "X-GitHub-Api-Version" = $apiVersion + } if (![string]::IsNullOrEmpty($token)) { $headers["Authorization"] = "token $token" } @@ -555,7 +577,7 @@ function GetReleaseNotes { [string] $previous_tag_name, [string] $target_commitish ) - + Write-Host "Generating release note $api_url/repos/$repository/releases/generate-notes" $postParams = @{ @@ -569,7 +591,7 @@ function GetReleaseNotes { $postParams["target_commitish"] = $target_commitish } - InvokeWebRequest -Headers (GetHeader -token $token) -Method POST -Body ($postParams | ConvertTo-Json) -Uri "$api_url/repos/$repository/releases/generate-notes" + InvokeWebRequest -Headers (GetHeader -token $token) -Method POST -Body ($postParams | ConvertTo-Json) -Uri "$api_url/repos/$repository/releases/generate-notes" } function DownloadRelease { @@ -588,19 +610,19 @@ function DownloadRelease { if ([string]::IsNullOrEmpty($token)) { $token = invoke-gh -silent -returnValue auth token } - $headers = @{ - "Accept" = "application/octet-stream" - "Authorization" = "token $token" - } - $projects.Split(',') | ForEach-Object { - $project = $_.Replace('\','_').Replace('/','_') + $headers = GetHeader -token $token -accept "application/octet-stream" + foreach($project in $projects.Split(',')) { + $project = $project.Replace('\','_').Replace('/','_') Write-Host "project '$project'" - - $release.assets | Where-Object { $_.name -like "$project-*-$mask-*.zip" -or $_.name -like "$project-$mask-*.zip" } | ForEach-Object { - $uri = "$api_url/repos/$repository/releases/assets/$($_.id)" + $assetPattern1 = "$project-*-$mask-*.zip" + $assetPattern2 = "$project-$mask-*.zip" + Write-Host "AssetPatterns: '$assetPattern1' | '$assetPattern2'" + $assets = @($release.assets | Where-Object { $_.name -like $assetPattern1 -or $_.name -like $assetPattern2 }) + foreach($asset in $assets) { + $uri = "$api_url/repos/$repository/releases/assets/$($asset.id)" Write-Host $uri - $filename = Join-Path $path $_.name - InvokeWebRequest -Headers $headers -Uri $uri -OutFile $filename + $filename = Join-Path $path $asset.name + InvokeWebRequest -Headers $headers -Uri $uri -OutFile $filename return $filename } } @@ -633,7 +655,9 @@ function Get-ContentLF { [string] $path ) - (Get-Content -Path $path -Encoding UTF8 -Raw).Replace("`r", "").TrimEnd("`n") + Process { + (Get-Content -Path $path -Encoding UTF8 -Raw).Replace("`r", "").TrimEnd("`n") + } } # Set-Content defaults to culture specific ANSI encoding, which is not what we want @@ -647,14 +671,16 @@ function Set-ContentLF { $content ) - $path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($path) - if ($content -is [array]) { - $content = $content -join "`n" - } - else { - $content = "$content".Replace("`r", "") + Process { + $path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($path) + if ($content -is [array]) { + $content = $content -join "`n" + } + else { + $content = "$content".Replace("`r", "") + } + [System.IO.File]::WriteAllText($path, "$content`n") } - [System.IO.File]::WriteAllText($path, "$content`n") } # Format Object to JSON and write to file with LF line endings and formatted as PowerShell 7 would do it @@ -675,14 +701,16 @@ function Set-JsonContentLF { [object] $object ) - $object | ConvertTo-Json -Depth 99 | Set-ContentLF -path $path - if ($PSVersionTable.PSVersion.Major -lt 6) { - try { - $path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($path) - . pwsh (Join-Path $PSScriptRoot 'prettyfyjson.ps1') $path - } - catch { - Write-Host "WARNING: pwsh (PowerShell 7) not installed, json will be formatted by PowerShell $($PSVersionTable.PSVersion)" + Process { + $object | ConvertTo-Json -Depth 99 | Set-ContentLF -path $path + if ($PSVersionTable.PSVersion.Major -lt 6) { + try { + $path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($path) + . pwsh (Join-Path $PSScriptRoot 'prettyfyjson.ps1') $path + } + catch { + Write-Host "WARNING: pwsh (PowerShell 7) not installed, json will be formatted by PowerShell $($PSVersionTable.PSVersion)" + } } } } @@ -710,13 +738,17 @@ function GetArtifacts { $artifactsJson = InvokeWebRequest -Headers $headers -Uri $uri $artifacts = $artifactsJson | ConvertFrom-Json $page++ - $allArtifacts += @($artifacts.artifacts | Where-Object { !$_.expired -and $_.name -like "*-$branch-$mask-$version" }) + $artifactPattern = "*-$branch-$mask-$version" + Write-Host "ArtifactPattern: $artifactPattern" + $allArtifacts += @($artifacts.artifacts | Where-Object { !$_.expired -and $_.name -like $artifactPattern }) $result = @() $allArtifactsFound = $true - $projects.Split(',') | ForEach-Object { - $project = $_.Replace('\','_').Replace('/','_') - Write-Host "project '$project'" - $projectArtifact = $allArtifacts | Where-Object { $_.name -like "$project-$branch-$mask-$version" } | Select-Object -First 1 + foreach($project in $projects.Split(',')) { + $project = $project.Replace('\','_').Replace('/','_') + Write-Host "Project: $project" + $artifactPattern = "$project-$branch-$mask-$version" + Write-Host "ArtifactPattern: $artifactPattern" + $projectArtifact = $allArtifacts | Where-Object { $_.name -like $artifactPattern } | Select-Object -First 1 if ($projectArtifact) { $result += @($projectArtifact) } @@ -741,10 +773,7 @@ function DownloadArtifact { if ([string]::IsNullOrEmpty($token)) { $token = invoke-gh -silent -returnValue auth token } - $headers = @{ - "Authorization" = "token $token" - "Accept" = "application/vnd.github.v3+json" - } + $headers = GetHeader -token $token $outFile = Join-Path $path "$($artifact.Name).zip" InvokeWebRequest -Headers $headers -Uri $artifact.archive_download_url -OutFile $outFile $outFile diff --git a/IncrementVersionNumber/IncrementVersionNumber.ps1 b/IncrementVersionNumber/IncrementVersionNumber.ps1 index 042da8d..4b89c0f 100644 --- a/IncrementVersionNumber/IncrementVersionNumber.ps1 +++ b/IncrementVersionNumber/IncrementVersionNumber.ps1 @@ -16,7 +16,6 @@ Param( ) $telemetryScope = $null -$bcContainerHelperPath = $null try { . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) @@ -26,12 +25,12 @@ try { $branch = "increment-version-number/$updateBranch/$((Get-Date).ToUniversalTime().ToString(`"yyMMddHHmmss`"))" # e.g. increment-version-number/main/210101120000 } $serverUrl = CloneIntoNewFolder -actor $actor -token $token -branch $branch - $repoBaseFolder = (Get-Location).path - $BcContainerHelperPath = DownloadAndImportBcContainerHelper -baseFolder $repoBaseFolder + $baseFolder = (Get-Location).path + DownloadAndImportBcContainerHelper -baseFolder $baseFolder import-module (Join-Path -path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) $telemetryScope = CreateScope -eventId 'DO0076' -parentTelemetryScopeJson $parentTelemetryScopeJson - + $addToVersionNumber = "$versionnumber".StartsWith('+') if ($addToVersionNumber) { $versionnumber = $versionnumber.Substring(1) @@ -46,7 +45,7 @@ try { if (!$project) { $project = '*' } if ($project -ne '.') { - $projects = @(Get-ChildItem -Path $repoBaseFolder -Directory -Recurse -Depth 2 | Where-Object { Test-Path (Join-Path $_.FullName ".AL-Go/settings.json") -PathType Leaf } | ForEach-Object { $_.FullName.Substring($repoBaseFolder.length+1) } | Where-Object { $_ -like $project }) + $projects = @(Get-ChildItem -Path $baseFolder -Directory -Recurse -Depth 2 | Where-Object { Test-Path (Join-Path $_.FullName ".AL-Go/settings.json") -PathType Leaf } | ForEach-Object { $_.FullName.Substring($baseFolder.length+1) } | Where-Object { $_ -like $project }) if ($projects.Count -eq 0) { if ($project -eq '*') { $projects = @( '.' ) @@ -130,9 +129,8 @@ try { TrackTrace -telemetryScope $telemetryScope } catch { - TrackException -telemetryScope $telemetryScope -errorRecord $_ + if (Get-Module BcContainerHelper) { + TrackException -telemetryScope $telemetryScope -errorRecord $_ + } throw } -finally { - CleanupAfterBcContainerHelper -bcContainerHelperPath $bcContainerHelperPath -} diff --git a/PipelineCleanup/PipelineCleanup.ps1 b/PipelineCleanup/PipelineCleanup.ps1 index f1ec946..7eeca36 100644 --- a/PipelineCleanup/PipelineCleanup.ps1 +++ b/PipelineCleanup/PipelineCleanup.ps1 @@ -6,15 +6,14 @@ Param( ) $telemetryScope = $null -$bcContainerHelperPath = $null try { . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) - $BcContainerHelperPath = DownloadAndImportBcContainerHelper -baseFolder $ENV:GITHUB_WORKSPACE + DownloadAndImportBcContainerHelper import-module (Join-Path -path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) $telemetryScope = CreateScope -eventId 'DO0077' -parentTelemetryScopeJson $parentTelemetryScopeJson - + if ($project -eq ".") { $project = "" } $containerName = GetContainerName($project) @@ -23,9 +22,8 @@ try { TrackTrace -telemetryScope $telemetryScope } catch { - TrackException -telemetryScope $telemetryScope -errorRecord $_ + if (Get-Module BcContainerHelper) { + TrackException -telemetryScope $telemetryScope -errorRecord $_ + } throw } -finally { - CleanupAfterBcContainerHelper -bcContainerHelperPath $bcContainerHelperPath -} diff --git a/PullRequestStatusCheck/PullRequestStatusCheck.ps1 b/PullRequestStatusCheck/PullRequestStatusCheck.ps1 new file mode 100644 index 0000000..a5a77e0 --- /dev/null +++ b/PullRequestStatusCheck/PullRequestStatusCheck.ps1 @@ -0,0 +1,20 @@ +function PullRequestStatusCheck() +{ + param( + [Parameter(HelpMessage = "Repository name", Mandatory = $true)] + [string] $Repository, + [Parameter(HelpMessage = "Run Id", Mandatory = $true)] + [string] $RunId + ) + 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 + $failedJobs = $workflowJobs.jobs | Where-Object { $_.conclusion -eq "failure" } + + if ($failedJobs) { + throw "PR Build failed. Failing jobs: $($failedJobs.name -join ', ')" + } +} + +PullRequestStatusCheck -Repository $env:GITHUB_REPOSITORY -RunId $env:GITHUB_RUN_ID +Write-Host "PR Build succeeded" diff --git a/PullRequestStatusCheck/README.md b/PullRequestStatusCheck/README.md new file mode 100644 index 0000000..0da5d82 --- /dev/null +++ b/PullRequestStatusCheck/README.md @@ -0,0 +1,17 @@ +# 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 new file mode 100644 index 0000000..e579d73 --- /dev/null +++ b/PullRequestStatusCheck/action.yaml @@ -0,0 +1,24 @@ +name: Pull Request Status Check +author: Microsoft Corporation +inputs: + shell: + description: Shell in which you want to run the action (powershell or pwsh) + required: false + default: powershell +runs: + using: composite + steps: + - name: run + shell: ${{ inputs.shell }} + run: | + $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + try { + ${{ 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 be7f4d1..c2d11a4 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,8 @@ We do not accept Pull Requests on the Actions repository directly. ## Trademarks -This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft -trademarks or logos is subject to and must follow +This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft +trademarks or logos is subject to and must follow [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies. diff --git a/ReadSecrets/README.md b/ReadSecrets/README.md index 20d54f8..b74771d 100644 --- a/ReadSecrets/README.md +++ b/ReadSecrets/README.md @@ -2,6 +2,8 @@ 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 ## INPUT @@ -15,14 +17,16 @@ Additionally, the secrets specified by the authToken secret in AppDependencyProb | :-- | :-: | :-- | :-- | | 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 | {} | -| getSecrets | Yes | Comma separated list of secrets to get (add appDependencyProbingPathsSecrets to request secrets needed for resolving dependencies in AppDependencyProbingPaths) | | +| getSecrets | Yes | Comma separated list of secrets to get (add appDependencyProbingPathsSecrets to request secrets needed for resolving dependencies in AppDependencyProbingPaths, add TokenForPush in order to request a token to use for pull requests and commits). Secrets preceded by an asterisk are returned encrypted | | +| useGhTokenWorkflowForPush | false | Determines whether you want to use the GhTokenWorkflow secret for TokenForPush | ## OUTPUT ### ENV variables -| Name | Description | -| :-- | :-- | -| Secrets | A compressed json construct with all secrets base64 encoded. The secret value + the base64 value of the secret value are masked in the log | +none ### OUTPUT variables -none +| 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 | +| 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 464e7e7..97303a6 100644 --- a/ReadSecrets/ReadSecrets.ps1 +++ b/ReadSecrets/ReadSecrets.ps1 @@ -1,15 +1,12 @@ Param( [Parameter(HelpMessage = "All GitHub Secrets in compressed JSON format", Mandatory = $true)] [string] $gitHubSecrets = "", - [Parameter(HelpMessage = "Comma separated list of Secrets to get", Mandatory = $true)] + [Parameter(HelpMessage = "Comma separated list of Secrets to get. Secrets preceded by an asterisk are returned encrypted", Mandatory = $true)] [string] $getSecrets = "", - [Parameter(HelpMessage = "Specifies the parent telemetry scope for the telemetry signal", Mandatory = $false)] - [string] $parentTelemetryScopeJson = '7b7d' + [Parameter(HelpMessage = "Determines whether you want to use the GhTokenWorkflow secret for TokenForPush", Mandatory = $false)] + [string] $useGhTokenWorkflowForPush = 'false' ) -$telemetryScope = $null -$bcContainerHelperPath = $null - $buildMutexName = "AL-Go-ReadSecrets" $buildMutex = New-Object System.Threading.Mutex($false, $buildMutexName) try { @@ -25,113 +22,132 @@ try { } . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) - $BcContainerHelperPath = DownloadAndImportBcContainerHelper -baseFolder $ENV:GITHUB_WORKSPACE - - Import-Module (Join-Path -path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) - $telemetryScope = CreateScope -eventId 'DO0078' -parentTelemetryScopeJson $parentTelemetryScopeJson - Import-Module (Join-Path $PSScriptRoot ".\ReadSecretsHelper.psm1") -ArgumentList $gitHubSecrets $outSecrets = [ordered]@{} $settings = $env:Settings | ConvertFrom-Json | ConvertTo-HashTable - $keyVaultName = "" - if (IsKeyVaultSet -and $settings.ContainsKey('keyVaultName')) { - $keyVaultName = $settings.keyVaultName - if ([string]::IsNullOrEmpty($keyVaultName)) { - $credentialsJson = Get-KeyVaultCredentials | ConvertTo-HashTable - if ($credentialsJson.Keys -contains "keyVaultName") { - $keyVaultName = $credentialsJson.keyVaultName - } - } - } + $keyVaultCredentials = GetKeyVaultCredentials $getAppDependencyProbingPathsSecrets = $false + $getTokenForPush = $false [System.Collections.ArrayList]$secretsCollection = @() - $getSecrets.Split(',') | Select-Object -Unique | ForEach-Object { - $secret = $_ - $secretNameProperty = "$($secret)SecretName" + foreach($secret in ($getSecrets.Split(',') | Select-Object -Unique)) { + if ($secret -eq 'TokenForPush') { + $getTokenForPush = $true + if ($useGhTokenWorkflowForPush -ne 'true') { continue } + # If we are using the ghTokenWorkflow for commits, we need to get ghTokenWorkflow secret + $secret = 'ghTokenWorkflow' + } + $secretNameProperty = "$($secret.TrimStart('*'))SecretName" if ($secret -eq 'AppDependencyProbingPathsSecrets') { $getAppDependencyProbingPathsSecrets = $true } else { + $secretName = $secret if ($settings.Keys -contains $secretNameProperty) { - $secret = "$($secret)=$($settings."$secretNameProperty")" + $secretName = $settings."$secretNameProperty" + } + # Secret is the AL-Go name of the secret + # SecretName is the actual name of the secret to get from the KeyVault or GitHub environment + if ($secretName) { + if ($secretName -ne $secret) { + # Setup mapping between AL-Go secret name and actual secret name + $secret = "$($secret)=$secretName" + } + if ($secretsCollection -notcontains $secret) { + # Add secret to the collection of secrets to get + $secretsCollection += $secret + } } - $secretsCollection += $secret } } # Loop through appDependencyProbingPaths and add secrets to the collection of secrets to get if ($getAppDependencyProbingPathsSecrets -and $settings.Keys -contains 'appDependencyProbingPaths') { - $settings.appDependencyProbingPaths | ForEach-Object { - if ($_.PsObject.Properties.name -eq "AuthTokenSecret") { - $secretsCollection += $_.authTokenSecret + foreach($appDependencyProbingPath in $settings.appDependencyProbingPaths) { + if ($appDependencyProbingPath.PsObject.Properties.name -eq "AuthTokenSecret") { + if ($secretsCollection -notcontains $appDependencyProbingPath.authTokenSecret) { + $secretsCollection += $appDependencyProbingPath.authTokenSecret + } } } } - @($secretsCollection) | ForEach-Object { - $secretSplit = $_.Split('=') - $envVar = $secretSplit[0] - $secret = $envVar + # Loop through secrets (use @() to allow us to remove items from the collection while looping) + foreach($secret in @($secretsCollection)) { + $secretSplit = $secret.Split('=') + $secretsProperty = $secretSplit[0] + # Secret names preceded by an asterisk are returned encrypted (and base64 encoded) + $secretsPropertyName = $secretsProperty.TrimStart('*') + $encrypted = $secretsProperty.StartsWith('*') + $secretName = $secretsPropertyName if ($secretSplit.Count -gt 1) { - $secret = $secretSplit[1] + $secretName = $secretSplit[1] } - if ($secret) { - $value = GetSecret -secret $secret -keyVaultName $keyVaultName - if ($value) { - $json = @{} + if ($secretName) { + $secretValue = GetSecret -secret $secretName -keyVaultCredentials $keyVaultCredentials -encrypted:$encrypted + if ($secretValue) { try { - $json = $value | ConvertFrom-Json | ConvertTo-HashTable + $json = $secretValue | ConvertFrom-Json | ConvertTo-HashTable } catch { + $json = @{} } if ($json.Keys.Count) { - if ($value.contains("`n")) { - throw "JSON Secret $secret contains line breaks. JSON Secrets should be compressed JSON (i.e. NOT contain any line breaks)." + if ($secretValue.contains("`n")) { + throw "JSON Secret $secretName contains line breaks. JSON Secrets should be compressed JSON (i.e. NOT contain any line breaks)." } - $json.Keys | ForEach-Object { - if (@("Scopes","TenantId","BlobName","ContainerName","StorageAccountName") -notcontains $_) { + foreach($keyName in $json.Keys) { + if (@("Scopes","TenantId","BlobName","ContainerName","StorageAccountName") -notcontains $keyName) { # Mask individual values (but not Scopes, TenantId, BlobName, ContainerName and StorageAccountName) - MaskValue -key "$($secret).$($_)" -value $json."$_" + MaskValue -key "$($secretName).$($keyName)" -value $json."$keyName" } } } - $base64value = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($value)) - $outSecrets += @{ "$envVar" = $base64value } - Write-Host "$envVar successfully read from secret $secret" - $secretsCollection.Remove($_) + $base64value = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($secretValue)) + $outSecrets += @{ "$secretsProperty" = $base64value } + Write-Host "$($secretsPropertyName) successfully read from secret $secretName" + $secretsCollection.Remove($secret) } } } if ($secretsCollection) { - Write-Host "The following secrets was not found: $(($secretsCollection | ForEach-Object { + $unresolvedSecrets = ($secretsCollection | ForEach-Object { $secretSplit = @($_.Split('=')) - if ($secretSplit.Count -eq 1) { - $secretSplit[0] + $secretsProperty = $secretSplit[0] + # Secret names preceded by an asterisk are returned encrypted (and base64 encoded) + $secretsPropertyName = $secretsProperty.TrimStart('*') + if ($secretSplit.Count -eq 1 -or ($secretSplit[1] -eq '')) { + $secretsPropertyName } else { - "$($secretSplit[0]) (Secret $($secretSplit[1]))" + "$($secretsPropertyName) (Secret $($secretSplit[1]))" } - $outSecrets += @{ ""$($secretSplit[0])"" = """" } - }) -join ', ')" + $outSecrets += @{ "$secretsProperty" = "" } + }) -join ', ' + Write-Host "The following secrets was not found: $unresolvedSecrets" } #region Action: Output $outSecretsJson = $outSecrets | ConvertTo-Json -Compress - Add-Content -Encoding UTF8 -Path $env:GITHUB_ENV -Value "Secrets=$outSecretsJson" + Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "Secrets=$outSecretsJson" - #endregion + if ($getTokenForPush) { + if ($useGhTokenWorkflowForPush -eq 'true' -and $outSecrets.ghTokenWorkflow) { + Write-Host "Use ghTokenWorkflow for Push" + $ghToken = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($outSecrets.ghTokenWorkflow)) + } + else { + Write-Host "Use github_token for Push" + $ghToken = GetGithubSecret -SecretName 'github_token' + } + Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "TokenForPush=$ghToken" + } - TrackTrace -telemetryScope $telemetryScope -} -catch { - TrackException -telemetryScope $telemetryScope -errorRecord $_ - throw + #endregion } finally { - CleanupAfterBcContainerHelper -bcContainerHelperPath $bcContainerHelperPath $buildMutex.ReleaseMutex() } diff --git a/ReadSecrets/ReadSecretsHelper.psm1 b/ReadSecrets/ReadSecretsHelper.psm1 index 120eb3a..2dc9dec 100644 --- a/ReadSecrets/ReadSecretsHelper.psm1 +++ b/ReadSecrets/ReadSecretsHelper.psm1 @@ -1,3 +1,4 @@ +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'GitHub Secrets come in as plain text')] Param( [string] $_gitHubSecrets ) @@ -40,7 +41,8 @@ function MaskValue { function GetGithubSecret { param ( - [string] $secretName + [string] $secretName, + [switch] $encrypted ) $secretSplit = $secretName.Split('=') $envVar = $secretSplit[0] @@ -48,46 +50,65 @@ function GetGithubSecret { if ($secretSplit.Count -gt 1) { $secret = $secretSplit[1] } - + if ($script:gitHubSecrets.PSObject.Properties.Name -eq $secret) { $value = $script:githubSecrets."$secret" if ($value) { - MaskValue -key $secret -value $value - return $value + MaskValue -key $envVar -value $value + if ($encrypted) { + # Return encrypted string + return (ConvertTo-SecureString -String $value -AsPlainText -Force | ConvertFrom-SecureString) + } + else { + # Return decrypted string + return $value + } } } return $null } - -function Get-KeyVaultCredentials { + +function GetKeyVaultCredentials { + $creds = $null if ($script:isKeyvaultSet) { - $jsonStr = $script:gitHuBSecrets.AZURE_CREDENTIALS + $jsonStr = $script:gitHubSecrets.AZURE_CREDENTIALS if ($jsonStr -contains "`n" -or $jsonStr -contains "`r") { - throw "Secret AZURE_CREDENTIALS cannot contain line breaks" + throw "Secret AZURE_CREDENTIALS 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 # Check thet $creds contains the needed properties $creds.ClientId | Out-Null $creds.subscriptionId | Out-Null $creds.TenantId | Out-Null - return $creds } 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." } + $keyVaultNameExists = $creds.PSObject.Properties.Name -eq 'keyVaultName' + $settings = $env:Settings | ConvertFrom-Json + # If KeyVaultName is defined in settings, use that value + if ($settings.keyVaultName) { + if ($keyVaultNameExists) { + $creds.keyVaultName = $settings.keyVaultName + } + else { + $creds | Add-Member -MemberType NoteProperty -Name 'keyVaultName' -Value $settings.keyVaultName + } + } + elseif (!($keyVaultNameExists)) { + # If KeyVaultName is not defined - return null (i.e. do not use a KeyVault) + $creds = $null + } } - throw "Secret AZURE_CREDENTIALS is missing. In order to use a Keyvault, please add a secret called AZURE_CREDENTIALS like explained here: https://go.microsoft.com/fwlink/?linkid=2217318&clcid=0x409 (remember to format the json string as compressed json, i.e. no line breaks)" + return $creds } function InstallKeyVaultModuleIfNeeded { - if (-not $script:isKeyvaultSet) { - return - } - if ($isWindows) { $azModulesPath = Get-ChildItem 'C:\Modules\az_*' | Where-Object { $_.PSIsContainer } if ($azModulesPath) { @@ -121,7 +142,7 @@ function InstallKeyVaultModuleIfNeeded { Disable-AzureRmDataCollection -WarningAction SilentlyContinue } else { - Write-Host "Installing and importing Az.KeyVault." + Write-Host "Installing and importing Az.KeyVault." Install-Module 'Az.KeyVault' -Force Import-Module 'Az.KeyVault' -DisableNameChecking -WarningAction SilentlyContinue | Out-Null } @@ -129,19 +150,15 @@ function InstallKeyVaultModuleIfNeeded { } } -function ConnectAzureKeyVaultIfNeeded { - param( +function ConnectAzureKeyVault { + param( [string] $subscriptionId, [string] $tenantId, - [string] $clientId , - [string] $clientSecret + [string] $clientId, + [SecureString] $clientSecret ) try { - if ($script:keyvaultConnectionExists) { - return - } - - $credential = New-Object PSCredential -argumentList $clientId, (ConvertTo-SecureString $clientSecret -AsPlainText -Force) + $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 @@ -163,60 +180,71 @@ function ConnectAzureKeyVaultIfNeeded { function GetKeyVaultSecret { param ( [string] $secretName, - [string] $keyVaultName + [PsCustomObject] $keyVaultCredentials, + [switch] $encrypted ) if (-not $script:isKeyvaultSet) { return $null } - + if (-not $script:keyvaultConnectionExists) { - InstallKeyVaultModuleIfNeeded - - $credentialsJson = Get-KeyVaultCredentials - ConnectAzureKeyVaultIfNeeded -subscriptionId $credentialsJson.subscriptionId -tenantId $credentialsJson.tenantId -clientId $credentialsJson.clientId -clientSecret $credentialsJson.clientSecret + ConnectAzureKeyVault -subscriptionId $keyVaultCredentials.subscriptionId -tenantId $keyVaultCredentials.tenantId -clientId $keyVaultCredentials.clientId -clientSecret $keyVaultCredentials.clientSecret } - $value = "" + $secretSplit = $secretName.Split('=') + $envVar = $secretSplit[0] + $secret = $envVar + if ($secretSplit.Count -gt 1) { + $secret = $secretSplit[1] + } + + $value = $null if ($script:azureRm210) { - $keyVaultSecret = Get-AzureKeyVaultSecret -VaultName $keyVaultName -Name $secret -ErrorAction SilentlyContinue + $keyVaultSecret = Get-AzureKeyVaultSecret -VaultName $keyVaultCredentials.keyVaultName -Name $secret -ErrorAction SilentlyContinue } else { - $keyVaultSecret = Get-AzKeyVaultSecret -VaultName $keyVaultName -Name $secret -ErrorAction SilentlyContinue + $keyVaultSecret = Get-AzKeyVaultSecret -VaultName $keyVaultCredentials.keyVaultName -Name $secret -ErrorAction SilentlyContinue } - if ($keyVaultSecret) { - $value = [Runtime.InteropServices.Marshal]::PtrToStringBSTR(([Runtime.InteropServices.Marshal]::SecureStringToBSTR($keyVaultSecret.SecretValue))) - MaskValue -key $secret -value $value - return $value + if ($encrypted) { + # Return encrypted string + $value = $keyVaultSecret.SecretValue | ConvertFrom-SecureString + } + else { + # Return decrypted string + $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($keyVaultSecret.SecretValue) + $value = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr) + [Runtime.InteropServices.Marshal]::FreeBSTR($bstr) + MaskValue -key $envVar -value $value + } } - - return $null + return $value } function GetSecret { param ( [string] $secret, - [string] $keyVaultName + [PSCustomObject] $keyVaultCredentials, + [switch] $encrypted ) - Write-Host "Trying to get the secret($secret) from the github environment." - $value = GetGithubSecret -secretName $secret + Write-Host "Trying to get the secret ($secret) from the github environment." + $value = GetGithubSecret -secretName $secret -encrypted:$encrypted if ($value) { - Write-Host "Secret($secret) was retrieved from the github environment." - return $value + Write-Host "Secret ($secret) was retrieved from the github environment." } - - if ($keyVaultName) { - Write-Host "Trying to get the secret($secret) from Key Vault ($keyVaultName)." - $value = GetKeyVaultSecret -secretName $secret -keyVaultName $keyVaultName + elseif ($keyVaultCredentials) { + Write-Host "Trying to get the secret ($secret) from Key Vault ($($keyVaultCredentials.keyVaultName))." + $value = GetKeyVaultSecret -secretName $secret -keyVaultCredentials $keyVaultCredentials -encrypted:$encrypted if ($value) { - Write-Host "Secret($secret) was retrieved from the Key Vault." - return $value + Write-Host "Secret ($secret) was retrieved from the Key Vault." } } - - Write-Host "Could not find secret $secret in Github secrets or Azure Key Vault." - return $null + else { + Write-Host "Could not find secret $secret in Github secrets or Azure Key Vault." + $value = $null + } + return $value } diff --git a/ReadSecrets/action.yaml b/ReadSecrets/action.yaml index 30c53b3..fd6090a 100644 --- a/ReadSecrets/action.yaml +++ b/ReadSecrets/action.yaml @@ -9,24 +9,32 @@ inputs: description: All GitHub Secrets in compressed JSON format required: true getSecrets: - description: Comma separated list of Secrets to get + description: Comma separated list of Secrets to get. Secrets preceded by an asterisk are returned encrypted required: true - parentTelemetryScopeJson: - description: Specifies the parent telemetry scope for the telemetry signal + useGhTokenWorkflowForPush: + description: Determines whether you want to use the GhTokenWorkflow secret for TokenForPush required: false - default: '7b7d' + default: 'false' +outputs: + Secrets: + description: All requested secrets in compressed JSON format + value: ${{ steps.ReadSecrets.outputs.Secrets }} + TokenForPush: + description: The token to use when workflows are pushing changes (either directly, or via pull requests). + value: ${{ steps.ReadSecrets.outputs.TokenForPush }} runs: using: composite steps: - name: run shell: ${{ inputs.shell }} + id: ReadSecrets env: _getSecrets: ${{ inputs.getSecrets }} - _parentTelemetryScopeJson: ${{ inputs.parentTelemetryScopeJson }} + _useGhTokenWorkflowForPush: ${{ inputs.useGhTokenWorkflowForPush }} run: | $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 try { - ${{ github.action_path }}/ReadSecrets.ps1 -gitHubSecrets '${{ inputs.gitHubSecrets }}' -getSecrets $ENV:_getSecrets -parentTelemetryScopeJson $ENV:_parentTelemetryScopeJson + ${{ 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",' <- '))"; diff --git a/ReadSettings/README.md b/ReadSettings/README.md index 6657413..0c08e47 100644 --- a/ReadSettings/README.md +++ b/ReadSettings/README.md @@ -10,13 +10,7 @@ none | 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 | . | -| getenvironments | | Specifies the pattern of the environments you want to retreive (or empty for no environments) | | -| includeProduction | | Specifies whether you want to include production environments | N | -| release | | Indicates whether this is called from a release pipeline | N | | get | | Specifies which properties to get from the settings file, default is all | | ## OUTPUT @@ -34,6 +28,3 @@ none | :-- | :-- | | GitHubRunnerJson | GitHubRunner in compressed Json format | | GitHubRunnerShell | Shell for GitHubRunner jobs | -| EnvironmentsJson | Environments in compressed Json format | -| EnvironmentCount | Number of environments in array | -| UnknownEnvironment | Determines whether we are publishing to an unknown environment | diff --git a/ReadSettings/ReadSettings.ps1 b/ReadSettings/ReadSettings.ps1 index 36a644b..d8826f7 100644 --- a/ReadSettings/ReadSettings.ps1 +++ b/ReadSettings/ReadSettings.ps1 @@ -1,206 +1,79 @@ Param( - [Parameter(HelpMessage = "The GitHub actor running the action", Mandatory = $false)] - [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 folder", Mandatory = $false)] [string] $project = ".", - [Parameter(HelpMessage = "Specifies the pattern of the environments you want to retreive (or empty for no environments)", Mandatory = $false)] - [string] $getenvironments = "", - [Parameter(HelpMessage = "Specifies whether you want to include production environments", Mandatory = $false)] - [bool] $includeProduction, - [Parameter(HelpMessage = "Indicates whether this is called from a release pipeline", Mandatory = $false)] - [bool] $release, [Parameter(HelpMessage = "Specifies which properties to get from the settings file, default is all", Mandatory = $false)] [string] $get = "" ) -$telemetryScope = $null -$bcContainerHelperPath = $null +. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) -try { - $baseFolder = $ENV:GITHUB_WORKSPACE - . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) - $BcContainerHelperPath = DownloadAndImportBcContainerHelper -baseFolder $baseFolder - - import-module (Join-Path -Path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) - $telemetryScope = CreateScope -eventId 'DO0079' -parentTelemetryScopeJson $parentTelemetryScopeJson - - $settings = ReadSettings -baseFolder $baseFolder -project $project - if ($get) { - $getSettings = $get.Split(',').Trim() - } - else { - $getSettings = @() - } +$settings = ReadSettings -project $project +if ($get) { + $getSettings = $get.Split(',').Trim() +} +else { + $getSettings = @() +} - if ($ENV:GITHUB_EVENT_NAME -in @("pull_request_target", "pull_request")) { - $settings.doNotSignApps = $true - $settings.versioningStrategy = 15 - } +if ($ENV:GITHUB_EVENT_NAME -in @("pull_request_target", "pull_request")) { + $settings.doNotSignApps = $true + $settings.versioningStrategy = 15 +} - if ($settings.appBuild -eq [int32]::MaxValue) { - $settings.versioningStrategy = 15 - } +if ($settings.appBuild -eq [int32]::MaxValue) { + $settings.versioningStrategy = 15 +} - if ($settings.versioningstrategy -ne -1) { - switch ($settings.versioningStrategy -band 15) { - 0 { # Use RUN_NUMBER and RUN_ATTEMPT - $settings.appBuild = $settings.runNumberOffset + [Int32]($ENV:GITHUB_RUN_NUMBER) - $settings.appRevision = [Int32]($ENV:GITHUB_RUN_ATTEMPT) - 1 - } - 1 { # Use RUN_ID and RUN_ATTEMPT - OutputError -message "Versioning strategy 1 is no longer supported" - } - 2 { # USE DATETIME - $settings.appBuild = [Int32]([DateTime]::UtcNow.ToString('yyyyMMdd')) - $settings.appRevision = [Int32]([DateTime]::UtcNow.ToString('HHmmss')) - } - 15 { # Use maxValue - $settings.appBuild = [Int32]::MaxValue - $settings.appRevision = 0 - } - default { - OutputError -message "Unknown version strategy $versionStrategy" - exit - } +if ($settings.versioningstrategy -ne -1) { + switch ($settings.versioningStrategy -band 15) { + 0 { # Use RUN_NUMBER and RUN_ATTEMPT + $settings.appBuild = $settings.runNumberOffset + [Int32]($ENV:GITHUB_RUN_NUMBER) + $settings.appRevision = [Int32]($ENV:GITHUB_RUN_ATTEMPT) - 1 } - } - - $outSettings = @{} - $settings.Keys | ForEach-Object { - $setting = $_ - $settingValue = $settings."$setting" - $outSettings += @{ "$setting" = $settingValue } - if ($getSettings -contains $setting) { - if ($settingValue -is [System.Collections.Specialized.OrderedDictionary] -or $settingValue -is [hashtable]) { - Add-Content -Encoding UTF8 -Path $env:GITHUB_ENV -Value "$setting=$(ConvertTo-Json $settingValue -Depth 99 -Compress)" - } - elseif ($settingValue -is [String] -and ($settingValue.contains("`n") -or $settingValue.contains("`r"))) { - throw "Setting $setting contains line breaks, which is not supported" - } - else { - Add-Content -Encoding UTF8 -Path $env:GITHUB_ENV -Value "$setting=$settingValue" - } + 1 { # Use RUN_ID and RUN_ATTEMPT + OutputError -message "Versioning strategy 1 is no longer supported" } - } - - Write-Host "SETTINGS:" - $outSettings | ConvertTo-Json -Depth 99 | Out-Host - Add-Content -Encoding UTF8 -Path $env:GITHUB_ENV -Value "Settings=$($outSettings | ConvertTo-Json -Depth 99 -Compress)" - - $gitHubRunner = $settings.githubRunner.Split(',').Trim() | ConvertTo-Json -compress - Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "GitHubRunnerJson=$githubRunner" - Write-Host "GitHubRunnerJson=$githubRunner" - - $gitHubRunnerShell = $settings.githubRunnerShell - Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "GitHubRunnerShell=$githubRunnerShell" - Write-Host "GitHubRunnerShell=$githubRunnerShell" - - if ($getenvironments) { - $environments = @() - $headers = @{ - "Authorization" = "token $token" - "Accept" = "application/vnd.github.v3+json" - "X-GitHub-Api-Version" = "2022-11-28" + 2 { # USE DATETIME + $settings.appBuild = [Int32]([DateTime]::UtcNow.ToString('yyyyMMdd')) + $settings.appRevision = [Int32]([DateTime]::UtcNow.ToString('HHmmss')) } - Write-Host "Requesting environments: $getEnvironments" - $url = "$($ENV:GITHUB_API_URL)/repos/$($ENV:GITHUB_REPOSITORY)/environments" - try { - Write-Host "Trying to get environments from GitHub API" - $ghEnvironments = @((InvokeWebRequest -Headers $headers -Uri $url -ignoreErrors | ConvertFrom-Json).environments | Where-Object { $_.name -like $getEnvironments }) - } - catch { - $ghEnvironments = @() - Write-Host "Failed to get environments from GitHub API - Environments are not supported in this repository" + 15 { # Use maxValue + $settings.appBuild = [Int32]::MaxValue + $settings.appRevision = 0 } - Write-Host "Requesting environments from settings" - $environments = @(@($ghEnvironments | ForEach-Object { $_.name })+@($settings.environments) | Select-Object -unique | Where-Object { $_ -ne "github-pages" }) - $unknownEnvironment = 0 - if (!($environments)) { - $unknownEnvironment = 1 - # If no environments are defined and the user specified a single environment, use that environment - # This allows the user to specify a single environment without having to define it in the settings - if ($getenvironments -notcontains '*' -and $getenvironments -notcontains '?' -and $getenvironments -notcontains ',') { - $environments = @($getenvironments) - } + default { + OutputError -message "Unknown version strategy $versionStrategy" + exit } - else { - if ($environments) { - Write-Host "Environments found: $($environments -join ', ')" - } - $environments = @($environments | Where-Object { - if ($includeProduction) { - $_ -like $getEnvironments -or $_ -like "$getEnvironments (PROD)" -or $_ -like "$getEnvironments (Production)" -or $_ -like "$getEnvironments (FAT)" -or $_ -like "$getEnvironments (Final Acceptance Test)" - } - else { - $_ -like $getEnvironments -and $_ -notlike '* (PROD)' -and $_ -notlike '* (Production)' -and $_ -notlike '* (FAT)' -and $_ -notlike '* (Final Acceptance Test)' - } - } | Where-Object { - $envName = $_ - Write-Host "Environment: $envName" - $ghEnvironment = $ghEnvironments | Where-Object { $_.name -eq $envName } - if ($ghEnvironment) { - $branchPolicy = ($ghEnvironment.protection_rules | Where-Object { $_.type -eq "branch_policy" }) - if ($branchPolicy) { - Write-Host "GitHub Environment $envName has branch policies, getting branches from GitHub API" - $branchesUrl = "$($ENV:GITHUB_API_URL)/repos/$($ENV:GITHUB_REPOSITORY)/environments/$([Uri]::EscapeDataString($envName))/deployment-branch-policies" - Write-Host "Getting branches for $envName from GitHub API" - $branches = @((InvokeWebRequest -Headers $headers -Uri $branchesUrl -ignoreErrors | ConvertFrom-Json).branch_policies | ForEach-Object { $_.name }) - } - else { - Write-Host "GitHub Environment $envName does not have branch policies, using main as default" - $branches = @( 'main' ) - } - } - else { - Write-Host "Environment $envName was defined in settings, using main as default" - $branches = @( 'main' ) - } - $environmentName = $_.Split(' ')[0] - $deployToName = "DeployTo$environmentName" - if (($settings.Contains($deployToName)) -and ($settings."$deployToName".Contains('Branches'))) { - $branches = @($settings."$deployToName".Branches) - } - Write-Host "- branches: $($branches -join ', ')" - $includeEnvironment = $false - $branches | ForEach-Object { - if ($ENV:GITHUB_REF_NAME -like $_) { - $includeEnvironment = $true - } - } - Write-Host "- include: $includeEnvironment" - $includeEnvironment - }) + } +} + +$outSettings = @{} +$settings.Keys | ForEach-Object { + $setting = $_ + $settingValue = $settings."$setting" + if ($settingValue -is [String] -and ($settingValue.contains("`n") -or $settingValue.contains("`r"))) { + throw "Setting $setting contains line breaks, which is not supported" + } + $outSettings += @{ "$setting" = $settingValue } + if ($getSettings -contains $setting) { + if ($settingValue -is [System.Collections.Specialized.OrderedDictionary] -or $settingValue -is [hashtable]) { + Add-Content -Encoding UTF8 -Path $env:GITHUB_ENV -Value "$setting=$(ConvertTo-Json $settingValue -Depth 99 -Compress)" } - $json = @{"matrix" = @{ "include" = @() }; "fail-fast" = $false } - $environments | Select-Object -Unique | ForEach-Object { - $environmentName = $_.Split(' ')[0] - $deployToName = "DeployTo$environmentName" - $runson = $settings."runs-on".Split(',').Trim() - if (($settings.Contains($deployToName)) -and ($settings."$deployToName".Contains('runs-on'))) { - $runson = $settings."$deployToName"."runs-on" - } - $json.matrix.include += @{ "environment" = $_; "os" = "$($runson | ConvertTo-Json -compress)" } + else { + Add-Content -Encoding UTF8 -Path $env:GITHUB_ENV -Value "$setting=$settingValue" } - $environmentsJson = $json | ConvertTo-Json -Depth 99 -compress - Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "EnvironmentsJson=$environmentsJson" - Add-Content -Encoding UTF8 -Path $env:GITHUB_ENV -Value "environments=$environmentsJson" - Write-Host "EnvironmentsJson=$environmentsJson" - Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "EnvironmentCount=$($environments.Count)" - Write-Host "EnvironmentCount=$($environments.Count)" - Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "UnknownEnvironment=$unknownEnvironment" - Write-Host "UnknownEnvironment=$unknownEnvironment" } - - TrackTrace -telemetryScope $telemetryScope -} -catch { - TrackException -telemetryScope $telemetryScope -errorRecord $_ - throw -} -finally { - CleanupAfterBcContainerHelper -bcContainerHelperPath $bcContainerHelperPath } + +Write-Host "SETTINGS:" +$outSettings | ConvertTo-Json -Depth 99 | Out-Host +Add-Content -Encoding UTF8 -Path $env:GITHUB_ENV -Value "Settings=$($outSettings | ConvertTo-Json -Depth 99 -Compress)" + +$gitHubRunner = $settings.githubRunner.Split(',').Trim() | ConvertTo-Json -compress +Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "GitHubRunnerJson=$githubRunner" +Write-Host "GitHubRunnerJson=$githubRunner" + +$gitHubRunnerShell = $settings.githubRunnerShell +Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "GitHubRunnerShell=$githubRunnerShell" +Write-Host "GitHubRunnerShell=$githubRunnerShell" diff --git a/ReadSettings/action.yaml b/ReadSettings/action.yaml index f6c7519..6208c5c 100644 --- a/ReadSettings/action.yaml +++ b/ReadSettings/action.yaml @@ -5,34 +5,10 @@ inputs: description: Shell in which you want to run the action (powershell or pwsh) required: false default: powershell - actor: - description: The GitHub actor running the action - required: false - default: ${{ github.actor }} - token: - 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 folder required: false default: '.' - getenvironments: - description: Specifies the pattern of the environments you want to retreive (or empty for no environments) - required: false - default: '' - includeProduction: - description: Specifies whether you want to include production environments - required: false - default: 'N' - release: - description: Indicates whether this is called from a release pipeline - required: false - default: 'N' get: description: Specifies which properties to get from the settings file, default is all required: false @@ -44,15 +20,6 @@ outputs: GitHubRunnerShell: description: Shell for GitHubRunner jobs value: ${{ steps.readsettings.outputs.GitHubRunnerShell }} - EnvironmentsJson: - description: Environments in compressed Json format - value: ${{ steps.readsettings.outputs.EnvironmentsJson }} - EnvironmentCount: - description: Number of environments in array - value: ${{ steps.readsettings.outputs.EnvironmentCount }} - UnknownEnvironment: - description: Determines whether we are publishing to an unknown environment - value: ${{ steps.readsettings.outputs.UnknownEnvironment }} runs: using: composite steps: @@ -60,18 +27,12 @@ runs: shell: ${{ inputs.shell }} id: readsettings env: - _actor: ${{ inputs.actor }} - _token: ${{ inputs.token }} - _parentTelemetryScopeJson: ${{ inputs.parentTelemetryScopeJson }} _project: ${{ inputs.project }} - _getenvironments: ${{ inputs.getenvironments }} - _includeProduction: ${{ inputs.includeProduction }} - _release: ${{ inputs.release }} _get: ${{ inputs.get }} run: | $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 try { - ${{ github.action_path }}/ReadSettings.ps1 -actor $ENV:_actor -token $ENV:_token -parentTelemetryScopeJson $ENV:_parentTelemetryScopeJson -project $ENV:_project -getenvironments $ENV:_getenvironments -includeProduction ($ENV:_includeProduction -eq 'Y') -release ($ENV:_release -eq 'Y') -get $ENV:_get + ${{ 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",' <- '))"; diff --git a/RunPipeline/README.md b/RunPipeline/README.md index fa26d51..021ca3b 100644 --- a/RunPipeline/README.md +++ b/RunPipeline/README.md @@ -13,7 +13,6 @@ Run pipeline in AL-Go repository | 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 | {} | | artifact | | ArtifactUrl to use for the build | settings.artifact | diff --git a/RunPipeline/RunPipeline.ps1 b/RunPipeline/RunPipeline.ps1 index 98ec6fe..2cc10cb 100644 --- a/RunPipeline/RunPipeline.ps1 +++ b/RunPipeline/RunPipeline.ps1 @@ -1,6 +1,4 @@ Param( - [Parameter(HelpMessage = "The GitHub actor running the action", Mandatory = $false)] - [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)] @@ -19,13 +17,12 @@ Param( ) $telemetryScope = $null -$bcContainerHelperPath = $null $containerBaseFolder = $null $projectPath = $null try { . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) - $BcContainerHelperPath = DownloadAndImportBcContainerHelper -baseFolder $ENV:GITHUB_WORKSPACE + DownloadAndImportBcContainerHelper import-module (Join-Path -path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) $telemetryScope = CreateScope -eventId 'DO0080' -parentTelemetryScopeJson $parentTelemetryScopeJson @@ -34,10 +31,10 @@ try { # Pull docker image in the background $genericImageName = Get-BestGenericImageName Start-Job -ScriptBlock { - docker pull --quiet $genericImageName - } -ArgumentList $genericImageName | Out-Null + docker pull --quiet $using:genericImageName + } | Out-Null } - + $containerName = GetContainerName($project) $ap = "$ENV:GITHUB_ACTION_PATH".Split('\') @@ -80,10 +77,17 @@ try { Write-Host "use settings and secrets" $settings = $env:Settings | ConvertFrom-Json | ConvertTo-HashTable - $secrets = $env:Secrets | ConvertFrom-Json | ConvertTo-HashTable + # ENV:Secrets is not set when running Pull_Request trigger + if ($env:Secrets) { + $secrets = $env:Secrets | ConvertFrom-Json | ConvertTo-HashTable + } + else { + $secrets = @{} + } + $appBuild = $settings.appBuild $appRevision = $settings.appRevision - 'licenseFileUrl','insiderSasToken','codeSignCertificateUrl','codeSignCertificatePassword','keyVaultCertificateUrl','keyVaultCertificatePassword','keyVaultClientId','gitHubPackagesContext','applicationInsightsConnectionString' | ForEach-Object { + 'licenseFileUrl','insiderSasToken','codeSignCertificateUrl','*codeSignCertificatePassword','keyVaultCertificateUrl','*keyVaultCertificatePassword','keyVaultClientId','gitHubPackagesContext','applicationInsightsConnectionString' | ForEach-Object { # Secrets might not be read during Pull Request runs if ($secrets.Keys -contains $_) { $value = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets."$_")) @@ -91,7 +95,9 @@ try { else { $value = "" } - Set-Variable -Name $_ -Value $value + # Secrets preceded by an asterisk are returned encrypted. + # Variable name should not include the asterisk + Set-Variable -Name $_.TrimStart('*') -Value $value } $analyzeRepoParams = @{} @@ -116,53 +122,54 @@ try { } } - $repo = AnalyzeRepo -settings $settings -token $token -baseFolder $baseFolder -project $project -insiderSasToken $insiderSasToken @analyzeRepoParams + $settings = AnalyzeRepo -settings $settings -baseFolder $baseFolder -project $project -insiderSasToken $insiderSasToken @analyzeRepoParams + $settings = CheckAppDependencyProbingPaths -settings $settings -token $token -baseFolder $baseFolder -project $project - if ((-not $repo.appFolders) -and (-not $repo.testFolders) -and (-not $repo.bcptTestFolders)) { + if ((-not $settings.appFolders) -and (-not $settings.testFolders) -and (-not $settings.bcptTestFolders)) { Write-Host "Repository is empty, exiting" exit } - if ($repo.type -eq "AppSource App" ) { + if ($settings.type -eq "AppSource App" ) { if ($licenseFileUrl -eq "") { OutputWarning -message "When building an AppSource App, you should create a secret called LicenseFileUrl, containing a secure URL to your license file with permission to the objects used in the app." } } - $installApps = $repo.installApps - $installTestApps = $repo.installTestApps + $installApps = $settings.installApps + $installTestApps = $settings.installTestApps $installApps += $installAppsJson | ConvertFrom-Json $installTestApps += $installTestAppsJson | ConvertFrom-Json # Analyze app.json version dependencies before launching pipeline - # Analyze InstallApps and InstallTestApps before launching pipeline + # Analyze InstallApps and InstallTestApps before launching pipeline # Check if codeSignCertificateUrl+Password is used (and defined) - if (!$repo.doNotSignApps -and $codeSignCertificateUrl -and $codeSignCertificatePassword -and !$repo.keyVaultCodesignCertificateName) { + if (!$settings.doNotSignApps -and $codeSignCertificateUrl -and $codeSignCertificatePassword -and !$settings.keyVaultCodesignCertificateName) { OutputWarning -message "Using the legacy CodeSignCertificateUrl and CodeSignCertificatePassword parameters. Consider using the new Azure Keyvault signing instead. Go to https://aka.ms/ALGoSettings#keyVaultCodesignCertificateName to find out more" - $runAlPipelineParams += @{ + $runAlPipelineParams += @{ "CodeSignCertPfxFile" = $codeSignCertificateUrl - "CodeSignCertPfxPassword" = ConvertTo-SecureString -string $codeSignCertificatePassword -AsPlainText -Force + "CodeSignCertPfxPassword" = ConvertTo-SecureString -string $codeSignCertificatePassword } } if ($applicationInsightsConnectionString) { - $runAlPipelineParams += @{ + $runAlPipelineParams += @{ "applicationInsightsConnectionString" = $applicationInsightsConnectionString } } if ($keyVaultCertificateUrl -and $keyVaultCertificatePassword -and $keyVaultClientId) { - $runAlPipelineParams += @{ + $runAlPipelineParams += @{ "KeyVaultCertPfxFile" = $keyVaultCertificateUrl - "keyVaultCertPfxPassword" = ConvertTo-SecureString -string $keyVaultCertificatePassword -AsPlainText -Force + "keyVaultCertPfxPassword" = ConvertTo-SecureString -string $keyVaultCertificatePassword "keyVaultClientId" = $keyVaultClientId } } $previousApps = @() - if (!$repo.skipUpgrade) { + if (!$settings.skipUpgrade) { Write-Host "::group::Locating previous release" try { $latestRelease = GetLatestRelease -token $token -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -ref $ENV:GITHUB_REF_NAME @@ -184,14 +191,14 @@ try { Write-Host "::endgroup::" } - $additionalCountries = $repo.additionalCountries + $additionalCountries = $settings.additionalCountries $imageName = "" if (-not $gitHubHostedRunner) { - $imageName = $repo.cacheImageName + $imageName = $settings.cacheImageName if ($imageName) { Write-Host "::group::Flush ContainerHelper Cache" - Flush-ContainerHelperCache -cache 'all,exitedcontainers' -keepdays $repo.cacheKeepDays + Flush-ContainerHelperCache -cache 'all,exitedcontainers' -keepdays $settings.cacheKeepDays Write-Host "::endgroup::" } } @@ -199,20 +206,20 @@ try { $environmentName = "" $CreateRuntimePackages = $false - if ($repo.versioningStrategy -eq -1) { - $artifactVersion = [Version]$repo.artifact.Split('/')[4] + if ($settings.versioningStrategy -eq -1) { + $artifactVersion = [Version]$settings.artifact.Split('/')[4] $runAlPipelineParams += @{ "appVersion" = "$($artifactVersion.Major).$($artifactVersion.Minor)" } $appBuild = $artifactVersion.Build $appRevision = $artifactVersion.Revision } - elseif (($repo.versioningStrategy -band 16) -eq 16) { + elseif (($settings.versioningStrategy -band 16) -eq 16) { $runAlPipelineParams += @{ - "appVersion" = $repo.repoVersion + "appVersion" = $settings.repoVersion } } - + $buildArtifactFolder = Join-Path $projectPath ".buildartifacts" New-Item $buildArtifactFolder -ItemType Directory | Out-Null @@ -251,12 +258,12 @@ try { } if ($runAlPipelineParams.Keys -notcontains 'ImportTestDataInBcContainer') { - if (($repo.configPackages) -or ($repo.Keys | Where-Object { $_ -like 'configPackages.*' })) { + if (($settings.configPackages) -or ($settings.Keys | Where-Object { $_ -like 'configPackages.*' })) { Write-Host "Adding Import Test Data override" Write-Host "Configured config packages:" - $repo.Keys | Where-Object { $_ -like 'configPackages*' } | ForEach-Object { + $settings.Keys | Where-Object { $_ -like 'configPackages*' } | ForEach-Object { Write-Host "- $($_):" - $repo."$_" | ForEach-Object { + $settings."$_" | ForEach-Object { Write-Host " - $_" } } @@ -265,16 +272,17 @@ try { Param([Hashtable]$parameters) $country = Get-BcContainerCountry -containerOrImageName $parameters.containerName $prop = "configPackages.$country" - if ($repo.Keys -notcontains $prop) { + if ($settings.Keys -notcontains $prop) { $prop = "configPackages" } - if ($repo."$prop") { + if ($settings."$prop") { Write-Host "Importing config packages from $prop" - $repo."$prop" | ForEach-Object { + $settings."$prop" | ForEach-Object { $configPackage = $_.Split(',')[0].Replace('{COUNTRY}',$country) $packageId = $_.Split(',')[1] UploadImportAndApply-ConfigPackageInBcContainer ` -containerName $parameters.containerName ` + -companyName $settings.companyName ` -Credential $parameters.credential ` -Tenant $parameters.tenant ` -ConfigPackage $configPackage ` @@ -333,12 +341,12 @@ try { "enableAppSourceCop", "enablePerTenantExtensionCop", "enableUICop" | ForEach-Object { - if ($repo."$_") { $runAlPipelineParams += @{ "$_" = $true } } + if ($settings."$_") { $runAlPipelineParams += @{ "$_" = $true } } } switch($buildMode){ 'Clean' { - $preprocessorsymbols = $repo.cleanModePreprocessorSymbols + $preprocessorsymbols = $settings.cleanModePreprocessorSymbols if (!$preprocessorsymbols) { throw "No cleanModePreprocessorSymbols defined in settings.json for this project. Please add the preprocessor symbols to use when building in clean mode or disable CLEAN mode." @@ -366,34 +374,34 @@ try { -imageName $imageName ` -bcAuthContext $authContext ` -environment $environmentName ` - -artifact $repo.artifact.replace('{INSIDERSASTOKEN}',$insiderSasToken) ` - -vsixFile $repo.vsixFile ` - -companyName $repo.companyName ` - -memoryLimit $repo.memoryLimit ` + -artifact $settings.artifact.replace('{INSIDERSASTOKEN}',$insiderSasToken) ` + -vsixFile $settings.vsixFile ` + -companyName $settings.companyName ` + -memoryLimit $settings.memoryLimit ` -baseFolder $projectPath ` -sharedFolder $sharedFolder ` -licenseFile $licenseFileUrl ` -installApps $installApps ` -installTestApps $installTestApps ` - -installOnlyReferencedApps:$repo.installOnlyReferencedApps ` - -generateDependencyArtifact:$repo.generateDependencyArtifact ` - -updateDependencies:$repo.updateDependencies ` + -installOnlyReferencedApps:$settings.installOnlyReferencedApps ` + -generateDependencyArtifact:$settings.generateDependencyArtifact ` + -updateDependencies:$settings.updateDependencies ` -previousApps $previousApps ` - -appFolders $repo.appFolders ` - -testFolders $repo.testFolders ` - -bcptTestFolders $repo.bcptTestFolders ` + -appFolders $settings.appFolders ` + -testFolders $settings.testFolders ` + -bcptTestFolders $settings.bcptTestFolders ` -buildOutputFile $buildOutputFile ` -containerEventLogFile $containerEventLogFile ` -testResultsFile $testResultsFile ` -testResultsFormat 'JUnit' ` - -customCodeCops $repo.customCodeCops ` + -customCodeCops $settings.customCodeCops ` -gitHubActions ` - -failOn $repo.failOn ` - -treatTestFailuresAsWarnings:$repo.treatTestFailuresAsWarnings ` - -rulesetFile $repo.rulesetFile ` - -appSourceCopMandatoryAffixes $repo.appSourceCopMandatoryAffixes ` + -failOn $settings.failOn ` + -treatTestFailuresAsWarnings:$settings.treatTestFailuresAsWarnings ` + -rulesetFile $settings.rulesetFile ` + -appSourceCopMandatoryAffixes $settings.appSourceCopMandatoryAffixes ` -additionalCountries $additionalCountries ` - -obsoleteTagMinAllowedMajorMinor $repo.obsoleteTagMinAllowedMajorMinor ` + -obsoleteTagMinAllowedMajorMinor $settings.obsoleteTagMinAllowedMajorMinor ` -buildArtifactFolder $buildArtifactFolder ` -CreateRuntimePackages:$CreateRuntimePackages ` -appBuild $appBuild -appRevision $appRevision ` @@ -414,7 +422,9 @@ try { TrackTrace -telemetryScope $telemetryScope } catch { - TrackException -telemetryScope $telemetryScope -errorRecord $_ + if (Get-Module BcContainerHelper) { + TrackException -telemetryScope $telemetryScope -errorRecord $_ + } throw } finally { @@ -427,8 +437,9 @@ finally { Copy-Item -Path $containerEventLogFile -Destination $destFolder } } - catch {} - CleanupAfterBcContainerHelper -bcContainerHelperPath $bcContainerHelperPath + catch { + Write-Host "Error getting event log from container: $($_.Exception.Message)" + } if ($containerBaseFolder -and (Test-Path $containerBaseFolder) -and $projectPath -and (Test-Path $projectPath)) { Write-Host "Removing temp folder" Remove-Item -Path (Join-Path $projectPath '*') -Recurse -Force diff --git a/RunPipeline/action.yaml b/RunPipeline/action.yaml index d0763ae..0369cb9 100644 --- a/RunPipeline/action.yaml +++ b/RunPipeline/action.yaml @@ -5,10 +5,6 @@ inputs: description: Shell in which you want to run the action (powershell or pwsh) required: false default: powershell - actor: - description: The GitHub actor running the action - required: false - default: ${{ github.actor }} token: description: The GitHub token running the action required: false @@ -43,7 +39,6 @@ runs: - name: run shell: ${{ inputs.shell }} env: - _actor: ${{ inputs.actor }} _token: ${{ inputs.token }} _parentTelemetryScopeJson: ${{ inputs.parentTelemetryScopeJson }} _artifact: ${{ inputs.artifact }} @@ -54,7 +49,7 @@ runs: run: | $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 try { - ${{ github.action_path }}/RunPipeline.ps1 -actor $ENV:_actor -token $ENV:_token -parentTelemetryScopeJson $ENV:_parentTelemetryScopeJson -artifact $ENV:_artifact -project $ENV:_project -buildMode $ENV:_buildMode -installAppsJson $ENV:_installAppsJson -installTestAppsJson $ENV:_installTestAppsJson + ${{ 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",' <- '))"; diff --git a/SECURITY.md b/SECURITY.md index 926b8ae..a050f36 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -14,7 +14,7 @@ Instead, please report them to the Microsoft Security Response Center (MSRC) at If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). -You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 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: diff --git a/SUPPORT.md b/SUPPORT.md index 96927d7..424e7e4 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -1,11 +1,11 @@ # Support -## How to file issues and get help +## How to file issues and get help This GitHub repo is auto-generated from https://github.com/microsoft/AL-Go. Issues, bug tracking and feature requests should be done there. Please follow the documentation [here](https://github.com/microsoft/AL-Go/blob/main/Scenarios/Contribute.md) if you want to contribute to AL-Go for GitHub. -## Microsoft Support Policy +## Microsoft Support Policy Support for this **PROJECT or PRODUCT** is limited to the resources listed above. diff --git a/Sign/Sign.ps1 b/Sign/Sign.ps1 index 3bc2247..d4b83c6 100644 --- a/Sign/Sign.ps1 +++ b/Sign/Sign.ps1 @@ -1,3 +1,4 @@ +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '', Justification = 'GitHub Secrets are transferred as plain text')] param( [Parameter(HelpMessage = "Azure Credentials secret", Mandatory = $true)] [string] $AzureCredentialsJson, @@ -12,13 +13,12 @@ param( ) $telemetryScope = $null -$bcContainerHelperPath = $null 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) - $BcContainerHelperPath = DownloadAndImportBcContainerHelper -baseFolder $ENV:GITHUB_WORKSPACE + DownloadAndImportBcContainerHelper $telemetryScope = CreateScope -eventId 'DO0083' -parentTelemetryScopeJson $ParentTelemetryScopeJson Write-Host "::group::Install AzureSignTool" @@ -27,23 +27,25 @@ try { $Files = Get-ChildItem -Path $PathToFiles -File | Select-Object -ExpandProperty FullName Write-Host "Signing files:" - $Files | ForEach-Object { - Write-Host "- $_" + $Files | ForEach-Object { + Write-Host "- $_" } $AzureCredentials = ConvertFrom-Json $AzureCredentialsJson $settings = $env:Settings | ConvertFrom-Json - if ($AzureCredentials.PSobject.Properties.name -eq "keyVaultName") { - $AzureKeyVaultName = $AzureCredentials.keyVaultName - } elseif ($settings.PSobject.Properties.name -eq "keyVaultName") { + if ($settings.keyVaultName) { $AzureKeyVaultName = $settings.keyVaultName - } else { + } + 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." } - Retry-Command -Command { + RetryCommand -Command { Param( $AzureKeyVaultName, $AzureCredentials, $digestAlgorithm, $TimestampService, $Certificate, $Files) Write-Host "::group::Register NavSip" - Register-NavSip + Register-NavSip Write-Host "::endgroup::" AzureSignTool sign --file-digest $digestAlgorithm ` @@ -51,18 +53,17 @@ try { --azure-key-vault-client-id $AzureCredentials.clientId ` --azure-key-vault-tenant-id $AzureCredentials.tenantId ` --azure-key-vault-client-secret $AzureCredentials.clientSecret ` - --azure-key-vault-certificate $Settings.keyVaultCodesignCertificateName ` + --azure-key-vault-certificate $Certificate ` --timestamp-rfc3161 "$TimestampService" ` --timestamp-digest $digestAlgorithm ` $Files - } -MaxRetries 3 - + } -MaxRetries 3 -ArgumentList $AzureKeyVaultName, $AzureCredentials, $digestAlgorithm, $TimestampService, $Settings.keyVaultCodesignCertificateName, $Files + TrackTrace -telemetryScope $telemetryScope } catch { - TrackException -telemetryScope $telemetryScope -errorRecord $_ + if (Get-Module BcContainerHelper) { + TrackException -telemetryScope $telemetryScope -errorRecord $_ + } throw } -finally { - CleanupAfterBcContainerHelper -bcContainerHelperPath $bcContainerHelperPath -} diff --git a/Sign/Sign.psm1 b/Sign/Sign.psm1 index d877984..cf2bb6a 100644 --- a/Sign/Sign.psm1 +++ b/Sign/Sign.psm1 @@ -1,7 +1,7 @@ -function Get-NavSipFromArtifacts +function GetNavSipFromArtifacts ( [string] $NavSipDestination -) +) { $artifactTempFolder = Join-Path $([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName()) @@ -18,12 +18,16 @@ function Get-NavSipFromArtifacts } } +<# +.SYNOPSIS +Register the navsip.dll in the system32 folder +#> function Register-NavSip() { $navSipDestination = "C:\Windows\System32" $navSipDllPath = Join-Path $navSipDestination "navsip.dll" try { if (-not (Test-Path $navSipDllPath)) { - Get-NavSipFromArtifacts -NavSipDestination $navSipDllPath + GetNavSipFromArtifacts -NavSipDestination $navSipDllPath } Write-Host "Unregistering dll $navSipDllPath" diff --git a/TelemetryHelper.psm1 b/TelemetryHelper.psm1 index 9ef9247..83feaaf 100644 --- a/TelemetryHelper.psm1 +++ b/TelemetryHelper.psm1 @@ -16,7 +16,7 @@ $signals = @{ "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" @@ -63,7 +63,7 @@ function CreateScope { [string] $parentTelemetryScopeJson = '7b7d' ) - $signalName = $signals[$eventId] + $signalName = $signals[$eventId] if (-not $signalName) { throw "Invalid event id ($eventId) is enountered." } diff --git a/VerifyPRChanges/VerifyPRChanges.ps1 b/VerifyPRChanges/VerifyPRChanges.ps1 index 67d46a3..d49c1fe 100644 --- a/VerifyPRChanges/VerifyPRChanges.ps1 +++ b/VerifyPRChanges/VerifyPRChanges.ps1 @@ -75,7 +75,8 @@ function ValidatePullRequestFiles $headers = @{ "Authorization" = "token $token" - "Accept" = "application/vnd.github.baptiste-preview+json" + "X-GitHub-Api-Version" = "2022-11-28" + "Accept" = "application/vnd.github+json" } ValidatePullRequest -PullRequestRepository $prBaseRepository -PullRequestId $pullRequestId -Headers $headers diff --git a/WorkflowInitialize/WorkflowInitialize.ps1 b/WorkflowInitialize/WorkflowInitialize.ps1 index 16d71d3..9405e95 100644 --- a/WorkflowInitialize/WorkflowInitialize.ps1 +++ b/WorkflowInitialize/WorkflowInitialize.ps1 @@ -4,7 +4,6 @@ Param( ) $telemetryScope = $null -$BcContainerHelperPath = "" try { . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) @@ -26,9 +25,9 @@ try { Write-Big -str "a$verstr" - Test-ALGoRepository -baseFolder $ENV:GITHUB_WORKSPACE + Test-ALGoRepository - $BcContainerHelperPath = DownloadAndImportBcContainerHelper -baseFolder $ENV:GITHUB_WORKSPACE + DownloadAndImportBcContainerHelper import-module (Join-Path -path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) $telemetryScope = CreateScope -eventId $eventId @@ -55,7 +54,7 @@ try { 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 - + $scopeJson = strToHexStr -str ($telemetryScope | ConvertTo-Json -Compress) $correlationId = ($telemetryScope.CorrelationId).ToString() } @@ -71,11 +70,8 @@ try { Write-Host "correlationId=$correlationId" } catch { - if ($bcContainerHelperPath) { + if (Get-Module BcContainerHelper) { TrackException -telemetryScope $telemetryScope -errorRecord $_ } throw } -finally { - CleanupAfterBcContainerHelper -bcContainerHelperPath $bcContainerHelperPath -} diff --git a/WorkflowPostProcess/WorkflowPostProcess.ps1 b/WorkflowPostProcess/WorkflowPostProcess.ps1 index c6978e6..ddb5bb2 100644 --- a/WorkflowPostProcess/WorkflowPostProcess.ps1 +++ b/WorkflowPostProcess/WorkflowPostProcess.ps1 @@ -6,22 +6,21 @@ Param( ) $telemetryScope = $null -$bcContainerHelperPath = $null try { . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) - $BcContainerHelperPath = DownloadAndImportBcContainerHelper -baseFolder $ENV:GITHUB_WORKSPACE + DownloadAndImportBcContainerHelper import-module (Join-Path -path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) + Write-Host "Post-processing workflow $eventId" if ($telemetryScopeJson -and $telemetryScopeJson -ne '7b7d') { $telemetryScope = RegisterTelemetryScope (hexStrToStr -hexStr $telemetryScopeJson) TrackTrace -telemetryScope $telemetryScope } } catch { - TrackException -telemetryScope $telemetryScope -errorRecord $_ + if (Get-Module BcContainerHelper) { + TrackException -telemetryScope $telemetryScope -errorRecord $_ + } throw } -finally { - CleanupAfterBcContainerHelper -bcContainerHelperPath $bcContainerHelperPath -}