Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Actions/.Modules/settings.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -805,5 +805,18 @@
},
"description": "An array of workflow input default values. See https://aka.ms/ALGoSettings#workflowDefaultInputs"
}
},
"patternProperties": {
"^DeployTo": {
"type": "object",
"description": "Structure with additional properties for the environment specified. See https://aka.ms/ALGoSettings#deployto",
"properties": {
"unpublishOldVersions": {
"type": "boolean",
"default": false,
"description": "When set to true, AL-Go unpublishes old, uninstalled versions of the deployed apps from the environment after a successful deployment. Only applies to PTE deployments (Scope PTE) and is non-fatal. See https://aka.ms/ALGoSettings#deployto"
}
}
}
}
}
5 changes: 5 additions & 0 deletions Actions/Deploy/Deploy.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,11 @@ else {

Write-Host "Publishing apps using automation API"
Publish-PerTenantExtensionApps @parameters

if (($deploymentSettings['unpublishOldVersions'] -is [bool]) -and $deploymentSettings['unpublishOldVersions']) {
Write-Host "Unpublishing old app versions"
UnpublishOldAppVersions -bcAuthContext $bcAuthContext -environment $deploymentSettings.EnvironmentName -appFiles $apps
}
}
}
}
Expand Down
97 changes: 97 additions & 0 deletions Actions/Deploy/Deploy.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,103 @@ function CheckInstalledApps {
}
}

<#
.SYNOPSIS
Unpublish old, uninstalled versions of the deployed apps from a Business Central environment.
.DESCRIPTION
After a new version of a Per Tenant Extension is installed, previous versions remain published (but uninstalled)
and clutter Extension Management. This function unpublishes those old versions using the automation API v2.0
Microsoft.NAV.unpublish action. Only versions that are not installed AND older than a currently installed version
of the same app (matched by app id) are unpublished. This is only supported for PTE deployments (automation API).
The function is non-fatal: any failure is reported as a warning and never fails the deployment.
.PARAMETER bcAuthContext
The Business Central authentication context.
.PARAMETER environment
The environment to unpublish old app versions from.
.PARAMETER appFiles
The list of deployed app files. Only the app id is read from these files to identify which apps to clean up;
for each such app, published versions are compared against the version currently installed in the environment
(not the deployed artifact version), and uninstalled versions older than the installed version are unpublished.
#>
function UnpublishOldAppVersions {
Param(
[hashtable] $bcAuthContext,
[string] $environment,
$appFiles
)
OutputDebugFunctionCall

try {
# Deployed app identities (id + version) read from the .app files
$deployedApps = @($appFiles | ForEach-Object {
$appJson = Get-AppJsonFromAppFile -appFile $_
[PSCustomObject]@{ Id = $appJson.id; Version = [version]$appJson.version }
})
if ($deployedApps.Count -eq 0) {
return
}

$authContext = Renew-BcAuthContext -bcAuthContext $bcAuthContext
$headers = @{ "Authorization" = "Bearer $($authContext.AccessToken)" }
$automationApiUrl = "$($bcContainerHelperConfig.apiBaseUrl.TrimEnd('/'))/v2.0/$environment/api/microsoft/automation/v2.0"

$companies = (Invoke-RestMethod -Method Get -Uri "$automationApiUrl/companies" -Headers $headers -UseBasicParsing).value
if (-not $companies) {
OutputWarning -message "Could not find any company in environment $environment - skipping unpublish of old app versions."
return
}
$companyId = $companies[0].id
$companyUrl = "$automationApiUrl/companies($companyId)"
$extensions = @((Invoke-RestMethod -Method Get -Uri "$companyUrl/extensions" -Headers $headers -UseBasicParsing).value)

$application = $extensions | Where-Object { $_.displayName -eq 'Application' -and $_.isInstalled } | Select-Object -First 1
if (-not $application) {
OutputWarning -message "Could not determine the Business Central version in environment $environment - skipping unpublish of old app versions."
return
}
$applicationVersion = [version]::new($application.versionMajor, $application.versionMinor, $application.versionBuild, $application.versionRevision)
if ($applicationVersion -lt [version]'25.4.0.0') {
OutputWarning -message "Unpublishing old app versions requires Business Central 25.4 or later; environment $environment is running $applicationVersion."
return
}

foreach($deployed in $deployedApps) {
# All published versions of this app (installed and uninstalled)
$matching = @($extensions | Where-Object { $_.id -eq $deployed.Id })
if ($matching.Count -le 1) {
# Only one (or no) published version - nothing to clean up
continue
}
# Use the currently installed version as the cleanup threshold. The environment may have a newer
# version installed than the deployed artifact, which Publish-PerTenantExtensionApps treats as success.
$installedVersions = @($matching | Where-Object { $_.isInstalled } | ForEach-Object { [version]::new($_.versionMajor, $_.versionMinor, $_.versionBuild, $_.versionRevision) })
if ($installedVersions.Count -eq 0) {
# No installed version - nothing to clean up against
continue
}
$installedVersion = @($installedVersions | Sort-Object -Descending)[0]
foreach($old in $matching) {
$oldVersion = [version]::new($old.versionMajor, $old.versionMinor, $old.versionBuild, $old.versionRevision)
if ($old.isInstalled -or $oldVersion -ge $installedVersion) {
# Keep anything still installed and any version at or above the installed version
continue
}
Write-Host "Unpublishing $($old.displayName) v$oldVersion"
try {
Invoke-RestMethod -Method Post -Headers $headers -Body '{}' -ContentType 'application/json' -UseBasicParsing `
-Uri "$companyUrl/extensions($($old.packageId))/Microsoft.NAV.unpublish" | Out-Null
}
catch {
OutputWarning -message "Failed to unpublish $($old.displayName) v$($oldVersion): $($_.Exception.Message)"
}
}
}
}
catch {
OutputWarning -message "Unpublishing old app versions in environment $environment failed: $($_.Exception.Message)"
}
}

<#
.SYNOPSIS
Install or upgrade apps in Business Central.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ if (!($environments)) {
"ppEnvironmentUrl" = ''
"includeTestAppsInSandboxEnvironment" = $false
"excludeAppIds" = @()
"unpublishOldVersions" = $false
}
}
$unknownEnvironment = 1
Expand Down Expand Up @@ -174,6 +175,7 @@ else {
"ppEnvironmentUrl" = ''
"includeTestAppsInSandboxEnvironment" = $false
"excludeAppIds" = @()
"unpublishOldVersions" = $false
}

# Check DeployTo<environmentName> setting
Expand Down
4 changes: 4 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### New `unpublishOldVersions` setting for deployment

The `DeployTo<environment>` setting now supports an opt-in `unpublishOldVersions` boolean (default `false`). When enabled, AL-Go unpublishes old, uninstalled versions of the deployed apps from the environment after a successful deployment, keeping Extension Management clean. This only applies to PTE deployments (Scope PTE / automation API), uses the Automation API v2.0 `Microsoft.NAV.unpublish` action, and is non-fatal (failures are reported as warnings and never fail the deployment).

### New `doNotPerformUpgrade` setting

AL-Go now supports a new `doNotPerformUpgrade` setting that is passed through to `Run-AlPipeline`. Use it to skip the upgrade phase while still running the rest of the pipeline.
Expand Down
2 changes: 1 addition & 1 deletion Scenarios/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ The repository settings are only read from the repository settings file (.github
| <a id="githubRunnerShell"></a>githubRunnerShell | Specifies which shell is used for build jobs in workflows including a build job. The default is to use the same as defined in **shell**. If the shell setting isn't defined, **powershell** is the default, which results in using _PowerShell 5.1_. Use **pwsh** for _PowerShell 7_. |
| <a id="environments"></a>environments | Array of logical environment names. You can specify environments in GitHub environments or in the repo settings file. If you specify environments in the settings file, you can create your AUTHCONTEXT secret using **\<environmentname>\_AUTHCONTEXT**. You can specify additional information about environments in a setting called **DeployTo\<environmentname>** |
| <a id="deliverto"></a>DeliverTo\<deliveryTarget> | Structure with additional properties for the deliveryTarget specified. Some properties are deliveryTarget specific. The structure can contain the following properties:<br />**Branches** = an array of branch patterns, which are allowed to deliver to this deliveryTarget. (Default main)<br />**CreateContainerIfNotExist** = *[Only for DeliverToStorage]* Create Blob Storage Container if it doesn't already exist. (Default false)<br /> |
| <a id="deployto"></a>DeployTo\<environmentname> | Structure with additional properties for the environment specified. `<environmentName>` refers to the GitHub environment name. The structure can contain the following properties:<br />**EnvironmentType** = specifies the type of environment. The environment type can be used to invoke a custom deployment. (Default SaaS)<br />**EnvironmentName** = specifies the "real" name of the environment if it differs from the GitHub environment.<br />**Branches** = an array of branch patterns, which are allowed to deploy to this environment. These branches can also be defined under the environment in GitHub settings and both settings are honored. If neither setting is defined, the default is the **main** branch only.<br />**Projects** = In multi-project repositories, this property can be a comma separated list of project patterns to deploy to this environment. (Default \*)<br />**DependencyInstallMode** = Determines how dependencies are deployed if `GenerateDependencyArtifact` is true. Default value is `install` to install dependencies if not already installed. Other values are `ignore` for ignoring dependencies and `upgrade` or `forceUpgrade` for upgrading dependencies.<br />**includeTestAppsInSandboxEnvironment** = deploys test apps and their dependencies if the environment type is sandbox (Default is `false`)<br />**excludeAppIds** = array of app ids to exclude from deployment. (Default is `[]`)<br />**Scope** = Determines the mechanism for deployment to the environment (Dev or PTE). If not specified, AL-Go for GitHub will always use the Dev Scope for AppSource Apps, but also for PTEs when deploying to sandbox environments when impersonation (refreshtoken) is used for authentication.<br />**SyncMode** = ForceSync if deployment to this environment should happen with ForceSync, else Add. If deploying to the development endpoint you can also specify Development or Clean. (Default Add)<br />**BuildMode** = specifies which buildMode to use for the deployment. Default is to use the Default buildMode<br />**ContinuousDeployment** = true if this environment should be used for continuous deployment, else false. (Default: AL-Go will continuously deploy to sandbox environments or environments, which doesn't end in (PROD) or (FAT)<br />**runs-on** = specifies which runner to use when deploying to this environment. (Default is settings.runs-on)<br />**shell** = specifies which shell to use when deploying to this environment, pwsh or powershell. (Default is settings.shell)<br />**companyId** = Company Id from Business Central (for PowerPlatform connection)<br />**ppEnvironmentUrl** = Url of the PowerPlatform environment to deploy to<br /> |
| <a id="deployto"></a>DeployTo\<environmentname> | Structure with additional properties for the environment specified. `<environmentName>` refers to the GitHub environment name. The structure can contain the following properties:<br />**EnvironmentType** = specifies the type of environment. The environment type can be used to invoke a custom deployment. (Default SaaS)<br />**EnvironmentName** = specifies the "real" name of the environment if it differs from the GitHub environment.<br />**Branches** = an array of branch patterns, which are allowed to deploy to this environment. These branches can also be defined under the environment in GitHub settings and both settings are honored. If neither setting is defined, the default is the **main** branch only.<br />**Projects** = In multi-project repositories, this property can be a comma separated list of project patterns to deploy to this environment. (Default \*)<br />**DependencyInstallMode** = Determines how dependencies are deployed if `GenerateDependencyArtifact` is true. Default value is `install` to install dependencies if not already installed. Other values are `ignore` for ignoring dependencies and `upgrade` or `forceUpgrade` for upgrading dependencies.<br />**includeTestAppsInSandboxEnvironment** = deploys test apps and their dependencies if the environment type is sandbox (Default is `false`)<br />**excludeAppIds** = array of app ids to exclude from deployment. (Default is `[]`)<br />**Scope** = Determines the mechanism for deployment to the environment (Dev or PTE). If not specified, AL-Go for GitHub will always use the Dev Scope for AppSource Apps, but also for PTEs when deploying to sandbox environments when impersonation (refreshtoken) is used for authentication.<br />**SyncMode** = ForceSync if deployment to this environment should happen with ForceSync, else Add. If deploying to the development endpoint you can also specify Development or Clean. (Default Add)<br />**unpublishOldVersions** = When set to `true`, AL-Go will unpublish old, uninstalled versions of the deployed apps from the environment after a successful deployment, to keep Extension Management clean. Only applies to PTE deployments (Scope PTE / automation API) and is non-fatal (failures are reported as warnings). (Default false)<br />**BuildMode** = specifies which buildMode to use for the deployment. Default is to use the Default buildMode<br />**ContinuousDeployment** = true if this environment should be used for continuous deployment, else false. (Default: AL-Go will continuously deploy to sandbox environments or environments, which doesn't end in (PROD) or (FAT)<br />**runs-on** = specifies which runner to use when deploying to this environment. (Default is settings.runs-on)<br />**shell** = specifies which shell to use when deploying to this environment, pwsh or powershell. (Default is settings.shell)<br />**companyId** = Company Id from Business Central (for PowerPlatform connection)<br />**ppEnvironmentUrl** = Url of the PowerPlatform environment to deploy to<br /> |
| <a id="aldoc"></a>alDoc | Structure with properties for the aldoc reference document generation. The structure can contain the following properties:<br />**continuousDeployment** = Determines if reference documentation will be deployed continuously as part of CI/CD. You can run the **Deploy Reference Documentation** workflow to deploy manually or on a schedule. (Default false)<br />**deployToGitHubPages** = Determines whether or not the reference documentation site should be deployed to GitHub Pages for the repository. In order to deploy to GitHub Pages, GitHub Pages must be enabled and set to GitHub Actuibs. (Default true)<br />**maxReleases** = Maximum number of releases to include in the reference documentation. (Default 3)<br />**groupByProject** = Determines whether projects in multi-project repositories are used as folders in reference documentation<br />**includeProjects** = An array of projects to include in the reference documentation. (Default all)<br />**excludeProjects** = An array of projects to exclude in the reference documentation. (Default none)<br />**header** = Header for the documentation site. (Default: Documentation for...)<br />**footer** = Footer for the documentation site. (Default: Made with...)<br />**defaultIndexMD** = Markdown for the landing page of the documentation site. (Default: Reference documentation...)<br />**defaultReleaseMD** = Markdown for the landing page of the release sites. (Default: Release reference documentation...)<br />*Note that in header, footer, defaultIndexMD and defaultReleaseMD you can use the following placeholders: {REPOSITORY}, {VERSION}, {INDEXTEMPLATERELATIVEPATH}, {RELEASENOTES}* |
| <a id="useProjectDependencies"></a>useProjectDependencies | Determines whether your projects are built using a multi-stage built workflow or single stage. After setting useProjectDependencies to true, you need to run Update AL-Go System Files and your workflows including a build job will change to have multiple build jobs, depending on each other. The number of build jobs will be determined by the dependency depth in your projects.<br />You can change dependencies between your projects, but if the dependency **depth** changes, AL-Go will warn you that updates for your AL-Go System Files are available and you will need to run the workflow. |
| <a id="CICDPushBranches"></a>CICDPushBranches | CICDPushBranches can be specified as an array of branches, which triggers a CI/CD workflow on commit. You need to run the Update AL-Go System Files workflow for the change to take effect.<br />Default is [ "main", "release/\*", "feature/\*" ]<br /><br />**Supported release branch naming formats:**<br />When using release branches, AL-Go supports various naming conventions for matching previous releases during upgrade testing. The following formats are recognized:<br />• `releases/26` - matches releases with major version 26<br />• `releases/26.x` - matches releases with major version 26<br />• `releases/26x` - matches releases with major version 26<br />• `releases/v26` - matches releases with major version 26<br />• `releases/v26.x` - matches releases with major version 26<br />• `releases/v26x` - matches releases with major version 26<br />• `releases/26.3` - matches releases with major.minor version 26.3<br />The same patterns work with the singular `release/` prefix. |
Expand Down
Loading
Loading