From 9e11aa9522ee1d8319bb32353ceef4a579f9e24d Mon Sep 17 00:00:00 2001 From: Christoph Blank Date: Fri, 26 Jun 2026 15:44:49 +0200 Subject: [PATCH 1/6] Add support for importantSettings to override lower priority settings --- Actions/.Modules/ReadSettings.psm1 | 48 +++- Actions/.Modules/settings.schema.json | 8 + Scenarios/settings.md | 77 ++++++ Tests/ReadSettings.Test.ps1 | 362 ++++++++++++++++++++++++++ 4 files changed, 489 insertions(+), 6 deletions(-) diff --git a/Actions/.Modules/ReadSettings.psm1 b/Actions/.Modules/ReadSettings.psm1 index ec6dc5588..7c6261e66 100644 --- a/Actions/.Modules/ReadSettings.psm1 +++ b/Actions/.Modules/ReadSettings.psm1 @@ -16,9 +16,16 @@ $CustomTemplateProjectSettingsFile = Join-Path '.github' $CustomTemplateProjectS function MergeCustomObjectIntoOrderedDictionary { Param( [System.Collections.Specialized.OrderedDictionary] $dst, - [PSCustomObject] $src + [PSCustomObject] $src, + [string[]] $importantSettingsFromHigherPriority = @(), + [ref]$importantSettingsAtThisLevel ) + # Initialize importantSettingsAtThisLevel if passed + if ($null -eq $importantSettingsAtThisLevel) { + $importantSettingsAtThisLevel = [ref]@() + } + # If the src object contains property 'overwriteSettings' (list of settings), remove these settings from the dst object, so that they can be re-added with the new value later on if ($src.PSObject.Properties.Name -contains "overwriteSettings") { $src.overwriteSettings | ForEach-Object { @@ -31,14 +38,22 @@ function MergeCustomObjectIntoOrderedDictionary { } } + # Collect important settings from this level + # Only set if importantSettings is explicitly defined; otherwise set to $null to signal no update + if ($src.PSObject.Properties.Name -contains "importantSettings") { + $importantSettingsAtThisLevel.Value = @($src.importantSettings) + } else { + $importantSettingsAtThisLevel.Value = $null + } + # Loop through all properties in the source object # If the property does not exist in the destination object, add it with the right type, but no value # Types supported: PSCustomObject, Object[] and simple types $src.PSObject.Properties.GetEnumerator() | ForEach-Object { $prop = $_.Name - # Skip overwriteSettings property as it's only used to remove settings from the destination object and is specific to the source object - if ($prop -eq "overwriteSettings") { + # Skip overwriteSettings and importantSettings properties as they're only used for configuration, not actual settings + if ($prop -eq "overwriteSettings" -or $prop -eq "importantSettings") { return } @@ -62,15 +77,22 @@ function MergeCustomObjectIntoOrderedDictionary { # If the property exists in the source object, but is of a different type, throw an error # If the property exists in the source object: # If the property is an Object, call this function recursively to merge values - # If the property is an Object[], merge the arrays + # If the property is an Object[], merge the arrays (even if important - arrays always merge) # If the property is a simple type, replace the value in the destination object with the value from the source object @($dst.Keys) | ForEach-Object { $prop = $_ + if ($src.PSObject.Properties.Name -eq $prop) { $dstProp = $dst."$prop" $srcProp = $src."$prop" $dstPropType = $dstProp.GetType().Name $srcPropType = $srcProp.GetType().Name + + # For non-array properties: skip if this setting is marked as important from higher priority source + if ($importantSettingsFromHigherPriority -contains $prop -and $srcPropType -ne "Object[]") { + OutputDebug "Skipping important setting '$prop' marked from higher priority source (non-array type)" + return + } if ($srcPropType -eq "PSCustomObject" -and $dstPropType -eq "OrderedDictionary") { MergeCustomObjectIntoOrderedDictionary -dst $dst."$prop" -src $srcProp } @@ -262,6 +284,7 @@ function GetDefaultSettings "filesToExclude" = @() } "postponeProjectInBuildOrder" = $false + "importantSettings" = @() } } @@ -482,11 +505,18 @@ function ReadSettings { } } + $currentImportantSettings = @() foreach($settingsObject in $settingsObjects) { $settingsJson = $settingsObject.Settings if ($settingsJson) { OutputDebug "Applying settings from $($settingsObject.Source) ($($settingsObject.Type))" - MergeCustomObjectIntoOrderedDictionary -dst $settings -src $settingsJson + $importantAtThisLevel = @() + $importantRef = [ref]$importantAtThisLevel + MergeCustomObjectIntoOrderedDictionary -dst $settings -src $settingsJson -importantSettingsFromHigherPriority $currentImportantSettings -importantSettingsAtThisLevel $importantRef + # Only update currentImportantSettings if this level explicitly defined importantSettings (not null) + if ($null -ne $importantAtThisLevel) { + $currentImportantSettings = $importantAtThisLevel + } if ($settingsJson.PSObject.Properties.Name -eq "ConditionalSettings") { foreach($conditionalSetting in $settingsJson.ConditionalSettings) { if ("$conditionalSetting" -ne "") { @@ -508,7 +538,13 @@ function ReadSettings { } if ($conditionMet) { OutputDebug "Applying conditional settings for $($conditions -join ", ")" - MergeCustomObjectIntoOrderedDictionary -dst $settings -src $conditionalSetting.settings + $importantAtThisLevel = @() + $importantRef = [ref]$importantAtThisLevel + MergeCustomObjectIntoOrderedDictionary -dst $settings -src $conditionalSetting.settings -importantSettingsFromHigherPriority $currentImportantSettings -importantSettingsAtThisLevel $importantRef + # Only update currentImportantSettings if conditional settings explicitly defined importantSettings + if ($null -ne $importantAtThisLevel) { + $currentImportantSettings = $importantAtThisLevel + } } } } diff --git a/Actions/.Modules/settings.schema.json b/Actions/.Modules/settings.schema.json index 8cf29ab0d..950506e71 100644 --- a/Actions/.Modules/settings.schema.json +++ b/Actions/.Modules/settings.schema.json @@ -730,6 +730,14 @@ }, "description": "An array of settings to be overwritten by the current settings. See https://aka.ms/ALGoSettings#overwriteSettings" }, + "importantSettings": { + "type": "array", + "items": { + "type": "string" + }, + "default": [], + "description": "An array of setting names that should take precedence over normal inheritance hierarchy. Settings listed here will override settings from lower priority sources. See https://aka.ms/ALGoSettings#importantSettings" + }, "reportSuppressedDiagnostics": { "type": "boolean", "description": "Report suppressed diagnostics. See https://aka.ms/ALGoSettings#reportsuppresseddiagnostics" diff --git a/Scenarios/settings.md b/Scenarios/settings.md index 6b58c1c37..71381e07e 100644 --- a/Scenarios/settings.md +++ b/Scenarios/settings.md @@ -316,6 +316,83 @@ then, after merging, the result settings object will contain the following value > _**Note**_: `overwriteSettings` isn't a setting on its own and it isn't available in the output of `ReadSetting` action, for example. It's merely used to control the settings merging mechanism and allow overwriting complex settings types. The value of `overwriteSettings` should only contain settings of types _array_ or _object_ and all the settings in `overwriteSettings` should be present with the new value. +## Important settings + +By default, AL-Go follows a standard settings hierarchy where settings from higher priority levels (closer to deployment) override settings from lower priority levels. However, you can mark specific settings as **important** to protect them from being overridden by lower priority settings using the `importantSettings` array. + +When a setting is marked as important at a higher level in the hierarchy, it cannot be overridden by settings from lower priority levels. This is useful for enforcing organizational or repository-wide policies that should not be changed at the project or workflow level. + +_Example_: +Say, `ALGoOrgSettings` (organization level) contains the following values: + +```json +{ + "importantSettings": ["country", "keyVaultName"], + "country": "de", + "keyVaultName": "OrgVault" +} +``` + +and `.AL-Go\settings.json` (project level, lower priority) contains the following values: + +```json +{ + "country": "us", + "keyVaultName": "ProjectVault" +} +``` + +then, after merging, the result settings object will contain the following values: + +```json +{ + "importantSettings": ["country", "keyVaultName"], + "country": "de", + "keyVaultName": "OrgVault" +} +``` + +The `country` and `keyVaultName` settings from the organization level are protected and cannot be overridden by the project level settings. + +_Example with ConditionalSettings_: +Say, `ALGoOrgSettings` (organization level) contains conditional settings that set country based on buildMode: + +```json +{ + "ConditionalSettings": [ + { + "buildModes": ["ValidateUS"], + "settings": { + "importantSettings": ["country"], + "country": "us" + } + } + ] +} +``` + +and `.AL-Go\settings.json` (project level) contains: + +```json +{ + "country": "w1", + "buildModes": ["Default", "ValidateUS"] +} +``` + +When reading settings for buildMode `ValidateUS`, the conditional setting from the organization level will apply. The result will be: + +```json +{ + "country": "us", + "buildModes": ["Default", "ValidateUS"] +} +``` + +Even though the project specifies `country: "w1"`, the conditional setting from the organization level marked the country as important for the `ValidateUS` buildMode, so it takes precedence. + +> _**Note**_: `importantSettings` is an array of setting names that should be protected from being overridden by lower priority settings. When a setting is marked as important at a higher level, it will not be overridden by normal (non-important) settings from lower levels. If a setting is marked as important at multiple levels, the value from the highest priority level wins (following normal hierarchy rules). Only top-level setting names can be marked as important; nested properties within complex objects cannot be individually marked as important. Array settings marked as important are still merged with lower-priority arrays, unless `overwriteSettings` is used to force replacement. + ## Custom Delivery diff --git a/Tests/ReadSettings.Test.ps1 b/Tests/ReadSettings.Test.ps1 index 569a9bcb6..bf6347742 100644 --- a/Tests/ReadSettings.Test.ps1 +++ b/Tests/ReadSettings.Test.ps1 @@ -540,5 +540,367 @@ InModuleScope ReadSettings { # Allows testing of private functions Pop-Location Remove-Item -Path $tempName -Recurse -Force } + + It 'importantSettings from higher priority source prevents overwrite from lower priority' { + Mock Write-Host { } + Mock Out-Host { } + + Push-Location + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + $githubFolder = Join-Path $tempName ".github" + $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" + + New-Item $githubFolder -ItemType Directory | Out-Null + New-Item $projectALGoFolder -ItemType Directory | Out-Null + + # Org settings: important settings includes "country", set country = "de" + @{ "importantSettings" = @("country"); "country" = "de" } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $githubFolder "AL-Go-Settings.json") -encoding utf8 -Force + + # Project settings: try to override country = "ch" + @{ "country" = "ch" } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -encoding utf8 -Force + + $ENV:ALGoOrgSettings = '' + $ENV:ALGoRepoSettings = '' + + # Important setting from repo should prevent project from overwriting + $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' + $settings.country | Should -Be 'de' # Org/Repo important value wins + $settings.importantSettings | Should -Contain 'country' + + # Clean up + Pop-Location + Remove-Item -Path $tempName -Recurse -Force + } + + It 'Multiple importantSettings are respected' { + Mock Write-Host { } + Mock Out-Host { } + + Push-Location + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + $githubFolder = Join-Path $tempName ".github" + $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" + + New-Item $githubFolder -ItemType Directory | Out-Null + New-Item $projectALGoFolder -ItemType Directory | Out-Null + + # Org settings: mark both country and keyVaultName as important + @{ + "importantSettings" = @("country", "keyVaultName") + "country" = "de" + "keyVaultName" = "orgVault" + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $githubFolder "AL-Go-Settings.json") -Encoding utf8 -Force + + # Project settings: try to override both + @{ + "country" = "ch" + "keyVaultName" = "projectVault" + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -Encoding utf8 -Force + + $ENV:ALGoOrgSettings = '' + $ENV:ALGoRepoSettings = '' + + $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' + $settings.country | Should -Be 'de' + $settings.keyVaultName | Should -Be 'orgVault' + $settings.importantSettings | Should -Contain 'country' + $settings.importantSettings | Should -Contain 'keyVaultName' + + # Clean up + Pop-Location + Remove-Item -Path $tempName -Recurse -Force + } + + It 'Normal settings from higher priority level override important settings from lower priority' { + Mock Write-Host { } + Mock Out-Host { } + + Push-Location + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + $githubFolder = Join-Path $tempName ".github" + $ALGoFolder = Join-Path $tempName $ALGoFolderName + $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" + + New-Item $githubFolder -ItemType Directory | Out-Null + New-Item $ALGoFolder -ItemType Directory | Out-Null + New-Item $projectALGoFolder -ItemType Directory | Out-Null + + # Org settings (via variable): mark country as important and set to "de" + $ENV:ALGoOrgSettings = @{ + "importantSettings" = @("country") + "country" = "de" + } | ConvertTo-Json -Depth 99 + + # Repo settings: try to override country with normal (non-important) setting = "us" + @{ "country" = "us" } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $githubFolder "AL-Go-Settings.json") -Encoding utf8 -Force + + # Project settings: try to override with important setting = "ch" + @{ + "importantSettings" = @("country") + "country" = "ch" + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -Encoding utf8 -Force + + # Important setting from Org (highest level) should win: expected "de" + # (even though Project marks it as important, Org's important setting has higher priority) + $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' + $settings.country | Should -Be 'de' # Org important wins over all others + + $ENV:ALGoOrgSettings = '' + + # Clean up + Pop-Location + Remove-Item -Path $tempName -Recurse -Force + } + + It 'importantSettings marked arrays are still merged with lower priority arrays' { + Mock Write-Host { } + Mock Out-Host { } + + Push-Location + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + $githubFolder = Join-Path $tempName ".github" + $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" + + New-Item $githubFolder -ItemType Directory | Out-Null + New-Item $projectALGoFolder -ItemType Directory | Out-Null + + # Org settings: mark additionalCountries as important with specific values + @{ + "importantSettings" = @("additionalCountries") + "additionalCountries" = @("de", "at") + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $githubFolder "AL-Go-Settings.json") -Encoding utf8 -Force + + # Project settings: try to add more countries + @{ + "additionalCountries" = @("ch", "be") + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -Encoding utf8 -Force + + $ENV:ALGoOrgSettings = '' + $ENV:ALGoRepoSettings = '' + + # Important array settings are still merged with lower priority arrays (exception: if overwriteSettings is used) + $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' + $settings.additionalCountries | Should -Be @("de", "at", "ch", "be") # Org + Project values merged + $settings.importantSettings | Should -Contain "additionalCountries" + + # Clean up + Pop-Location + Remove-Item -Path $tempName -Recurse -Force + } + + It 'importantSettings can be overridden using overwriteSettings in lower priority' { + Mock Write-Host { } + Mock Out-Host { } + + Push-Location + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + $githubFolder = Join-Path $tempName ".github" + $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" + + New-Item $githubFolder -ItemType Directory | Out-Null + New-Item $projectALGoFolder -ItemType Directory | Out-Null + + # Org settings: mark additionalCountries as important + @{ + "importantSettings" = @("additionalCountries") + "additionalCountries" = @("de", "at") + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $githubFolder "AL-Go-Settings.json") -Encoding utf8 -Force + + # Project settings: use overwriteSettings to override additionalCountries (force replacement instead of merge) + @{ + "overwriteSettings" = @("additionalCountries") + "additionalCountries" = @("ch", "be") + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -Encoding utf8 -Force + + $ENV:ALGoOrgSettings = '' + $ENV:ALGoRepoSettings = '' + + # When overwriteSettings is used, it overrides the importantSettings behavior + $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' + $settings.additionalCountries | Should -Be @("ch", "be") # Only project values (org values replaced) + + # Clean up + Pop-Location + Remove-Item -Path $tempName -Recurse -Force + } + + It 'Non-important array settings are merged normally (baseline)' { + Mock Write-Host { } + Mock Out-Host { } + + Push-Location + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + $githubFolder = Join-Path $tempName ".github" + $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" + + New-Item $githubFolder -ItemType Directory | Out-Null + New-Item $projectALGoFolder -ItemType Directory | Out-Null + + # Org settings: additionalCountries WITHOUT marking as important + @{ + "additionalCountries" = @("de", "at") + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $githubFolder "AL-Go-Settings.json") -Encoding utf8 -Force + + # Project settings: add more countries + @{ + "additionalCountries" = @("ch", "be") + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -Encoding utf8 -Force + + $ENV:ALGoOrgSettings = '' + $ENV:ALGoRepoSettings = '' + + # Without important marking, arrays should be merged + $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' + $settings.additionalCountries | Should -Be @("de", "at", "ch", "be") # All values merged + + # Clean up + Pop-Location + Remove-Item -Path $tempName -Recurse -Force + } + + It 'Empty importantSettings has no effect (backward compatibility)' { + Mock Write-Host { } + Mock Out-Host { } + + Push-Location + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + $githubFolder = Join-Path $tempName ".github" + $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" + + New-Item $githubFolder -ItemType Directory | Out-Null + New-Item $projectALGoFolder -ItemType Directory | Out-Null + + # Org settings: importantSettings is empty + @{ + "importantSettings" = @() + "country" = "us" + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $githubFolder "AL-Go-Settings.json") -Encoding utf8 -Force + + # Project settings: override country + @{ + "country" = "ch" + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -encoding utf8 -Force + + $ENV:ALGoOrgSettings = '' + $ENV:ALGoRepoSettings = '' + + # Without important marking, normal hierarchy applies + $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' + $settings.country | Should -Be 'ch' # Project wins (normal behavior) + $settings.importantSettings | Should -Be @() # Empty array preserved + + # Clean up + Pop-Location + Remove-Item -Path $tempName -Recurse -Force + } + + It 'importantSettings in ConditionalSetting prevents override from repo settings' { + Mock Write-Host { } + Mock Out-Host { } + + Push-Location + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + $githubFolder = Join-Path $tempName ".github" + + New-Item $githubFolder -ItemType Directory | Out-Null + + # Repo settings: set country to "us" (no important marking) + @{ + "country" = "us" + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $githubFolder "AL-Go-Settings.json") -Encoding utf8 -Force + + # Workflow settings with ConditionalSetting for buildModes = "Clean" + # The conditional setting marks country as important and sets it to "de" + @{ + "conditionalSettings" = @( + @{ + "buildModes" = @("Clean") + "settings" = @{ + "importantSettings" = @("country") + "country" = "de" + } + } + ) + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $githubFolder "Workflow.settings.json") -Encoding utf8 -Force + + $ENV:ALGoOrgSettings = '' + $ENV:ALGoRepoSettings = '' + + # When reading settings for buildMode "Clean", the country from ConditionalSetting (de) with important marking should win + $settings = ReadSettings -baseFolder $tempName -project '' -repoName 'repo' -workflowName 'Workflow' -branchName '' -buildMode 'Clean' -userName '' + $settings.country | Should -Be 'de' # Conditional important setting wins over repo setting + $settings.importantSettings | Should -Contain 'country' + + # Clean up + Pop-Location + Remove-Item -Path $tempName -Recurse -Force + } + + It 'ConditionalSetting with importantSettings at org level overrides project setting for specific buildMode' { + Mock Write-Host { } + Mock Out-Host { } + + Push-Location + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + $githubFolder = Join-Path $tempName ".github" + $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" + + New-Item $githubFolder -ItemType Directory | Out-Null + New-Item $projectALGoFolder -ItemType Directory | Out-Null + + # Org settings: ConditionalSetting for buildMode "ValidateUS" with important country marking + @{ + "ConditionalSettings" = @( + @{ + "buildModes" = @("ValidateUS") + "settings" = @{ + "importantSettings" = @("country") + "country" = "us" + } + } + ) + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $githubFolder "AL-Go-Settings.json") -Encoding utf8 -Force + + # Project settings: country = "w1", buildModes include "ValidateUS" + @{ + "country" = "w1" + "buildModes" = @("Default", "ValidateUS") + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -Encoding utf8 -Force + + $ENV:ALGoOrgSettings = '' + $ENV:ALGoRepoSettings = '' + + # When reading for buildMode "Default", project country "w1" should be used + $settingsDefault = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -buildMode 'Default' -userName '' + $settingsDefault.country | Should -Be 'w1' # No org conditional applies for "Default" + + # When reading for buildMode "ValidateUS", org conditional with important marking should override project + $settingsValidateUS = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -buildMode 'ValidateUS' -userName '' + $settingsValidateUS.country | Should -Be 'us' # Org conditional important setting wins + $settingsValidateUS.buildModes | Should -Contain 'ValidateUS' + $settingsValidateUS.importantSettings | Should -Contain 'country' + + # Clean up + Pop-Location + Remove-Item -Path $tempName -Recurse -Force + } } } From dfcf3930bdc5fcd8094c81829c00d69d9c71546a Mon Sep 17 00:00:00 2001 From: Christoph Blank Date: Fri, 26 Jun 2026 15:47:45 +0200 Subject: [PATCH 2/6] Enhance importantSettings feature with detailed behavior and examples --- RELEASENOTES.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f55ae4740..ba066a73c 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,37 @@ +### Important settings protection + +A new `importantSettings` setting allows you to protect specific settings from being overridden by lower-priority sources in the settings hierarchy. When a setting is marked as important at a higher priority level, it cannot be overridden by non-important settings from lower priority sources. + +```json +{ + "importantSettings": ["country", "keyVaultName"], + "country": "de", + "keyVaultName": "orgVault" +} +``` + +**Behavior:** +- Settings marked as important in organization or repository settings cannot be overridden by project, workflow, user, or environment settings +- Important settings from higher priority sources always win, even if lower levels mark the same setting as important +- Important arrays are still merged (not replaced) with lower-priority arrays, unless `overwriteSettings` is explicitly used +- The `overwriteSettings` mechanism can override important settings when explicitly specified +- `ConditionalSettings` respect importantSettings markings, allowing you to enforce conditional important settings based on buildMode, branch, trigger, or user + +**Example with ConditionalSettings:** +```json +{ + "ConditionalSettings": [ + { + "buildModes": ["Validate"], + "settings": { + "importantSettings": ["country"], + "country": "us" + } + } + ] +} +``` + ### Resilient Pull Request Status Check for large builds The Pull Request Status Check action no longer fails on builds with more than one page of jobs (more than 100 jobs). The jobs API call now uses `--slurp` so multi-page responses are parsed as a single JSON array (previously `gh api --paginate | ConvertFrom-Json` failed with "Invalid JSON primitive" when more than one page was returned). The call is also retried, and requests a smaller page size, to tolerate the intermittent HTTP 502 responses that the GitHub jobs endpoint returns for large builds. From a9f94586ee465d05ffa027109eff52940219c4b4 Mon Sep 17 00:00:00 2001 From: Christoph Blank Date: Mon, 29 Jun 2026 14:06:37 +0200 Subject: [PATCH 3/6] Refine importantSettings behavior and documentation for clarity --- Actions/.Modules/ReadSettings.psm1 | 194 +++++++++++++------------- Actions/.Modules/settings.schema.json | 10 +- RELEASENOTES.md | 8 +- Scenarios/settings.md | 6 +- Tests/ReadSettings.Test.ps1 | 75 +++++----- 5 files changed, 139 insertions(+), 154 deletions(-) diff --git a/Actions/.Modules/ReadSettings.psm1 b/Actions/.Modules/ReadSettings.psm1 index 7c6261e66..94f1409ea 100644 --- a/Actions/.Modules/ReadSettings.psm1 +++ b/Actions/.Modules/ReadSettings.psm1 @@ -17,19 +17,18 @@ function MergeCustomObjectIntoOrderedDictionary { Param( [System.Collections.Specialized.OrderedDictionary] $dst, [PSCustomObject] $src, - [string[]] $importantSettingsFromHigherPriority = @(), - [ref]$importantSettingsAtThisLevel + [string[]] $srcImportantSettings = @(), + [string[]] $dstImportantSettings = @() ) - # Initialize importantSettingsAtThisLevel if passed - if ($null -eq $importantSettingsAtThisLevel) { - $importantSettingsAtThisLevel = [ref]@() - } - # If the src object contains property 'overwriteSettings' (list of settings), remove these settings from the dst object, so that they can be re-added with the new value later on if ($src.PSObject.Properties.Name -contains "overwriteSettings") { $src.overwriteSettings | ForEach-Object { $prop = $_ + if ($dstImportantSettings -contains $prop -and $srcImportantSettings -notcontains $prop) { + OutputDebug "Ignoring overwriteSettings for '$prop' because it is important in higher priority settings and not marked important in source" + return + } if ($dst.Contains($prop) -and $src.PSObject.Properties.Name -contains $prop) { # Remove the property from the destination object only if it also exists in the source object. The property will be re-added with the new value later on. OutputDebug "Overwriting setting $prop" @@ -38,22 +37,14 @@ function MergeCustomObjectIntoOrderedDictionary { } } - # Collect important settings from this level - # Only set if importantSettings is explicitly defined; otherwise set to $null to signal no update - if ($src.PSObject.Properties.Name -contains "importantSettings") { - $importantSettingsAtThisLevel.Value = @($src.importantSettings) - } else { - $importantSettingsAtThisLevel.Value = $null - } - # Loop through all properties in the source object # If the property does not exist in the destination object, add it with the right type, but no value # Types supported: PSCustomObject, Object[] and simple types $src.PSObject.Properties.GetEnumerator() | ForEach-Object { $prop = $_.Name - # Skip overwriteSettings and importantSettings properties as they're only used for configuration, not actual settings - if ($prop -eq "overwriteSettings" -or $prop -eq "importantSettings") { + # Skip overwriteSettings property as it's only used for configuration, not actual settings + if ($prop -eq "overwriteSettings") { return } @@ -88,8 +79,10 @@ function MergeCustomObjectIntoOrderedDictionary { $dstPropType = $dstProp.GetType().Name $srcPropType = $srcProp.GetType().Name - # For non-array properties: skip if this setting is marked as important from higher priority source - if ($importantSettingsFromHigherPriority -contains $prop -and $srcPropType -ne "Object[]") { + # For non-array properties: skip if this setting is marked as important from higher priority source, + # unless the lower-priority source also marks this property as important. + if ($dstImportantSettings -contains $prop -and $srcPropType -ne "Object[]" -and $srcImportantSettings -notcontains $prop) { + #$dstImportantSettings : better would be dest. OutputDebug "Skipping important setting '$prop' marked from higher priority source (non-array type)" return } @@ -116,7 +109,7 @@ function MergeCustomObjectIntoOrderedDictionary { } else { # Add source element to destination array, but only if it does not already exist - $dst."$prop" = @($dst."$prop" + $srcElm | Select-Object -Unique) + $dst."$prop" = @($dst."$prop" + $srcElm | Select-Object -Unique) #TODO: nur wenn dieses property hier auch definiert wurde. } } } @@ -131,8 +124,7 @@ function MergeCustomObjectIntoOrderedDictionary { function GetDefaultSettings ( [string] $repoName -) -{ +) { return [ordered]@{ "type" = "PTE" "unusedALGoSystemFiles" = @() @@ -202,10 +194,10 @@ function GetDefaultSettings "configPackages" = @() "appSourceCopMandatoryAffixes" = @() "deliverToAppSource" = [ordered]@{ - "mainAppFolder" = "" - "productId" = "" - "includeDependencies" = @() - "continuousDelivery" = $false + "mainAppFolder" = "" + "productId" = "" + "includeDependencies" = @() + "continuousDelivery" = $false } "obsoleteTagMinAllowedMajorMinor" = "" "memoryLimit" = "" @@ -223,11 +215,11 @@ function GetDefaultSettings "cacheKeepDays" = 3 "alwaysBuildAllProjects" = $false "incrementalBuilds" = [ordered]@{ - "onPush" = $false - "onPull_Request" = $true - "onSchedule" = $false - "retentionDays" = 30 - "mode" = "modifiedApps" # modifiedProjects, modifiedApps + "onPush" = $false + "onPull_Request" = $true + "onSchedule" = $false + "retentionDays" = 30 + "mode" = "modifiedApps" # modifiedProjects, modifiedApps } "microsoftTelemetryConnectionString" = "InstrumentationKey=cd2cc63e-0f37-4968-b99a-532411a314b8;IngestionEndpoint=https://northeurope-2.in.applicationinsights.azure.com/" "partnerTelemetryConnectionString" = "" @@ -236,55 +228,55 @@ function GetDefaultSettings "buildModes" = @() "useCompilerFolder" = $false "workspaceCompilation" = [ordered]@{ - "enabled" = $false - "parallelism" = 1 + "enabled" = $false + "parallelism" = 1 } "pullRequestTrigger" = "pull_request" "bcptThresholds" = [ordered]@{ - "DurationWarning" = 10 - "DurationError" = 25 - "NumberOfSqlStmtsWarning" = 5 - "NumberOfSqlStmtsError" = 10 + "DurationWarning" = 10 + "DurationError" = 25 + "NumberOfSqlStmtsWarning" = 5 + "NumberOfSqlStmtsError" = 10 } "fullBuildPatterns" = @() "excludeEnvironments" = @() "alDoc" = [ordered]@{ - "continuousDeployment" = $false - "deployToGitHubPages" = $true - "maxReleases" = 3 - "groupByProject" = $true - "includeProjects" = @() - "excludeProjects" = @() - "header" = "Documentation for {REPOSITORY} {VERSION}" - "footer" = "Documentation for {REPOSITORY} made with AL-Go for GitHub, ALDoc and DocFx" - "defaultIndexMD" = "## Reference documentation\n\nThis is the generated reference documentation for [{REPOSITORY}](https://github.com/{REPOSITORY}).\n\nYou can use the navigation bar at the top and the table of contents to the left to navigate your documentation.\n\nYou can change this content by creating/editing the **{INDEXTEMPLATERELATIVEPATH}** file in your repository or use the alDoc:defaultIndexMD setting in your repository settings file (.github/AL-Go-Settings.json)\n\n{RELEASENOTES}" - "defaultReleaseMD" = "## Release reference documentation\n\nThis is the generated reference documentation for [{REPOSITORY}](https://github.com/{REPOSITORY}).\n\nYou can use the navigation bar at the top and the table of contents to the left to navigate your documentation.\n\nYou can change this content by creating/editing the **{INDEXTEMPLATERELATIVEPATH}** file in your repository or use the alDoc:defaultReleaseMD setting in your repository settings file (.github/AL-Go-Settings.json)\n\n{RELEASENOTES}" + "continuousDeployment" = $false + "deployToGitHubPages" = $true + "maxReleases" = 3 + "groupByProject" = $true + "includeProjects" = @() + "excludeProjects" = @() + "header" = "Documentation for {REPOSITORY} {VERSION}" + "footer" = "Documentation for {REPOSITORY} made with AL-Go for GitHub, ALDoc and DocFx" + "defaultIndexMD" = "## Reference documentation\n\nThis is the generated reference documentation for [{REPOSITORY}](https://github.com/{REPOSITORY}).\n\nYou can use the navigation bar at the top and the table of contents to the left to navigate your documentation.\n\nYou can change this content by creating/editing the **{INDEXTEMPLATERELATIVEPATH}** file in your repository or use the alDoc:defaultIndexMD setting in your repository settings file (.github/AL-Go-Settings.json)\n\n{RELEASENOTES}" + "defaultReleaseMD" = "## Release reference documentation\n\nThis is the generated reference documentation for [{REPOSITORY}](https://github.com/{REPOSITORY}).\n\nYou can use the navigation bar at the top and the table of contents to the left to navigate your documentation.\n\nYou can change this content by creating/editing the **{INDEXTEMPLATERELATIVEPATH}** file in your repository or use the alDoc:defaultReleaseMD setting in your repository settings file (.github/AL-Go-Settings.json)\n\n{RELEASENOTES}" } "trustMicrosoftNuGetFeeds" = $true "nuGetFeedSelectMode" = "LatestMatching" "commitOptions" = [ordered]@{ - "messageSuffix" = "" - "pullRequestAutoMerge" = $false - "pullRequestMergeMethod" = "squash" - "pullRequestLabels" = @() - "createPullRequest" = $true + "messageSuffix" = "" + "pullRequestAutoMerge" = $false + "pullRequestMergeMethod" = "squash" + "pullRequestLabels" = @() + "createPullRequest" = $true } "trustedSigning" = [ordered]@{ - "Endpoint" = "" - "Account" = "" - "CertificateProfile" = "" + "Endpoint" = "" + "Account" = "" + "CertificateProfile" = "" } "useGitSubmodules" = "false" "gitSubmodulesTokenSecretName" = "gitSubmodulesToken" "shortLivedArtifactsRetentionDays" = 1 # 0 means use GitHub default "reportSuppressedDiagnostics" = $false "workflowDefaultInputs" = @() - "customALGoFiles" = [ordered]@{ - "filesToInclude" = @() - "filesToExclude" = @() + "customALGoFiles" = [ordered]@{ + "filesToInclude" = @() + "filesToExclude" = @() } - "postponeProjectInBuildOrder" = $false - "importantSettings" = @() + "postponeProjectInBuildOrder" = $false + "importantSettings" = @() } } @@ -334,7 +326,7 @@ function GetDefaultSettings JSON formatted string that will be applied last to override any other settings. These settings have the highest precedence. #> function ReadSettings { - Param( + param( [string] $baseFolder = "$ENV:GITHUB_WORKSPACE", [string] $repoName = "$ENV:GITHUB_REPOSITORY", [string] $project = '.', @@ -356,7 +348,7 @@ function ReadSettings { } function GetSettingsObject { - Param( + param( [string] $path ) @@ -389,8 +381,8 @@ function ReadSettings { if ($orgSettingsVariableValue) { $orgSettingsVariableObject = $orgSettingsVariableValue | ConvertFrom-Json $settingsObjects += @{ - "Source" = "ALGoOrgSettings" - "Type" = "Variable" + "Source" = "ALGoOrgSettings" + "Type" = "Variable" "Settings" = $orgSettingsVariableObject } } @@ -398,16 +390,16 @@ function ReadSettings { # Read settings from the custom template repository settings file $customTemplateRepoSettingsObject = GetSettingsObject -Path (Join-Path $baseFolder $CustomTemplateRepoSettingsFile) $settingsObjects += @{ - "Source" = "$CustomTemplateRepoSettingsFile" - "Type" = "File" + "Source" = "$CustomTemplateRepoSettingsFile" + "Type" = "File" "Settings" = $customTemplateRepoSettingsObject } # Read settings from repository settings file $repoSettingsObject = GetSettingsObject -Path (Join-Path $baseFolder $RepoSettingsFile) $settingsObjects += @{ - "Source" = "$RepoSettingsFile" - "Type" = "File" + "Source" = "$RepoSettingsFile" + "Type" = "File" "Settings" = $repoSettingsObject } @@ -415,8 +407,8 @@ function ReadSettings { if ($repoSettingsVariableValue) { $repoSettingsVariableObject = $repoSettingsVariableValue | ConvertFrom-Json $settingsObjects += @{ - "Source" = "ALGoRepoSettings" - "Type" = "Variable" + "Source" = "ALGoRepoSettings" + "Type" = "Variable" "Settings" = $repoSettingsVariableObject } } @@ -424,8 +416,8 @@ function ReadSettings { if ($project) { $customTemplateProjectSettingsObject = GetSettingsObject -Path (Join-Path $baseFolder $CustomTemplateProjectSettingsFile) $settingsObjects += @{ - "Source" = "$CustomTemplateProjectSettingsFile" - "Type" = "File" + "Source" = "$CustomTemplateProjectSettingsFile" + "Type" = "File" "Settings" = $customTemplateProjectSettingsObject } @@ -433,8 +425,8 @@ function ReadSettings { $projectFolder = Join-Path $baseFolder $project -Resolve $projectSettingsObject = GetSettingsObject -Path (Join-Path $projectFolder $ALGoSettingsFile) $settingsObjects += @{ - "Source" = "$(Join-Path $project $ALGoSettingsFile)" - "Type" = "File" + "Source" = "$(Join-Path $project $ALGoSettingsFile)" + "Type" = "File" "Settings" = $projectSettingsObject } } @@ -443,8 +435,8 @@ function ReadSettings { # Read settings from workflow settings file $workflowSettingsObject = GetSettingsObject -Path (Join-Path $githubFolder "$workflowName.settings.json") $settingsObjects += @{ - "Source" = "$(Join-Path ".github" "$workflowName.settings.json")" - "Type" = "File" + "Source" = "$(Join-Path ".github" "$workflowName.settings.json")" + "Type" = "File" "Settings" = $workflowSettingsObject } @@ -452,16 +444,16 @@ function ReadSettings { # Read settings from project workflow settings file $projectWorkflowSettingsObject = GetSettingsObject -Path (Join-Path $projectFolder "$ALGoFolderName/$workflowName.settings.json") $settingsObjects += @{ - "Source" = "$(Join-Path $project "$ALGoFolderName/$workflowName.settings.json")" - "Type" = "File" + "Source" = "$(Join-Path $project "$ALGoFolderName/$workflowName.settings.json")" + "Type" = "File" "Settings" = $projectWorkflowSettingsObject } # Read settings from user settings file - $userSettingsObject = GetSettingsObject -Path (Join-Path $projectFolder "$ALGoFolderName/$userName.settings.json") + $userSettingsObject = GetSettingsObject -Path (Join-Path $projectFolder "$ALGoFolderName/$userName.settings.json") $settingsObjects += @{ - "Source" = "$(Join-Path $project "$ALGoFolderName/$userName.settings.json")" - "Type" = "File" + "Source" = "$(Join-Path $project "$ALGoFolderName/$userName.settings.json")" + "Type" = "File" "Settings" = $userSettingsObject } } @@ -484,8 +476,8 @@ function ReadSettings { } } $settingsObjects += @{ - "Source" = "ALGoEnvSettings for $environmentName" - "Type" = "Variable" + "Source" = "ALGoEnvSettings for $environmentName" + "Type" = "Variable" "Settings" = $environmentVariableObject } } @@ -499,26 +491,27 @@ function ReadSettings { throw "Failed to parse customSettings JSON: $($_.Exception.Message)" } $settingsObjects += @{ - "Source" = "CustomSettings" - "Type" = "Parameter" + "Source" = "CustomSettings" + "Type" = "Parameter" "Settings" = $customSettingsObject } } $currentImportantSettings = @() - foreach($settingsObject in $settingsObjects) { + foreach ($settingsObject in $settingsObjects) { $settingsJson = $settingsObject.Settings if ($settingsJson) { OutputDebug "Applying settings from $($settingsObject.Source) ($($settingsObject.Type))" - $importantAtThisLevel = @() - $importantRef = [ref]$importantAtThisLevel - MergeCustomObjectIntoOrderedDictionary -dst $settings -src $settingsJson -importantSettingsFromHigherPriority $currentImportantSettings -importantSettingsAtThisLevel $importantRef - # Only update currentImportantSettings if this level explicitly defined importantSettings (not null) - if ($null -ne $importantAtThisLevel) { - $currentImportantSettings = $importantAtThisLevel + $srcImportantSettings = @() + if ($settingsJson.PSObject.Properties.Name -contains "importantSettings") { + $srcImportantSettings = @($settingsJson.importantSettings) + } + MergeCustomObjectIntoOrderedDictionary -dst $settings -src $settingsJson -srcImportantSettings $srcImportantSettings -dstImportantSettings $currentImportantSettings + if ($settingsJson.PSObject.Properties.Name -contains "importantSettings") { + $currentImportantSettings = $srcImportantSettings } if ($settingsJson.PSObject.Properties.Name -eq "ConditionalSettings") { - foreach($conditionalSetting in $settingsJson.ConditionalSettings) { + foreach ($conditionalSetting in $settingsJson.ConditionalSettings) { if ("$conditionalSetting" -ne "") { $conditionMet = $true $conditions = @() @@ -528,7 +521,7 @@ function ReadSettings { if ($conditionMet -and $conditionalSetting.PSObject.Properties.Name -eq $propName) { # If the property name is workflows then we should sanitize the workflow name in the same way we sanitize the $workflowName variable - if($propName -eq "workflows") { + if ($propName -eq "workflows") { $conditionalSetting."$propName" = $conditionalSetting."$propName" | ForEach-Object { SanitizeWorkflowName -workflowName $_ } } @@ -538,12 +531,13 @@ function ReadSettings { } if ($conditionMet) { OutputDebug "Applying conditional settings for $($conditions -join ", ")" - $importantAtThisLevel = @() - $importantRef = [ref]$importantAtThisLevel - MergeCustomObjectIntoOrderedDictionary -dst $settings -src $conditionalSetting.settings -importantSettingsFromHigherPriority $currentImportantSettings -importantSettingsAtThisLevel $importantRef - # Only update currentImportantSettings if conditional settings explicitly defined importantSettings - if ($null -ne $importantAtThisLevel) { - $currentImportantSettings = $importantAtThisLevel + $srcImportantSettings = @() + if ($conditionalSetting.settings.PSObject.Properties.Name -contains "importantSettings") { + $srcImportantSettings = @($conditionalSetting.settings.importantSettings) + } + MergeCustomObjectIntoOrderedDictionary -dst $settings -src $conditionalSetting.settings -srcImportantSettings $srcImportantSettings -dstImportantSettings $currentImportantSettings + if ($conditionalSetting.settings.PSObject.Properties.Name -contains "importantSettings") { + $currentImportantSettings = $srcImportantSettings } } } diff --git a/Actions/.Modules/settings.schema.json b/Actions/.Modules/settings.schema.json index 950506e71..5667e10a5 100644 --- a/Actions/.Modules/settings.schema.json +++ b/Actions/.Modules/settings.schema.json @@ -395,7 +395,7 @@ "type": "string", "enum": ["includeDependencies"] }, - "description": "An array of settings to be overwritten by the current deliverToAppSource setting. See https://aka.ms/ALGoSettings#overwriteSettings" + "description": "An array of settings to be overwritten by the current deliverToAppSource setting. If a setting is marked as important in higher-priority settings, overwrite is ignored unless the current source also marks that same setting as important. See https://aka.ms/ALGoSettings#overwriteSettings" } }, "required": [ @@ -650,7 +650,7 @@ "type": "string", "enum": ["includeProjects", "excludeProjects"] }, - "description": "An array of settings to be overwritten by the current alDoc setting. See https://aka.ms/ALGoSettings#overwriteSettings" + "description": "An array of settings to be overwritten by the current alDoc setting. If a setting is marked as important in higher-priority settings, overwrite is ignored unless the current source also marks that same setting as important. See https://aka.ms/ALGoSettings#overwriteSettings" } }, "commitOptions": { @@ -680,7 +680,7 @@ "type": "string", "enum": ["pullRequestLabels"] }, - "description": "An array of settings to be overwritten by the current commitOptions setting. See https://aka.ms/ALGoSettings#overwriteSettings" + "description": "An array of settings to be overwritten by the current commitOptions setting. If a setting is marked as important in higher-priority settings, overwrite is ignored unless the current source also marks that same setting as important. See https://aka.ms/ALGoSettings#overwriteSettings" } }, "required": [ @@ -728,7 +728,7 @@ "type": "string", "enum": ["unusedALGoSystemFiles", "projects", "additionalCountries", "appDependencies", "appFolders", "testDependencies", "testFolders", "bcptTestFolders", "pageScriptingTests", "restoreDatabases", "installApps", "installTestApps", "customCodeCops", "configPackages", "appSourceCopMandatoryAffixes", "deliverToAppSource", "appDependencyProbingPaths", "incrementalBuilds", "environments", "buildModes", "bcptThresholds", "fullBuildPatterns", "excludeEnvironments", "alDoc", "commitOptions", "trustedSigning"] }, - "description": "An array of settings to be overwritten by the current settings. See https://aka.ms/ALGoSettings#overwriteSettings" + "description": "An array of settings to be overwritten by the current settings. If a setting is marked as important in higher-priority settings, overwrite is ignored unless the current source also marks that same setting as important. See https://aka.ms/ALGoSettings#overwriteSettings" }, "importantSettings": { "type": "array", @@ -736,7 +736,7 @@ "type": "string" }, "default": [], - "description": "An array of setting names that should take precedence over normal inheritance hierarchy. Settings listed here will override settings from lower priority sources. See https://aka.ms/ALGoSettings#importantSettings" + "description": "An array of top-level setting names that are protected from non-important lower-priority overrides. If the same setting is marked as important in both source and destination, the source value is allowed to override. See https://aka.ms/ALGoSettings#importantSettings" }, "reportSuppressedDiagnostics": { "type": "boolean", diff --git a/RELEASENOTES.md b/RELEASENOTES.md index ba066a73c..14c874dec 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -11,10 +11,10 @@ A new `importantSettings` setting allows you to protect specific settings from b ``` **Behavior:** -- Settings marked as important in organization or repository settings cannot be overridden by project, workflow, user, or environment settings -- Important settings from higher priority sources always win, even if lower levels mark the same setting as important -- Important arrays are still merged (not replaced) with lower-priority arrays, unless `overwriteSettings` is explicitly used -- The `overwriteSettings` mechanism can override important settings when explicitly specified +- Settings marked as important in organization or repository settings cannot be overridden by non-important values from project, workflow, user, or environment settings +- If a lower-priority source also marks the same setting as important, the lower-priority value is allowed to override +- Important arrays are still merged by default +- The `overwriteSettings` mechanism can replace an important setting only when the source also marks that same setting as important - `ConditionalSettings` respect importantSettings markings, allowing you to enforce conditional important settings based on buildMode, branch, trigger, or user **Example with ConditionalSettings:** diff --git a/Scenarios/settings.md b/Scenarios/settings.md index 71381e07e..1199bfd1f 100644 --- a/Scenarios/settings.md +++ b/Scenarios/settings.md @@ -320,7 +320,7 @@ then, after merging, the result settings object will contain the following value By default, AL-Go follows a standard settings hierarchy where settings from higher priority levels (closer to deployment) override settings from lower priority levels. However, you can mark specific settings as **important** to protect them from being overridden by lower priority settings using the `importantSettings` array. -When a setting is marked as important at a higher level in the hierarchy, it cannot be overridden by settings from lower priority levels. This is useful for enforcing organizational or repository-wide policies that should not be changed at the project or workflow level. +When a setting is marked as important at a higher level in the hierarchy, it cannot be overridden by non-important values from lower priority levels. If a lower-priority source also marks the same setting as important, then the lower-priority value is allowed to override. _Example_: Say, `ALGoOrgSettings` (organization level) contains the following values: @@ -389,9 +389,9 @@ When reading settings for buildMode `ValidateUS`, the conditional setting from t } ``` -Even though the project specifies `country: "w1"`, the conditional setting from the organization level marked the country as important for the `ValidateUS` buildMode, so it takes precedence. +Even though the project specifies `country: "w1"`, the conditional setting from the organization level marked the country as important for the `ValidateUS` buildMode and the project value is not marked important, so the conditional value takes precedence. -> _**Note**_: `importantSettings` is an array of setting names that should be protected from being overridden by lower priority settings. When a setting is marked as important at a higher level, it will not be overridden by normal (non-important) settings from lower levels. If a setting is marked as important at multiple levels, the value from the highest priority level wins (following normal hierarchy rules). Only top-level setting names can be marked as important; nested properties within complex objects cannot be individually marked as important. Array settings marked as important are still merged with lower-priority arrays, unless `overwriteSettings` is used to force replacement. +> _**Note**_: `importantSettings` is an array of setting names that should be protected from non-important overrides from lower priority settings. If the same setting is marked as important at both levels, the source (lower-priority) value is allowed to override the destination value. Only top-level setting names can be marked as important; nested properties within complex objects cannot be individually marked as important. Array settings marked as important are still merged with lower-priority arrays. `overwriteSettings` can force replacement for important settings only when the source also marks that same setting as important. diff --git a/Tests/ReadSettings.Test.ps1 b/Tests/ReadSettings.Test.ps1 index bf6347742..2a426d01f 100644 --- a/Tests/ReadSettings.Test.ps1 +++ b/Tests/ReadSettings.Test.ps1 @@ -615,7 +615,7 @@ InModuleScope ReadSettings { # Allows testing of private functions Remove-Item -Path $tempName -Recurse -Force } - It 'Normal settings from higher priority level override important settings from lower priority' { + It 'Lower-priority important settings can override higher-priority important settings' { Mock Write-Host { } Mock Out-Host { } @@ -646,10 +646,10 @@ InModuleScope ReadSettings { # Allows testing of private functions } | ConvertTo-Json -Depth 99 | Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -Encoding utf8 -Force - # Important setting from Org (highest level) should win: expected "de" - # (even though Project marks it as important, Org's important setting has higher priority) + # Project setting is also marked as important and should be allowed to override + # an important setting from a higher-priority source. $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' - $settings.country | Should -Be 'de' # Org important wins over all others + $settings.country | Should -Be 'ch' # Source important overrides destination important $ENV:ALGoOrgSettings = '' @@ -696,7 +696,7 @@ InModuleScope ReadSettings { # Allows testing of private functions Remove-Item -Path $tempName -Recurse -Force } - It 'importantSettings can be overridden using overwriteSettings in lower priority' { + It 'importantSettings are not overridden by overwriteSettings unless source also marks them important' { Mock Write-Host { } Mock Out-Host { } @@ -725,16 +725,16 @@ InModuleScope ReadSettings { # Allows testing of private functions $ENV:ALGoOrgSettings = '' $ENV:ALGoRepoSettings = '' - # When overwriteSettings is used, it overrides the importantSettings behavior + # overwriteSettings should be ignored because source does not mark the setting as important. $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' - $settings.additionalCountries | Should -Be @("ch", "be") # Only project values (org values replaced) + $settings.additionalCountries | Should -Be @("de", "at", "ch", "be") # Clean up Pop-Location Remove-Item -Path $tempName -Recurse -Force } - It 'Non-important array settings are merged normally (baseline)' { + It 'importantSettings can be overridden with overwriteSettings when source also marks them important' { Mock Write-Host { } Mock Out-Host { } @@ -746,14 +746,15 @@ InModuleScope ReadSettings { # Allows testing of private functions New-Item $githubFolder -ItemType Directory | Out-Null New-Item $projectALGoFolder -ItemType Directory | Out-Null - # Org settings: additionalCountries WITHOUT marking as important @{ + "importantSettings" = @("additionalCountries") "additionalCountries" = @("de", "at") } | ConvertTo-Json -Depth 99 | Set-Content -Path (Join-Path $githubFolder "AL-Go-Settings.json") -Encoding utf8 -Force - # Project settings: add more countries @{ + "importantSettings" = @("additionalCountries") + "overwriteSettings" = @("additionalCountries") "additionalCountries" = @("ch", "be") } | ConvertTo-Json -Depth 99 | Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -Encoding utf8 -Force @@ -761,16 +762,14 @@ InModuleScope ReadSettings { # Allows testing of private functions $ENV:ALGoOrgSettings = '' $ENV:ALGoRepoSettings = '' - # Without important marking, arrays should be merged $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' - $settings.additionalCountries | Should -Be @("de", "at", "ch", "be") # All values merged + $settings.additionalCountries | Should -Be @("ch", "be") - # Clean up Pop-Location Remove-Item -Path $tempName -Recurse -Force } - It 'Empty importantSettings has no effect (backward compatibility)' { + It 'Non-important array settings are merged normally (baseline)' { Mock Write-Host { } Mock Out-Host { } @@ -782,70 +781,62 @@ InModuleScope ReadSettings { # Allows testing of private functions New-Item $githubFolder -ItemType Directory | Out-Null New-Item $projectALGoFolder -ItemType Directory | Out-Null - # Org settings: importantSettings is empty + # Org settings: additionalCountries WITHOUT marking as important @{ - "importantSettings" = @() - "country" = "us" + "additionalCountries" = @("de", "at") } | ConvertTo-Json -Depth 99 | Set-Content -Path (Join-Path $githubFolder "AL-Go-Settings.json") -Encoding utf8 -Force - # Project settings: override country + # Project settings: add more countries @{ - "country" = "ch" + "additionalCountries" = @("ch", "be") } | ConvertTo-Json -Depth 99 | - Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -encoding utf8 -Force + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -Encoding utf8 -Force $ENV:ALGoOrgSettings = '' $ENV:ALGoRepoSettings = '' - # Without important marking, normal hierarchy applies + # Without important marking, arrays should be merged $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' - $settings.country | Should -Be 'ch' # Project wins (normal behavior) - $settings.importantSettings | Should -Be @() # Empty array preserved + $settings.additionalCountries | Should -Be @("de", "at", "ch", "be") # All values merged # Clean up Pop-Location Remove-Item -Path $tempName -Recurse -Force } - It 'importantSettings in ConditionalSetting prevents override from repo settings' { + It 'Empty importantSettings has no effect (backward compatibility)' { Mock Write-Host { } Mock Out-Host { } Push-Location $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) $githubFolder = Join-Path $tempName ".github" + $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" New-Item $githubFolder -ItemType Directory | Out-Null + New-Item $projectALGoFolder -ItemType Directory | Out-Null - # Repo settings: set country to "us" (no important marking) + # Org settings: importantSettings is empty @{ - "country" = "us" + "importantSettings" = @() + "country" = "us" } | ConvertTo-Json -Depth 99 | Set-Content -Path (Join-Path $githubFolder "AL-Go-Settings.json") -Encoding utf8 -Force - # Workflow settings with ConditionalSetting for buildModes = "Clean" - # The conditional setting marks country as important and sets it to "de" + # Project settings: override country @{ - "conditionalSettings" = @( - @{ - "buildModes" = @("Clean") - "settings" = @{ - "importantSettings" = @("country") - "country" = "de" - } - } - ) + "country" = "ch" } | ConvertTo-Json -Depth 99 | - Set-Content -Path (Join-Path $githubFolder "Workflow.settings.json") -Encoding utf8 -Force + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -encoding utf8 -Force $ENV:ALGoOrgSettings = '' $ENV:ALGoRepoSettings = '' - # When reading settings for buildMode "Clean", the country from ConditionalSetting (de) with important marking should win - $settings = ReadSettings -baseFolder $tempName -project '' -repoName 'repo' -workflowName 'Workflow' -branchName '' -buildMode 'Clean' -userName '' - $settings.country | Should -Be 'de' # Conditional important setting wins over repo setting - $settings.importantSettings | Should -Contain 'country' + # Without important marking, normal hierarchy applies + $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' + $settings.country | Should -Be 'ch' # Project wins (normal behavior) + $settings.importantSettings | Should -Be @() # Empty array preserved # Clean up Pop-Location From 26015132f5cb66555206d4a69a080adafec92bbd Mon Sep 17 00:00:00 2001 From: Christoph Blank Date: Mon, 29 Jun 2026 14:45:42 +0200 Subject: [PATCH 4/6] Refactor importantSettings handling in ReadSettings and update tests for clarity --- Actions/.Modules/ReadSettings.psm1 | 7 ++- Tests/ReadSettings.Test.ps1 | 80 ++++++++++++++++++++++++------ 2 files changed, 68 insertions(+), 19 deletions(-) diff --git a/Actions/.Modules/ReadSettings.psm1 b/Actions/.Modules/ReadSettings.psm1 index 94f1409ea..38bd6a494 100644 --- a/Actions/.Modules/ReadSettings.psm1 +++ b/Actions/.Modules/ReadSettings.psm1 @@ -82,7 +82,6 @@ function MergeCustomObjectIntoOrderedDictionary { # For non-array properties: skip if this setting is marked as important from higher priority source, # unless the lower-priority source also marks this property as important. if ($dstImportantSettings -contains $prop -and $srcPropType -ne "Object[]" -and $srcImportantSettings -notcontains $prop) { - #$dstImportantSettings : better would be dest. OutputDebug "Skipping important setting '$prop' marked from higher priority source (non-array type)" return } @@ -109,7 +108,7 @@ function MergeCustomObjectIntoOrderedDictionary { } else { # Add source element to destination array, but only if it does not already exist - $dst."$prop" = @($dst."$prop" + $srcElm | Select-Object -Unique) #TODO: nur wenn dieses property hier auch definiert wurde. + $dst."$prop" = @($dst."$prop" + $srcElm | Select-Object -Unique) } } } @@ -508,7 +507,7 @@ function ReadSettings { } MergeCustomObjectIntoOrderedDictionary -dst $settings -src $settingsJson -srcImportantSettings $srcImportantSettings -dstImportantSettings $currentImportantSettings if ($settingsJson.PSObject.Properties.Name -contains "importantSettings") { - $currentImportantSettings = $srcImportantSettings + $currentImportantSettings = @($currentImportantSettings + $srcImportantSettings | Select-Object -Unique) } if ($settingsJson.PSObject.Properties.Name -eq "ConditionalSettings") { foreach ($conditionalSetting in $settingsJson.ConditionalSettings) { @@ -537,7 +536,7 @@ function ReadSettings { } MergeCustomObjectIntoOrderedDictionary -dst $settings -src $conditionalSetting.settings -srcImportantSettings $srcImportantSettings -dstImportantSettings $currentImportantSettings if ($conditionalSetting.settings.PSObject.Properties.Name -contains "importantSettings") { - $currentImportantSettings = $srcImportantSettings + $currentImportantSettings = @($currentImportantSettings + $srcImportantSettings | Select-Object -Unique) } } } diff --git a/Tests/ReadSettings.Test.ps1 b/Tests/ReadSettings.Test.ps1 index 2a426d01f..ec2baa6ba 100644 --- a/Tests/ReadSettings.Test.ps1 +++ b/Tests/ReadSettings.Test.ps1 @@ -553,20 +553,20 @@ InModuleScope ReadSettings { # Allows testing of private functions New-Item $githubFolder -ItemType Directory | Out-Null New-Item $projectALGoFolder -ItemType Directory | Out-Null - # Org settings: important settings includes "country", set country = "de" + # Repo settings: important settings includes "country", set country = "de" @{ "importantSettings" = @("country"); "country" = "de" } | ConvertTo-Json -Depth 99 | - Set-Content -Path (Join-Path $githubFolder "AL-Go-Settings.json") -encoding utf8 -Force + Set-Content -Path (Join-Path $githubFolder "AL-Go-Settings.json") -Encoding utf8 -Force # Project settings: try to override country = "ch" @{ "country" = "ch" } | ConvertTo-Json -Depth 99 | - Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -encoding utf8 -Force + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -Encoding utf8 -Force $ENV:ALGoOrgSettings = '' $ENV:ALGoRepoSettings = '' # Important setting from repo should prevent project from overwriting $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' - $settings.country | Should -Be 'de' # Org/Repo important value wins + $settings.country | Should -Be 'de' # Repo important value wins $settings.importantSettings | Should -Contain 'country' # Clean up @@ -586,7 +586,7 @@ InModuleScope ReadSettings { # Allows testing of private functions New-Item $githubFolder -ItemType Directory | Out-Null New-Item $projectALGoFolder -ItemType Directory | Out-Null - # Org settings: mark both country and keyVaultName as important + # Repo settings: mark both country and keyVaultName as important @{ "importantSettings" = @("country", "keyVaultName") "country" = "de" @@ -670,7 +670,7 @@ InModuleScope ReadSettings { # Allows testing of private functions New-Item $githubFolder -ItemType Directory | Out-Null New-Item $projectALGoFolder -ItemType Directory | Out-Null - # Org settings: mark additionalCountries as important with specific values + # Repo settings: mark additionalCountries as important with specific values @{ "importantSettings" = @("additionalCountries") "additionalCountries" = @("de", "at") @@ -708,7 +708,7 @@ InModuleScope ReadSettings { # Allows testing of private functions New-Item $githubFolder -ItemType Directory | Out-Null New-Item $projectALGoFolder -ItemType Directory | Out-Null - # Org settings: mark additionalCountries as important + # Repo settings: mark additionalCountries as important @{ "importantSettings" = @("additionalCountries") "additionalCountries" = @("de", "at") @@ -781,7 +781,7 @@ InModuleScope ReadSettings { # Allows testing of private functions New-Item $githubFolder -ItemType Directory | Out-Null New-Item $projectALGoFolder -ItemType Directory | Out-Null - # Org settings: additionalCountries WITHOUT marking as important + # Repo settings: additionalCountries WITHOUT marking as important @{ "additionalCountries" = @("de", "at") } | ConvertTo-Json -Depth 99 | @@ -817,7 +817,7 @@ InModuleScope ReadSettings { # Allows testing of private functions New-Item $githubFolder -ItemType Directory | Out-Null New-Item $projectALGoFolder -ItemType Directory | Out-Null - # Org settings: importantSettings is empty + # Repo settings: importantSettings is empty @{ "importantSettings" = @() "country" = "us" @@ -828,7 +828,7 @@ InModuleScope ReadSettings { # Allows testing of private functions @{ "country" = "ch" } | ConvertTo-Json -Depth 99 | - Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -encoding utf8 -Force + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -Encoding utf8 -Force $ENV:ALGoOrgSettings = '' $ENV:ALGoRepoSettings = '' @@ -843,7 +843,7 @@ InModuleScope ReadSettings { # Allows testing of private functions Remove-Item -Path $tempName -Recurse -Force } - It 'ConditionalSetting with importantSettings at org level overrides project setting for specific buildMode' { + It 'ConditionalSetting with importantSettings at repo level overrides project setting for specific buildMode' { Mock Write-Host { } Mock Out-Host { } @@ -855,12 +855,12 @@ InModuleScope ReadSettings { # Allows testing of private functions New-Item $githubFolder -ItemType Directory | Out-Null New-Item $projectALGoFolder -ItemType Directory | Out-Null - # Org settings: ConditionalSetting for buildMode "ValidateUS" with important country marking + # Repo settings: ConditionalSetting for buildMode "ValidateUS" with important country marking @{ "ConditionalSettings" = @( @{ "buildModes" = @("ValidateUS") - "settings" = @{ + "settings" = @{ "importantSettings" = @("country") "country" = "us" } @@ -883,12 +883,62 @@ InModuleScope ReadSettings { # Allows testing of private functions $settingsDefault = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -buildMode 'Default' -userName '' $settingsDefault.country | Should -Be 'w1' # No org conditional applies for "Default" - # When reading for buildMode "ValidateUS", org conditional with important marking should override project + # When reading for buildMode "ValidateUS", repo conditional with important marking should override project $settingsValidateUS = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -buildMode 'ValidateUS' -userName '' - $settingsValidateUS.country | Should -Be 'us' # Org conditional important setting wins + $settingsValidateUS.country | Should -Be 'us' # Repo conditional important setting wins $settingsValidateUS.buildModes | Should -Contain 'ValidateUS' $settingsValidateUS.importantSettings | Should -Contain 'country' + # Clean up + Pop-Location + Remove-Item -Path $tempName -Recurse -Force + } + + It 'importantSettings are merged correctly' { + Mock Write-Host { } + Mock Out-Host { } + + Push-Location + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + $githubFolder = Join-Path $tempName ".github" + $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" + + New-Item $githubFolder -ItemType Directory | Out-Null + New-Item $projectALGoFolder -ItemType Directory | Out-Null + + # Org settings: importantSettings is filled + $ENV:ALGoOrgSettings = @{ + "importantSettings" = @("country") + "country" = "us" + } | ConvertTo-Json -Depth 99 + + # Repo settings: add another important setting + $ENV:ALGoRepoSettings = @{ + "importantSettings" = @("companyName") + "country" = "de" + "companyName" = "MyCompany" + } | ConvertTo-Json -Depth 99 + + # Project settings: add another important setting + @{ + "importantSettings" = @("keyVaultName") + "country" = "ch" + "keyVaultName" = "mykv" + "companyName" = "AnotherCompany" + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -Encoding utf8 -Force + + # Without important marking, normal hierarchy applies + $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' + $settings.importantSettings | Should -Contain 'country' # from repo settings + $settings.importantSettings | Should -Contain 'companyName' # from repo settings + $settings.importantSettings | Should -Contain 'keyVaultName' # from project settings + + $settings.country | Should -Be 'us' # from org settings + $settings.companyName | Should -Be 'MyCompany' # from repo settings + $settings.keyVaultName | Should -Be 'mykv' # from project settings + + # Clean up Pop-Location Remove-Item -Path $tempName -Recurse -Force From 428e1d8bf6752a402cf74590473368b8b6764fe6 Mon Sep 17 00:00:00 2001 From: Christoph Blank Date: Mon, 29 Jun 2026 14:49:58 +0200 Subject: [PATCH 5/6] Remove unnecessary directory creation for ALGoFolder in ReadSettings tests --- Tests/ReadSettings.Test.ps1 | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/ReadSettings.Test.ps1 b/Tests/ReadSettings.Test.ps1 index ec2baa6ba..2396a96a0 100644 --- a/Tests/ReadSettings.Test.ps1 +++ b/Tests/ReadSettings.Test.ps1 @@ -622,11 +622,9 @@ InModuleScope ReadSettings { # Allows testing of private functions Push-Location $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) $githubFolder = Join-Path $tempName ".github" - $ALGoFolder = Join-Path $tempName $ALGoFolderName $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" New-Item $githubFolder -ItemType Directory | Out-Null - New-Item $ALGoFolder -ItemType Directory | Out-Null New-Item $projectALGoFolder -ItemType Directory | Out-Null # Org settings (via variable): mark country as important and set to "de" From f4b42e652fda9ca3a8e4f1cba8195aa32de68d68 Mon Sep 17 00:00:00 2001 From: Christoph Blank Date: Mon, 29 Jun 2026 16:08:21 +0200 Subject: [PATCH 6/6] Add tests for merging conditional importantSettings in ReadSettings --- Tests/ReadSettings.Test.ps1 | 126 ++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/Tests/ReadSettings.Test.ps1 b/Tests/ReadSettings.Test.ps1 index 2396a96a0..ef8b8b450 100644 --- a/Tests/ReadSettings.Test.ps1 +++ b/Tests/ReadSettings.Test.ps1 @@ -937,6 +937,132 @@ InModuleScope ReadSettings { # Allows testing of private functions $settings.keyVaultName | Should -Be 'mykv' # from project settings + # Clean up + Pop-Location + Remove-Item -Path $tempName -Recurse -Force + } + It 'conditional importantSettings are merged correctly' { + Mock Write-Host { } + Mock Out-Host { } + + Push-Location + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + $githubFolder = Join-Path $tempName ".github" + $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" + + New-Item $githubFolder -ItemType Directory | Out-Null + New-Item $projectALGoFolder -ItemType Directory | Out-Null + + # Org settings: importantSettings is filled + $ENV:ALGoOrgSettings = @{ + "ConditionalSettings" = @( + @{ + "buildModes" = @("CustomBuildMode") + "settings" = @{ + "importantSettings" = @("country") + "country" = "us" + } + }) + } | ConvertTo-Json -Depth 99 + + # Repo settings: add another important setting + $ENV:ALGoRepoSettings = @{ + "ConditionalSettings" = @( + @{ + "buildModes" = @("CustomBuildMode") + "settings" = @{ "importantSettings" = @("companyName") + "country" = "de" + "companyName" = "MyCompany" + } + }) + } | ConvertTo-Json -Depth 99 + + # Project settings: add another important setting + @{ + "ConditionalSettings" = @( + @{ + "buildModes" = @("CustomBuildMode") + "settings" = @{ "importantSettings" = @("keyVaultName") + "country" = "ch" + "keyVaultName" = "mykv" + "companyName" = "AnotherCompany" + } + }) + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -Encoding utf8 -Force + + # Without important marking, normal hierarchy applies + $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' -buildMode 'CustomBuildMode' + $settings.importantSettings | Should -Contain 'country' # from repo settings + $settings.importantSettings | Should -Contain 'companyName' # from repo settings + $settings.importantSettings | Should -Contain 'keyVaultName' # from project settings + + $settings.country | Should -Be 'us' # from org settings + $settings.companyName | Should -Be 'MyCompany' # from repo settings + $settings.keyVaultName | Should -Be 'mykv' # from project settings + + + # Clean up + Pop-Location + Remove-Item -Path $tempName -Recurse -Force + } + It 'mixed importantSettings are merged correctly' { + Mock Write-Host { } + Mock Out-Host { } + + Push-Location + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + $githubFolder = Join-Path $tempName ".github" + $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" + + New-Item $githubFolder -ItemType Directory | Out-Null + New-Item $projectALGoFolder -ItemType Directory | Out-Null + + # Org settings: importantSettings is filled + $ENV:ALGoOrgSettings = @{ + "ConditionalSettings" = @( + @{ + "buildModes" = @("CustomBuildMode") + "settings" = @{ + "importantSettings" = @("country") + "country" = "us" + } + }) + } | ConvertTo-Json -Depth 99 + + # Repo settings: add another important setting + $ENV:ALGoRepoSettings = @{ + "importantSettings" = @("companyName") + "country" = "de" + "companyName" = "MyCompany" + + } | ConvertTo-Json -Depth 99 + + # Project settings: add another important setting + @{ + "ConditionalSettings" = @( + @{ + "buildModes" = @("CustomBuildMode") + "settings" = @{ "importantSettings" = @("keyVaultName") + "country" = "ch" + "keyVaultName" = "mykv" + "companyName" = "AnotherCompany" + } + }) + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -Encoding utf8 -Force + + # Without important marking, normal hierarchy applies + $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' -buildMode 'CustomBuildMode' + $settings.importantSettings | Should -Contain 'country' # from repo settings + $settings.importantSettings | Should -Contain 'companyName' # from repo settings + $settings.importantSettings | Should -Contain 'keyVaultName' # from project settings + + $settings.country | Should -Be 'us' # from org settings + $settings.companyName | Should -Be 'MyCompany' # from repo settings + $settings.keyVaultName | Should -Be 'mykv' # from project settings + + # Clean up Pop-Location Remove-Item -Path $tempName -Recurse -Force