diff --git a/Actions/AL-Go-Helper.ps1 b/Actions/AL-Go-Helper.ps1 index e2e4dd549..3b817b6e3 100644 --- a/Actions/AL-Go-Helper.ps1 +++ b/Actions/AL-Go-Helper.ps1 @@ -657,6 +657,7 @@ function ReadSettings { "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 } # Read settings from files and merge them into the settings object diff --git a/Actions/Deliver/Deliver.ps1 b/Actions/Deliver/Deliver.ps1 index 5c62254ff..d2de49ae3 100644 --- a/Actions/Deliver/Deliver.ps1 +++ b/Actions/Deliver/Deliver.ps1 @@ -1,4 +1,4 @@ -Param( +Param( [Parameter(HelpMessage = "The GitHub actor running the action", Mandatory = $false)] [string] $actor, [Parameter(HelpMessage = "The GitHub token running the action", Mandatory = $false)] @@ -189,7 +189,6 @@ foreach ($thisProject in $projectList) { "RepoSettings" = $settings "ProjectSettings" = $projectSettings } - #Calculate the folders per artifact type #Calculate the folders per artifact type 'Apps', 'TestApps', 'Dependencies' | ForEach-Object { @@ -226,9 +225,24 @@ foreach ($thisProject in $projectList) { Write-Host "Calling custom script: $customScript" . $customScript -parameters $parameters } - elseif ($deliveryTarget -eq "GitHubPackages") { - $githubPackagesCredential = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets.githubPackagesContext)) | ConvertFrom-Json - 'Apps' | ForEach-Object { + elseif ($deliveryTarget -eq 'GitHubPackages' -or $deliveryTarget -eq 'NuGet') { + $preReleaseTag = '' + try { + $nuGetAccount = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets."$($deliveryTarget)Context")) | ConvertFrom-Json | ConvertTo-HashTable + if ($deliveryTarget -eq 'NuGet' -and $type -eq 'CD') { + # When doing continuous delivery to NuGet, we always use the preview tag + # When doing a release, we do not add a preview tag + $preReleaseTag = 'preview' + } + $nuGetServerUrl = $nuGetAccount.ServerUrl + Write-Host $nuGetAccount.ServerUrl + $nuGetToken = $nuGetAccount.Token + Write-Host "$($deliveryTarget)Context secret OK" + } + catch { + throw "$($deliveryTarget)Context secret is malformed. Needs to be formatted as Json, containing serverUrl and token as a minimum." + } + 'Apps','TestApps' | ForEach-Object { $folder = @(Get-ChildItem -Path (Join-Path $artifactsFolder "$project-$refname-$($_)-*.*.*.*") | Where-Object { $_.PSIsContainer }) if ($folder.Count -gt 1) { $folder | Out-Host @@ -237,93 +251,16 @@ foreach ($thisProject in $projectList) { elseif ($folder.Count -eq 1) { Get-Item -Path (Join-Path $folder[0] "*.app") | ForEach-Object { $parameters = @{ - "gitHubRepository" = "$ENV:GITHUB_SERVER_URL/$ENV:GITHUB_REPOSITORY" - "includeNuGetDependencies" = $true - "dependencyIdTemplate" = "AL-Go-{id}" - "packageId" = "AL-Go-{id}" + "gitHubRepository" = "$ENV:GITHUB_SERVER_URL/$ENV:GITHUB_REPOSITORY" + "preReleaseTag" = $preReleaseTag + "appFile" = $_.FullName } - $parameters.appFiles = $_.FullName $package = New-BcNuGetPackage @parameters - Push-BcNuGetPackage -nuGetServerUrl $gitHubPackagesCredential.serverUrl -nuGetToken $gitHubPackagesCredential.token -bcNuGetPackage $package + Push-BcNuGetPackage -nuGetServerUrl $nuGetServerUrl -nuGetToken $nuGetToken -bcNuGetPackage $package } } } } - elseif ($deliveryTarget -eq "NuGet") { - try { - $nuGetAccount = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets.nuGetContext)) | ConvertFrom-Json | ConvertTo-HashTable - $nuGetServerUrl = $nuGetAccount.ServerUrl - $nuGetToken = $nuGetAccount.Token - Write-Host "NuGetContext secret OK" - } - catch { - throw "NuGetContext secret is malformed. Needs to be formatted as Json, containing serverUrl and token as a minimum." - } - $appsfolder = @(Get-ChildItem -Path (Join-Path $artifactsFolder "$project-$refname-Apps-*.*.*.*") | Where-Object { $_.PSIsContainer }) - if ($appsFolder.Count -eq 0) { - throw "Internal error - unable to locate apps folder" - } - elseif ($appsFolder.Count -gt 1) { - $appsFolder | Out-Host - throw "Internal error - multiple apps folders located" - } - $testAppsFolder = @(Get-ChildItem -Path (Join-Path $artifactsFolder "$project-$refname-TestApps-*.*.*.*") | Where-Object { $_.PSIsContainer }) - if ($testAppsFolder.Count -gt 1) { - $testAppsFolder | Out-Host - throw "Internal error - multiple testApps folders located" - } - $dependenciesFolder = @(Get-ChildItem -Path (Join-Path $artifactsFolder "$project-$refname-Dependencies-*.*.*.*") | Where-Object { $_.PSIsContainer }) - if ($dependenciesFolder.Count -gt 1) { - $dependenciesFolder | Out-Host - throw "Internal error - multiple dependencies folders located" - } - - $parameters = @{ - "gitHubRepository" = "$ENV:GITHUB_SERVER_URL/$ENV:GITHUB_REPOSITORY" - } - $parameters.appFiles = @(Get-Item -Path (Join-Path $appsFolder[0] "*.app") | ForEach-Object { $_.FullName }) - if ($testAppsFolder.Count -gt 0) { - $parameters.testAppFiles = @(Get-Item -Path (Join-Path $testAppsFolder[0] "*.app") | ForEach-Object { $_.FullName }) - } - if ($dependenciesFolder.Count -gt 0) { - $parameters.dependencyAppFiles = @(Get-Item -Path (Join-Path $dependenciesFolder[0] "*.app") | ForEach-Object { $_.FullName }) - } - if ($nuGetAccount.Keys -contains 'PackageName') { - $parameters.packageId = $nuGetAccount.PackageName.replace('{project}', $projectName).replace('{owner}', $ENV:GITHUB_REPOSITORY_OWNER).replace('{repo}', $settings.repoName) - } - else { - if ($thisProject -and ($thisProject -eq '.')) { - $parameters.packageId = "$($ENV:GITHUB_REPOSITORY_OWNER)-$($settings.repoName)" - } - else { - $parameters.packageId = "$($ENV:GITHUB_REPOSITORY_OWNER)-$($settings.repoName)-$ProjectName" - } - } - if ($type -eq 'CD') { - $parameters.packageId += "-preview" - } - $parameters.packageVersion = [System.Version]$appsFolder[0].Name.SubString($appsFolder[0].Name.IndexOf("-Apps-") + 6) - if ($nuGetAccount.Keys -contains 'PackageTitle') { - $parameters.packageTitle = $nuGetAccount.PackageTitle - } - else { - $parameters.packageTitle = $parameters.packageId - } - if ($nuGetAccount.Keys -contains 'PackageDescription') { - $parameters.packageDescription = $nuGetAccount.PackageDescription - } - else { - $parameters.packageDescription = $parameters.packageTitle - } - if ($nuGetAccount.Keys -contains 'PackageAuthors') { - $parameters.packageAuthors = $nuGetAccount.PackageAuthors - } - else { - $parameters.packageAuthors = $actor - } - $package = New-BcNuGetPackage @parameters - Push-BcNuGetPackage -nuGetServerUrl $nuGetServerUrl -nuGetToken $nuGetToken -bcNuGetPackage $package - } elseif ($deliveryTarget -eq "Storage") { InstallAzModuleIfNeeded -name 'Az.Storage' try { @@ -457,9 +394,9 @@ foreach ($thisProject in $projectList) { } $depfolder = $depfolder[0].FullName $libraryAppFiles += @(Get-ChildItem -path $depFolder | Where-Object { - $name = $_.name - $appSourceIncludeDependencies | Where-Object { $name -like $_ } - } | ForEach-Object { $_.FullName }) + $name = $_.name + $appSourceIncludeDependencies | Where-Object { $name -like $_ } + } | ForEach-Object { $_.FullName }) } Write-Host "Main App File:" diff --git a/Actions/DetermineDeliveryTargets/DetermineDeliveryTargets.ps1 b/Actions/DetermineDeliveryTargets/DetermineDeliveryTargets.ps1 index 3988aee2d..affdc79af 100644 --- a/Actions/DetermineDeliveryTargets/DetermineDeliveryTargets.ps1 +++ b/Actions/DetermineDeliveryTargets/DetermineDeliveryTargets.ps1 @@ -5,6 +5,16 @@ [bool] $checkContextSecrets ) +function ContinuousDelivery([string] $deliveryTarget) { + $settingsName = "DeliverTo$deliveryTarget" + if ($settings.Contains($settingsName) -and $settings."$settingsName".Contains('ContinuousDelivery')) { + return $settings."$settingsName".ContinuousDelivery + } + else { + return $true + } +} + function IncludeBranch([string] $deliveryTarget) { $settingsName = "DeliverTo$deliveryTarget" if ($settings.Contains($settingsName) -and $settings."$settingsName".Contains('Branches')) { @@ -26,7 +36,7 @@ function IncludeDeliveryTarget([string] $deliveryTarget) { Write-Host "- Secret '$contextName' not found" return $false } - return (IncludeBranch -deliveryTarget $deliveryTarget) + return (IncludeBranch -deliveryTarget $deliveryTarget) -and (ContinuousDelivery -deliveryTarget $deliveryTarget) } . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) diff --git a/Actions/DownloadProjectDependencies/README.md b/Actions/DownloadProjectDependencies/README.md index 4d4b3bbdd..5f3888ec5 100644 --- a/Actions/DownloadProjectDependencies/README.md +++ b/Actions/DownloadProjectDependencies/README.md @@ -11,7 +11,7 @@ The action constructs arrays of paths to .app files, that are dependencies of th | Name | Description | | :-- | :-- | | Settings | env.Settings must be set by a prior call to the ReadSettings Action | -| Secrets | env.Secrets must be read by a prior call to the ReadSecrets Action with appDependencyProbingPathsSecrets in getSecrets | +| Secrets | env.Secrets must be read by a prior call to the ReadSecrets Action with appDependencySecrets in getSecrets | ### Parameters diff --git a/Actions/ReadSecrets/README.md b/Actions/ReadSecrets/README.md index f2aff30f0..dcbf1d8b7 100644 --- a/Actions/ReadSecrets/README.md +++ b/Actions/ReadSecrets/README.md @@ -2,7 +2,7 @@ Read secrets from GitHub secrets or Azure Keyvault for AL-Go workflows The secrets read and added to the output are the secrets specified in the getSecrets parameter -Additionally, the secrets specified by the authToken secret in AppDependencyProbingPaths are read if appDependencyProbingPathsSecrets is specified in getSecrets +Additionally, the secrets specified by the authTokenSecret in AppDependencyProbingPaths and TrustedNuGetFeeds are read if appDependencySecrets is specified in getSecrets All secrets included in the Secrets output are Base64 encoded to avoid issues with national characters Secrets, which name is preceded by an asterisk (\*) are encrypted and Base64 encoded @@ -20,7 +20,7 @@ Secrets, which name is preceded by an asterisk (\*) are encrypted and Base64 enc | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | | gitHubSecrets | Yes | GitHub secrets in a json structure | | -| getSecrets | Yes | Comma-separated list of secrets to get (add appDependencyProbingPathsSecrets to request secrets needed for resolving dependencies in AppDependencyProbingPaths, add TokenForPush in order to request a token to use for pull requests and commits). Secrets preceded by an asterisk are returned encrypted | | +| getSecrets | Yes | Comma-separated list of secrets to get (add appDependencySecrets to request secrets needed for resolving dependencies in AppDependencyProbingPaths and TrustedNuGetFeeds, add TokenForPush in order to request a token to use for pull requests and commits). Secrets preceded by an asterisk are returned encrypted | | | useGhTokenWorkflowForPush | false | Determines whether you want to use the GhTokenWorkflow secret for TokenForPush | false | ## OUTPUT diff --git a/Actions/ReadSecrets/ReadSecrets.ps1 b/Actions/ReadSecrets/ReadSecrets.ps1 index be8ec10ae..96401257a 100644 --- a/Actions/ReadSecrets/ReadSecrets.ps1 +++ b/Actions/ReadSecrets/ReadSecrets.ps1 @@ -27,7 +27,7 @@ try { $outSecrets = [ordered]@{} $settings = $env:Settings | ConvertFrom-Json | ConvertTo-HashTable $keyVaultCredentials = GetKeyVaultCredentials - $getAppDependencyProbingPathsSecrets = $false + $getAppDependencySecrets = $false $getTokenForPush = $false [System.Collections.ArrayList]$secretsCollection = @() foreach($secret in ($getSecrets.Split(',') | Select-Object -Unique)) { @@ -38,8 +38,8 @@ try { $secret = 'ghTokenWorkflow' } $secretNameProperty = "$($secret.TrimStart('*'))SecretName" - if ($secret -eq 'AppDependencyProbingPathsSecrets') { - $getAppDependencyProbingPathsSecrets = $true + if ($secret -eq 'AppDependencySecrets') { + $getAppDependencySecrets = $true } else { $secretName = $secret @@ -61,12 +61,19 @@ try { } } - # Loop through appDependencyProbingPaths and add secrets to the collection of secrets to get - if ($getAppDependencyProbingPathsSecrets -and $settings.Keys -contains 'appDependencyProbingPaths') { - foreach($appDependencyProbingPath in $settings.appDependencyProbingPaths) { - if ($appDependencyProbingPath.PsObject.Properties.name -eq "AuthTokenSecret") { - if ($secretsCollection -notcontains $appDependencyProbingPath.authTokenSecret) { - $secretsCollection += $appDependencyProbingPath.authTokenSecret + if ($getAppDependencySecrets) { + # Loop through appDependencyProbingPaths and trustedNuGetFeeds and add secrets to the collection of secrets to get + $settingsCollection = @() + if ($settings.Keys -contains 'appDependencyProbingPaths') { + $settingsCollection += $settings.appDependencyProbingPaths + } + if ($settings.Keys -contains 'trustedNuGetFeeds') { + $settingsCollection += $settings.trustedNuGetFeeds + } + foreach($settingsItem in $settingsCollection) { + if ($settingsItem.PsObject.Properties.name -eq "AuthTokenSecret") { + if ($secretsCollection -notcontains $settingsItem.authTokenSecret) { + $secretsCollection += $settingsItem.authTokenSecret } } } diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index b102a3a91..fb0a42573 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -118,6 +118,38 @@ try { exit } + if ($bcContainerHelperConfig.ContainsKey('TrustedNuGetFeeds')) { + Write-Host "Reading TrustedNuGetFeeds" + foreach($trustedNuGetFeed in $bcContainerHelperConfig.TrustedNuGetFeeds) { + if ($trustedNuGetFeed.PSObject.Properties.Name -eq 'Token') { + if ($trustedNuGetFeed.Token -ne '') { + OutputWarning -message "Auth token for NuGet feed is defined in settings. This is not recommended. Use a secret instead and specify the secret name in the AuthTokenSecret property" + } + } + else { + $trustedNuGetFeed | Add-Member -MemberType NoteProperty -Name 'Token' -Value '' + } + if ($trustedNuGetFeed.PSObject.Properties.Name -eq 'AuthTokenSecret' -and $trustedNuGetFeed.AuthTokenSecret) { + $authTokenSecret = $trustedNuGetFeed.AuthTokenSecret + if ($secrets.Keys -notcontains $authTokenSecret) { + OutputWarning -message "Secret $authTokenSecret needed for trusted NuGetFeeds cannot be found" + } + else { + $trustedNuGetFeed.Token = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets."$authTokenSecret")) + } + } + } + } + else { + $bcContainerHelperConfig.TrustedNuGetFeeds = @() + } + if ($settings.trustMicrosoftNuGetFeeds) { + $bcContainerHelperConfig.TrustedNuGetFeeds += @([PSCustomObject]@{ + "url" = "https://dynamicssmb2.pkgs.visualstudio.com/DynamicsBCPublicFeeds/_packaging/AppSourceSymbols/nuget/v3/index.json" + "token" = '' + }) + } + $installApps = $settings.installApps $installTestApps = $settings.installTestApps @@ -276,8 +308,13 @@ try { } } - if ($gitHubPackagesContext -and ($runAlPipelineParams.Keys -notcontains 'InstallMissingDependencies')) { - $gitHubPackagesCredential = $gitHubPackagesContext | ConvertFrom-Json + if ((($bcContainerHelperConfig.ContainsKey('TrustedNuGetFeeds') -and ($bcContainerHelperConfig.TrustedNuGetFeeds.Count -gt 0)) -or ($gitHubPackagesContext)) -and ($runAlPipelineParams.Keys -notcontains 'InstallMissingDependencies')) { + if ($githubPackagesContext) { + $gitHubPackagesCredential = $gitHubPackagesContext | ConvertFrom-Json + } + else { + $gitHubPackagesCredential = [PSCustomObject]@{ "serverUrl" = ''; "token" = '' } + } $runAlPipelineParams += @{ "InstallMissingDependencies" = { Param([Hashtable]$parameters) @@ -289,7 +326,7 @@ try { $publishParams = @{ "nuGetServerUrl" = $gitHubPackagesCredential.serverUrl "nuGetToken" = $gitHubPackagesCredential.token - "packageName" = "AL-Go-$appId" + "packageName" = $appId "version" = $version } if ($parameters.ContainsKey('CopyInstalledAppsToFolder')) { @@ -298,12 +335,11 @@ try { } } if ($parameters.ContainsKey('containerName')) { - Publish-BcNuGetPackageToContainer -containerName $parameters.containerName -tenant $parameters.tenant -skipVerification @publishParams + Publish-BcNuGetPackageToContainer -containerName $parameters.containerName -tenant $parameters.tenant -skipVerification -appSymbolsFolder $parameters.appSymbolsFolder @publishParams -ErrorAction SilentlyContinue } else { - Copy-BcNuGetPackageToFolder -appSymbolsFolder $parameters.appSymbolsFolder @publishParams + Download-BcNuGetPackageToFolder -folder $parameters.appSymbolsFolder @publishParams | Out-Null } - } } } diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 96df15282..bf77cdaa9 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,6 +2,20 @@ - Issue 1184 Publish to Environment fails on 'Permission Denied' +### New Settings + +- `deliverTo` now has an additional property called `ContinuousDelivery`, indicating whether or not to run continuous delivery to this deliveryTarget. Default is true. +- `trustMicrosoftNuGetFeeds` Unless this setting is set to false, AL-Go for GitHub will trust the NuGet feeds provided by Microsoft. The feeds provided by Microsoft contains all Microsoft apps, all Microsoft symbols and symbols for all AppSource apps. +- `trustedNuGetFeeds` - can be an array of NuGet feed specifications, which AL-Go for GitHub will use for dependency resolution. Every feed specification must include a URL property and can optionally include a few other properties: + - url - The URL of the feed (examples: https://pkgs.dev.azure.com/myorg/apps/\_packaging/myrepo/nuget/v3/index.json or https://nuget.pkg.github.com/mygithuborg/index.json"). + - patterns - AL-Go for GitHub will only trust packages, where the ID matches this pattern. Default is all packages (\*). + - fingerprints - If specified, AL-Go for GitHub will only trust packages signed with a certificate with a fingerprint matching one of the fingerprints in this array. + - authTokenSecret - If the NuGet feed specified by URL is private, the authTokenSecret must be the name of a secret containing the authentication token with permissions to search and read packages from the NuGet feed. + +### Support for delivering to GitHub Packages and NuGet + +With this release the implementation for delivering to NuGet packages (by adding the NuGetContext secret), is similar to the functionality behind delivering to GitHub packages and the implementation is no longer in preview. + ### Allow GitHubRunner and GitHubRunnerShell as project settings Previously, AL-Go required the GitHubRunner and GitHubRunnerShell settings to be set on repository level. This has now been changed such that they can be set on project level. diff --git a/Scenarios/secrets.md b/Scenarios/secrets.md index 01676892c..d1f1a6823 100644 --- a/Scenarios/secrets.md +++ b/Scenarios/secrets.md @@ -77,13 +77,13 @@ Whether you use a managed identity or an app registration for authentication, yo Using a federated credential, you need to register your GitHub repository in your managed identity under settings -> federated credentials or in the app registration under Certificates & Secrets. This registration will allow AL-Go for GitHub running in this repository to authenticate without the Client Secret stored. You still need to create a secret containing the clientId and the tenantId. The way this works is that AL-Go for GitHub will request an ID_TOKEN from GitHub as a proof of authenticity and use this when authenticating. This way, only workflows running in the specified branch/environment in GitHub will be able to authenticate. -Example: `{"keyVaultName":"MyKeyVault","clientId":"ed79570c-0384-4826-8099-bf0577af6667","tenantId":"c645f7e7-0613-4b82-88ca-71f3dbb40045"}` +Example: `{"keyVaultName":"MyKeyVault","clientId":"","tenantId":""}` #### ClientSecret ClientSecret can only be used using an app registration. Under Certificates & Secrets in the app registration, you need to create a Client Secret, which you can specify in the Azure_Credentials secret in AL-Go for GitHub. With the ClientId and ClientSecret, anybody can authenticate and perform actions as the connected user inside Business Central. -Example: `{"keyVaultName":"MyKeyVault","clientId":"d48b773f-2c26-4394-8bd2-c5b64e0cae32","clientSecret":"OPXxxxxxxxxxxxxxxxxxxxxxxabge","tenantId":"c645f7e7-0613-4b82-88ca-71f3dbb40045"}` +Example: `{"keyVaultName":"MyKeyVault","clientId":"","clientSecret":"","tenantId":""}` With this setup, you can create a setting called `keyVaultCodesignCertificateName` containing the name of the imported certificate in your Key Vault in order for AL-Go for GitHub to sign your apps. @@ -101,7 +101,7 @@ Specifying a RefreshToken allows AL-Go for GitHub to get access to impersonate t Providing an AuthContext secret with a refreshtoken typically allows you to get access for 90 days. After the 90 days, you need to refresh the AuthContext secret with a new refreshToken. Note that anybody with the refreshToken can get access to call the API on behalf of the user, it doesn't have to be inside a workflow/pipeline. -Example: `{"tenantId":"d630ce39-5a0c-41ec-bf0d-6758ad558f0c","scopes":"https://api.businesscentral.dynamics.com/","RefreshToken":"0.AUUAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_s6Eo4YOI","clientId":"1950a258-227b-4e31-a9cf-717495945fc2"}` +Example: `{"tenantId":"","scopes":"https://api.businesscentral.dynamics.com/","RefreshToken":"","clientId":""}` ### App Registration (Service to service authentication) @@ -117,7 +117,7 @@ Example:`{"tenantId":"d630ce39-5a0c-41ec-bf0d-6758ad558f0c","scopes":"https://ap Under Certificates & Secrets in the app registration, you can create a Client Secret, which you can specify in the AuthContext secret in AL-Go for GitHub. With the ClientId and ClientSecret, anybody can authenticate and perform actions as the connected user inside Business Central. -Example: `{"tenantId":"d630ce39-5a0c-41ec-bf0d-6758ad558f0c","scopes":"https://api.businesscentral.dynamics.com/","clientId":"d48b773f-2c26-4394-8bd2-c5b64e0cae32","clientSecret":"OPXxxxxxxxxxxxxxxxxxxxxxxabge"}` +Example: `{"tenantId":"","scopes":"https://api.businesscentral.dynamics.com/","clientId":"","clientSecret":""}` ## **AppSourceContext** -> Deliver to AppSource @@ -135,13 +135,13 @@ In order to use an app registration for publishing apps to AppSource, you need t Using a federated credential, you need to register your GitHub repository in the app registration under Certificates & Secrets. This registration will allow AL-Go for GitHub running in this repository to authenticate without the Client Secret stored. You still need to create a secret containing this information. The way this works is that AL-Go for GitHub will request an ID_TOKEN from GitHub as a proof of authenticity and use this when authenticating. This way, only workflows running in the specified branch/environment in GitHub will be able to authenticate. -Example:`{"clientId":"d48b773f-2c26-4394-8bd2-c5b64e0cae32","tenantId":"c645f7e7-0613-4b82-88ca-71f3dbb40045","scopes":"https://api.partner.microsoft.com/.default"}` +Example:`{"clientId":"","tenantId":"","scopes":"https://api.partner.microsoft.com/.default"}` #### Client Secret Under Certificates & Secrets in the app registration, you can create a Client Secret, which you can specify in the AuthContext secret in AL-Go for GitHub. Note that who ever has access to the clientId and clientSecret can publish apps on AppSource on your behalf. -Example: `{"tenantId":"c645f7e7-0613-4b82-88ca-71f3dbb40045","scopes":"https://api.partner.microsoft.com/.default","clientId":"d48b773f-2c26-4394-8bd2-c5b64e0cae32","clientSecret":"OPXxxxxxxxxxxxxxxxxxxxxxxabge"}` +Example: `{"tenantId":"c645f7e7-0613-4b82-88ca-71f3dbb40045","scopes":"https://api.partner.microsoft.com/.default","clientId":"","clientSecret":""}` ## **StorageContext** -> Deliver to storage @@ -153,34 +153,40 @@ In AL-Go for GitHub, the Storage Context can be specified in 5 different ways, 5 As a storage account is an Azure resource, we can use managed identities. Managed identities are like virtual users in Azure, using federated credentials for authentication. Using a federated credential, you need to register your GitHub repository in the managed identity under Settings -> Federated Credentials. The way this works is that AL-Go for GitHub will request an ID_TOKEN from GitHub as a proof of authenticity and use this when authenticating. This way, only workflows running in the specified branch/environment in GitHub will be able to authenticate. -Example: `{"storageAccountName":"MyStorageName","clientId":"08b6d80c-68cf-48f9-a5ff-b054326e2ec3","tenantId":"c645f7e7-0613-4b82-88ca-71f3dbb40045","containerName":"{project}","blobName":"{version}/{project}-{type}.zip"}` +Example: `{"storageAccountName":"MyStorageName","clientId":"","tenantId":"","containerName":"{project}","blobName":"{version}/{project}-{type}.zip"}` ### App Registration/Federated credential An app registration with federated credential is harder to setup than a managed identity, but just as secure. The mechanism is the same for obtaining an ID_TOKEN and providing this as proof of authenticity towards the app registration. -Example: `{"storageAccountName":"MyStorageName","clientId":"d48b773f-2c26-4394-8bd2-c5b64e0cae32","tenantId":"c645f7e7-0613-4b82-88ca-71f3dbb40045","containerName":"{project}","blobName":"{version}/{project}-{type}.zip"}` +Example: `{"storageAccountName":"MyStorageName","clientId":"","tenantId":"","containerName":"{project}","blobName":"{version}/{project}-{type}.zip"}` ### App Registration/Client Secret An app registration with a client Secret is less secure than using federated credentials. Who ever has access to the clientSecret has access to everything the app registration has access to, until you recycle the client Secret. -Example: `{"storageAccountName":"MyStorageName","clientId":"d48b773f-2c26-4394-8bd2-c5b64e0cae32","clientSecret":"OPXxxxxxxxxxxxxxxxxxxxxxxabge","tenantId":"c645f7e7-0613-4b82-88ca-71f3dbb40045","containerName":"{project}","blobName":"{version}/{project}-{type}.zip"}` +Example: `{"storageAccountName":"MyStorageName","clientId":"","clientSecret":"","tenantId":"","containerName":"{project}","blobName":"{version}/{project}-{type}.zip"}` ### storageAccountName/sastoken A sas token for a storage account can be setup to function in a limited timeframe, giving access to perform a certain number of tasks on the storage account. Who ever has access to the sastoken can perform these tasks on the storage account until it expires or you recycle the storage account key used to create the sastoken. -Example: `{"storageAccountName":"MyStorageName","sastoken":"sv=2022-11-02&ss=b&srt=sco&sp=rwdlaciytf&se=2024-08-06T20:22:08Z&st=2024-04-06T12:22:08Z&spr=https&sig=IZyIf5xxxxxxxxxxxxxxxxxxxxxtq7tj6b5I%3D","containerName":"{project}","blobName":"{version}/{project}-{type}.zip"}` +Example: `{"storageAccountName":"MyStorageName","sastoken":"sv=2022-11-02&ss=b&srt=sco&sp=rwdlaciytf&se=2024-08-06T20:22:08Z&st=2024-04-06T12:22:08Z&spr=https&sig=","containerName":"{project}","blobName":"{version}/{project}-{type}.zip"}` ### storageAccountName/storageAccountKey Using storageAccount Name and Key is by far the most unsecure way of authenticating to an Azure Storage Account. If ever compromised, people can do anything with these credentials, until the storageAccount key is cycled. -Example: `{"storageAccountName":"MyStorageName","storageAccountKey":"JHFZErCyfQ8xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxStj7AHXQ==","containerName":"{project}","blobName":"{version}/{project}-{type}.zip"} ` +Example: `{"storageAccountName":"MyStorageName","storageAccountKey":"","containerName":"{project}","blobName":"{version}/{project}-{type}.zip"} ` ## **GitHubPackagesContext** -> Deliver to GitHub Packages -If you create a secret called GitHubPackagesContext, then AL-Go for GitHub will automagically deliver apps to this NuGet feed after every successful build. AL-Go for GitHub will also use this NuGet feed for dependency resolution when building apps, giving you automatic dependency resolution within all your apps. +If you create a secret called GitHubPackagesContext, then AL-Go for GitHub will automagically deliver apps to this NuGet feed after every successful build. AL-Go for GitHub will also use this NuGet feed for dependency resolution when building apps, giving you automatic dependency resolution within all your repositories sharing this secret. -Example: `{"token":"ghp_NDdI2ExxxxxxxxxxxxxxxxxAYQh","serverUrl":"https://nuget.pkg.github.com/mygithuborg/index.json"}` +Example: `{"token":"","serverUrl":"https://nuget.pkg.github.com/mygithuborg/index.json"}` + +## **NuGetContext** -> Deliver to NuGet + +If you create a secret called NuGetContext, then AL-Go for GitHub will automagically deliver apps to this NuGet feed after every successful build. AL-Go for GitHub will NOT use this NuGet feed for dependency resolution when building apps. If you want to use this feed for dependency resolution as well, you need to add this to the [trustedNuGetFeeds](https://aka.ms/algosettings#trustedNuGetFeeds) setting. + +Example: `{"token":"","serverUrl":"https://pkgs.dev.azure.com/myorg/apps/_packaging/myrepo/nuget/v3/index.json"}` diff --git a/Scenarios/settings.md b/Scenarios/settings.md index 72297b761..295c647f8 100644 --- a/Scenarios/settings.md +++ b/Scenarios/settings.md @@ -116,6 +116,8 @@ The repository settings are only read from the repository settings file (.github | enableTaskScheduler | Setting enableTaskScheduler to true in your project setting file, causes the build container to be created with the Task Scheduler running. | false | | useCompilerFolder | Setting useCompilerFolder to true causes your pipelines to use containerless compiling. Unless you also set **doNotPublishApps** to true, setting useCompilerFolder to true won't give you any performance advantage, since AL-Go for GitHub will still need to create a container in order to publish and test the apps. In the future, publishing and testing will be split from building and there will be other options for getting an instance of Business Central for publishing and testing. **Note** when using UseCompilerFolder you need to sign apps using the new signing mechanism described [here](../Scenarios/Codesigning.md). | false | | excludeEnvironments | excludeEnvironments can be an array of GitHub Environments, which should be excluded from the list of environments considered for deployment. github-pages is automatically added to this array and cannot be used as environment for deployment of AL-Go for GitHub projects. | \[ \] | +| trustMicrosoftNuGetFeeds | Unless this setting is set to false, AL-Go for GitHub will trust the NuGet feeds provided by Microsoft. The feeds provided by Microsoft contains all Microsoft apps, all Microsoft symbols and symbols for all AppSource apps. | true | +| trustedNuGetFeeds | trustedNuGetFeeds can be an array of NuGet feed specifications, which AL-Go for GitHub will use for dependency resolution. Every feed specification must include a URL property and can optionally include a few other properties:
**url** = The URL of the feed (examples: https://pkgs.dev.azure.com/myorg/apps/\_packaging/myrepo/nuget/v3/index.json or https://nuget.pkg.github.com/mygithuborg/index.json").
**authTokenSecret** = If the NuGet feed specified by URL is private, the authTokenSecret must be the name of a secret containing the authentication token with permissions to search and read packages from the NuGet feed.
**patterns** = AL-Go for GitHub will only trust packages, where the ID matches this pattern. Default is all packages (\*).
**fingerprints** = If specified, AL-Go for GitHub will only trust packages signed with a certificate with a fingerprint matching one of the fingerprints in this array. | \[ \] | ## AppSource specific advanced settings diff --git a/Templates/AppSource App/.github/workflows/_BuildALGoProject.yaml b/Templates/AppSource App/.github/workflows/_BuildALGoProject.yaml index 3ad3da5b4..1e1ef8a8b 100644 --- a/Templates/AppSource App/.github/workflows/_BuildALGoProject.yaml +++ b/Templates/AppSource App/.github/workflows/_BuildALGoProject.yaml @@ -106,7 +106,7 @@ jobs: with: shell: ${{ inputs.shell }} gitHubSecrets: ${{ toJson(secrets) }} - getSecrets: '${{ inputs.secrets }},appDependencyProbingPathsSecrets,AZURE_CREDENTIALS' + getSecrets: '${{ inputs.secrets }},appDependencySecrets,AZURE_CREDENTIALS' - name: Determine ArtifactUrl uses: microsoft/AL-Go-Actions/DetermineArtifactUrl@main diff --git a/Templates/Per Tenant Extension/.github/workflows/_BuildALGoProject.yaml b/Templates/Per Tenant Extension/.github/workflows/_BuildALGoProject.yaml index 3ad3da5b4..1e1ef8a8b 100644 --- a/Templates/Per Tenant Extension/.github/workflows/_BuildALGoProject.yaml +++ b/Templates/Per Tenant Extension/.github/workflows/_BuildALGoProject.yaml @@ -106,7 +106,7 @@ jobs: with: shell: ${{ inputs.shell }} gitHubSecrets: ${{ toJson(secrets) }} - getSecrets: '${{ inputs.secrets }},appDependencyProbingPathsSecrets,AZURE_CREDENTIALS' + getSecrets: '${{ inputs.secrets }},appDependencySecrets,AZURE_CREDENTIALS' - name: Determine ArtifactUrl uses: microsoft/AL-Go-Actions/DetermineArtifactUrl@main