Skip to content

Commit 8c51fa4

Browse files
freddydkmazhelezPatrickSchieferps610jwikman
authored
Add support for custom templates + customizations (#853)
Changes included in this PR: - Support custom template repository = Point to an existing AL-Go repository as a template, like: ![image](https://github.com/microsoft/AL-Go/assets/10775043/683a394b-3a9e-42b7-acce-8ab39b76a74f) In this case, org/myptetemplate is a standard AL-Go repository, which gets updated as any other AL-Go repository using Update AL-Go System Files. This is a way of standardizing your repository based on type specific AL-Go templates (which can be private or public). Normally, Update AL-Go System Files follows this mechanism: | File Type | Repository file(s) | AL-Go Template | | :-- | :-- | :-: | | Workflows | .github/workflows/*.yaml | <- | | Repo Scripts | .github/*.ps1 | <- | | Project Scripts | .AL-Go/*.ps1 | <- | Workflows and scripts are read from the template repository and modified based on settings and saved to the repository. Using a custom template repository, the picture looks like this: | File Type | Repository file(s) | Custom Template | AL-Go Template | | :-- | :-- | :-: | :-: | | Workflows | .github/workflows/*.yaml | <- Customizations | <- | | Workflows | .github/workflows/*.yaml | <- New files | | | Repo Scripts | .github/*.ps1 | | <- | | Repo Scripts | .github/*.ps1 | <- New files | | | Repo Settings | .github/templateRepoSettings.doNotEdit.json | <- .github/AL-Go-Settings.json | | | Project Scripts | .AL-Go/*.ps1 | | <- | | Project Scripts | .AL-Go/*.ps1 | <- New files | | | Project Settings | .github/templateProjectSettings.doNotEdit.json | <- .AL-Go/settings.json | | Workflows and scripts are read from the "real" template repository and modified based on settings. Customizations for workflows in the custom template repository are applied. New workflows and scripts from the custom template repository are copied over. Repo and Project settings from the custom template repository are copied over to new settings files, which are applied before the final repos repo and project settings in order to allow the indirect template to add new defaults to settings to be applied to all repositories. The final repository can also contain customizations to workflows and the winner here is the indirect template repository (if a custom job exists in both places). This PR: freddydk/customized#17 - is an example of a repository with customizations of CI/CD and _BuildALGoProject, which runs Update AL-Go System Files and gets customizations from the indirect template repository (both settings, scripts and workflow customizations) Documentation on customization capabilities is here: https://github.com/freddydk/AL-Go/blob/customize/Scenarios/CustomizingALGoForGitHub.md --------- Co-authored-by: freddydk <[email protected]> Co-authored-by: Maria Zhelezova <[email protected]> Co-authored-by: Patrick Schiefer <[email protected]> Co-authored-by: Philipp Stöhr <[email protected]> Co-authored-by: Johannes Wikman <[email protected]> Co-authored-by: Alexander Holstrup <[email protected]>
1 parent a9d3f75 commit 8c51fa4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1102
-111
lines changed

.github/workflows/CI.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ jobs:
1919
runs-on: [ ubuntu-latest ]
2020
steps:
2121
- name: Harden Runner
22+
if: github.repository_owner == 'microsoft'
2223
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
2324
with:
2425
egress-policy: audit

.github/workflows/CleanupTempRepos.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ jobs:
2626
name: Cleanup Temp Repos
2727
steps:
2828
- name: Harden Runner
29+
if: github.repository_owner == 'microsoft'
2930
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
3031
with:
3132
egress-policy: audit

.github/workflows/Deploy.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ jobs:
5353
defaultBcContainerHelperVersion: ${{ steps.CreateInputs.outputs.defaultBcContainerHelperVersion }}
5454
steps:
5555
- name: Harden Runner
56+
if: github.repository_owner == 'microsoft'
5657
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
5758
with:
5859
egress-policy: audit
@@ -85,6 +86,7 @@ jobs:
8586
needs: [ Inputs ]
8687
steps:
8788
- name: Harden Runner
89+
if: github.repository_owner == 'microsoft'
8890
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
8991
with:
9092
egress-policy: audit
@@ -126,6 +128,7 @@ jobs:
126128
contents: write
127129
steps:
128130
- name: Harden Runner
131+
if: github.repository_owner == 'microsoft'
129132
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
130133
with:
131134
egress-policy: audit

.github/workflows/E2E.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ jobs:
5252
githubOwner: ${{ steps.check.outputs.githubOwner }}
5353
steps:
5454
- name: Harden Runner
55+
if: github.repository_owner == 'microsoft'
5556
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
5657
with:
5758
egress-policy: audit
@@ -113,6 +114,7 @@ jobs:
113114
appSourceAppRepo: ${{ steps.setup.outputs.appSourceAppRepo }}
114115
steps:
115116
- name: Harden Runner
117+
if: github.repository_owner == 'microsoft'
116118
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
117119
with:
118120
egress-policy: audit
@@ -147,6 +149,7 @@ jobs:
147149
scenarios: ${{ steps.Analyze.outputs.scenarios }}
148150
steps:
149151
- name: Harden Runner
152+
if: github.repository_owner == 'microsoft'
150153
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
151154
with:
152155
egress-policy: audit
@@ -236,6 +239,7 @@ jobs:
236239
strategy: ${{ fromJson(needs.Analyze.outputs.scenarios) }}
237240
steps:
238241
- name: Harden Runner
242+
if: github.repository_owner == 'microsoft'
239243
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
240244
with:
241245
egress-policy: audit
@@ -272,6 +276,7 @@ jobs:
272276
strategy: ${{ fromJson(needs.Analyze.outputs.scenarios) }}
273277
steps:
274278
- name: Harden Runner
279+
if: github.repository_owner == 'microsoft'
275280
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
276281
with:
277282
egress-policy: audit
@@ -308,6 +313,7 @@ jobs:
308313
strategy: ${{ fromJson(needs.Analyze.outputs.publictestruns) }}
309314
steps:
310315
- name: Harden Runner
316+
if: github.repository_owner == 'microsoft'
311317
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
312318
with:
313319
egress-policy: audit
@@ -356,6 +362,7 @@ jobs:
356362
strategy: ${{ fromJson(needs.Analyze.outputs.privatetestruns) }}
357363
steps:
358364
- name: Harden Runner
365+
if: github.repository_owner == 'microsoft'
359366
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
360367
with:
361368
egress-policy: audit
@@ -404,6 +411,7 @@ jobs:
404411
strategy: ${{ fromJson(needs.Analyze.outputs.releases) }}
405412
steps:
406413
- name: Harden Runner
414+
if: github.repository_owner == 'microsoft'
407415
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
408416
with:
409417
egress-policy: audit

.github/workflows/powershell.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ jobs:
2121
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
2222
steps:
2323
- name: Harden Runner
24+
if: github.repository_owner == 'microsoft'
2425
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
2526
with:
2627
egress-policy: audit

.github/workflows/pre-commit.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ jobs:
1414
runs-on: windows-latest
1515
steps:
1616
- name: Harden Runner
17+
if: github.repository_owner == 'microsoft'
1718
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
1819
with:
1920
egress-policy: audit

.github/workflows/scorecard-analysis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ jobs:
1818

1919
steps:
2020
- name: Harden Runner
21+
if: github.repository_owner == 'microsoft'
2122
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
2223
with:
2324
egress-policy: audit

Actions/AL-Go-Helper.ps1

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-S
1313
$ALGoFolderName = '.AL-Go'
1414
$ALGoSettingsFile = Join-Path '.AL-Go' 'settings.json'
1515
$RepoSettingsFile = Join-Path '.github' 'AL-Go-Settings.json'
16+
$CustomTemplateRepoSettingsFile = Join-Path '.github' 'AL-Go-TemplateRepoSettings.doNotEdit.json'
17+
$CustomTemplateProjectSettingsFile = Join-Path '.github' 'AL-Go-TemplateProjectSettings.doNotEdit.json'
1618
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'defaultCICDPushBranches', Justification = 'False positive.')]
1719
$defaultCICDPushBranches = @( 'main', 'release/*', 'feature/*' )
1820
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'defaultCICDPullRequestBranches', Justification = 'False positive.')]
1921
$defaultCICDPullRequestBranches = @( 'main' )
2022
$runningLocal = $local.IsPresent
2123
$defaultBcContainerHelperVersion = "preview" # Must be double quotes. Will be replaced by BcContainerHelperVersion if necessary in the deploy step - ex. "https://github.com/organization/navcontainerhelper/archive/refs/heads/branch.zip"
22-
$notSecretProperties = @("Scopes","TenantId","BlobName","ContainerName","StorageAccountName","ServerUrl","ppUserName","GitHubAppClientId")
24+
$notSecretProperties = @("Scopes","TenantId","BlobName","ContainerName","StorageAccountName","ServerUrl","ppUserName","GitHubAppClientId","EnvironmentName")
2325

2426
$runAlPipelineOverrides = @(
2527
"DockerPull"
@@ -38,6 +40,8 @@ $runAlPipelineOverrides = @(
3840
"InstallMissingDependencies"
3941
"PreCompileApp"
4042
"PostCompileApp"
43+
"PipelineInitialize"
44+
"PipelineFinalize"
4145
)
4246

4347
# Well known AppIds
@@ -676,13 +680,15 @@ function GetDefaultSettings
676680

677681
# Read settings from the settings files
678682
# Settings are read from the following files:
679-
# - ALGoOrgSettings (github Variable) = Organization settings variable
680-
# - .github/AL-Go-Settings.json = Repository Settings file
681-
# - ALGoRepoSettings (github Variable) = Repository settings variable
682-
# - <project>/.AL-Go/settings.json = Project settings file
683-
# - .github/<workflowName>.settings.json = Workflow settings file
684-
# - <project>/.AL-Go/<workflowName>.settings.json = Project workflow settings file
685-
# - <project>/.AL-Go/<userName>.settings.json = User settings file
683+
# - ALGoOrgSettings (github Variable) = Organization settings variable
684+
# - .github/AL-Go-TemplateRepoSettings.doNotEdit.json = Repository settings from custom template
685+
# - .github/AL-Go-Settings.json = Repository Settings file
686+
# - ALGoRepoSettings (github Variable) = Repository settings variable
687+
# - .github/AL-Go-TemplateProjectSettings.doNotEdit.json = Project settings from custom template
688+
# - <project>/.AL-Go/settings.json = Project settings file
689+
# - .github/<workflowName>.settings.json = Workflow settings file
690+
# - <project>/.AL-Go/<workflowName>.settings.json = Project workflow settings file
691+
# - <project>/.AL-Go/<userName>.settings.json = User settings file
686692
function ReadSettings {
687693
Param(
688694
[string] $baseFolder = "$ENV:GITHUB_WORKSPACE",
@@ -740,20 +746,31 @@ function ReadSettings {
740746
$orgSettingsVariableObject = $orgSettingsVariableValue | ConvertFrom-Json
741747
$settingsObjects += @($orgSettingsVariableObject)
742748
}
749+
750+
# Read settings from repository settings file
751+
$customTemplateRepoSettingsObject = GetSettingsObject -Path (Join-Path $baseFolder $CustomTemplateRepoSettingsFile)
752+
$settingsObjects += @($customTemplateRepoSettingsObject)
753+
743754
# Read settings from repository settings file
744755
$repoSettingsObject = GetSettingsObject -Path (Join-Path $baseFolder $RepoSettingsFile)
745756
$settingsObjects += @($repoSettingsObject)
757+
746758
# Read settings from repository settings variable (parameter)
747759
if ($repoSettingsVariableValue) {
748760
$repoSettingsVariableObject = $repoSettingsVariableValue | ConvertFrom-Json
749761
$settingsObjects += @($repoSettingsVariableObject)
750762
}
763+
751764
if ($project) {
765+
# Read settings from repository settings file
766+
$customTemplateProjectSettingsObject = GetSettingsObject -Path (Join-Path $baseFolder $CustomTemplateProjectSettingsFile)
767+
$settingsObjects += @($customTemplateProjectSettingsObject)
752768
# Read settings from project settings file
753769
$projectFolder = Join-Path $baseFolder $project -Resolve
754770
$projectSettingsObject = GetSettingsObject -Path (Join-Path $projectFolder $ALGoSettingsFile)
755771
$settingsObjects += @($projectSettingsObject)
756772
}
773+
757774
if ($workflowName) {
758775
# Read settings from workflow settings file
759776
$workflowSettingsObject = GetSettingsObject -Path (Join-Path $gitHubFolder "$workflowName.settings.json")
@@ -766,6 +783,7 @@ function ReadSettings {
766783
$settingsObjects += @($projectWorkflowSettingsObject, $userSettingsObject)
767784
}
768785
}
786+
769787
foreach($settingsJson in $settingsObjects) {
770788
if ($settingsJson) {
771789
MergeCustomObjectIntoOrderedDictionary -dst $settings -src $settingsJson

Actions/AL-Go-TestRepoHelper.ps1

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
function Test-Property {
1+
. (Join-Path $PSScriptRoot "AL-Go-Helper.ps1")
2+
3+
function Test-Property {
24
Param(
35
[HashTable] $json,
46
[string] $settingsDescription,
@@ -169,13 +171,16 @@ function TestALGoRepository {
169171
# Test .json files are formatted correctly
170172
# Get-ChildItem needs -force to include folders starting with . (e.x. .github / .AL-Go) on Linux
171173
Get-ChildItem -Path $baseFolder -Filter '*.json' -Recurse -Force | ForEach-Object {
172-
if ($_.Directory.Name -eq '.AL-Go' -and $_.BaseName -eq 'settings') {
174+
if ($_.Directory.Name -eq ([System.IO.Path]::GetDirectoryName($ALGoSettingsFile)) -and $_.Name -eq ([System.IO.Path]::GetFileName($ALGoSettingsFile))) {
173175
Test-JsonFile -jsonFile $_.FullName -baseFolder $baseFolder -type 'Project'
174176
}
175-
elseif ($_.Directory.Name -eq '.github' -and $_.BaseName -like '*ettings') {
176-
if ($_.BaseName -eq 'AL-Go-Settings') {
177+
elseif ($_.Directory.Name -eq ([System.IO.Path]::GetDirectoryName($RepoSettingsFile)) -and $_.BaseName -like '*ettings') {
178+
if ($_.Name -eq ([System.IO.Path]::GetFileName($RepoSettingsFile)) -or $_.Name -eq ([System.IO.Path]::GetFileName($CustomTemplateRepoSettingsFile))) {
177179
$type = 'Repo'
178180
}
181+
elseif ($_.Name -eq ([System.IO.Path]::GetFileName($CustomTemplateProjectSettingsFile))) {
182+
$type = 'Project'
183+
}
179184
else {
180185
$type = 'Workflow'
181186
}

Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<#
22
.SYNOPSIS
33
Downloads a template repository and returns the path to the downloaded folder
4-
.PARAMETER headers
5-
The headers to use when calling the GitHub API
4+
.PARAMETER token
5+
The GitHub token / PAT to use for authentication (if the template repository is private/internal)
66
.PARAMETER templateUrl
77
The URL to the template repository
88
.PARAMETER templateSha
@@ -12,12 +12,31 @@ If true, the latest SHA of the template repository will be downloaded
1212
#>
1313
function DownloadTemplateRepository {
1414
Param(
15-
[hashtable] $headers,
15+
[string] $token,
1616
[string] $templateUrl,
1717
[ref] $templateSha,
1818
[bool] $downloadLatest
1919
)
2020

21+
$templateRepositoryUrl = $templateUrl.Split('@')[0]
22+
$templateRepository = $templateRepositoryUrl.Split('/')[-2..-1] -join '/'
23+
24+
# Use Authenticated API request if possible to avoid the 60 API calls per hour limit
25+
$headers = GetHeaders -token $env:GITHUB_TOKEN -repository $templateRepository
26+
try {
27+
$response = Invoke-WebRequest -Headers $headers -Method Head -Uri $templateRepositoryUrl
28+
}
29+
catch {
30+
# Ignore error
31+
$response = $null
32+
}
33+
if (-not $response -or $response.StatusCode -ne 200) {
34+
# GITHUB_TOKEN doesn't have access to template repository, must be private/internal
35+
# Get token with read permissions for the template repository
36+
# NOTE that the GitHub app needs to be installed in the template repository for this to work
37+
$headers = GetHeaders -token $token -repository $templateRepository
38+
}
39+
2140
# Construct API URL
2241
$apiUrl = $templateUrl.Split('@')[0] -replace "^(https:\/\/github\.com\/)(.*)$", "$ENV:GITHUB_API_URL/repos/`$2"
2342

@@ -427,7 +446,7 @@ function IsDirectALGo {
427446

428447
function GetSrcFolder {
429448
Param(
430-
[hashtable] $repoSettings,
449+
[string] $repoType,
431450
[string] $templateUrl,
432451
[string] $templateFolder,
433452
[string] $srcPath
@@ -439,7 +458,7 @@ function GetSrcFolder {
439458
return ''
440459
}
441460
if (IsDirectALGo -templateUrl $templateUrl) {
442-
switch ($repoSettings.type) {
461+
switch ($repoType) {
443462
"PTE" {
444463
$typePath = "Per Tenant Extension"
445464
}
@@ -486,48 +505,50 @@ function GetModifiedSettingsContent {
486505
else {
487506
# Change the $schema property to be the same as the source settings file (add it if it doesn't exist)
488507
$schemaKey = '$schema'
489-
$schemaValue = $srcSettings."$schemaKey"
508+
if ($srcSettings.PSObject.Properties.Name -eq $schemaKey) {
509+
$schemaValue = $srcSettings."$schemaKey"
490510

491-
$dstSettings | Add-Member -MemberType NoteProperty -Name "$schemaKey" -Value $schemaValue -Force
511+
$dstSettings | Add-Member -MemberType NoteProperty -Name "$schemaKey" -Value $schemaValue -Force
492512

493-
# Make sure the $schema property is the first property in the object
494-
$dstSettings = $dstSettings | Select-Object @{ Name = '$schema'; Expression = { $_.'$schema' } }, * -ExcludeProperty '$schema'
513+
# Make sure the $schema property is the first property in the object
514+
$dstSettings = $dstSettings | Select-Object @{ Name = '$schema'; Expression = { $_.'$schema' } }, * -ExcludeProperty '$schema'
515+
}
495516
}
496517

497-
498518
return $dstSettings | ConvertTo-JsonLF
499519
}
500520

501521
function UpdateSettingsFile {
502522
Param(
503523
[string] $settingsFile,
504-
[hashtable] $updateSettings,
505-
[hashtable] $additionalSettings = @{}
524+
[hashtable] $updateSettings
506525
)
507526

527+
$modified = $false
508528
# Update Repo Settings file with the template URL
509529
if (Test-Path $settingsFile) {
510530
$settings = Get-Content $settingsFile -Encoding UTF8 | ConvertFrom-Json
511531
}
512532
else {
513533
$settings = [PSCustomObject]@{}
534+
$modified = $true
514535
}
515536
foreach($key in $updateSettings.Keys) {
516537
if ($settings.PSObject.Properties.Name -eq $key) {
517-
$settings."$key" = $updateSettings."$key"
538+
if ($settings."$key" -ne $updateSettings."$key") {
539+
$settings."$key" = $updateSettings."$key"
540+
$modified = $true
541+
}
518542
}
519543
else {
520544
# Add the property if it doesn't exist
521545
$settings | Add-Member -MemberType NoteProperty -Name "$key" -Value $updateSettings."$key"
546+
$modified = $true
522547
}
523548
}
524-
# Grab settings from additionalSettings if they are not already in settings
525-
foreach($key in $additionalSettings.Keys) {
526-
if (!($settings.PSObject.Properties.Name -eq $key)) {
527-
# Add the property if it doesn't exist
528-
$settings | Add-Member -MemberType NoteProperty -Name "$key" -Value $additionalSettings."$key"
529-
}
549+
if ($modified) {
550+
# Save the file with LF line endings and UTF8 encoding
551+
$settings | Set-JsonContentLF -path $settingsFile
530552
}
531-
# Save the file with LF line endings and UTF8 encoding
532-
$settings | Set-JsonContentLF -path $settingsFile
553+
return $modified
533554
}

0 commit comments

Comments
 (0)