diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index b96152d5d..1670d22a2 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -21,7 +21,7 @@ jobs: run: | . (Join-Path "." "Tests/runtests.ps1") - - name: Test AL-GO Workflows + - name: Test AL-Go Workflows if: github.repository_owner == 'microsoft' run: | try { diff --git a/Actions/AL-Go-Helper.ps1 b/Actions/AL-Go-Helper.ps1 index a4113ca9a..0a5b1432c 100644 --- a/Actions/AL-Go-Helper.ps1 +++ b/Actions/AL-Go-Helper.ps1 @@ -20,7 +20,7 @@ $defaultCICDPullRequestBranches = @( 'main' ) $runningLocal = $local.IsPresent $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" $microsoftTelemetryConnectionString = "InstrumentationKey=84bd9223-67d4-4378-8590-9e4a46023be2;IngestionEndpoint=https://westeurope-1.in.applicationinsights.azure.com/" -$notSecretProperties = @("Scopes","TenantId","BlobName","ContainerName","StorageAccountName","ServerUrl") +$notSecretProperties = @("Scopes","TenantId","BlobName","ContainerName","StorageAccountName","ServerUrl","ppUserName") $runAlPipelineOverrides = @( "DockerPull" @@ -554,6 +554,7 @@ function ReadSettings { "type" = "PTE" "unusedALGoSystemFiles" = @() "projects" = @() + "powerPlatformSolutionFolder" = "" "country" = "us" "artifact" = "" "companyName" = "" @@ -2301,12 +2302,30 @@ function RetryCommand { } } +function GetMatchingProjects { + Param( + [string[]] $projects, + [string] $selectProjects = '' + ) + + if ($selectProjects) { + # Filter the project list based on the projects parameter + if ($selectProjects.StartsWith('[')) { + $selectProjects = ($selectProjects | ConvertFrom-Json) -join "," + } + $projectArr = $selectProjects.Split(',').Trim() + $projects = @($projects | Where-Object { $project = $_; if ($projectArr | Where-Object { $project -like $_ }) { $project } }) + } + return $projects +} + function GetProjectsFromRepository { Param( [string] $baseFolder, [string[]] $projectsFromSettings, [string] $selectProjects = '' ) + if ($projectsFromSettings) { $projects = $projectsFromSettings } @@ -2318,18 +2337,7 @@ function GetProjectsFromRepository { $projects += @(".") } } - if ($selectProjects) { - # Filter the project list based on the projects parameter - if ($selectProjects.StartsWith('[')) { - $selectProjects = ($selectProjects | ConvertFrom-Json) -join "," - } - $projectArr = $selectProjects.Split(',').Trim() - $projects = @($projects | Where-Object { $project = $_; if ($projectArr | Where-Object { $project -like $_ }) { $project } }) - if ($projects.Count -eq 0) { - throw "No projects matches '$selectProjects'" - } - } - return $projects + return @(GetMatchingProjects -projects $projects -selectProjects $selectProjects) } function Get-PackageVersion($PackageName) { diff --git a/Actions/AL-Go-TestRepoHelper.ps1 b/Actions/AL-Go-TestRepoHelper.ps1 index 2186b9031..1b1b44a53 100644 --- a/Actions/AL-Go-TestRepoHelper.ps1 +++ b/Actions/AL-Go-TestRepoHelper.ps1 @@ -54,6 +54,12 @@ function Test-SettingsJson { Test-Shell -json $json -settingsDescription $settingsDescription -property 'shell' Test-Shell -json $json -settingsDescription $settingsDescription -property 'gitHubRunnerShell' + if ($json.Keys -contains 'bcContainerHelperVersion') { + if ($json.bcContainerHelperVersion -ne 'latest' -and $json.bcContainerHelperVersion -ne 'preview') { + OutputWarning -Message "Using a specific version of BcContainerHelper in $settingsDescription is not recommended and will lead to build failures in the future. Consider removing the setting." + } + } + if ($type -eq 'Repo') { # Test for things that should / should not exist in a repo settings file Test-Property -settingsDescription $settingsDescription -json $json -key 'templateUrl' -should @@ -61,6 +67,7 @@ function Test-SettingsJson { if ($type -eq 'Project') { # GitHubRunner should not be in a project settings file (only read from repo or workflow settings) Test-Property -settingsDescription $settingsDescription -json $json -key 'githubRunner' -shouldnot + Test-Property -settingsDescription $settingsDescription -json $json -key 'bcContainerHelperVersion' -shouldnot } if ($type -eq 'Workflow') { # Test for things that should / should not exist in a workflow settings file diff --git a/Actions/BuildPowerPlatform/BuildPowerPlatform.ps1 b/Actions/BuildPowerPlatform/BuildPowerPlatform.ps1 new file mode 100644 index 000000000..511405849 --- /dev/null +++ b/Actions/BuildPowerPlatform/BuildPowerPlatform.ps1 @@ -0,0 +1,274 @@ +[CmdletBinding()] +param( + [Parameter(mandatory = $true)] + [string] $solutionFolder, + [Parameter(mandatory = $false)] + [string] $companyId, + [Parameter(mandatory = $false)] + [string] $environmentName, + [Parameter(mandatory = $false)] + [string] $appBuild, + [Parameter(mandatory = $false)] + [string] $appRevision +) +$ErrorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + +function Update-PowerAppSettings { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string] $SolutionFolder, + [Parameter(Mandatory = $true)] + [string] $environmentName, + [Parameter(Mandatory = $true)] + [string] $companyId + ) + + # There are multiple files that contain the BC connection info for PowerApps with different structures + # So instead of parsing all of them, we simple find the current connection strings and run a replace operation. + # Note: The connection string has a format of: "EnvironmentName,CompanyId" where companyId is a guid. So the + # replace operation should be safe to run a all json and XML files. + Write-Host "Updating PowerApp settings" + $currentPowerAppSettings = Get-CurrentPowerAppSettings -solutionFolder $SolutionFolder + if ($null -eq $currentPowerAppSettings) { + Write-Host "::Notice::No Power Apps found" + return + } + + Write-Host "Number of Business Central Power App connections found: "$currentPowerAppSettings.Count + $newSettings = "$environmentName,$companyId" + foreach ($currentSetting in $currentPowerAppSettings) { + if ($currentSetting -eq $newSettings) { + Write-Host "No changes needed for: "$currentSetting + continue + } + Update-PowerAppFiles -oldSetting $currentSetting -newSetting $newSettings -solutionFolder $SolutionFolder + } +} + +function Update-PowerAppFiles { + param( + [Parameter(mandatory = $true)] + [string] $solutionFolder, + [Parameter(mandatory = $true)] + [string] $oldSetting, + [Parameter(mandatory = $true)] + [string] $newSetting + ) + + $powerAppFiles = Get-ChildItem -Recurse -File "$solutionFolder/CanvasApps" + foreach ($file in $powerAppFiles) { + # only check json and xml files + if (($file.Extension -eq ".json") -or ($file.Extension -eq ".xml")) { + $fileContent = Get-Content $file.FullName + if (Select-String -Pattern $oldSetting -InputObject $fileContent) { + $fileContent = $fileContent -creplace $oldSetting, $newSetting + Set-Content -Path $file.FullName -Value $fileContent + Write-Host "Updated: $($file.FullName)" + } + } + } +} + +function Get-CurrentPowerAppSettings { + param ( + [Parameter(mandatory = $true)] + [string] $solutionFolder + ) + + if (-not (Test-Path -Path "$solutionFolder/CanvasApps")) { + # No Canvas apps present in the solution + return @() + } + + $currentSettingsList = @() + $connectionsFilePaths = Get-ChildItem -Path "$solutionFolder/CanvasApps" -Recurse -File -Include "Connections.json" | Select-Object -ExpandProperty FullName + foreach ($connectionsFilePath in $connectionsFilePaths) { + $jsonFile = Get-Content $connectionsFilePath | ConvertFrom-Json + + # We don't know the name of the connector node, so we need to loop through all of them + $ConnectorNodeNames = ($jsonFile | Get-Member -MemberType NoteProperty).Name + + foreach ($connectorNodeName in $ConnectorNodeNames) { + $connectorNode = $jsonFile.$connectorNodeName + # Find the Business Central connection node + if ($connectorNode.connectionRef.displayName -eq "Dynamics 365 Business Central") { + $currentEnvironmentAndCompany = ($connectorNode.datasets | Get-Member -MemberType NoteProperty).Name + + if ($null -eq $currentEnvironmentAndCompany) { + # Connections sections for Power Automate flow does not have a dataset node + # Note: Flows are handled in a different function + continue + } + + if (!$currentSettingsList.Contains($currentEnvironmentAndCompany)) { + $currentSettingsList += $currentEnvironmentAndCompany + + # The Business Central environment can be inconsistent - Either starting with a capital letter or all caps. + # Add both variants to ensure we find all connections + $currentSettingsParts = @($currentEnvironmentAndCompany.Split(",")) + $currentSettingsList += "$($currentSettingsParts[0].ToUpperInvariant()),$($currentSettingsParts[1])" + } + } + } + } + return $currentSettingsList +} + +function Update-FlowSettings { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string] $SolutionFolder, + [Parameter(Mandatory = $true)] + [string] $environmentName, + [Parameter(Mandatory = $true)] + [string] $companyId + ) + + Write-Host "Updating Flow settings" + $flowFilePaths = Get-ChildItem -Path (Join-Path $SolutionFolder 'Workflows') -Recurse -Filter '*.json' | Select-Object -ExpandProperty FullName + + if ($null -eq $flowFilePaths) { + Write-Host "::Notice::No Power Automate flows found" + return + } + + foreach ($flowFilePath in $flowFilePaths) { + Update-FlowFile -FilePath $flowFilePath -CompanyId $companyId -EnvironmentName $environmentName + } +} + +function Update-FlowFile { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string] $filePath, + [Parameter(Mandatory = $true)] + [string] $companyId, + [Parameter(Mandatory = $true)] + [string] $environmentName + ) + # Read the JSON file + $jsonObject = Get-Content $filePath | ConvertFrom-Json + $shouldUpdate = $false + + # Update all flow triggers + $triggersObject = $jsonObject.properties.definition.triggers + $triggers = $triggersObject | Get-Member -MemberType Properties + foreach ($trigger in $triggers) { + $triggerInputs = $triggersObject.($trigger.Name).inputs + + if ($triggerInputs | Get-Member -MemberType Properties -name 'parameters') { + # Business Central triggers have connection information in the input parameters + if (Update-ParameterObject -parametersObject $triggerInputs.parameters -CompanyId $companyId -EnvironmentName $environmentName) { + $shouldUpdate = $true + } + } + } + + # Update all flow actions + $actionsObject = $jsonObject.properties.definition.actions + $actions = $actionsObject | Get-Member -MemberType Properties + foreach ($action in $actions) { + $actionInput = $actionsObject.($action.Name).inputs + if ($actionInput | Get-Member -MemberType Properties -name 'parameters') { + # Business Central actions have connection information in the input parameters + if (Update-ParameterObject -parametersObject $actionInput.parameters -CompanyId $companyId -EnvironmentName $environmentName) { + $shouldUpdate = $true + } + } + } + if ($shouldUpdate) { + # Save the updated JSON back to the file + $jsonObject | ConvertTo-Json -Depth 100 | Set-Content $filePath + Write-Host "Updated: $filePath" + } + else { + Write-Host "No update needed for: $filePath" + } +} + +function Update-ParameterObject { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [System.Object] $parametersObject, + [Parameter(Mandatory = $true)] + [string] $companyId, + [Parameter(Mandatory = $true)] + [string] $environmentName + ) + # Check if paramers are for Business Central + if ((-not $parametersObject.company) -or (-not $parametersObject.bcEnvironment)) { + return $false + } + + $oldCompany = $parametersObject.company + $oldBcEnvironment = $parametersObject.bcenvironment + + # Check if parameters are already set to the correct values + if (($oldCompany -eq $companyId) -and ($oldBcEnvironment -eq $environmentName)) { + return $false + } + + $enviromentVariablePlaceHolder = "@parameters(" + + # Check if parameters are set using a different approach (e.g. environment variables or passed in parameters) + if ($oldCompany.contains($enviromentVariablePlaceHolder) -or $oldBcEnvironment.contains($enviromentVariablePlaceHolder)) { + return $false + } + + $parametersObject.company = $companyId + $parametersObject.bcEnvironment = $environmentName + + return $true +} + +function Update-SolutionVersionNode { + param( + [Parameter(mandatory = $true)] + [string] $appBuild, + [Parameter(mandatory = $true)] + [string] $appRevision, + [Parameter(mandatory = $true)] + [xml] $xmlFile + ) + + if ($appBuild -and $appRevision) { + $versionNode = $xmlFile.SelectSingleNode("//Version") + $versionNodeText = $versionNode.'#text' + + $versionParts = $versionNodeText.Split('.') + # Only update the last two parts of the version number - major and minor version should be set manually + $newVersionNumber = $versionParts[0] + '.' + $versionParts[1] + '.' + $appBuild + '.' + $appRevision + + Write-Host "New version: "$newVersionNumber + $versionNode.'#text' = $newVersionNumber + } + +} + +if ($appBuild -and $appRevision) { + Write-Host "Updating Power Platform solution file ($solutionFolder)" + $solutionDefinitionFile = Join-Path $solutionFolder 'other/Solution.xml' + if (-not (Test-Path -Path $solutionDefinitionFile)) { + throw "Solution file not found: $solutionDefinitionFile" + } + $xmlFile = [xml](Get-Content -Encoding UTF8 -Path $solutionDefinitionFile) + Update-SolutionVersionNode -appBuild $appBuild -appRevision $appRevision -xmlFile $xmlFile + $xmlFile.Save($solutionDefinitionFile) +} +else { + Write-Host "Skip solution version update since appBuild and appRevision are not set" +} + +if ($environmentName -and $companyId) { + Write-Host "Updating the Power Platform solution Business Central connection settings" + Write-Host "New connections settings: $environmentName, $companyId" + Update-PowerAppSettings -SolutionFolder $SolutionFolder -EnvironmentName $environmentName -CompanyId $companyId + Update-FlowSettings -SolutionFolder $SolutionFolder -EnvironmentName $environmentName -CompanyId $companyId +} +else { + Write-Host "Skip Business Central connection settings update since EnvironmentName and CompanyId are not set" +} \ No newline at end of file diff --git a/Actions/BuildPowerPlatform/README.md b/Actions/BuildPowerPlatform/README.md new file mode 100644 index 000000000..9024460d5 --- /dev/null +++ b/Actions/BuildPowerPlatform/README.md @@ -0,0 +1,22 @@ +# Build Power Platform +Build the Power Platform solution + +## INPUT + +### ENV variables +none + +### Parameters +| Name | Required | Description | Default value | +| :-- | :-: | :-- | :-- | +| shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | +| solutionFolder | Yes | The Power Platform solution path | | +| outputFolder | Yes | Output folder where the zip file will be placed | | +| outputFileName | Yes | The name of the output zip file | | +| companyId | | The Business Central company ID | | +| environmentName | | The Business Central environment name | | +| appBuild | | The app build number | | +| appRevision | | The app revision number | | + +## OUTPUT +none diff --git a/Actions/BuildPowerPlatform/action.yaml b/Actions/BuildPowerPlatform/action.yaml new file mode 100644 index 000000000..463504097 --- /dev/null +++ b/Actions/BuildPowerPlatform/action.yaml @@ -0,0 +1,80 @@ +name: Build Power Platform Solution +author: Microsoft Corporation +inputs: + shell: + description: Shell in which you want to run the action (powershell or pwsh) + required: false + default: powershell + solutionFolder: + description: The Power Platform solution path + required: true + outputFolder: + description: Output folder where the zip file will be placed + required: true + outputFileName: + description: The name of the output zip file + required: true + companyId: + description: The Business Central company ID + required: false + default: '' + environmentName: + description: The Business Central environment name + required: false + default: '' + appBuild: + description: The app build number + required: false + default: '' + appRevision: + description: The app revision number + required: false + default: '' + +runs: + using: composite + steps: + - name: Install Power Platform Tools + uses: microsoft/powerplatform-actions/actions-install@v1 + + - name: Update Power Platform Files + shell: ${{ inputs.shell }} + env: + _solutionFolder: ${{ inputs.solutionFolder }} + _companyId: ${{ inputs.companyId }} + _environmentName: ${{ inputs.environmentName }} + _appBuild: ${{ inputs.appBuild }} + _appRevision: ${{ inputs.appRevision }} + run: | + $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + try { + ${{ github.action_path }}/BuildPowerPlatform.ps1 -solutionFolder $ENV:_solutionFolder -companyId $ENV:_companyId -environmentName $ENV:_environmentName -appBuild $ENV:_appBuild -appRevision $ENV:_appRevision + } + catch { + Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; + exit 1 + } + + - name: Test Pack (Not real - just workaround for https://github.com/microsoft/powerplatform-vscode/issues/412) + uses: microsoft/powerplatform-actions/pack-solution@v1 + with: + solution-file: forgetMe.zip + solution-folder: ${{ inputs.solutionFolder }} + process-canvas-apps: true + + - name: Remove test pack + shell: ${{ inputs.shell }} + run: | + Remove-Item -Path forgetMe.zip -Force + + - name: Pack Solution + uses: microsoft/powerplatform-actions/pack-solution@v1 + with: + solution-file: ${{ inputs.outputFolder }}/${{ inputs.outputFileName }}.zip + solution-folder: ${{ inputs.solutionFolder }} + solution-type: "Unmanaged" + process-canvas-apps: true + +branding: + icon: terminal + color: blue \ No newline at end of file diff --git a/Actions/CalculateArtifactNames/CalculateArtifactNames.ps1 b/Actions/CalculateArtifactNames/CalculateArtifactNames.ps1 index 58b189abc..d59f3a8c6 100644 --- a/Actions/CalculateArtifactNames/CalculateArtifactNames.ps1 +++ b/Actions/CalculateArtifactNames/CalculateArtifactNames.ps1 @@ -42,7 +42,7 @@ else { $suffix = "$($settings.repoVersion).$($settings.appBuild).$($settings.appRevision)" } -'Apps', 'Dependencies', 'TestApps', 'TestResults', 'BcptTestResults', 'BuildOutput', 'ContainerEventLog' | ForEach-Object { +'Apps', 'Dependencies', 'TestApps', 'TestResults', 'BcptTestResults', 'BuildOutput', 'ContainerEventLog', 'PowerPlatformSolution' | ForEach-Object { $name = "$($_)ArtifactsName" $value = "$($projectName)-$($branchName)-$buildMode$_-$suffix" Set-OutputVariable -name $name -value $value diff --git a/Actions/CalculateArtifactNames/README.md b/Actions/CalculateArtifactNames/README.md index eabbdd4c6..807f5575b 100644 --- a/Actions/CalculateArtifactNames/README.md +++ b/Actions/CalculateArtifactNames/README.md @@ -19,19 +19,7 @@ Calculate Artifact Names for AL-Go workflows ## OUTPUT ### ENV variables -| Name | Description | -| :-- | :-- | -| ThisBuildAppsArtifactsName | Artifact name for apps being built in the current workflow run | -| ThisBuildDependenciesArtifactsName | Artifact name for dependencies of apps being built in the current workflow run | -| ThisBuildTestAppsArtifactsName | Artifact name for test apps being built in the current workflow run | -| AppsArtifactsName | Artifacts name for Apps | -| DependenciesArtifactsName | Artifacts name for Dependencies | -| TestAppsArtifactsName | Artifacts name for TestApps | -| TestResultsArtifactsName | Artifacts name for TestResults | -| BcptTestResultsArtifactsName | Artifacts name for BcptTestResults | -| BuildOutputArtifactsName | Artifacts name for BuildOutput | -| ContainerEventLogArtifactsName | Artifacts name for ContainerEventLog | -| BuildMode | Build mode used when building the artifacts | +none ### OUTPUT variables | Name | Description | @@ -40,6 +28,7 @@ Calculate Artifact Names for AL-Go workflows | ThisBuildDependenciesArtifactsName | Artifact name for dependencies of apps being built in the current workflow run | | ThisBuildTestAppsArtifactsName | Artifact name for test apps being built in the current workflow run | | AppsArtifactsName | Artifacts name for Apps | +| PowerPlatformSolutionArtifactsName | Artifacts name for PowerPlatform Solution | | DependenciesArtifactsName | Artifacts name for Dependencies | | TestAppsArtifactsName | Artifacts name for TestApps | | TestResultsArtifactsName | Artifacts name for TestResults | diff --git a/Actions/CalculateArtifactNames/action.yaml b/Actions/CalculateArtifactNames/action.yaml index 143f786c4..dd7e32c79 100644 --- a/Actions/CalculateArtifactNames/action.yaml +++ b/Actions/CalculateArtifactNames/action.yaml @@ -28,6 +28,9 @@ outputs: AppsArtifactsName: description: Artifacts name for Apps value: ${{ steps.calculateartifactnames.outputs.AppsArtifactsName }} + PowerPlatformSolutionArtifactsName: + description: Artifacts name for PowerPlatform Solution + value: ${{ steps.calculateartifactnames.outputs.PowerPlatformSolutionArtifactsName }} DependenciesArtifactsName: description: Artifacts name for Dependencies value: ${{ steps.calculateartifactnames.outputs.DependenciesArtifactsName }} diff --git a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 index fe3d8eeff..9a312a3d7 100644 --- a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 +++ b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 @@ -103,7 +103,7 @@ function ModifyRunsOnAndShell { # The default for runs-on is windows-latest and the default for shell is powershell # The default for GitHubRunner/GitHubRunnerShell is runs-on/shell (unless Ubuntu-latest are selected here, as build jobs cannot run on Ubuntu) - # We do not change runs-on in Update AL Go System Files and Pull Request Handler workflows + # We do not change runs-on in Update AL-Go System Files and Pull Request Handler workflows # These workflows will always run on windows-latest (or maybe Ubuntu-latest later) and not follow settings # Reasons: # - Update AL-Go System files is needed for changing runs-on - by having non-functioning runners, you might dead-lock yourself @@ -122,11 +122,17 @@ function ModifyRunsOnAndShell { function ModifyBuildWorkflows { Param( [Yaml] $yaml, - [int] $depth + [int] $depth, + [bool] $includeBuildPP ) $yaml.Replace('env:/workflowDepth:',"workflowDepth: $depth") $build = $yaml.Get('jobs:/Build:/') + $buildPP = $yaml.Get('jobs:/BuildPP:/') + $deliver = $yaml.Get('jobs:/Deliver:/') + $deploy = $yaml.Get('jobs:/Deploy:/') + $deployALDoc = $yaml.Get('jobs:/DeployALDoc:/') + $postProcess = $yaml.Get('jobs:/PostProcess:/') if (!$build) { throw "No build job found in the workflow" } @@ -179,6 +185,44 @@ function ModifyBuildWorkflows { # Replace the entire build: job with the new build job list $yaml.Replace('jobs:/Build:', $newBuild) + + if (!$includeBuildPP -and $buildPP) { + # Remove the BuildPP job from the workflow + [int]$start = 0 + [int]$count = 0 + if ($yaml.Find('jobs:/BuildPP:', [ref] $start, [ref] $count)) { + $yaml.Remove($start, $count+1) + } + } + + $needs += @("Build") + $ifpart += " && (needs.Build.result == 'success' || needs.Build.result == 'skipped')" + if ($includeBuildPP -and $buildPP) { + $needs += @("BuildPP") + $ifpart += " && (needs.BuildPP.result == 'success' || needs.BuildPP.result == 'skipped')" + } + + $postProcessNeeds = $needs + # Modify Deliver and Deploy steps depending on build jobs + if ($deploy) { + $deploy.Replace('needs:', "needs: [ $($needs -join ', ') ]") + $deploy.Replace('if:', "if: (!cancelled())$ifpart && needs.Initialization.outputs.environmentCount > 0") + $yaml.Replace('jobs:/Deploy:/', $deploy.content) + $postProcessNeeds += @('Deploy') + } + if ($deliver) { + $deliver.Replace('needs:', "needs: [ $($needs -join ', ') ]") + $deliver.Replace('if:', "if: (!cancelled())$ifpart && needs.Initialization.outputs.deliveryTargetsJson != '[]'") + $yaml.Replace('jobs:/Deliver:/', $deliver.content) + $postProcessNeeds += @('Deliver') + } + if ($deployALDoc) { + $postProcessNeeds += @('DeployALDoc') + } + if ($postProcess) { + $postProcess.Replace('needs:', "needs: [ $($postProcessNeeds -join ', ') ]") + $yaml.Replace('jobs:/PostProcess:/', $postProcess.content) + } } function ModifyUpdateALGoSystemFiles { @@ -220,7 +264,8 @@ function GetWorkflowContentWithChangesFromSettings { Param( [string] $srcFile, [hashtable] $repoSettings, - [int] $depth + [int] $depth, + [bool] $includeBuildPP ) $baseName = [System.IO.Path]::GetFileNameWithoutExtension($srcFile) @@ -250,8 +295,8 @@ function GetWorkflowContentWithChangesFromSettings { # PullRequestHandler, CICD, Current, NextMinor and NextMajor workflows all include a build step. # If the dependency depth is higher than 1, we need to add multiple dependent build jobs to the workflow - if ($depth -gt 1 -and ($baseName -eq 'PullRequestHandler' -or $baseName -eq 'CICD' -or $baseName -eq 'Current' -or $baseName -eq 'NextMinor' -or $baseName -eq 'NextMajor')) { - ModifyBuildWorkflows -yaml $yaml -depth $depth + if ($baseName -eq 'PullRequestHandler' -or $baseName -eq 'CICD' -or $baseName -eq 'Current' -or $baseName -eq 'NextMinor' -or $baseName -eq 'NextMajor') { + ModifyBuildWorkflows -yaml $yaml -depth $depth -includeBuildPP $includeBuildPP } if($baseName -eq 'UpdateGitHubGoSystemFiles') { diff --git a/Actions/CheckForUpdates/CheckForUpdates.ps1 b/Actions/CheckForUpdates/CheckForUpdates.ps1 index 0a1f798fd..3051d8948 100644 --- a/Actions/CheckForUpdates/CheckForUpdates.ps1 +++ b/Actions/CheckForUpdates/CheckForUpdates.ps1 @@ -58,6 +58,11 @@ $templateUrl = $templateUrl -replace "^(https:\/\/)(www\.)(.*)$", '$1$3' $repoSettings = ReadSettings -project '' -workflowName '' -userName '' -branchName '' | ConvertTo-HashTable -recurse $templateSha = $repoSettings.templateSha $unusedALGoSystemFiles = $repoSettings.unusedALGoSystemFiles +$includeBuildPP = $repoSettings.type -eq 'PTE' -and $repoSettings.powerPlatformSolutionFolder -ne '' +if (!$includeBuildPP) { + # Remove PowerPlatform workflows if no PowerPlatformSolution exists + $unusedALGoSystemFiles += @('_BuildPowerPlatformSolution.yaml','PushPowerPlatformChanges.yaml','PullPowerPlatformChanges.yaml') +} # If templateUrl has changed, download latest version of the template repository (ignore templateSha) if ($repoSettings.templateUrl -ne $templateUrl -or $templateSha -eq '') { @@ -143,7 +148,7 @@ foreach($checkfile in $checkfiles) { Write-Host "SrcFolder: $srcFolder" if ($type -eq "workflow") { # for workflow files, we might need to modify the file based on the settings - $srcContent = GetWorkflowContentWithChangesFromSettings -srcFile $srcFile -repoSettings $repoSettings -depth $depth + $srcContent = GetWorkflowContentWithChangesFromSettings -srcFile $srcFile -repoSettings $repoSettings -depth $depth -includeBuildPP $includeBuildPP } else { # For non-workflow files, just read the file content diff --git a/Actions/Deploy/Deploy.ps1 b/Actions/Deploy/Deploy.ps1 index 98415dc5e..2a5dfca8e 100644 --- a/Actions/Deploy/Deploy.ps1 +++ b/Actions/Deploy/Deploy.ps1 @@ -1,12 +1,10 @@ -Param( +Param( [Parameter(HelpMessage = "The GitHub token running the action", Mandatory = $false)] [string] $token, - [Parameter(HelpMessage = "Specifies the parent telemetry scope for the telemetry signal", Mandatory = $false)] - [string] $parentTelemetryScopeJson = '7b7d', [Parameter(HelpMessage = "Name of environment to deploy to", Mandatory = $true)] [string] $environmentName, - [Parameter(HelpMessage = "The artifacts to deploy or a folder in which the artifacts have been downloaded", Mandatory = $true)] - [string] $artifacts, + [Parameter(HelpMessage = "Path to the downloaded artifacts to deploy", Mandatory = $true)] + [string] $artifactsFolder, [Parameter(HelpMessage = "Type of deployment (CD or Publish)", Mandatory = $false)] [ValidateSet('CD','Publish')] [string] $type = "CD", @@ -14,233 +12,147 @@ [string] $deploymentEnvironmentsJson ) -$telemetryScope = $null +. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) +DownloadAndImportBcContainerHelper -try { - . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) - DownloadAndImportBcContainerHelper +$deploymentEnvironments = $deploymentEnvironmentsJson | ConvertFrom-Json | ConvertTo-HashTable -recurse +$deploymentSettings = $deploymentEnvironments."$environmentName" +$envName = $environmentName.Split(' ')[0] +$secrets = $env:Secrets | ConvertFrom-Json - import-module (Join-Path -path $PSScriptRoot -ChildPath "..\TelemetryHelper.psm1" -Resolve) - $telemetryScope = CreateScope -eventId 'DO0075' -parentTelemetryScopeJson $parentTelemetryScopeJson - - $deploymentEnvironments = $deploymentEnvironmentsJson | ConvertFrom-Json | ConvertTo-HashTable -recurse - $deploymentSettings = $deploymentEnvironments."$environmentName" - $envName = $environmentName.Split(' ')[0] - $secrets = $env:Secrets | ConvertFrom-Json - - # Check obsolete secrets - "$($envName)-EnvironmentName","$($envName)_EnvironmentName","EnvironmentName" | ForEach-Object { - if ($secrets."$_") { - throw "The secret $_ is obsolete and should be replaced by using the EnvironmentName property in the DeployTo$envName setting in .github/AL-Go-Settings.json instead" - } - } - if ($secrets.Projects) { - throw "The secret Projects is obsolete and should be replaced by using the Projects property in the DeployTo$envName setting in .github/AL-Go-Settings.json instead" - } - - $authContext = $null - foreach($secretName in "$($envName)-AuthContext","$($envName)_AuthContext","AuthContext") { - if ($secrets."$secretName") { - $authContext = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets."$secretName")) - } - } - if (-not $authContext) { - # No AuthContext secret provided, if deviceCode is present, use it - else give an error - if ($env:deviceCode) { - $authContext = "{""deviceCode"":""$($env:deviceCode)""}" - } - else { - throw "No Authentication Context found for environment ($environmentName). You must create an environment secret called AUTHCONTEXT or a repository secret called $($envName)_AUTHCONTEXT." - } - } - - $artifacts = $artifacts.Replace('/',([System.IO.Path]::DirectorySeparatorChar)).Replace('\',([System.IO.Path]::DirectorySeparatorChar)) - - $apps = @() - $artifactsFolder = Join-Path $ENV:GITHUB_WORKSPACE ".artifacts" - $artifactsFolderCreated = $false - if ($artifacts -eq ".artifacts") { - $artifacts = $artifactsFolder +# Check obsolete secrets +"$($envName)-EnvironmentName","$($envName)_EnvironmentName","EnvironmentName" | ForEach-Object { + if ($secrets."$_") { + throw "The secret $_ is obsolete and should be replaced by using the EnvironmentName property in the DeployTo$envName setting in .github/AL-Go-Settings.json instead" } +} +if ($secrets.Projects) { + throw "The secret Projects is obsolete and should be replaced by using the Projects property in the DeployTo$envName setting in .github/AL-Go-Settings.json instead" +} - $searchArtifacts = $false - if ($artifacts -like "$($ENV:GITHUB_WORKSPACE)*") { - if (Test-Path $artifacts -PathType Container) { - $deploymentSettings.Projects.Split(',') | ForEach-Object { - $project = $_.Replace('\','_').Replace('/','_') - $refname = "$ENV:GITHUB_REF_NAME".Replace('/','_') - Write-Host "project '$project'" - $apps += @((Get-ChildItem -Path $artifacts -Filter "$project-$refname-Apps-*.*.*.*") | ForEach-Object { $_.FullName }) - if (!($apps)) { - throw "There is no artifacts present in $artifacts matching $project-$refname-Apps-." - } - $apps += @((Get-ChildItem -Path $artifacts -Filter "$project-$refname-Dependencies-*.*.*.*") | ForEach-Object { $_.FullName }) - } - } - elseif (Test-Path $artifacts) { - $apps = $artifacts - } - else { - throw "Artifact $artifacts was not found. Make sure that the artifact files exist and files are not corrupted." - } +$authContext = $null +foreach($secretName in "$($envName)-AuthContext","$($envName)_AuthContext","AuthContext") { + if ($secrets."$secretName") { + $authContext = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets."$secretName")) + break } - elseif ($artifacts -eq "current" -or $artifacts -eq "prerelease" -or $artifacts -eq "draft") { - # latest released version - $releases = GetReleases -token $token -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY - if ($releases) { - if ($artifacts -eq "current") { - $release = $releases | Where-Object { -not ($_.prerelease -or $_.draft) } | Select-Object -First 1 - } - elseif ($artifacts -eq "prerelease") { - $release = $releases | Where-Object { -not ($_.draft) } | Select-Object -First 1 - } - elseif ($artifacts -eq "draft") { - $release = $releases | Select-Object -First 1 - } - if (!($release)) { - throw "Unable to locate $artifacts release" - } - New-Item $artifactsFolder -ItemType Directory | Out-Null - $artifactsFolderCreated = $true - DownloadRelease -token $token -projects $deploymentSettings.Projects -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -release $release -path $artifactsFolder -mask "Apps" - DownloadRelease -token $token -projects $deploymentSettings.Projects -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -release $release -path $artifactsFolder -mask "Dependencies" - $apps = @((Get-ChildItem -Path $artifactsFolder) | ForEach-Object { $_.FullName }) - if (!$apps) { - throw "Artifact $artifacts was not found on any release. Make sure that the artifact files exist and files are not corrupted." - } - } - else { - if ($artifacts -eq "current") { - Write-Host "::Warning::Current release was specified, but no releases were found. Searching for latest build artifacts instead." - $artifacts = "latest" - $searchArtifacts = $true - } - else { - throw "Artifact $artifacts was not found on any release." - } - } +} +if (-not $authContext) { + # No AuthContext secret provided, if deviceCode is present, use it - else give an error + if ($env:deviceCode) { + $authContext = "{""deviceCode"":""$($env:deviceCode)""}" } else { - $searchArtifacts = $true + throw "No Authentication Context found for environment ($environmentName). You must create an environment secret called AUTHCONTEXT or a repository secret called $($envName)_AUTHCONTEXT." } +} - if ($searchArtifacts) { - New-Item $artifactsFolder -ItemType Directory | Out-Null +$apps = @() +$artifactsFolder = Join-Path $ENV:GITHUB_WORKSPACE $artifactsFolder +if (Test-Path $artifactsFolder -PathType Container) { + $deploymentSettings.Projects.Split(',') | ForEach-Object { + $project = $_.Replace('\','_').Replace('/','_') $refname = "$ENV:GITHUB_REF_NAME".Replace('/','_') - $allArtifacts = @(GetArtifacts -token $token -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -mask "Apps" -projects $deploymentSettings.Projects -version $artifacts -branch $ENV:GITHUB_REF_NAME) - $allArtifacts += @(GetArtifacts -token $token -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -mask "Dependencies" -projects $deploymentSettings.Projects -version $artifacts -branch $ENV:GITHUB_REF_NAME) - if ($allArtifacts) { - $allArtifacts | ForEach-Object { - $appFile = DownloadArtifact -token $token -artifact $_ -path $artifactsFolder - if (!(Test-Path $appFile)) { - throw "Unable to download artifact $($_.name)" - } - $apps += @($appFile) + Write-Host "project '$project'" + $projectApps = @((Get-ChildItem -Path $artifactsFolder -Filter "$project-$refname-Apps-*.*.*.*") | ForEach-Object { $_.FullName }) + if (!($projectApps)) { + if ($project -ne '*') { + throw "There are no artifacts present in $artifactsFolder matching $project-$refname-Apps-." } } else { - throw "Could not find any Apps artifacts for projects $($deploymentSettings.Projects), version $artifacts" + $apps += $projectApps } } +} +else { + throw "Artifact $artifactsFolder was not found. Make sure that the artifact files exist and files are not corrupted." +} - Write-Host "Apps to deploy" - $apps | Out-Host +Write-Host "Apps to deploy" +$apps | Out-Host - Set-Location $ENV:GITHUB_WORKSPACE +Set-Location $ENV:GITHUB_WORKSPACE - $customScript = Join-Path $ENV:GITHUB_WORKSPACE ".github/DeployTo$($deploymentSettings.EnvironmentType).ps1" - if (Test-Path $customScript) { - Write-Host "Executing custom deployment script $customScript" - $parameters = @{ - "type" = $type - "AuthContext" = $authContext - "Apps" = $apps - } + $deploymentSettings - . $customScript -parameters $parameters +$customScript = Join-Path $ENV:GITHUB_WORKSPACE ".github/DeployTo$($deploymentSettings.EnvironmentType).ps1" +if (Test-Path $customScript) { + Write-Host "Executing custom deployment script $customScript" + $parameters = @{ + "type" = $type + "AuthContext" = $authContext + "Apps" = $apps + } + $deploymentSettings + . $customScript -parameters $parameters +} +else { + try { + $authContextParams = $authContext | ConvertFrom-Json | ConvertTo-HashTable + $bcAuthContext = New-BcAuthContext @authContextParams + if ($null -eq $bcAuthContext) { + throw "Authentication failed" + } + } catch { + throw "Authentication failed. $([environment]::Newline) $($_.exception.message)" } - else { - try { - $authContextParams = $authContext | ConvertFrom-Json | ConvertTo-HashTable - $bcAuthContext = New-BcAuthContext @authContextParams - if ($null -eq $bcAuthContext) { - throw "Authentication failed" - } - } catch { - throw "Authentication failed. $([environment]::Newline) $($_.exception.message)" - } - $environmentUrl = "$($bcContainerHelperConfig.baseUrl.TrimEnd('/'))/$($bcAuthContext.tenantId)/$($deploymentSettings.EnvironmentName)" - Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "environmentUrl=$environmentUrl" - Write-Host "EnvironmentUrl: $environmentUrl" - $response = Invoke-RestMethod -UseBasicParsing -Method Get -Uri "$environmentUrl/deployment/url" - if ($response.Status -eq "DoesNotExist") { - OutputError -message "Environment with name $($deploymentSettings.EnvironmentName) does not exist in the current authorization context." - exit - } - if ($response.Status -ne "Ready") { - OutputError -message "Environment with name $($deploymentSettings.EnvironmentName) is not ready (Status is $($response.Status))." - exit - } + $environmentUrl = "$($bcContainerHelperConfig.baseUrl.TrimEnd('/'))/$($bcAuthContext.tenantId)/$($deploymentSettings.EnvironmentName)" + Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "environmentUrl=$environmentUrl" + Write-Host "EnvironmentUrl: $environmentUrl" + $response = Invoke-RestMethod -UseBasicParsing -Method Get -Uri "$environmentUrl/deployment/url" + if ($response.Status -eq "DoesNotExist") { + OutputError -message "Environment with name $($deploymentSettings.EnvironmentName) does not exist in the current authorization context." + exit + } + if ($response.Status -ne "Ready") { + OutputError -message "Environment with name $($deploymentSettings.EnvironmentName) is not ready (Status is $($response.Status))." + exit + } - try { - $sandboxEnvironment = ($response.environmentType -eq 1) - if ($sandboxEnvironment -and !($bcAuthContext.ClientSecret)) { - # Sandbox and not S2S -> use dev endpoint (Publish-BcContainerApp) - $parameters = @{ - "bcAuthContext" = $bcAuthContext - "environment" = $deploymentSettings.EnvironmentName - "appFile" = $apps - } - if ($deploymentSettings.SyncMode) { - if (@('Add','ForceSync', 'Clean', 'Development') -notcontains $deploymentSettings.SyncMode) { - throw "Invalid SyncMode $($deploymentSettings.SyncMode) when deploying using the development endpoint. Valid values are Add, ForceSync, Development and Clean." - } - Write-Host "Using $($deploymentSettings.SyncMode)" - $parameters += @{ "SyncMode" = $deploymentSettings.SyncMode } + try { + $sandboxEnvironment = ($response.environmentType -eq 1) + if ($sandboxEnvironment -and !($bcAuthContext.ClientSecret)) { + # Sandbox and not S2S -> use dev endpoint (Publish-BcContainerApp) + $parameters = @{ + "bcAuthContext" = $bcAuthContext + "environment" = $deploymentSettings.EnvironmentName + "appFile" = $apps + } + if ($deploymentSettings.SyncMode) { + if (@('Add','ForceSync', 'Clean', 'Development') -notcontains $deploymentSettings.SyncMode) { + throw "Invalid SyncMode $($deploymentSettings.SyncMode) when deploying using the development endpoint. Valid values are Add, ForceSync, Development and Clean." } - Write-Host "Publishing apps using development endpoint" - Publish-BcContainerApp @parameters -useDevEndpoint -checkAlreadyInstalled -excludeRuntimePackages + Write-Host "Using $($deploymentSettings.SyncMode)" + $parameters += @{ "SyncMode" = $deploymentSettings.SyncMode } } - elseif (!$sandboxEnvironment -and $type -eq 'CD' -and !($deploymentSettings.continuousDeployment)) { - # Continuous deployment is undefined in settings - we will not deploy to production environments - Write-Host "::Warning::Ignoring environment $($deploymentSettings.EnvironmentName), which is a production environment" + Write-Host "Publishing apps using development endpoint" + Publish-BcContainerApp @parameters -useDevEndpoint -checkAlreadyInstalled -excludeRuntimePackages -replacePackageId + } + elseif (!$sandboxEnvironment -and $type -eq 'CD' -and !($deploymentSettings.continuousDeployment)) { + # Continuous deployment is undefined in settings - we will not deploy to production environments + Write-Host "::Warning::Ignoring environment $($deploymentSettings.EnvironmentName), which is a production environment" + } + else { + # Use automation API for production environments (Publish-PerTenantExtensionApps) + $parameters = @{ + "bcAuthContext" = $bcAuthContext + "environment" = $deploymentSettings.EnvironmentName + "appFiles" = $apps } - else { - # Use automation API for production environments (Publish-PerTenantExtensionApps) - $parameters = @{ - "bcAuthContext" = $bcAuthContext - "environment" = $deploymentSettings.EnvironmentName - "appFiles" = $apps + if ($deploymentSettings.SyncMode) { + if (@('Add','ForceSync') -notcontains $deploymentSettings.SyncMode) { + throw "Invalid SyncMode $($deploymentSettings.SyncMode) when deploying using the automation API. Valid values are Add and ForceSync." } - if ($deploymentSettings.SyncMode) { - if (@('Add','ForceSync') -notcontains $deploymentSettings.SyncMode) { - throw "Invalid SyncMode $($deploymentSettings.SyncMode) when deploying using the automation API. Valid values are Add and ForceSync." - } - Write-Host "Using $($deploymentSettings.SyncMode)" - $syncMode = $deploymentSettings.SyncMode - if ($syncMode -eq 'ForceSync') { $syncMode = 'Force' } - $parameters += @{ "SchemaSyncMode" = $syncMode } - } - Write-Host "Publishing apps using automation API" - Publish-PerTenantExtensionApps @parameters + Write-Host "Using $($deploymentSettings.SyncMode)" + $syncMode = $deploymentSettings.SyncMode + if ($syncMode -eq 'ForceSync') { $syncMode = 'Force' } + $parameters += @{ "SchemaSyncMode" = $syncMode } } + Write-Host "Publishing apps using automation API" + Publish-PerTenantExtensionApps @parameters } - catch { - OutputError -message "Deploying to $environmentName failed.$([environment]::Newline) $($_.Exception.Message)" - exit - } - } - - if ($artifactsFolderCreated) { - Remove-Item $artifactsFolder -Recurse -Force } - - TrackTrace -telemetryScope $telemetryScope - -} -catch { - if (Get-Module BcContainerHelper) { - TrackException -telemetryScope $telemetryScope -errorRecord $_ + catch { + OutputError -message "Deploying to $environmentName failed.$([environment]::Newline) $($_.Exception.Message)" + exit } - throw } diff --git a/Actions/Deploy/README.md b/Actions/Deploy/README.md index 73850dc85..099be6404 100644 --- a/Actions/Deploy/README.md +++ b/Actions/Deploy/README.md @@ -15,11 +15,10 @@ Deploy Apps to online environment | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | | token | | The GitHub token running the action | github.token | -| parentTelemetryScopeJson | | Specifies the parent telemetry scope for the telemetry signal | {} | -| projects | | Comma-separated list of projects to deploy. | | | environmentName | Yes | Name of environment to deploy to | -| artifacts | Yes | The artifacts to deploy or a folder in which the artifacts have been downloaded | | +| artifactsFolder | Yes | Path to the downloaded artifacts to deploy | | | type | | Type of delivery (CD or Release) | CD | +| deploymentEnvironmentsJson | Yes | The settings for all Deployment Environments | | ## OUTPUT | Name | Description | diff --git a/Actions/Deploy/action.yaml b/Actions/Deploy/action.yaml index 1c198ace5..238cf7c7f 100644 --- a/Actions/Deploy/action.yaml +++ b/Actions/Deploy/action.yaml @@ -9,15 +9,11 @@ inputs: description: The GitHub token running the action required: false default: ${{ github.token }} - parentTelemetryScopeJson: - description: Specifies the parent telemetry scope for the telemetry signal - required: false - default: '7b7d' environmentName: description: Name of environment to deploy to required: true - artifacts: - description: The artifacts to deploy or a folder in which the artifacts have been downloaded + artifactsFolder: + description: Path to the downloaded artifacts to deploy required: true type: description: Type of deployment (CD or Publish) @@ -38,15 +34,14 @@ runs: id: Deploy env: _token: ${{ inputs.token }} - _parentTelemetryScopeJson: ${{ inputs.parentTelemetryScopeJson }} _environmentName: ${{ inputs.environmentName }} - _artifacts: ${{ inputs.artifacts }} + _artifactsFolder: ${{ inputs.artifactsFolder }} _type: ${{ inputs.type }} _deploymentEnvironmentsJson: ${{ inputs.deploymentEnvironmentsJson }} run: | $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 try { - ${{ github.action_path }}/Deploy.ps1 -token $ENV:_token -parentTelemetryScopeJson $ENV:_parentTelemetryScopeJson -environmentName $ENV:_environmentName -artifacts $ENV:_artifacts -type $ENV:_type -deploymentEnvironmentsJson $ENV:_deploymentEnvironmentsJson + ${{ github.action_path }}/Deploy.ps1 -token $ENV:_token -environmentName $ENV:_environmentName -artifactsFolder $ENV:_artifactsFolder -type $ENV:_type -deploymentEnvironmentsJson $ENV:_deploymentEnvironmentsJson } catch { Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; diff --git a/Actions/DeployPowerPlatform/DeterminePowerPlatformSolutionFolder.ps1 b/Actions/DeployPowerPlatform/DeterminePowerPlatformSolutionFolder.ps1 new file mode 100644 index 000000000..0eeefd4f5 --- /dev/null +++ b/Actions/DeployPowerPlatform/DeterminePowerPlatformSolutionFolder.ps1 @@ -0,0 +1,24 @@ +Param( + [string] $artifactsFolder, + [string] $solutionFolder +) + +if ($artifactsFolder -ne '') { + $artifactsFiles = Get-ChildItem -Path (Join-Path $ENV:GITHUB_WORKSPACE $artifactsFolder) -Recurse -File | Select-Object -ExpandProperty FullName + foreach($filePath in $artifactsFiles){ + ## Find file containing Power Platform keyword + if($filePath.contains("-PowerPlatformSolution-")){ + Write-Host "Power Platform solution file:"$filePath + Add-Content -encoding utf8 -path $env:GITHUB_ENV -value "powerPlatformSolutionFilePath=$filePath" + Add-Content -encoding utf8 -path $env:GITHUB_ENV -value "powerPlatformSolutionFolder=.artifacts/_tempPPSolution/source" + return + } + } + throw "Not able to find Power Platform solution file in $artifactFolder that contains the artifact keyword '-PowerPlatformSolution-'" +} +elseif ($solutionFolder -ne '') { + Add-Content -encoding utf8 -path $env:GITHUB_ENV -value "powerPlatformSolutionFolder=$solutionFolder" +} +else { + throw "No artifactsFolder or solutionFolder specified" +} diff --git a/Actions/DeployPowerPlatform/README.md b/Actions/DeployPowerPlatform/README.md new file mode 100644 index 000000000..37958ae72 --- /dev/null +++ b/Actions/DeployPowerPlatform/README.md @@ -0,0 +1,26 @@ +# Deploy Power Platform +Deploy the Power Platform solution from the artifacts folder + +## INPUT + +### ENV variables +| Name | Description | +| :-- | :-- | +| Settings | env.Settings must be set by a prior call to the ReadSettings Action | +| Secrets | env.Secrets with delivery target context secrets must be read by a prior call to the ReadSecrets Action | + +### Parameters +| Name | Required | Description | Default value | +| :-- | :-: | :-- | :-- | +| shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | +| environmentName | Yes | Name of environment to deploy to | +| artifactsFolder | | Path to the downloaded artifacts to deploy (when deploying from a build) | | +| solutionFolder | | Path to the unpacked solutions to deploy (when deploying from branch) | | +| deploymentEnvironmentsJson | Yes | The settings for all Deployment Environments | | + +Either artifactsFolder or solutionFolder needs to be specified + +## OUTPUT +| Name | Description | +| :-- | :-- | +| environmentUrl | The URL for the environment. This URL is presented in the Deploy Step in summary under the environment name | diff --git a/Actions/DeployPowerPlatform/action.yaml b/Actions/DeployPowerPlatform/action.yaml new file mode 100644 index 000000000..90a9f0126 --- /dev/null +++ b/Actions/DeployPowerPlatform/action.yaml @@ -0,0 +1,117 @@ +name: Deploy PowerPlatform Solution +author: Microsoft Corporation +inputs: + shell: + description: Shell in which you want to run the action (powershell or pwsh) + required: false + default: powershell + environmentName: + description: Name of environment to deploy to + required: true + artifactsFolder: + description: Path to the downloaded artifacts to deploy (when deploying from a build) + required: false + default: '' + solutionFolder: + description: Path to the unpacked solution to deploy (when deploying from branch) + required: false + default: '' + deploymentEnvironmentsJson: + description: The settings for all Deployment Environments + required: true +runs: + using: composite + steps: + - name: Install Power Platform Tools + uses: microsoft/powerplatform-actions/actions-install@v1 + + - name: Set Actions Repo and Ref + shell: ${{ inputs.shell }} + env: + actionsRepo: ${{ github.action_repository }} + actionsRef: ${{ github.action_ref }} + run: | + $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + try { + ${{ github.action_path }}/../SetActionsRepoAndRef.ps1 -actionsRepo $ENV:actionsRepo -actionsRef $ENV:actionsRef + } + catch { + Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; + exit 1 + } + + - name: Check out AL-Go Actions + uses: actions/checkout@v4 + with: + repository: ${{ env.actionsRepo }} + ref: ${{ env.actionsRef }} + path: ${{ env.actionsPath }} + + - name: Parse DeployToSettings and AuthContext + id: ReadPowerPlatformSettings + uses: ./_AL-Go/Actions/ReadPowerPlatformSettings + with: + shell: ${{ inputs.shell }} + deploymentEnvironmentsJson: ${{ inputs.deploymentEnvironmentsJson }} + environmentName: ${{ inputs.environmentName }} + + - name: Determine Power Platform solution location + shell: ${{ inputs.shell }} + env: + _artifactsFolder: ${{ inputs.artifactsFolder }} + _solutionFolder: ${{ inputs.solutionFolder }} + run: | + $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + try { + ${{ github.action_path }}/DeterminePowerPlatformSolutionFolder.ps1 -artifactsFolder $ENV:_artifactsFolder -solutionFolder $ENV:_solutionFolder + } + catch { + Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; + exit 1 + } + + - name: Unpack solution artifact + if: env.powerPlatformSolutionFilePath != '' + uses: microsoft/powerplatform-actions/unpack-solution@v1 + with: + solution-file: ${{ env.powerPlatformSolutionFilePath }} + solution-folder: ${{ env.powerPlatformSolutionFolder }} + solution-type: "Unmanaged" + process-canvas-apps: true + + - name: Rebuild solution + uses: ./_AL-Go/Actions/BuildPowerPlatform + with: + shell: ${{ inputs.shell }} + solutionFolder: ${{ env.powerPlatformSolutionFolder }} + outputFolder: .artifacts/_tempPPSolution + outputFileName: ppsolution + companyId: ${{ steps.ReadPowerPlatformSettings.outputs.companyId }} + environmentName: ${{ steps.ReadPowerPlatformSettings.outputs.environmentName }} + + - name: Publish solution to Power Platform (userName) + if: steps.ReadPowerPlatformSettings.outputs.ppUserName != '' + uses: microsoft/powerplatform-actions/import-solution@v1 + with: + user-name: ${{ steps.ReadPowerPlatformSettings.outputs.ppUserName }} + password-secret: ${{ steps.ReadPowerPlatformSettings.outputs.ppPassword }} + environment-url: ${{ steps.ReadPowerPlatformSettings.outputs.ppEnvironmentUrl }} + solution-file: .artifacts/_tempPPSolution/ppsolution.zip + force-overwrite: true + publish-changes: true + + - name: Publish solution to Power Platform (ApplicationId) + if: steps.ReadPowerPlatformSettings.outputs.ppApplicationId != '' + uses: microsoft/powerplatform-actions/import-solution@v1 + with: + tenant-id: ${{ steps.ReadPowerPlatformSettings.outputs.ppTenantId }} + app-id: ${{ steps.ReadPowerPlatformSettings.outputs.ppApplicationId }} + client-secret: ${{ steps.ReadPowerPlatformSettings.outputs.ppClientSecret }} + environment-url: ${{ steps.ReadPowerPlatformSettings.outputs.ppEnvironmentUrl }} + solution-file: .artifacts/_tempPPSolution/ppsolution.zip + force-overwrite: true + publish-changes: true + +branding: + icon: terminal + color: blue diff --git a/Actions/DetermineDeploymentEnvironments/DetermineDeploymentEnvironments.ps1 b/Actions/DetermineDeploymentEnvironments/DetermineDeploymentEnvironments.ps1 index 65ea41353..1daa162ff 100644 --- a/Actions/DetermineDeploymentEnvironments/DetermineDeploymentEnvironments.ps1 +++ b/Actions/DetermineDeploymentEnvironments/DetermineDeploymentEnvironments.ps1 @@ -1,8 +1,8 @@ Param( [Parameter(HelpMessage = "Specifies the pattern of the environments you want to retreive (* for all)", Mandatory = $true)] [string] $getEnvironments, - [Parameter(HelpMessage = "Type of deployment (CD or Publish)", Mandatory = $true)] - [ValidateSet('CD','Publish')] + [Parameter(HelpMessage = "Type of deployment (CD, Publish or All)", Mandatory = $true)] + [ValidateSet('CD','Publish','All')] [string] $type ) @@ -108,6 +108,8 @@ if (!($environments)) { "continuousDeployment" = !($getEnvironments -like '* (PROD)' -or $getEnvironments -like '* (Production)' -or $getEnvironments -like '* (FAT)' -or $getEnvironments -like '* (Final Acceptance Test)') "runs-on" = @($settings."runs-on".Split(',').Trim()) "shell" = $settings."shell" + "companyId" = '' + "ppEnvironmentUrl" = '' } } $unknownEnvironment = 1 @@ -132,6 +134,8 @@ else { # - continuous deployment: only for environments not tagged with PROD or FAT # - runs-on: same as settings."runs-on" # - shell: same as settings."shell" + # - no companyId + # - no ppEnvironmentUrl $deploymentSettings = @{ "EnvironmentType" = "SaaS" "EnvironmentName" = $envName @@ -142,15 +146,20 @@ else { "continuousDeployment" = $null "runs-on" = @($settings."runs-on".Split(',').Trim()) "shell" = $settings."shell" + "companyId" = '' + "ppEnvironmentUrl" = '' } # Check DeployTo setting $settingsName = "DeployTo$envName" if ($settings.ContainsKey($settingsName)) { # If a DeployTo setting exists - use values from this (over the defaults) + Write-Host "Setting $settingsName" $deployTo = $settings."$settingsName" - foreach($key in 'EnvironmentType','EnvironmentName','Branches','Projects','SyncMode','ContinuousDeployment','runs-on','shell') { + $keys = @($deploymentSettings.Keys) + foreach($key in $keys) { if ($deployTo.ContainsKey($key)) { + Write-Host "Property $key = $($deployTo."$key")" $deploymentSettings."$key" = $deployTo."$key" } } @@ -167,13 +176,34 @@ else { # - Type is not Continous Deployment # - Environment is setup for Continuous Deployment (in settings) # - Continuous Deployment is unset in settings and environment name doesn't contain PROD or FAT tags - $includeEnvironment = ($type -ne "CD" -or $deploymentSettings.ContinuousDeployment -or ($null -eq $deploymentSettings.ContinuousDeployment -and !($environmentName -like '* (PROD)' -or $environmentName -like '* (Production)' -or $environmentName -like '* (FAT)' -or $environmentName -like '* (Final Acceptance Test)'))) + switch ($type) { + 'CD' { + if ($null -eq $deploymentSettings.continuousDeployment) { + # Continuous Deployment is unset in settings - only include environments not tagged with PROD or FAT + $includeEnvironment = !($environmentName -like '* (PROD)' -or $environmentName -like '* (Production)' -or $environmentName -like '* (FAT)' -or $environmentName -like '* (Final Acceptance Test)') + } + else { + # Continuous Deployment is set in settings, use this value + $includeEnvironment = $deploymentSettings.continuousDeployment + } + } + 'Publish' { + # Publish can publish to all environments + $includeEnvironment = $true + } + 'All' { + $includeEnvironment = $true + } + default { + throw "Unknown type: $type" + } + } # Check branch policies and settings if (-not $includeEnvironment) { Write-Host "Environment $environmentName is not setup for continuous deployment" } - else { + elseif ($type -ne 'All') { # Check whether any GitHub policy disallows this branch to deploy to this environment if ($deploymentSettings.BranchesFromPolicy) { # Check whether GITHUB_REF_NAME is allowed to deploy to this environment diff --git a/Actions/DetermineDeploymentEnvironments/README.md b/Actions/DetermineDeploymentEnvironments/README.md index 3216c571f..717a6def4 100644 --- a/Actions/DetermineDeploymentEnvironments/README.md +++ b/Actions/DetermineDeploymentEnvironments/README.md @@ -14,7 +14,7 @@ Determines the environments to be used for a build or a publish | :-- | :-: | :-- | :-- | | shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | | getEnvironments | Yes | Specifies the pattern of the environments you want to retrieve (* for all) | | -| type | Yes | Type of deployment (CD or Publish) | | +| type | Yes | Type of deployment to get environments for (CD, Publish or All) | | ## OUTPUT | EnvironmentsMatrixJson | The Environment matrix to use for the Deploy step in compressed JSON format | diff --git a/Actions/DetermineDeploymentEnvironments/action.yaml b/Actions/DetermineDeploymentEnvironments/action.yaml index 6cfecd0c7..e4cef4203 100644 --- a/Actions/DetermineDeploymentEnvironments/action.yaml +++ b/Actions/DetermineDeploymentEnvironments/action.yaml @@ -9,7 +9,7 @@ inputs: description: Specifies the pattern of the environments you want to retreive (* for all) required: true type: - description: Type of deployment (CD or Publish) + description: Type of deployment (CD, Publish or All) required: true outputs: EnvironmentsMatrixJson: diff --git a/Actions/GetArtifactsForDeployment/GetArtifactsForDeployment.ps1 b/Actions/GetArtifactsForDeployment/GetArtifactsForDeployment.ps1 new file mode 100644 index 000000000..728baedcd --- /dev/null +++ b/Actions/GetArtifactsForDeployment/GetArtifactsForDeployment.ps1 @@ -0,0 +1,75 @@ +Param( + [Parameter(HelpMessage = "The GitHub token running the action", Mandatory = $false)] + [string] $token, + [Parameter(HelpMessage = "Artifacts version to download (current, prerelease, draft, latest or version number)", Mandatory = $true)] + [string] $artifactsVersion, + [Parameter(HelpMessage = "Folder in which the artifacts will be downloaded", Mandatory = $true)] + [string] $artifactsFolder +) + +. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) +DownloadAndImportBcContainerHelper + +# Get artifacts for all projects +$projects = "*" + +Write-Host "Get artifacts for version: '$artifactsVersion' for these projects: '$projects' to folder: '$artifactsFolder'" + +$artifactsFolder = Join-Path $ENV:GITHUB_WORKSPACE $artifactsFolder +if (!(Test-Path $artifactsFolder)) { + New-Item $artifactsFolder -ItemType Directory | Out-Null +} +$searchArtifacts = $false +if ($artifactsVersion -eq "current" -or $artifactsVersion -eq "prerelease" -or $artifactsVersion -eq "draft") { + Write-Host "Getting $artifactsVersion release artifacts" + $releases = GetReleases -token $token -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY + if ($releases) { + if ($artifactsVersion -eq "current") { + $release = $releases | Where-Object { -not ($_.prerelease -or $_.draft) } | Select-Object -First 1 + } + elseif ($artifactsVersion -eq "prerelease") { + $release = $releases | Where-Object { -not ($_.draft) } | Select-Object -First 1 + } + elseif ($artifactsVersion -eq "draft") { + $release = $releases | Select-Object -First 1 + } + if (!($release)) { + throw "Unable to locate $artifactsVersion release" + } + 'Apps','Dependencies','PowerPlatformSolution' | ForEach-Object { + DownloadRelease -token $token -projects $projects -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -release $release -path $artifactsFolder -mask $_ -unpack + } + } + else { + if ($artifactsVersion -eq "current") { + Write-Host "::Warning::Current release was specified, but no releases were found. Searching for latest build artifacts instead." + $artifactsVersion = "latest" + $searchArtifacts = $true + } + else { + throw "Artifact $artifactsVersion was not found on any release." + } + } +} +else { + $searchArtifacts = $true +} + +if ($searchArtifacts) { + $allArtifacts = @() + 'Apps','Dependencies','PowerPlatformSolution' | ForEach-Object { + $allArtifacts += @(GetArtifacts -token $token -api_url $ENV:GITHUB_API_URL -repository $ENV:GITHUB_REPOSITORY -mask $_ -projects $projects -version $artifactsVersion -branch $ENV:GITHUB_REF_NAME) + } + + if ($allArtifacts) { + $allArtifacts | ForEach-Object { + $folder = DownloadArtifact -token $token -artifact $_ -path $artifactsFolder -unpack + if (!(Test-Path $folder)) { + throw "Unable to download artifact $($_.name)" + } + } + } + else { + throw "Could not find any artifacts for projects: '$projects', version: '$artifactsVersion'" + } +} diff --git a/Actions/GetArtifactsForDeployment/README.md b/Actions/GetArtifactsForDeployment/README.md new file mode 100644 index 000000000..34eb67502 --- /dev/null +++ b/Actions/GetArtifactsForDeployment/README.md @@ -0,0 +1,24 @@ +# Get artifacts for deployment +Download artifacts for deployment + +## INPUT + +### ENV variables +none + +### Parameters +| Name | Required | Description | Default value | +| :-- | :-: | :-- | :-- | +| shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | +| token | | The GitHub token running the action | github.token | +| artifactsVersion | Yes | Artifacts version to download (current, prerelease, draft, latest or version number) | | +| artifactsFolder | Yes | Folder in which the artifacts will be downloaded | | + +## OUTPUT +none + +### ENV variables +none + +### OUTPUT variables +none diff --git a/Actions/GetArtifactsForDeployment/action.yaml b/Actions/GetArtifactsForDeployment/action.yaml new file mode 100644 index 000000000..4230f728f --- /dev/null +++ b/Actions/GetArtifactsForDeployment/action.yaml @@ -0,0 +1,38 @@ +name: Get Artifacts for deployment +author: Microsoft Corporation +inputs: + shell: + description: Shell in which you want to run the action (powershell or pwsh) + required: false + default: powershell + token: + description: The GitHub token running the action + required: false + default: ${{ github.token }} + artifactsVersion: + description: Artifacts version to download (current, prerelease, draft, latest or version number) + required: true + artifactsFolder: + description: Folder in which the artifacts will be downloaded + required: true +runs: + using: composite + steps: + - name: run + shell: ${{ inputs.shell }} + env: + _token: ${{ inputs.token }} + _artifactsVersion: ${{ inputs.artifactsVersion }} + _artifactsFolder: ${{ inputs.artifactsFolder }} + run: | + $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + try { + ${{ github.action_path }}/GetArtifactsForDeployment.ps1 -token $ENV:_token -artifactsVersion $ENV:_artifactsVersion -artifactsFolder $ENV:_artifactsFolder + } + catch { + Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; + exit 1 + } +branding: + icon: terminal + color: blue diff --git a/Actions/Github-Helper.psm1 b/Actions/Github-Helper.psm1 index b70160642..c4439d7f4 100644 --- a/Actions/Github-Helper.psm1 +++ b/Actions/Github-Helper.psm1 @@ -649,17 +649,22 @@ function DownloadRelease { $uri = "$api_url/repos/$repository/releases/assets/$($asset.id)" Write-Host $uri $filename = Join-Path $path $asset.name + if ($filename -notlike '*.zip') { + throw "Expecting a zip file, but got '$filename'" + } InvokeWebRequest -Headers $headers -Uri $uri -OutFile $filename if ($unpack) { - $unzipPath = Join-Path $path $asset.name.Replace('.zip','') - if (Test-Path $unzipPath) { - Remove-Item $unzipPath -Recurse -Force + $foldername = Join-Path $path ([System.IO.Path]::GetFileNameWithoutExtension($asset.name)) + if (Test-Path $foldername) { + Remove-Item $foldername -Recurse -Force } - Expand-Archive -Path $filename -DestinationPath $unzipPath + Expand-Archive -Path $filename -DestinationPath $foldername Remove-Item $filename -Force - $filename = $unzipPath + $foldername + } + else { + $filename } - $filename } } } @@ -1068,7 +1073,8 @@ function DownloadArtifact { Param( [string] $token, [string] $path, - $artifact + $artifact, + [switch] $unpack ) Write-Host "Downloading artifact $($artifact.Name)" @@ -1077,7 +1083,18 @@ function DownloadArtifact { $token = invoke-gh -silent -returnValue auth token } $headers = GetHeader -token $token - $outFile = Join-Path $path "$($artifact.Name).zip" - InvokeWebRequest -Headers $headers -Uri $artifact.archive_download_url -OutFile $outFile - $outFile + $foldername = Join-Path $path $artifact.Name + $filename = "$foldername.zip" + InvokeWebRequest -Headers $headers -Uri $artifact.archive_download_url -OutFile $filename + if ($unpack) { + if (Test-Path $foldername) { + Remove-Item $foldername -Recurse -Force + } + Expand-Archive -Path $filename -DestinationPath $foldername + Remove-Item $filename -Force + return $foldername + } + else { + return $filename + } } diff --git a/Actions/IncrementVersionNumber/IncrementVersionNumber.ps1 b/Actions/IncrementVersionNumber/IncrementVersionNumber.ps1 index b3debdfee..5f629f16c 100644 --- a/Actions/IncrementVersionNumber/IncrementVersionNumber.ps1 +++ b/Actions/IncrementVersionNumber/IncrementVersionNumber.ps1 @@ -29,37 +29,70 @@ try { $telemetryScope = CreateScope -eventId 'DO0076' -parentTelemetryScopeJson $parentTelemetryScopeJson $settings = $env:Settings | ConvertFrom-Json + if ($versionNumber.StartsWith('+')) { + # Handle incremental version number + $allowedIncrementalVersionNumbers = @('+1', '+0.1') + if (-not $allowedIncrementalVersionNumbers.Contains($versionNumber)) { + throw "Incremental version number $versionNumber is not allowed. Allowed incremental version numbers are: $($allowedIncrementalVersionNumbers -join ', ')" + } + } + else { + # Handle absolute version number + $versionNumberFormat = '^\d+\.\d+$' # Major.Minor + if (-not ($versionNumber -match $versionNumberFormat)) { + throw "Version number $versionNumber is not in the correct format. The version number must be in the format Major.Minor (e.g. 1.0 or 1.2)" + } + } + # Collect all projects (AL and PowerPlatform Solution) $projectList = @(GetProjectsFromRepository -baseFolder $baseFolder -projectsFromSettings $settings.projects -selectProjects $projects) + $PPprojects = @(GetMatchingProjects -projects @($settings.powerPlatformSolutionFolder) -selectProjects $projects) + if ($projectList.Count -eq 0 -and $PPproject.Count -eq 0) { + throw "No projects matches '$selectProjects'" + } - $allAppFolders = @() - foreach($project in $projectList) { - $projectPath = Join-Path $baseFolder $project - - $projectSettingsPath = Join-Path $projectPath $ALGoSettingsFile # $ALGoSettingsFile is defined in AL-Go-Helper.ps1 - $settings = ReadSettings -baseFolder $baseFolder -project $project - - # Ensure the repoVersion setting exists in the project settings. Defaults to 1.0 if it doesn't exist. - Set-VersionInSettingsFile -settingsFilePath $projectSettingsPath -settingName 'repoVersion' -newValue $settings.repoVersion -Force - - # Set repoVersion in project settings according to the versionNumber parameter - Set-VersionInSettingsFile -settingsFilePath $projectSettingsPath -settingName 'repoVersion' -newValue $versionNumber - - # Resolve project folders to get all app folders that contain an app.json file - $projectSettings = ReadSettings -baseFolder $baseFolder -project $project - ResolveProjectFolders -baseFolder $baseFolder -project $project -projectSettings ([ref] $projectSettings) - - # Set version in app manifests (app.json files) - Set-VersionInAppManifests -projectPath $projectPath -projectSettings $projectSettings -newValue $versionNumber - - # Collect all project's app folders - $allAppFolders += $projectSettings.appFolders | ForEach-Object { Join-Path $projectPath $_ -Resolve } - $allAppFolders += $projectSettings.testFolders | ForEach-Object { Join-Path $projectPath $_ -Resolve } - $allAppFolders += $projectSettings.bcptTestFolders | ForEach-Object { Join-Path $projectPath $_ -Resolve } + # Increment version number in AL Projects + if ($projectList.Count -gt 0) { + $allAppFolders = @() + foreach($project in $projectList) { + $projectPath = Join-Path $baseFolder $project + $projectSettingsPath = Join-Path $projectPath $ALGoSettingsFile # $ALGoSettingsFile is defined in AL-Go-Helper.ps1 + $settings = ReadSettings -baseFolder $baseFolder -project $project + + # Ensure the repoVersion setting exists in the project settings. Defaults to 1.0 if it doesn't exist. + Set-VersionInSettingsFile -settingsFilePath $projectSettingsPath -settingName 'repoVersion' -newValue $settings.repoVersion -Force + + # Set repoVersion in project settings according to the versionNumber parameter + Set-VersionInSettingsFile -settingsFilePath $projectSettingsPath -settingName 'repoVersion' -newValue $versionNumber + + # Resolve project folders to get all app folders that contain an app.json file + $projectSettings = ReadSettings -baseFolder $baseFolder -project $project + ResolveProjectFolders -baseFolder $baseFolder -project $project -projectSettings ([ref] $projectSettings) + + # Set version in app manifests (app.json files) + Set-VersionInAppManifests -projectPath $projectPath -projectSettings $projectSettings -newValue $versionNumber + + # Collect all project's app folders + $allAppFolders += $projectSettings.appFolders | ForEach-Object { Join-Path $projectPath $_ -Resolve } + $allAppFolders += $projectSettings.testFolders | ForEach-Object { Join-Path $projectPath $_ -Resolve } + $allAppFolders += $projectSettings.bcptTestFolders | ForEach-Object { Join-Path $projectPath $_ -Resolve } + } + + # Set dependencies in app manifests + if($allAppFolders.Count -eq 0) { + Write-Host "No App folders found for projects $projects" + } + else { + # Set dependencies in app manifests + Set-DependenciesVersionInAppManifests -appFolders $allAppFolders + } } - # Set dependencies in app manifests - Set-DependenciesVersionInAppManifests -appFolders $allAppFolders + # Increment version number in PowerPlatform Solution + foreach($PPproject in $PPprojects) { + $projectPath = Join-Path $baseFolder $PPproject + Set-PowerPlatformSolutionVersion -powerPlatformSolutionPath $projectPath -newValue $versionNumber + } $commitMessage = "New Version number $versionNumber" if ($versionNumber.StartsWith('+')) { diff --git a/Actions/IncrementVersionNumber/IncrementVersionNumber.psm1 b/Actions/IncrementVersionNumber/IncrementVersionNumber.psm1 index 81eb4b4ac..40179d269 100644 --- a/Actions/IncrementVersionNumber/IncrementVersionNumber.psm1 +++ b/Actions/IncrementVersionNumber/IncrementVersionNumber.psm1 @@ -53,11 +53,11 @@ function Set-VersionInSettingsFile { if ($newValue.StartsWith('+')) { # Handle incremental version number + # Defensive check. Should never happen. $allowedIncrementalVersionNumbers = @('+1', '+0.1') if (-not $allowedIncrementalVersionNumbers.Contains($newValue)) { throw "Incremental version number $newValue is not allowed. Allowed incremental version numbers are: $($allowedIncrementalVersionNumbers -join ', ')" } - # Defensive check. Should never happen. if($null -eq $oldVersion) { throw "The setting $settingName does not exist in the settings file. It must exist to be able to increment the version number." @@ -201,4 +201,61 @@ function Set-DependenciesVersionInAppManifests { } } -Export-ModuleMember -Function Set-VersionInSettingsFile, Set-VersionInAppManifests, Set-DependenciesVersionInAppManifests \ No newline at end of file +<# + .Synopsis + Sets the version number of a Power Platform solution. + .Description + Sets the version number of a Power Platform solution. + The version number is changed in the Solution.xml file of the Power Platform solution. + .Parameter powerPlatformSolutionPath + Path to the Power Platform solution. + .Parameter newValue + New version number. If the version number starts with a +, the new version number will be added to the old version number. Else the new version number will replace the old version number. + Allowed values are: +1 (increment major version number), +0.1 (increment minor version number), or a full version number (e.g. major.minor.build.revision). +#> +function Set-PowerPlatformSolutionVersion { + param( + [Parameter(Mandatory = $true)] + [string] $powerPlatformSolutionPath, + [Parameter(Mandatory = $true)] + [string] $newValue + ) + + Write-Host "Updating Power Platform solution version" + $files = Get-ChildItem -Path $powerPlatformSolutionPath -filter 'Solution.xml' -Recurse -File | Where-Object { $_.Directory.Name -eq "other" } + if (-not $files) { + Write-Host "Power Platform solution file not found" + } + else { + foreach ($file in $files) { + $xml = [xml](Get-Content -Encoding UTF8 -Path $file.FullName) + if ($newValue.StartsWith('+')) { + # Increment version + $versionNumbers = $xml.SelectNodes("//Version")[0].InnerText.Split(".") + switch($newValue) { + '+1' { + # Increment major version number + $versionNumbers[0] = "$(([int]$versionNumbers[0])+1)" + } + '+0.1' { + # Increment minor version number + $versionNumbers[1] = "$(([int]$versionNumbers[1])+1)" + } + default { + throw "Unexpected version number $newValue" + } + } + $newVersion = [string]::Join(".", $versionNumbers) + } + else { + $newVersion = $newValue + } + + Write-Host "Updating $($file.FullName) with new version $newVersion" + $xml.SelectNodes("//Version")[0].InnerText = $newVersion + $xml.Save($file.FullName) + } + } +} + +Export-ModuleMember -Function Set-VersionInSettingsFile, Set-VersionInAppManifests, Set-DependenciesVersionInAppManifests, Set-PowerPlatformSolutionVersion \ No newline at end of file diff --git a/Actions/PullPowerPlatformChanges/GitCloneReponsitory.ps1 b/Actions/PullPowerPlatformChanges/GitCloneReponsitory.ps1 new file mode 100644 index 000000000..9a39678c3 --- /dev/null +++ b/Actions/PullPowerPlatformChanges/GitCloneReponsitory.ps1 @@ -0,0 +1,25 @@ +Param( + [Parameter(HelpMessage = "The GitHub actor running the action", Mandatory = $false)] + [string] $actor, + [Parameter(HelpMessage = "The GitHub token running the action", Mandatory = $false)] + [string] $token, + [Parameter(HelpMessage = "Set the branch to update", Mandatory = $false)] + [string] $updateBranch, + [Parameter(HelpMessage = "Direct Commit", Mandatory = $false)] + [bool] $directCommit +) + +$ErrorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + +Write-Host "Starting GitCloneRepository.ps1 with parameters: $([environment]::Newline)actor: $actor$([environment]::Newline)updateBranch: $updateBranch$([environment]::Newline)directCommit: $directCommit" + +# Import the helper script +. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) + +Write-Host "Cloning the repository into a new folder" +$serverUrl, $branch = CloneIntoNewFolder -actor $actor -token $token -updateBranch $updateBranch -DirectCommit $directCommit -newBranchPrefix "pull-powerplatform-changes" +$baseFolder = (Get-Location).Path + +Add-Content -encoding utf8 -Path $env:GITHUB_ENV -Value "clonedRepoPath=$baseFolder" +Add-Content -encoding utf8 -Path $env:GITHUB_ENV -Value "serverUrl=$serverUrl" +Add-Content -encoding utf8 -Path $env:GITHUB_ENV -Value "gitHubBranch=$branch" diff --git a/Actions/PullPowerPlatformChanges/GitCommitChanges.ps1 b/Actions/PullPowerPlatformChanges/GitCommitChanges.ps1 new file mode 100644 index 000000000..27775ceee --- /dev/null +++ b/Actions/PullPowerPlatformChanges/GitCommitChanges.ps1 @@ -0,0 +1,31 @@ +Param( + [Parameter(HelpMessage = "The GitHub actor running the action", Mandatory = $false)] + [string] $actor, + [Parameter(HelpMessage = "The GitHub token running the action", Mandatory = $false)] + [string] $token, + [Parameter(HelpMessage = "Name of the Power Platform solution.", Mandatory = $false)] + [string] $powerPlatformSolutionName, + [Parameter(HelpMessage = "The name of the environment as defined in GitHub", Mandatory = $false)] + [string] $environmentName, + [Parameter(HelpMessage = "The current location for files to be checked in", Mandatory = $false)] + [string] $location, + [Parameter(HelpMessage = "ServerUrl for Git Push", Mandatory = $false)] + [string] $serverUrl, + [Parameter(HelpMessage = "Branch to update", Mandatory = $false)] + [string] $gitHubBranch +) + +$ErrorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + +# Import the helper script +. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) + +Set-Location -Path $location + +# Environment variables for hub commands +$env:GITHUB_USER = $actor +$env:GITHUB_TOKEN = $token + +# Commit from the new folder +Write-Host "Committing changes from the new folder $Location\$PowerPlatformSolutionName to branch $gitHubBranch" +CommitFromNewFolder -ServerUrl $serverUrl -CommitMessage "Update solution: $PowerPlatformSolutionName with latest from environment: $environmentName" -Branch $gitHubBranch diff --git a/Actions/PullPowerPlatformChanges/README.md b/Actions/PullPowerPlatformChanges/README.md new file mode 100644 index 000000000..5bfa3a178 --- /dev/null +++ b/Actions/PullPowerPlatformChanges/README.md @@ -0,0 +1,30 @@ +# Pull Power Platform Changes +Pull the Power Platform solution from the specified Power Platform environment + +## INPUT + +### ENV variables +| Name | Description | +| :-- | :-- | +| Settings | env.Settings must be set by a prior call to the ReadSettings Action | +| Secrets | env.Secrets with delivery target context secrets must be read by a prior call to the ReadSecrets Action | + +### Parameters +| Name | Required | Description | Default value | +| :-- | :-: | :-- | :-- | +| shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | +| actor | | The GitHub actor running the action | github.actor | +| token | | The GitHub token running the action | github.token | +| environmentName | Yes | Name of environment to pull changes from | +| solutionFolder | | Name of the solution to download and folder in which to download the solution | | +| deploymentEnvironmentsJson | Yes | The settings for all Deployment Environments | | +| updateBranch | | The branch to update | github.ref_name | +| directCommit | | true if the action should create a direct commit against the branch or false to create a Pull Request | false | + + +Either artifactsFolder or solutionFolder needs to be specified + +## OUTPUT +| Name | Description | +| :-- | :-- | +| environmentUrl | The URL for the environment. This URL is presented in the Deploy Step in summary under the environment name | diff --git a/Actions/PullPowerPlatformChanges/action.yaml b/Actions/PullPowerPlatformChanges/action.yaml new file mode 100644 index 000000000..38f06002d --- /dev/null +++ b/Actions/PullPowerPlatformChanges/action.yaml @@ -0,0 +1,142 @@ +name: Pull PowerPlatform Changes +author: Microsoft Corporation +inputs: + shell: + description: Shell in which you want to run the action (powershell or pwsh) + required: false + default: powershell + actor: + description: The GitHub actor running the action + required: false + default: ${{ github.actor }} + token: + description: The GitHub token running the action + required: false + default: ${{ github.token }} + environmentName: + description: Name of environment to pull changes from + required: true + solutionFolder: + description: Name of the solution to download and folder in which to download the solution + required: false + default: '' + deploymentEnvironmentsJson: + description: The settings for all Deployment Environments + required: true + updateBranch: + description: Set the branch to update + required: false + default: ${{ github.ref_name }} + directCommit: + description: Direct Commit? + required: false + default: 'false' +runs: + using: composite + steps: + - name: Install Power Platform Tools + uses: microsoft/powerplatform-actions/actions-install@v1 + + - name: Set Actions Repo and Ref + shell: ${{ inputs.shell }} + env: + actionsRepo: ${{ github.action_repository }} + actionsRef: ${{ github.action_ref }} + run: | + $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + try { + ${{ github.action_path }}/../SetActionsRepoAndRef.ps1 -actionsRepo $ENV:actionsRepo -actionsRef $ENV:actionsRef + } + catch { + Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; + exit 1 + } + + - name: Check out AL-Go Actions + uses: actions/checkout@v4 + with: + repository: ${{ env.actionsRepo }} + ref: ${{ env.actionsRef }} + path: ${{ env.actionsPath }} + + - name: Parse DeployToSettings and AuthContext + id: ReadPowerPlatformSettings + uses: ./_AL-Go/Actions/ReadPowerPlatformSettings + with: + shell: ${{ inputs.shell }} + deploymentEnvironmentsJson: ${{ inputs.deploymentEnvironmentsJson }} + environmentName: ${{ inputs.environmentName }} + + - name: Set up new branch for changes + shell: ${{ inputs.shell }} + env: + _actor: ${{ inputs.actor }} + _token: ${{ inputs.token }} + _updateBranch: ${{ inputs.updateBranch }} + _directCommit: ${{ inputs.directCommit }} + run: | + $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + try { + ${{ github.action_path }}/GitCloneReponsitory.ps1 -actor $ENV:_actor -token $ENV:_token -updateBranch $ENV:_updateBranch -directCommit ($ENV:_directCommit -eq 'true') + } + catch { + Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; + exit 1 + } + + - name: Export Solution (username) + if: steps.ReadPowerPlatformSettings.outputs.ppUserName != '' + uses: microsoft/powerplatform-actions/export-solution@v1 + with: + user-name: ${{ steps.ReadPowerPlatformSettings.outputs.ppUserName }} + password-secret: ${{ steps.ReadPowerPlatformSettings.outputs.ppPassword }} + environment-url: ${{ steps.ReadPowerPlatformSettings.outputs.ppEnvironmentUrl }} + solution-name: ${{ inputs.solutionFolder }} + solution-output-file: ${{ env.clonedRepoPath }}/${{ inputs.solutionFolder }}.zip + + - name: Export Solution (application ID) + if: steps.ReadPowerPlatformSettings.outputs.ppApplicationId != '' + uses: microsoft/powerplatform-actions/export-solution@v1 + with: + tenant-id: ${{ steps.ReadPowerPlatformSettings.outputs.ppTenantId }} + app-id: ${{ steps.ReadPowerPlatformSettings.outputs.ppApplicationId }} + client-secret: ${{ steps.ReadPowerPlatformSettings.outputs.ppClientSecret }} + environment-url: ${{ steps.ReadPowerPlatformSettings.outputs.ppEnvironmentUrl }} + solution-name: ${{ inputs.solutionFolder }} + solution-output-file: ${{ env.clonedRepoPath }}/${{ inputs.solutionFolder }}.zip + + - name: Unpack Solution + uses: microsoft/powerplatform-actions/unpack-solution@v1 + with: + solution-file: ${{ env.clonedRepoPath }}/${{ inputs.solutionFolder }}.zip + solution-folder: ${{ env.clonedRepoPath }}/${{ inputs.solutionFolder }} + solution-type: "Unmanaged" + overwrite-files: false + process-canvas-apps: true + + - name: Remove zip file + shell: ${{ inputs.shell }} + env: + _solutionFolder: ${{ inputs.solutionFolder }} + run: Remove-Item '${{ env.clonedRepoPath }}/${{ env._solutionFolder }}.zip' -Force + + - name: Commit to git repository + shell: ${{ inputs.shell }} + env: + _actor: ${{ inputs.actor }} + _token: ${{ inputs.token }} + _solutionFolder: ${{ inputs.solutionFolder }} + _environmentName: ${{ inputs.environmentName }} + run: | + $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + try { + ${{ github.action_path }}/GitCommitChanges.ps1 -Actor $ENV:_actor -Token $ENV:_token -PowerPlatformSolutionName $ENV:_solutionFolder -EnvironmentName $ENV:_environmentName -Location $ENV:clonedRepoPath -ServerUrl $ENV:serverUrl -GitHubBranch $ENV:gitHubBranch + } + catch { + Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; + exit 1 + } + +branding: + icon: terminal + color: blue diff --git a/Actions/ReadPowerPlatformSettings/README.md b/Actions/ReadPowerPlatformSettings/README.md new file mode 100644 index 000000000..199bb0da1 --- /dev/null +++ b/Actions/ReadPowerPlatformSettings/README.md @@ -0,0 +1,34 @@ +# Read Power Platform Settings +Read settings for Power Platform deployment from settings and secrets + +## INPUT + +### ENV variables +| Name | Description | +| :-- | :-- | +| Settings | env.Settings must be set by a prior call to the ReadSettings Action | +| Secrets | env.Secrets with delivery target context secrets must be read by a prior call to the ReadSecrets Action | + +### Parameters +| Name | Required | Description | Default value | +| :-- | :-: | :-- | :-- | +| shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell | +| deploymentEnvironmentsJson | Yes | The settings for all Deployment Environments | | +| environmentName | Yes | Name of environment to deploy to | | + +## OUTPUT + +### ENV variables +none + +### OUTPUT variables +| Name | Description | +| :-- | :-- | +| ppEnvironmentUrl | Power Platform Environment URL | +| ppUserName | Power Platform Username | +| ppPassword | Power Platform Password | +| ppApplicationId | Power Platform Application Id | +| ppTenantId | Power Platform Tenant Id | +| ppClientSecret | Power Platform Client Secret | +| companyId | Business Central Company Id | +| environmentName | Business Central Environment Name | diff --git a/Actions/ReadPowerPlatformSettings/ReadPowerPlatformSettings.ps1 b/Actions/ReadPowerPlatformSettings/ReadPowerPlatformSettings.ps1 new file mode 100644 index 000000000..8df001acd --- /dev/null +++ b/Actions/ReadPowerPlatformSettings/ReadPowerPlatformSettings.ps1 @@ -0,0 +1,61 @@ +param( + [Parameter(Mandatory = $true)] + [string] $deploymentEnvironmentsJson, + [Parameter(Mandatory = $true)] + [string] $environmentName +) +$ErrorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + +$envName = $environmentName.Split(' ')[0] + +# Read the deployment settings +$deploymentEnvironments = $deploymentEnvironmentsJson | ConvertFrom-Json +$deploymentSettings = $deploymentEnvironments."$environmentName" + +foreach($property in 'ppEnvironmentUrl','companyId','environmentName') { + if ($deploymentSettings | Get-Member -MemberType Properties -name $property) { + Write-Host "Setting $property" + Add-Content -Encoding utf8 -Path $env:GITHUB_OUTPUT -Value "$property=$($deploymentSettings."$property")" + } + else { + throw "$envName setting must contain '$property' property" + } +} + +$secrets = $env:Secrets | ConvertFrom-Json + +# Read the authentication context from secrets +$authContext = $null +foreach($secretName in "$($envName)-AuthContext","$($envName)_AuthContext","AuthContext") { + if ($secrets."$secretName") { + Write-Host "Setting authentication context from secret $secretName" + $authContext = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets."$secretName")) | ConvertFrom-Json + 'ppTenantId','ppApplicationId','ppClientSecret','ppUserName','ppPassword' | ForEach-Object { + if ($authContext.PSObject.Properties.Name -eq $_) { + Write-Host "Setting $_" + Add-Content -Encoding utf8 -Path $env:GITHUB_OUTPUT -Value "$_=$($authContext."$_")" + Set-Variable -Name $_ -Value $authContext."$_" + } + else { + Add-Content -Encoding utf8 -Path $env:GITHUB_OUTPUT -Value "$_=" + Set-Variable -Name $_ -Value "" + } + } + + if ($ppApplicationId -and $ppClientSecret -and $ppTenantId) { + Write-Host "Authenticating with application ID and client secret" + } + elseif ($ppUserName -and $ppPassword) { + Write-Host "Authenticating with user name" + } + else { + throw "Secret $secretName must contain either 'ppUserName' and 'ppPassword' properties or 'ppApplicationId', 'ppClientSecret' and 'ppTenantId' properties" + } + break + } +} + +# Verify the authentication context has been set +if ($null -eq $authContext) { + throw "Unable to find authentication context for GitHub environment $envName in secrets" +} \ No newline at end of file diff --git a/Actions/ReadPowerPlatformSettings/action.yaml b/Actions/ReadPowerPlatformSettings/action.yaml new file mode 100644 index 000000000..5b5b0a537 --- /dev/null +++ b/Actions/ReadPowerPlatformSettings/action.yaml @@ -0,0 +1,59 @@ +name: Read Power Platform Settings +author: Microsoft Corporation +inputs: + shell: + description: Shell in which you want to run the action (powershell or pwsh) + required: false + default: powershell + deploymentEnvironmentsJson: + description: The settings for all Deployment Environments + required: true + environmentName: + description: Name of environment to deploy to + required: true +outputs: + ppEnvironmentUrl: + description: Power Platform Environment URL + value: ${{ steps.ReadPowerPlatformSettings.outputs.ppEnvironmentUrl }} + ppUserName: + description: Power Platform Username + value: ${{ steps.ReadPowerPlatformSettings.outputs.ppUserName }} + ppPassword: + description: Power Platform Password + value: ${{ steps.ReadPowerPlatformSettings.outputs.ppPassword }} + ppApplicationId: + description: Power Platform Application Id + value: ${{ steps.ReadPowerPlatformSettings.outputs.ppApplicationId }} + ppTenantId: + description: Power Platform Tenant Id + value: ${{ steps.ReadPowerPlatformSettings.outputs.ppTenantId }} + ppClientSecret: + description: Power Platform Client Secret + value: ${{ steps.ReadPowerPlatformSettings.outputs.ppClientSecret }} + companyId: + description: Business Central Company Id + value: ${{ steps.ReadPowerPlatformSettings.outputs.companyId }} + environmentName: + description: Business Central Environment Name + value: ${{ steps.ReadPowerPlatformSettings.outputs.environmentName }} +runs: + using: composite + steps: + - name: run + shell: ${{ inputs.shell }} + id: ReadPowerPlatformSettings + env: + _deploymentEnvironmentsJson: ${{ inputs.deploymentEnvironmentsJson }} + _environmentName: ${{ inputs.environmentName }} + run: | + $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + try { + ${{ github.action_path }}/ReadPowerPlatformSettings.ps1 -deploymentEnvironmentsJson $ENV:_deploymentEnvironmentsJson -environmentName $ENV:_environmentName + } + catch { + Write-Host "::ERROR::Unexpected error when running action. Error Message: $($_.Exception.Message.Replace("`r",'').Replace("`n",' ')), StackTrace: $($_.ScriptStackTrace.Replace("`r",'').Replace("`n",' <- '))"; + exit 1 + } +branding: + icon: terminal + color: blue diff --git a/Actions/SetActionsRepoAndRef.ps1 b/Actions/SetActionsRepoAndRef.ps1 new file mode 100644 index 000000000..458d890d6 --- /dev/null +++ b/Actions/SetActionsRepoAndRef.ps1 @@ -0,0 +1,22 @@ +Param( + [Parameter(HelpMessage = "Actions Repository", Mandatory = $true)] + [string] $actionsRepo, + [Parameter(HelpMessage = "Actions Ref", Mandatory = $true)] + [string] $actionsRef +) + +$ErrorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + +Write-Host "Current action repository: $actionsRepo" +Write-Host "Current action ref: $actionsRef" + +# Set the path where the actions will be checked out +# Default path is ./_AL-Go/Actions +$actionsPath = './_AL-Go/Actions' +if ($actionsRepo -like '*/AL-Go') { # direct development + # Set the path one level up as that where the Actions folder will be + $actionsPath = './_AL-Go' +} +Add-Content -encoding utf8 -Path $env:GITHUB_ENV -Value "actionsRepo=$actionsRepo" +Add-Content -encoding utf8 -Path $env:GITHUB_ENV -Value "actionsRef=$actionsRef" +Add-Content -encoding utf8 -Path $env:GITHUB_ENV -Value "actionsPath=$actionsPath" diff --git a/Actions/Troubleshooting/Troubleshooting.ps1 b/Actions/Troubleshooting/Troubleshooting.ps1 index 442ae31a7..278391d77 100644 --- a/Actions/Troubleshooting/Troubleshooting.ps1 +++ b/Actions/Troubleshooting/Troubleshooting.ps1 @@ -42,6 +42,7 @@ function OutputSuggestion { } . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-TestRepoHelper.ps1" -Resolve) + TestALGoRepository . (Join-Path -Path $PSScriptRoot -ChildPath "Troubleshoot.Secrets.ps1" -Resolve) -gitHubSecrets ($gitHubSecrets | ConvertFrom-Json) -displayNameOfSecrets $displayNameOfSecrets diff --git a/README.md b/README.md index 0277d1aac..4e448fb98 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,9 @@ Try out the [AL-Go workshop](https://aka.ms/algoworkshop) for an in-depth worksh 15. [Enabling Telemetry for AL-Go workflows and actions](Scenarios/EnablingTelemetry.md) 16. [Add a performance test app to an existing project](Scenarios/AddAPerformanceTestApp.md) 17. [Publish your app to AppSource](Scenarios/PublishToAppSource.md) +18. [Connect your GitHub repository to Power Platform](Scenarios/SetupPowerPlatform.md) +19. [How to set up Service Principal for Power Platform](Scenarios/SetupServicePrincipalForPowerPlatform.md) +20. [Try one of the Business Central and Power Platform samples](Scenarios/TryPowerPlatformSamples.md) ## Migration scenarios A. [Migrate a repository from Azure DevOps to AL-Go for GitHub without history](Scenarios/MigrateFromAzureDevOpsWithoutHistory.md)
diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 5a0887f9a..06ef58616 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,34 @@ +### New Settings + +- `PowerPlatformSolutionFolder`: Contains the name of the folder containing a PowerPlatform Solution (only one) +- `DeployTo` now has two additional properties `companyId` is the Company Id from Business Central (for PowerPlatform connection) and `ppEnvironmentUrl` is the Url of the PowerPlatform environment to deploy to. + +### New Actions + +- `BuildPowerPlatform`: to build a PowerPlatform Solution +- `DeployPowerPlatform`: to deploy a PowerPlatform Solution +- `PullPowerPlatformChanges`: to pull changes made in PowerPlatform studio into the repository +- `ReadPowerPlatformSettings`: to read settings and secrets for PowerPlatform deployment +- `GetArtifactsForDeployment`: originally code from deploy.ps1 to retrieve artifacts for releases or builds - now as an action to read apps into a folder. + +### New Workflows + +- **Pull PowerPlatform Changes** for pulling changes from your PowerPlatform development environment into your AL-Go for GitHub repository +- **Push PowerPlatform Changes** for pushing changes from your AL-Go for GitHub repository to your PowerPlatform development environment + +> [!NOTE] +> PowerPlatform workflows are only available in the PTE template and will be removed if no PowerPlatformSolutionFolder is defined in settings. + +### New Scenarios (Documentation) + +- [Connect your GitHub repository to Power Platform](https://github.com/microsoft/AL-Go/blob/main/Scenarios/SetupPowerPlatform.md) +- [How to set up Service Principal for Power Platform](https://github.com/microsoft/AL-Go/blob/main/Scenarios/SetupServicePrincipalForPowerPlatform.md) +- [Try one of the Business Central and Power Platform samples](https://github.com/microsoft/AL-Go/blob/main/Scenarios/TryPowerPlatformSamples.md) +- [Publish To AppSource](https://github.com/microsoft/AL-Go/blob/main/Scenarios/PublishToAppSource.md) + +> [!NOTE] +> PowerPlatform functionality are only available in the PTE template. + ## v5.1 ### Issues diff --git a/Scenarios/AddPowerPlatformProject.md b/Scenarios/AddPowerPlatformProject.md new file mode 100644 index 000000000..f2b6d0c17 --- /dev/null +++ b/Scenarios/AddPowerPlatformProject.md @@ -0,0 +1,35 @@ +# Create a New Business Central and Power Platform Project + +## Prerequisites + +- Valid Business Central license +- Valid Power Platform license +- A GitHub Account + +> **NOTE:** AL-Go App source does not currently support Power Platform solutions. +## Steps + +1. Create a new repository based on the AL-Go-PTE template from [microsoft/AL-Go-PTE](https://github.com/microsoft/AL-Go-PTE) + + ![Screen shot from GitHub showing how ot create a new repository based on the AL-Go-PTE template](https://github.com/microsoft/AL-Go/assets/10775043/09c4998b-7c1b-4b3e-ad0d-412a377fff0d) + +1. Once the repository is set up, configure your Business Central and Power Platform authentication and deployment settings (See [Power Platform Repository setup](./SetupPowerPlatform.md) for detailed steps). + +1. If you already have a solution ready in your Power App repository, use the new "Pull Power Platform changes" action to have your solution and artifacts pulled into your repository. + + ![Screen shot from GitHub showing how to pull Power Platform changes](https://github.com/microsoft/AL-Go/assets/10775043/2d0a9321-8446-401d-8644-39c4dad62b53) + +1. If you do not already have a solution ready in your Power App repository, follow these steps: + + - Go to [make.powerapps.com](https://make.powerapps.com) + - Create a new solution + - Start building your Power Platform artifacts + - Run the pull action when you are ready to pull the artifacts into your repository + +1. Use the **Pull Power Platform Changes** action to get the latest changes from your Power Platform development environment and either check-in directly or create a PR to review them beforehand. + + > **NOTE:** Changes need to be published in Power Platform before they are included in the Pull action. A lot of the Power App source files contain state information and are safe to overwrite in case of conflicts. The connection files and the YAML files under Srs are the ones it makes sense to review. +1. After your changes are checked into a branch in the repository, you can use the **Push Power Platform changes** to deploy them to an environment. Or use the **CI/CD** flow or **Publish to Environment** action to deploy both the AL and Power Platform solutions. + +--- +[back](../README.md) \ No newline at end of file diff --git a/Scenarios/EnablingTelemetry.md b/Scenarios/EnablingTelemetry.md index 9e4bedcd5..6c3dde225 100644 --- a/Scenarios/EnablingTelemetry.md +++ b/Scenarios/EnablingTelemetry.md @@ -1,12 +1,12 @@ # 15. Enabling telemetry -If you want to enable partner telemetry add your Application Insights connection string to the AL-GO settings file. the settings structure is: +If you want to enable partner telemetry add your Application Insights connection string to the AL-Go settings file. the settings structure is: ``` "PartnerTelemetryConnectionString": "" ``` -You can also decide to send extended telelmetry to Microsoft. This would be helpful to investigate an issue. To enable the extended telemetry add the following property to the AL-GO settings file: +You can also decide to send extended telelmetry to Microsoft. This would be helpful to investigate an issue. To enable the extended telemetry add the following property to the AL-Go settings file: ``` "SendExtendedTelemetryToMicrosoft" : true diff --git a/Scenarios/GetStarted.md b/Scenarios/GetStarted.md index d186996e3..0af83047e 100644 --- a/Scenarios/GetStarted.md +++ b/Scenarios/GetStarted.md @@ -1,4 +1,4 @@ -# #1 Create a new per-tenant extension (like AL Go) and start developing in VS Code +# #1 Create a new per-tenant extension (like AL:Go in VS Code) and start developing in VS Code *Prerequisites: A GitHub account, VS-Code (with AL and PowerShell extensions installed), and Docker installed locally* 1. Navigate to [https://github.com/microsoft/AL-Go-PTE](https://github.com/microsoft/AL-Go-PTE) and choose **Use this template** diff --git a/Scenarios/SetupPowerPlatform.md b/Scenarios/SetupPowerPlatform.md new file mode 100644 index 000000000..401b66e22 --- /dev/null +++ b/Scenarios/SetupPowerPlatform.md @@ -0,0 +1,63 @@ +# Connect your GitHub repository to Power Platform + +There are 2 steps to connect your GitHub repository to your Power Platform tenant and environments: + +1. Setup the authentication context +2. Setup the AL-Go Repository settings (.github/AL-Go-Settings.json) + +## Authentication Context +The authentication context is a JSON object that you save in your GitHub secrets with the following naming convention: `_AUTHCONTEXT` + +The authentication context specifies how the GitHub Environment you have created authenticates against your Power Platform and Business Central environments. You can use UserName/Password to create the authentication context or create a service principal account and use a client secret to connect (see a [Setting up service principal](./SetupServicePrincipalForPowerPlatform.md) for detailed steps). + +> **NOTE:** Username/password authentication is only supported for tenants that do NOT have 2-factor authentication enabled. +The recommended way to get the auth context is to use the BCContainerHelper to generate the JSON - open a PowerShell window and run the following commands: + +### Getting service principal authentication context + +```powershell +# If not already installed, install latest BcContainerHelper +Install-Module BcContainerHelper -force +$ppClientId = Read-Host -Prompt "Enter client id" +$ppClientSecret = Read-Host -AsSecureString -Prompt 'Enter client secret' +New-BcAuthContext -includeDeviceLogin | New-ALGoAuthContext -ppClientSecret $ppClientSecret -ppApplicationId $ppClientId | Set-Clipboard +``` + +### Getting username/password authentication context: + +```powershell +# If not already installed, install latest BcContainerHelper +Install-Module BcContainerHelper -force +$ppUserName = Read-Host -Prompt "Enter Power Platform user name" +$ppPassword = Read-Host -AsSecureString -Prompt 'Enter Power Platform password' +New-BcAuthContext -includeDeviceLogin | New-ALGoAuthContext -ppUsername $ppUserName -ppPassword $ppPassword | Set-Clipboard +``` + +## AL-Go Repository settings + +The AL-Go repository settings are used to define what resources you have in your repository and which GitHub environment you want to deploy to. + +The settings are located at: `.github/AL-Go-Settings.json` + +**Example of the AL-Go settings format:** + +```json +{ + "type": "PTE", + "templateUrl": "https://github.com/microsoft/AL-Go-PTE@main", + "powerPlatformSolutionFolder": "", + "environments": [ + "" + ], + "DeployTo": { + "environmentName": "", + "companyId": "", + "ppEnvironmentUrl": "" + } +} +``` + +> **NOTE:** GitHubEnvironmentName is the name of the environment you are creating in GitHub to represent the Business Central and Power Platform environments you are deploying to. The GitHub environment must have a corresponding authentication context. + +--- +[back](../README.md) diff --git a/Scenarios/SetupServicePrincipalForPowerPlatform.md b/Scenarios/SetupServicePrincipalForPowerPlatform.md new file mode 100644 index 000000000..d8f5f3d52 --- /dev/null +++ b/Scenarios/SetupServicePrincipalForPowerPlatform.md @@ -0,0 +1,21 @@ +# How to set up Service Principal for Power Platform + +Setting up a service principal can be done in 2 steps: setting up the principal and adding appropriate roles to the Power Platform environment. + +1. In Azure AD, create the service principal. + + To create a service principal, you'll have to register an app with Azure AD and set it up for password-based authentication (with a client secret). + + You can do this step using the Azure portal or PowerShell. For more information, see one the following articles: + + - [Create an Azure AD application and service principal \(using Azure portal\)](https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal) + - [Create service principal and client secret using PowerShell](https://learn.microsoft.com/en-us/power-platform/alm/devops-build-tools#create-service-principal-and-client-secret-using-powershell) + +
+ +2. In Power Platform, add the service principal as an app user on the environment. + + Using Power Platform admin center, add the service principal as an application user of the environment and assign it either the **System admin** or **Super** role. **System admin** is recommended. + + For more information, see [Manage app users in Power Platform](https://learn.microsoft.com/en-us/power-platform/admin/manage-application-users). + \ No newline at end of file diff --git a/Scenarios/TryPowerPlatformSamples.md b/Scenarios/TryPowerPlatformSamples.md new file mode 100644 index 000000000..2382ae4cb --- /dev/null +++ b/Scenarios/TryPowerPlatformSamples.md @@ -0,0 +1,48 @@ +# Try one of the Business Central and Power Platform samples + +The easiest way to get started with Business Central and Power Apps is to find one of our sample apps: + +- [Take Order](https://github.com/microsoft/bcsamples-takeorder) +- [Warehouse helper](https://github.com/microsoft/bcsamples-warehousehelper) + +> **NOTE:** Other samples might be available here: [https://github.com/topics/bcsamples](https://github.com/topics/bcsamples). +From the sample app repositories, you have two options: + +- [Manual installation](#manual-installation) +- [Fork the repository](#fork-the-repository) + + +## Manual Installation +If you just want to try the apps and not the ALM functionality, follow these steps: + +1. In the App repository, find the latest release and download the Power platform solution and the Business Central extension zip files. *Note:* You need to unzip the files to access the actual extension and solution file. +![Screen shot from showing where the release artifacts are located](https://github.com/microsoft/AL-Go/assets/10775043/1c8bea93-5ec3-410b-b1b8-a94e7c5e533e) + +2. Upload the Business Central extension to your Business Central environment. + +3. (Optional) Open the sample page to generate demo data. + +4. Import the Power Platform solution to your Power Platform environment. You will be asked to add a Business Central connection if your environment does not have one. Follow the steps in the wizard to add it. + > **Note:** You might receive a warning about the imported flow. This is expected and will be addressed in a subsequent step. +5. Update the flow so it is pointing to your Business Central environment and company. Clear the current values and use the dropdown to select your settings. +![Screen shot from Power Automate showing where to update the Business Central connection reference](https://github.com/microsoft/AL-Go/assets/10775043/025787fa-38eb-4290-ad6a-4c607d2fa2d1) + +6. Update the Power App data sources to point to your Business Central environment. When you hover over the data source, the connection information is displayed under the 'Dataset name'. + + Delete the existing connections. Then, add new ones that point to your environment and company. You can do this by using the '+ Add data' button. + ![Screen shot from Power Apps showing where to update the Business Central connection reference](https://github.com/microsoft/AL-Go/assets/10775043/3b9d1ed6-4257-4234-8f0f-8b87df398b4f) + + > **Note:** After the data sources have been updated and you verified that the app is working, use the 'Publish' action in the upper right corner to make the changes available when running the app outside the editor. +If you experience any issues while setting up the app, please log them in the 'Issues' tab of the sample repository - [Take order issues](https://github.com/microsoft/bcsamples-takeorder/issues), [Warehouse Helper issues](https://github.com/microsoft/bcsamples-warehousehelper/issues) - or post the issue in the [Business Central partner community](https://www.yammer.com/dynamicsnavdev/#/threads/inGroup?type=in_group&feedId=64329121792) for assistance. + +## Fork the Repository + +This gives you access to all the source code for the Power Platform solution and AL extension and the ALM infrastructure. Follow the steps in the [Power Platform repository setup guide](./SetupPowerPlatform.md) to get started. Once set up, you can easily publish the latest changes to your environment. + +> **NOTE:** The first time you import the solution into your environment, you need to set up the Business Central connection reference authentication. See an example in the screen shot below +![Screen shot from Power Apps showing how to set up a the Business Central connection reference](https://github.com/microsoft/AL-Go/assets/10775043/dd90b6ae-f54f-4851-8073-c32d7fa54c7a) + +Choose the method that suits you best and get started with exploring the capabilities of Business Central and Power Platform! + +--- +[back](../README.md) diff --git a/Scenarios/settings.md b/Scenarios/settings.md index dcab5f117..8c0851084 100644 --- a/Scenarios/settings.md +++ b/Scenarios/settings.md @@ -21,12 +21,12 @@ When running a workflow or a local script, the settings are applied by reading s 1. `.AL-Go/.settings.json` is the **user-specific settings file**. This option is rarely used, but if you have special settings, which should only be used for one specific user (potentially in the local scripts), these settings can be added to a settings file with the name of the user followed by `.settings.json`. -## Basic settings +## Basic Project settings | Name | Description | Default value | | :-- | :-- | :-- | | country | Specifies which country this app is built against. | us | -| repoVersion | RepoVersion is the repository version number. The Repo Version number consists of \.\ only and is used for naming build artifacts in the CI/CD workflow. Build artifacts are named **\-Apps-\.\.\** and can contain multiple apps. The Repo Version number is used as major.minor for individual apps if versioningStrategy is +16. | 1.0 | +| repoVersion | RepoVersion is the project version number. The Repo Version number consists of \.\ only and is used for naming build artifacts in the CI/CD workflow. Build artifacts are named **\-Apps-\.\.\** and can contain multiple apps. The Repo Version number is used as major.minor for individual apps if versioningStrategy is +16. | 1.0 | | projectName | Friendly name for an AL-Go project to be used in the UI for various workflows (CICD, Pull Request Build, etc.). If not set, the name for the project will be the relative path from the root of the repository. | '' | | appFolders | appFolders should be an array of folders (relative to project root), which contains apps for this project. Apps in these folders are sorted based on dependencies and built and published in that order.
If appFolders are not specified, AL-Go for GitHub will try to locate appFolders in the root of the project. | [ ] | | testFolders | testFolders should be an array of folders (relative to project root), which contains test apps for this project. Apps in these folders are sorted based on dependencies and built, published and tests are run in that order.
If testFolders are not specified, AL-Go for GitHub will try to locate testFolders in the root of the project. | [ ] | @@ -34,7 +34,7 @@ When running a workflow or a local script, the settings are applied by reading s | appDependencyProbingPaths | Array of dependency specifications, from which apps will be downloaded when the CI/CD workflow is starting. Every dependency specification consists of the following properties:
**repo** = repository
**version** = version (default latest)
**release_status** = latestBuild/release/prerelease/draft (default release)
**projects** = projects (default * = all)
**branch** = branch (default main)
**AuthTokenSecret** = Name of secret containing auth token (default none)
| [ ] | | cleanModePreprocessorSymbols | List of clean tags to be used in _Clean_ build mode | [ ] | -## AppSource specific basic settings +## AppSource specific basic project settings | Name | Description | Default value | | :-- | :-- | :-- | | appSourceCopMandatoryAffixes | This setting is only used if the type is AppSource App. The value is an array of affixes, which is used for running AppSource Cop. | [ ] | @@ -47,6 +47,10 @@ The repository settings are only read from the repository settings file (.github | Name | Description | | :-- | :-- | | type | Specifies the type of project. Allowed values are **PTE** or **AppSource App**. This value comes with the default repository. Default value is PTE. | +| appFolders | appFolders should be an array of folders (relative to project root), which contains apps for this project. Apps in these folders are sorted based on dependencies and built and published in that order.
If appFolders are not specified, AL-Go for GitHub will try to locate appFolders in the root of the project. | +| testFolders | testFolders should be an array of folders (relative to project root), which contains test apps for this project. Apps in these folders are sorted based on dependencies and built, published and tests are run in that order.
If testFolders are not specified, AL-Go for GitHub will try to locate testFolders in the root of the project.| +| bcptTestFolders | bcptTestFolders should be an array of folders (relative to project root), which contains performance test apps for this project. Apps in these folders are sorted based on dependencies and built, published and bcpt tests are run in that order.
If bcptTestFolders are not specified, AL-Go for GitHub will try to locate bcptTestFolders in the root of the project. | +| powerPlatformSolutionFolder | Contains the name of the folder containing a PowerPlatform Solution (only one) | | templateUrl | Defines the URL of the template repository used to create this project and is used for checking and downloading updates to AL-Go System files. | | nextMajorSchedule | CRON schedule for when NextMajor workflow should run. Default is no scheduled run, only manual trigger. Build your CRON string here: [https://crontab.guru](https://crontab.guru). You need to run the Update AL-Go System Files workflow for the schedule to take effect. | | nextMinorSchedule | CRON schedule for when NextMinor workflow should run. Default is no scheduled run, only manual trigger. Build your CRON string here: [https://crontab.guru](https://crontab.guru). You need to run the Update AL-Go System Files workflow for the schedule to take effect. | @@ -58,7 +62,7 @@ The repository settings are only read from the repository settings file (.github | 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_. | | 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>** | | DeliverTo<deliveryTarget> | Structure with additional properties for the deliveryTarget specified. Some properties are deliveryTarget specific. The structure can contain the following properties:
**Branches** = an array of branch patterns, which are allowed to deliver to this deliveryTarget. (Default main)
**CreateContainerIfNotExist** = *[Only for DeliverToStorage]* Create Blob Storage Container if it doesn't already exist. (Default false)
| -| DeployTo<environmentname> | Structure with additional properties for the environment specified. The structure can contain the following properties:
**EnvironmentType** = specifies the type of environment. The environment type can be used to invoke a custom deployment. (Default SaaS)
**EnvironmentName** = specifies the "real" name of the environment if it differs from the GitHub environment.
**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.
**Projects** = In multi-project repositories, this property can be a comma separated list of project patterns to deploy to this environment. (Default *)
**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)
**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)
**runs-on** = specifies which runner to use when deploying to this environment. (Default is settings.runs-on)
**shell** = specifies which shell to use when deploying to this environment, pwsh or powershell. (Default is settings.shell)
| +| DeployTo<environmentname> | Structure with additional properties for the environment specified. The structure can contain the following properties:
**EnvironmentType** = specifies the type of environment. The environment type can be used to invoke a custom deployment. (Default SaaS)
**EnvironmentName** = specifies the "real" name of the environment if it differs from the GitHub environment.
**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.
**Projects** = In multi-project repositories, this property can be a comma separated list of project patterns to deploy to this environment. (Default *)
**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)
**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)
**runs-on** = specifies which runner to use when deploying to this environment. (Default is settings.runs-on)
**shell** = specifies which shell to use when deploying to this environment, pwsh or powershell. (Default is settings.shell)
**companyId** = Company Id from Business Central (for PowerPlatform connection)
**ppEnvironmentUrl** = Url of the PowerPlatform environment to deploy to
| | alDoc | Structure with properties for the aldoc reference document generation. The structure can contain the following properties:
**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)
**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)
**maxReleases** = Maximum number of releases to include in the reference documentation. (Default 3)
**groupByProject** = Determines whether projects in multi-project repositories are used as folders in reference documentation
**includeProjects** = An array of projects to include in the reference documentation. (Default all)
**excludeProjects** = An array of projects to exclude in the reference documentation. (Default none)
**header** = Header for the documentation site. (Default: Documentation for...)
**footer** = Footer for the documentation site. (Default: Made with...)
**defaultIndexMD** = Markdown for the landing page of the documentation site. (Default: Reference documentation...)
**defaultReleaseMD** = Markdown for the landing page of the release sites. (Default: Release reference documentation...)
*Note that in header, footer, defaultIndexMD and defaultReleaseMD you can use the following placeholders: {REPOSITORY}, {VERSION}, {INDEXTEMPLATERELATIVEPATH}, {RELEASENOTES}* | | 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.
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. | | 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 schedule to take effect.
Default is [ "main", "release/\*", "feature/\*" ] | @@ -95,8 +99,8 @@ The repository settings are only read from the repository settings file (.github | rulesetFile | Filename of the custom ruleset file | | | enableExternalRulesets | If enableExternalRulesets is set to true, then you can have external rule references in the ruleset | false | | vsixFile | Determines which version of the AL Language Extension to use for building the apps. This can be:
**default** to use the AL Language Extension which ships with the Business Central version you are building for
**latest** to always download the latest AL Language Extension from the marketplace
**preview** to always download the preview AL Language Extension from the marketplace.
or a **direct download URL** pointing to the AL Language VSIX file to use for building the apps.
By default, AL-Go uses the AL Language extension, which is shipped with the artifacts used for the build. | default | -| codeSignCertificateUrlSecretName
codeSignCertificatePasswordSecretName | **Note** there is a new way of signing apps, which is described [here](../Scenarios/Codesigning.md).
Using the old mechanism, you need a certificate .pfx file and password and you need to add secrets to GitHub secrets or Azure KeyVault, specifying the secure URL from which your codesigning certificate pfx file can be downloaded and the password for this certificate. These settings specifies the names (**NOT the secrets**) of the code signing certificate url and password. Default is to look for secrets called CodeSignCertificateUrl and CodeSignCertificatePassword. Read [this](SetupCiCdForExistingAppSourceApp.md) for more information. | CodeSignCertificateUrl
CodeSignCertificatePassword | -| keyVaultCodesignCertificateName | Name of a certificate stored in your KeyVault that can be used to codesigning. To use this setting you will also need enable Azure KeyVault in your AL-GO project. Read [this](UseAzureKeyVault.md) for more information | | +| codeSignCertificateUrlSecretName
codeSignCertificatePasswordSecretName | When developing AppSource Apps, your app needs to be code signed.
**Note** there is a new way of signing apps, which is described [here](../Scenarios/Codesigning.md).
Using the old mechanism, you need a certificate .pfx file and password and you need to add secrets to GitHub secrets or Azure KeyVault, specifying the secure URL from which your codesigning certificate pfx file can be downloaded and the password for this certificate. These settings specifies the names (**NOT the secrets**) of the code signing certificate url and password. Default is to look for secrets called CodeSignCertificateUrl and CodeSignCertificatePassword. Read [this](SetupCiCdForExistingAppSourceApp.md) for more information. | CodeSignCertificateUrl
CodeSignCertificatePassword | +| keyVaultCodesignCertificateName | When developing AppSource Apps, your app needs to be code signed.
Name of a certificate stored in your KeyVault that can be used to codesigning. To use this setting you will also need enable Azure KeyVault in your AL-Go project. Read [this](UseAzureKeyVault.md) for more information | | | applicationInsightsConnectionStringSecretName | This setting specifies the name (**NOT the secret**) of a secret containing the application insights connection string for the apps. | applicationInsightsConnectionString | | storageContextSecretName | This setting specifies the name (**NOT the secret**) of a secret containing a json string with StorageAccountName, ContainerName, BlobName and StorageAccountKey or SAS Token. If this secret exists, AL-Go will upload builds to this storage account for every successful build.
The BcContainerHelper function New-ALGoStorageContext can create a .json structure with this content. | StorageContext | | alwaysBuildAllProjects | This setting only makes sense if the repository is setup for multiple projects.
Standard behavior of the CI/CD workflow is to only build the projects, in which files have changes when running the workflow due to a push or a pull request | false | diff --git a/Templates/AppSource App/.github/workflows/CICD.yaml b/Templates/AppSource App/.github/workflows/CICD.yaml index c7e69bd77..c7c8c0ef6 100644 --- a/Templates/AppSource App/.github/workflows/CICD.yaml +++ b/Templates/AppSource App/.github/workflows/CICD.yaml @@ -40,6 +40,7 @@ jobs: projects: ${{ steps.determineProjectsToBuild.outputs.ProjectsJson }} projectDependenciesJson: ${{ steps.determineProjectsToBuild.outputs.ProjectDependenciesJson }} buildOrderJson: ${{ steps.determineProjectsToBuild.outputs.BuildOrderJson }} + powerPlatformSolutionFolder: ${{ steps.DeterminePowerPlatformSolutionFolder.outputs.powerPlatformSolutionFolder }} workflowDepth: ${{ steps.DetermineWorkflowDepth.outputs.WorkflowDepth }} steps: - name: Dump Workflow Information @@ -64,7 +65,7 @@ jobs: uses: microsoft/AL-Go-Actions/ReadSettings@main with: shell: powershell - get: type + get: type, powerPlatformSolutionFolder - name: Determine Workflow Depth id: DetermineWorkflowDepth @@ -78,6 +79,12 @@ jobs: shell: powershell maxBuildDepth: ${{ env.workflowDepth }} + - name: Determine PowerPlatform Solution Folder + id: DeterminePowerPlatformSolutionFolder + if: env.type == 'PTE' + run: | + Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "powerPlatformSolutionFolder=$($env:powerPlatformSolutionFolder)" + - name: Determine Delivery Target Secrets id: DetermineDeliveryTargetSecrets uses: microsoft/AL-Go-Actions/DetermineDeliveryTargets@main @@ -160,7 +167,7 @@ jobs: DeployALDoc: needs: [ Initialization, Build ] - if: always() && needs.Build.result == 'Success' && needs.Initialization.outputs.generateALDocArtifact == 1 && github.ref_name == 'main' + if: (!cancelled()) && needs.Build.result == 'Success' && needs.Initialization.outputs.generateALDocArtifact == 1 && github.ref_name == 'main' runs-on: windows-latest name: Deploy Reference Documentation permissions: @@ -207,7 +214,7 @@ jobs: Deploy: needs: [ Initialization, Build ] - if: always() && needs.Build.result == 'Success' && needs.Initialization.outputs.environmentCount > 0 + if: (!cancelled()) && (needs.Build.result == 'success' || needs.Build.result == 'skipped') && needs.Initialization.outputs.environmentCount > 0 strategy: ${{ fromJson(needs.Initialization.outputs.environmentsMatrixJson) }} runs-on: ${{ fromJson(matrix.os) }} name: Deploy to ${{ matrix.environment }} @@ -230,6 +237,7 @@ jobs: uses: microsoft/AL-Go-Actions/ReadSettings@main with: shell: ${{ matrix.shell }} + get: type,powerPlatformSolutionFolder - name: EnvName id: envName @@ -246,7 +254,7 @@ jobs: gitHubSecrets: ${{ toJson(secrets) }} getSecrets: '${{ steps.envName.outputs.envName }}-AuthContext,${{ steps.envName.outputs.envName }}_AuthContext,AuthContext,${{ steps.envName.outputs.envName }}-EnvironmentName,${{ steps.envName.outputs.envName }}_EnvironmentName,EnvironmentName,projects' - - name: Deploy + - name: Deploy to Business Central id: Deploy uses: microsoft/AL-Go-Actions/Deploy@main env: @@ -254,13 +262,24 @@ jobs: with: shell: ${{ matrix.shell }} environmentName: ${{ matrix.environment }} - artifacts: '.artifacts' + artifactsFolder: '.artifacts' type: 'CD' deploymentEnvironmentsJson: ${{ needs.Initialization.outputs.deploymentEnvironmentsJson }} + - name: Deploy to Power Platform + if: env.type == 'PTE' && env.powerPlatformSolutionFolder != '' + uses: microsoft/AL-Go-Actions/DeployPowerPlatform@main + env: + Secrets: '${{ steps.ReadSecrets.outputs.Secrets }}' + with: + shell: powershell + environmentName: ${{ matrix.environment }} + artifactsFolder: '.artifacts' + deploymentEnvironmentsJson: ${{ needs.Initialization.outputs.deploymentEnvironmentsJson }} + Deliver: needs: [ Initialization, Build ] - if: always() && needs.Build.result == 'Success' && needs.Initialization.outputs.deliveryTargetsJson != '[]' + if: (!cancelled()) && (needs.Build.result == 'success' || needs.Build.result == 'skipped') && needs.Initialization.outputs.deliveryTargetsJson != '[]' strategy: matrix: deliveryTarget: ${{ fromJson(needs.Initialization.outputs.deliveryTargetsJson) }} diff --git a/Templates/AppSource App/.github/workflows/CreateRelease.yaml b/Templates/AppSource App/.github/workflows/CreateRelease.yaml index 2ffba5b1b..1054bb8bf 100644 --- a/Templates/AppSource App/.github/workflows/CreateRelease.yaml +++ b/Templates/AppSource App/.github/workflows/CreateRelease.yaml @@ -91,7 +91,7 @@ jobs: uses: microsoft/AL-Go-Actions/ReadSettings@main with: shell: powershell - get: templateUrl,repoName + get: templateUrl,repoName,type,powerPlatformSolutionFolder - name: Read secrets id: ReadSecrets @@ -117,11 +117,18 @@ jobs: - name: Analyze Artifacts id: analyzeartifacts + env: + _appVersion: ${{ github.event.inputs.appVersion }} run: | $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 $projects = '${{ steps.determineProjects.outputs.ProjectsJson }}' | ConvertFrom-Json Write-Host "projects:" $projects | ForEach-Object { Write-Host "- $_" } + if ($env:type -eq "PTE" -and $env:powerPlatformSolutionFolder -ne "") { + Write-Host "PowerPlatformSolution:" + Write-Host "- $($env:powerPlatformSolutionFolder)" + $projects += @($env:powerPlatformSolutionFolder) + } $include = @() $sha = '' $allArtifacts = @() @@ -149,17 +156,18 @@ jobs: } $refname = "$ENV:GITHUB_REF_NAME".Replace('/','_') Write-Host "Analyzing artifacts for project $project" - $appVersion = '${{ github.event.inputs.appVersion }}' + $appVersion = "$env:_appVersion" if ($appVersion -eq "latest") { Write-Host "Grab latest" - $artifact = $allArtifacts | Where-Object { $_.name -like "$project-$refname-Apps-*" } | Select-Object -First 1 + $artifact = $allArtifacts | Where-Object { $_.name -like "$project-$refname-Apps-*" -or $_.name -like "$project-$refname-PowerPlatformSolution-*" } | Select-Object -First 1 } else { - Write-Host "Search for $project-$refname-Apps-$appVersion" - $artifact = $allArtifacts | Where-Object { $_.name -eq "$project-$refname-Apps-$appVersion" } | Select-Object -First 1 + Write-Host "Search for $project-$refname-Apps-$appVersion or $project-$refname-PowerPlatformSolution-$appVersion" + $artifact = $allArtifacts | Where-Object { $_.name -eq "$project-$refname-Apps-$appVersion"-or $_.name -eq "$project-$refname-PowerPlatformSolution-$appVersion" } | Select-Object -First 1 } if ($artifact) { - $artifactsVersion = $artifact.name.SubString($artifact.name.LastIndexOf('-Apps-')+6) + $startIndex = $artifact.name.LastIndexOf('-') + 1 + $artifactsVersion = $artifact.name.SubString($startIndex) } else { Write-Host "::Error::No artifacts found for this project" @@ -175,13 +183,14 @@ jobs: $sha = $artifact.workflow_run.head_sha } - $allArtifacts | Where-Object { ($_.name -like "$project-$refname-Apps-$($artifactsVersion)" -or $_.name -like "$project-$refname-TestApps-$($artifactsVersion)" -or $_.name -like "$project-$refname-Dependencies-$($artifactsVersion)") } | ForEach-Object { + write-host "looking for $project-$refname-Apps-$artifactsVersion or $project-$refname-TestApps-$artifactsVersion or $project-$refname-Dependencies-$artifactsVersion or $project-$refname-PowerPlatformSolution-$artifactsVersion" + $allArtifacts | Where-Object { ($_.name -like "$project-$refname-Apps-$artifactsVersion" -or $_.name -like "$project-$refname-TestApps-$artifactsVersion" -or $_.name -like "$project-$refname-Dependencies-$artifactsVersion" -or $_.name -like "$project-$refname-PowerPlatformSolution-$artifactsVersion") } | ForEach-Object { $atype = $_.name.SubString(0,$_.name.Length-$artifactsVersion.Length-1) $atype = $atype.SubString($atype.LastIndexOf('-')+1) $include += $( [ordered]@{ "name" = $_.name; "url" = $_.archive_download_url; "atype" = $atype; "project" = $thisproject } ) } if ($include.Count -eq 0) { - Write-Host "::Error::No artifacts found" + Write-Host "::Error::No artifacts found for version $artifactsVersion" exit 1 } } diff --git a/Templates/AppSource App/.github/workflows/PublishToEnvironment.yaml b/Templates/AppSource App/.github/workflows/PublishToEnvironment.yaml index 7cec8a327..3a41664d0 100644 --- a/Templates/AppSource App/.github/workflows/PublishToEnvironment.yaml +++ b/Templates/AppSource App/.github/workflows/PublishToEnvironment.yaml @@ -144,6 +144,7 @@ jobs: uses: microsoft/AL-Go-Actions/ReadSettings@main with: shell: ${{ matrix.shell }} + get: type,powerPlatformSolutionFolder - name: Read secrets id: ReadSecrets @@ -153,7 +154,14 @@ jobs: gitHubSecrets: ${{ toJson(secrets) }} getSecrets: '${{ steps.envName.outputs.envName }}-AuthContext,${{ steps.envName.outputs.envName }}_AuthContext,AuthContext,${{ steps.envName.outputs.envName }}-EnvironmentName,${{ steps.envName.outputs.envName }}_EnvironmentName,EnvironmentName,projects' - - name: Deploy + - name: Get Artifacts for deployment + uses: microsoft/AL-Go-Actions/GetArtifactsForDeployment@main + with: + shell: powershell + artifactsVersion: ${{ github.event.inputs.appVersion }} + artifactsFolder: '.artifacts' + + - name: Deploy to Business Central id: Deploy uses: microsoft/AL-Go-Actions/Deploy@main env: @@ -161,10 +169,21 @@ jobs: with: shell: ${{ matrix.shell }} environmentName: ${{ matrix.environment }} - artifacts: ${{ github.event.inputs.appVersion }} + artifactsFolder: '.artifacts' type: 'Publish' deploymentEnvironmentsJson: ${{ needs.Initialization.outputs.deploymentEnvironmentsJson }} + - name: Deploy to Power Platform + if: env.type == 'PTE' && env.powerPlatformSolutionFolder != '' + uses: microsoft/AL-Go-Actions/DeployPowerPlatform@main + env: + Secrets: '${{ steps.ReadSecrets.outputs.Secrets }}' + with: + shell: powershell + environmentName: ${{ matrix.environment }} + artifactsFolder: '.artifacts' + deploymentEnvironmentsJson: ${{ needs.Initialization.outputs.deploymentEnvironmentsJson }} + PostProcess: needs: [ Initialization, Deploy ] if: always() diff --git a/Templates/AppSource App/.github/workflows/_BuildALGoProject.yaml b/Templates/AppSource App/.github/workflows/_BuildALGoProject.yaml index a250be74a..64cd99e7b 100644 --- a/Templates/AppSource App/.github/workflows/_BuildALGoProject.yaml +++ b/Templates/AppSource App/.github/workflows/_BuildALGoProject.yaml @@ -1,6 +1,6 @@ -name: '_Build AL-GO project' +name: '_Build AL-Go project' -run-name: 'Build project ${{ inputs.project }}' +run-name: 'Build ${{ inputs.project }}' on: workflow_call: @@ -11,7 +11,7 @@ on: default: powershell type: string runsOn: - description: JSON-formatted string og the types of machine to run the build job on + description: JSON-formatted string of the types of machine to run the build job on required: true type: string checkoutRef: diff --git a/Templates/Per Tenant Extension/.github/workflows/CICD.yaml b/Templates/Per Tenant Extension/.github/workflows/CICD.yaml index c7e69bd77..cc7380e42 100644 --- a/Templates/Per Tenant Extension/.github/workflows/CICD.yaml +++ b/Templates/Per Tenant Extension/.github/workflows/CICD.yaml @@ -40,6 +40,7 @@ jobs: projects: ${{ steps.determineProjectsToBuild.outputs.ProjectsJson }} projectDependenciesJson: ${{ steps.determineProjectsToBuild.outputs.ProjectDependenciesJson }} buildOrderJson: ${{ steps.determineProjectsToBuild.outputs.BuildOrderJson }} + powerPlatformSolutionFolder: ${{ steps.DeterminePowerPlatformSolutionFolder.outputs.powerPlatformSolutionFolder }} workflowDepth: ${{ steps.DetermineWorkflowDepth.outputs.WorkflowDepth }} steps: - name: Dump Workflow Information @@ -64,7 +65,7 @@ jobs: uses: microsoft/AL-Go-Actions/ReadSettings@main with: shell: powershell - get: type + get: type, powerPlatformSolutionFolder - name: Determine Workflow Depth id: DetermineWorkflowDepth @@ -78,6 +79,12 @@ jobs: shell: powershell maxBuildDepth: ${{ env.workflowDepth }} + - name: Determine PowerPlatform Solution Folder + id: DeterminePowerPlatformSolutionFolder + if: env.type == 'PTE' + run: | + Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "powerPlatformSolutionFolder=$($env:powerPlatformSolutionFolder)" + - name: Determine Delivery Target Secrets id: DetermineDeliveryTargetSecrets uses: microsoft/AL-Go-Actions/DetermineDeliveryTargets@main @@ -158,9 +165,23 @@ jobs: signArtifacts: true useArtifactCache: true + BuildPP: + needs: [ Initialization ] + if: (!failure()) && (!cancelled()) && needs.Initialization.outputs.powerPlatformSolutionFolder != '' + name: Build PowerPlatform Solution + uses: ./.github/workflows/_BuildPowerPlatformSolution.yaml + secrets: inherit + with: + shell: ${{ needs.Initialization.outputs.githubRunnerShell }} + runsOn: ${{ needs.Initialization.outputs.githubRunner }} + parentTelemetryScopeJson: ${{ needs.Initialization.outputs.telemetryScopeJson }} + project: ${{ needs.Initialization.outputs.powerPlatformSolutionFolder }} + projectName: ${{ needs.Initialization.outputs.powerPlatformSolutionFolder }} + publishArtifacts: ${{ github.ref_name == 'main' || startswith(github.ref_name, 'release/') || startswith(github.ref_name, 'releases/') || needs.Initialization.outputs.deliveryTargetsJson != '[]' || needs.Initialization.outputs.environmentCount > 0 }} + DeployALDoc: needs: [ Initialization, Build ] - if: always() && needs.Build.result == 'Success' && needs.Initialization.outputs.generateALDocArtifact == 1 && github.ref_name == 'main' + if: (!cancelled()) && needs.Build.result == 'Success' && needs.Initialization.outputs.generateALDocArtifact == 1 && github.ref_name == 'main' runs-on: windows-latest name: Deploy Reference Documentation permissions: @@ -206,8 +227,8 @@ jobs: uses: actions/deploy-pages@v4 Deploy: - needs: [ Initialization, Build ] - if: always() && needs.Build.result == 'Success' && needs.Initialization.outputs.environmentCount > 0 + needs: [ Initialization, Build, BuildPP ] + if: (!cancelled()) && (needs.Build.result == 'success' || needs.Build.result == 'skipped') && (needs.BuildPP.result == 'success' || needs.BuildPP.result == 'skipped') && needs.Initialization.outputs.environmentCount > 0 strategy: ${{ fromJson(needs.Initialization.outputs.environmentsMatrixJson) }} runs-on: ${{ fromJson(matrix.os) }} name: Deploy to ${{ matrix.environment }} @@ -230,6 +251,7 @@ jobs: uses: microsoft/AL-Go-Actions/ReadSettings@main with: shell: ${{ matrix.shell }} + get: type,powerPlatformSolutionFolder - name: EnvName id: envName @@ -246,7 +268,7 @@ jobs: gitHubSecrets: ${{ toJson(secrets) }} getSecrets: '${{ steps.envName.outputs.envName }}-AuthContext,${{ steps.envName.outputs.envName }}_AuthContext,AuthContext,${{ steps.envName.outputs.envName }}-EnvironmentName,${{ steps.envName.outputs.envName }}_EnvironmentName,EnvironmentName,projects' - - name: Deploy + - name: Deploy to Business Central id: Deploy uses: microsoft/AL-Go-Actions/Deploy@main env: @@ -254,13 +276,24 @@ jobs: with: shell: ${{ matrix.shell }} environmentName: ${{ matrix.environment }} - artifacts: '.artifacts' + artifactsFolder: '.artifacts' type: 'CD' deploymentEnvironmentsJson: ${{ needs.Initialization.outputs.deploymentEnvironmentsJson }} + - name: Deploy to Power Platform + if: env.type == 'PTE' && env.powerPlatformSolutionFolder != '' + uses: microsoft/AL-Go-Actions/DeployPowerPlatform@main + env: + Secrets: '${{ steps.ReadSecrets.outputs.Secrets }}' + with: + shell: powershell + environmentName: ${{ matrix.environment }} + artifactsFolder: '.artifacts' + deploymentEnvironmentsJson: ${{ needs.Initialization.outputs.deploymentEnvironmentsJson }} + Deliver: - needs: [ Initialization, Build ] - if: always() && needs.Build.result == 'Success' && needs.Initialization.outputs.deliveryTargetsJson != '[]' + needs: [ Initialization, Build, BuildPP ] + if: (!cancelled()) && (needs.Build.result == 'success' || needs.Build.result == 'skipped') && (needs.BuildPP.result == 'success' || needs.BuildPP.result == 'skipped') && needs.Initialization.outputs.deliveryTargetsJson != '[]' strategy: matrix: deliveryTarget: ${{ fromJson(needs.Initialization.outputs.deliveryTargetsJson) }} @@ -301,7 +334,7 @@ jobs: artifacts: '.artifacts' PostProcess: - needs: [ Initialization, Build, Deploy, Deliver, DeployALDoc ] + needs: [ Initialization, Build, BuildPP, Deploy, Deliver, DeployALDoc ] if: (!cancelled()) runs-on: [ windows-latest ] steps: diff --git a/Templates/Per Tenant Extension/.github/workflows/CreateRelease.yaml b/Templates/Per Tenant Extension/.github/workflows/CreateRelease.yaml index 2ffba5b1b..2ed67c263 100644 --- a/Templates/Per Tenant Extension/.github/workflows/CreateRelease.yaml +++ b/Templates/Per Tenant Extension/.github/workflows/CreateRelease.yaml @@ -91,7 +91,7 @@ jobs: uses: microsoft/AL-Go-Actions/ReadSettings@main with: shell: powershell - get: templateUrl,repoName + get: templateUrl,repoName,type,powerPlatformSolutionFolder - name: Read secrets id: ReadSecrets @@ -117,11 +117,18 @@ jobs: - name: Analyze Artifacts id: analyzeartifacts + env: + _appVersion: ${{ github.event.inputs.appVersion }} run: | $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 $projects = '${{ steps.determineProjects.outputs.ProjectsJson }}' | ConvertFrom-Json Write-Host "projects:" $projects | ForEach-Object { Write-Host "- $_" } + if ($env:type -eq "PTE" -and $env:powerPlatformSolutionFolder -ne "") { + Write-Host "PowerPlatformSolution:" + Write-Host "- $($env:powerPlatformSolutionFolder)" + $projects += @($env:powerPlatformSolutionFolder) + } $include = @() $sha = '' $allArtifacts = @() @@ -149,17 +156,18 @@ jobs: } $refname = "$ENV:GITHUB_REF_NAME".Replace('/','_') Write-Host "Analyzing artifacts for project $project" - $appVersion = '${{ github.event.inputs.appVersion }}' + $appVersion = "$env:_appVersion" if ($appVersion -eq "latest") { Write-Host "Grab latest" - $artifact = $allArtifacts | Where-Object { $_.name -like "$project-$refname-Apps-*" } | Select-Object -First 1 + $artifact = $allArtifacts | Where-Object { $_.name -like "$project-$refname-Apps-*" -or $_.name -like "$project-$refname-PowerPlatformSolution-*" } | Select-Object -First 1 } else { - Write-Host "Search for $project-$refname-Apps-$appVersion" - $artifact = $allArtifacts | Where-Object { $_.name -eq "$project-$refname-Apps-$appVersion" } | Select-Object -First 1 + Write-Host "Search for $project-$refname-Apps-$appVersion or $project-$refname-PowerPlatformSolution-$appVersion" + $artifact = $allArtifacts | Where-Object { $_.name -eq "$project-$refname-Apps-$appVersion"-or $_.name -eq "$project-$refname-PowerPlatformSolution-$appVersion" } | Select-Object -First 1 } if ($artifact) { - $artifactsVersion = $artifact.name.SubString($artifact.name.LastIndexOf('-Apps-')+6) + $startIndex = $artifact.name.LastIndexOf('-') + 1 + $artifactsVersion = $artifact.name.SubString($startIndex) } else { Write-Host "::Error::No artifacts found for this project" @@ -175,13 +183,14 @@ jobs: $sha = $artifact.workflow_run.head_sha } - $allArtifacts | Where-Object { ($_.name -like "$project-$refname-Apps-$($artifactsVersion)" -or $_.name -like "$project-$refname-TestApps-$($artifactsVersion)" -or $_.name -like "$project-$refname-Dependencies-$($artifactsVersion)") } | ForEach-Object { + Write-host "Looking for $project-$refname-Apps-$artifactsVersion or $project-$refname-TestApps-$artifactsVersion or $project-$refname-Dependencies-$artifactsVersion or $project-$refname-PowerPlatformSolution-$artifactsVersion" + $allArtifacts | Where-Object { ($_.name -like "$project-$refname-Apps-$artifactsVersion" -or $_.name -like "$project-$refname-TestApps-$artifactsVersion" -or $_.name -like "$project-$refname-Dependencies-$artifactsVersion" -or $_.name -like "$project-$refname-PowerPlatformSolution-$artifactsVersion") } | ForEach-Object { $atype = $_.name.SubString(0,$_.name.Length-$artifactsVersion.Length-1) $atype = $atype.SubString($atype.LastIndexOf('-')+1) $include += $( [ordered]@{ "name" = $_.name; "url" = $_.archive_download_url; "atype" = $atype; "project" = $thisproject } ) } if ($include.Count -eq 0) { - Write-Host "::Error::No artifacts found" + Write-Host "::Error::No artifacts found for version $artifactsVersion" exit 1 } } diff --git a/Templates/Per Tenant Extension/.github/workflows/PublishToEnvironment.yaml b/Templates/Per Tenant Extension/.github/workflows/PublishToEnvironment.yaml index 7cec8a327..3a41664d0 100644 --- a/Templates/Per Tenant Extension/.github/workflows/PublishToEnvironment.yaml +++ b/Templates/Per Tenant Extension/.github/workflows/PublishToEnvironment.yaml @@ -144,6 +144,7 @@ jobs: uses: microsoft/AL-Go-Actions/ReadSettings@main with: shell: ${{ matrix.shell }} + get: type,powerPlatformSolutionFolder - name: Read secrets id: ReadSecrets @@ -153,7 +154,14 @@ jobs: gitHubSecrets: ${{ toJson(secrets) }} getSecrets: '${{ steps.envName.outputs.envName }}-AuthContext,${{ steps.envName.outputs.envName }}_AuthContext,AuthContext,${{ steps.envName.outputs.envName }}-EnvironmentName,${{ steps.envName.outputs.envName }}_EnvironmentName,EnvironmentName,projects' - - name: Deploy + - name: Get Artifacts for deployment + uses: microsoft/AL-Go-Actions/GetArtifactsForDeployment@main + with: + shell: powershell + artifactsVersion: ${{ github.event.inputs.appVersion }} + artifactsFolder: '.artifacts' + + - name: Deploy to Business Central id: Deploy uses: microsoft/AL-Go-Actions/Deploy@main env: @@ -161,10 +169,21 @@ jobs: with: shell: ${{ matrix.shell }} environmentName: ${{ matrix.environment }} - artifacts: ${{ github.event.inputs.appVersion }} + artifactsFolder: '.artifacts' type: 'Publish' deploymentEnvironmentsJson: ${{ needs.Initialization.outputs.deploymentEnvironmentsJson }} + - name: Deploy to Power Platform + if: env.type == 'PTE' && env.powerPlatformSolutionFolder != '' + uses: microsoft/AL-Go-Actions/DeployPowerPlatform@main + env: + Secrets: '${{ steps.ReadSecrets.outputs.Secrets }}' + with: + shell: powershell + environmentName: ${{ matrix.environment }} + artifactsFolder: '.artifacts' + deploymentEnvironmentsJson: ${{ needs.Initialization.outputs.deploymentEnvironmentsJson }} + PostProcess: needs: [ Initialization, Deploy ] if: always() diff --git a/Templates/Per Tenant Extension/.github/workflows/PullPowerPlatformChanges.yaml b/Templates/Per Tenant Extension/.github/workflows/PullPowerPlatformChanges.yaml new file mode 100644 index 000000000..e0c152459 --- /dev/null +++ b/Templates/Per Tenant Extension/.github/workflows/PullPowerPlatformChanges.yaml @@ -0,0 +1,108 @@ +name: ' Pull Power Platform changes' + +on: + workflow_dispatch: + inputs: + environment: + description: Environment to pull changes from + required: true + solutionFolder: + description: Folder name of the Power Platform solution (leave empty to use AL-Go setting) + required: false + directCommit: + description: Direct Commit? + type: boolean + default: false + useGhTokenWorkflow: + description: Use GhTokenWorkflow for PR/Commit? + type: boolean + default: false + +permissions: + contents: write + pull-requests: write + +defaults: + run: + shell: powershell + +jobs: + PullChanges: + runs-on: [windows-latest] + name: Pull changes from ${{ inputs.environment }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Initialize the workflow + id: init + uses: microsoft/AL-Go-Actions/WorkflowInitialize@main + with: + shell: powershell + eventId: "DO0103" + + - name: EnvName + env: + _environment: ${{ inputs.environment }} + run: | + $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + $envName = "$env:_environment".Split(' ')[0] + Add-Content -encoding utf8 -Path $env:GITHUB_ENV -Value "envName=$envName" + + - name: Read settings + uses: microsoft/AL-Go-Actions/ReadSettings@main + with: + shell: powershell + get: powerPlatformSolutionFolder + + - name: Read secrets + id: ReadSecrets + uses: microsoft/AL-Go-Actions/ReadSecrets@main + with: + shell: powershell + gitHubSecrets: ${{ toJson(secrets) }} + getSecrets: '${{ env.envName }}-AuthContext,${{ env.envName }}_AuthContext,AuthContext,TokenForPush' + useGhTokenWorkflowForPush: '${{ github.event.inputs.useGhTokenWorkflow }}' + + - name: Determine Deployment Environments + id: DetermineDeploymentEnvironments + uses: microsoft/AL-Go-Actions/DetermineDeploymentEnvironments@main + env: + GITHUB_TOKEN: ${{ github.token }} + with: + shell: powershell + getEnvironments: ${{ inputs.environment }} + type: 'All' + + - name: Set Power Platform solution folder + env: + _solutionFolder: ${{ inputs.solutionFolder }} + run: | + $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + $solutionFolder = $env:_solutionFolder + if ($solutionFolder -eq '') { + Write-Host "Solution folder is not provided. Taking the folder from AL-Go settings" + $solutionFolder = $env:powerPlatformSolutionFolder + } + Write-Host "Solution folder: $solutionFolder" + Add-Content -encoding utf8 -Path $env:GITHUB_ENV -Value "solutionFolder=$solutionFolder" + + - name: Pull changes from Power Platform environment + uses: microsoft/AL-Go-Actions/PullPowerPlatformChanges@main + env: + Secrets: '${{ steps.ReadSecrets.outputs.Secrets }}' + with: + shell: powershell + token: ${{ steps.ReadSecrets.outputs.TokenForPush }} + directCommit: ${{ inputs.directCommit }} + environmentName: ${{ inputs.environment }} + solutionFolder: ${{ env.solutionFolder }} + deploymentEnvironmentsJson: ${{ steps.DetermineDeploymentEnvironments.outputs.deploymentEnvironmentsJson }} + + - name: Finalize the workflow + if: always() + uses: microsoft/AL-Go-Actions/WorkflowPostProcess@main + with: + shell: powershell + eventId: "DO0103" + telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} diff --git a/Templates/Per Tenant Extension/.github/workflows/PushPowerPlatformChanges.yaml b/Templates/Per Tenant Extension/.github/workflows/PushPowerPlatformChanges.yaml new file mode 100644 index 000000000..ac4492287 --- /dev/null +++ b/Templates/Per Tenant Extension/.github/workflows/PushPowerPlatformChanges.yaml @@ -0,0 +1,97 @@ +name: " Push Power Platform changes" + +on: + workflow_dispatch: + inputs: + environment: + description: Environment to push changes to + required: true + solutionFolder: + description: Folder name of the Power Platform solution (leave empty to use AL-Go setting) + required: false + +permissions: + contents: read + +defaults: + run: + shell: powershell + +jobs: + PushChanges: + runs-on: [windows-latest] + name: Push changes to ${{ inputs.environment }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Initialize the workflow + id: init + uses: microsoft/AL-Go-Actions/WorkflowInitialize@main + with: + shell: powershell + eventId: "DO0103" + + - name: EnvName + env: + _environment: ${{ inputs.environment }} + run: | + $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + # Environment names can contains spaces and tags (like (PROD) etc. We need to remove them to get the correct environment name) + $envName = "$env:_environment".Split(' ')[0] + Add-Content -encoding utf8 -Path $env:GITHUB_ENV -Value "envName=$envName" + + - name: Read settings + uses: microsoft/AL-Go-Actions/ReadSettings@main + with: + shell: powershell + get: powerPlatformSolutionFolder + + - name: Read secrets + id: ReadSecrets + uses: microsoft/AL-Go-Actions/ReadSecrets@main + with: + shell: powershell + gitHubSecrets: ${{ toJson(secrets) }} + getSecrets: '${{ env.envName }}-AuthContext,${{ env.envName }}_AuthContext,AuthContext' + + - name: Determine Deployment Environments + id: DetermineDeploymentEnvironments + uses: microsoft/AL-Go-Actions/DetermineDeploymentEnvironments@main + env: + GITHUB_TOKEN: ${{ github.token }} + with: + shell: powershell + getEnvironments: ${{ inputs.environment }} + type: 'All' + + - name: Set Power Platform solution folder + env: + _solutionFolder: ${{ inputs.solutionFolder }} + run: | + $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + $solutionFolder = $env:_solutionFolder + if ($solutionFolder -eq '') { + Write-Host "Solution folder is not provided. Taking the folder from AL-Go settings" + $solutionFolder = $env:powerPlatformSolutionFolder + } + Write-Host "Solution folder: $solutionFolder" + Add-Content -encoding utf8 -Path $env:GITHUB_ENV -Value "solutionFolder=$solutionFolder" + + - name: Export and push changes to Power Platform + uses: microsoft/AL-Go-Actions/DeployPowerPlatform@main + env: + Secrets: '${{ steps.ReadSecrets.outputs.Secrets }}' + with: + shell: powershell + environmentName: ${{ inputs.environment }} + solutionFolder: ${{ env.solutionFolder }} + deploymentEnvironmentsJson: ${{ steps.DetermineDeploymentEnvironments.outputs.deploymentEnvironmentsJson }} + + - name: Finalize the workflow + if: always() + uses: microsoft/AL-Go-Actions/WorkflowPostProcess@main + with: + shell: powershell + eventId: "DO0103" + telemetryScopeJson: ${{ steps.init.outputs.telemetryScopeJson }} diff --git a/Templates/Per Tenant Extension/.github/workflows/_BuildALGoProject.yaml b/Templates/Per Tenant Extension/.github/workflows/_BuildALGoProject.yaml index a250be74a..64cd99e7b 100644 --- a/Templates/Per Tenant Extension/.github/workflows/_BuildALGoProject.yaml +++ b/Templates/Per Tenant Extension/.github/workflows/_BuildALGoProject.yaml @@ -1,6 +1,6 @@ -name: '_Build AL-GO project' +name: '_Build AL-Go project' -run-name: 'Build project ${{ inputs.project }}' +run-name: 'Build ${{ inputs.project }}' on: workflow_call: @@ -11,7 +11,7 @@ on: default: powershell type: string runsOn: - description: JSON-formatted string og the types of machine to run the build job on + description: JSON-formatted string of the types of machine to run the build job on required: true type: string checkoutRef: diff --git a/Templates/Per Tenant Extension/.github/workflows/_BuildPowerPlatformSolution.yaml b/Templates/Per Tenant Extension/.github/workflows/_BuildPowerPlatformSolution.yaml new file mode 100644 index 000000000..c799cb840 --- /dev/null +++ b/Templates/Per Tenant Extension/.github/workflows/_BuildPowerPlatformSolution.yaml @@ -0,0 +1,96 @@ +name: '_Build PowerPlatform Solution' + +run-name: 'Build PowerPlatform Solution' + +on: + workflow_call: + inputs: + shell: + description: Shell in which you want to run the action (powershell or pwsh) + required: false + default: powershell + type: string + runsOn: + description: JSON-formatted string of the types of machine to run the build job on + required: true + type: string + checkoutRef: + description: Ref to checkout + required: false + default: ${{ github.ref }} + type: string + project: + description: Name of the built project + required: true + type: string + projectName: + description: Friendly name of the built project + required: true + type: string + publishArtifacts: + description: Flag indicating whether the artifacts should be published + type: boolean + default: false + artifactsNameSuffix: + description: Suffix to add to the artifacts names + required: false + default: '' + type: string + parentTelemetryScopeJson: + description: Specifies the telemetry scope for the telemetry signal + required: false + type: string + +env: + ALGoOrgSettings: ${{ vars.ALGoOrgSettings }} + ALGoRepoSettings: ${{ vars.ALGoRepoSettings }} + +jobs: + Build: + needs: [ ] + runs-on: ${{ fromJson(inputs.runsOn) }} + defaults: + run: + shell: ${{ inputs.shell }} + name: '${{ inputs.projectName }}' + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ inputs.checkoutRef }} + lfs: true + + - name: Read settings + uses: microsoft/AL-Go-Actions/ReadSettings@main + with: + shell: ${{ inputs.shell }} + project: ${{ inputs.project }} + get: type,powerPlatformSolutionFolder,appRevision,appBuild + + - name: Build + uses: microsoft/AL-Go-Actions/BuildPowerPlatform@main + with: + shell: ${{ inputs.shell }} + solutionFolder: ${{ inputs.project }} + outputFolder: ${{ inputs.project }}/.buildartifacts/_PowerPlatformSolution/ + outputFileName: ${{ inputs.project }} + appRevision: ${{ env.appRevision }} + appBuild: ${{ env.appBuild }} + + - name: Calculate Artifact names + id: calculateArtifactsNames + uses: microsoft/AL-Go-Actions/CalculateArtifactNames@main + if: success() || failure() + with: + shell: ${{ inputs.shell }} + project: ${{ inputs.project }} + buildMode: 'default' + suffix: ${{ inputs.artifactsNameSuffix }} + + - name: Publish artifacts - Power Platform Solution + uses: actions/upload-artifact@v4 + if: inputs.publishArtifacts + with: + name: ${{ steps.calculateArtifactsNames.outputs.PowerPlatformSolutionArtifactsName }} + path: '${{ inputs.project }}/.buildartifacts/_PowerPlatformSolution/' + if-no-files-found: ignore diff --git a/Tests/AddExistingApp.Action.Test.ps1 b/Tests/AddExistingApp.Action.Test.ps1 index 92f8a2777..110e9ec5c 100644 --- a/Tests/AddExistingApp.Action.Test.ps1 +++ b/Tests/AddExistingApp.Action.Test.ps1 @@ -1,5 +1,6 @@ Get-Module TestActionsHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') +$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 Describe "AddExistingApp Action Tests" { BeforeAll { diff --git a/Tests/BuildPowerPlatform.Action.Test.ps1 b/Tests/BuildPowerPlatform.Action.Test.ps1 new file mode 100644 index 000000000..4e59a0a4b --- /dev/null +++ b/Tests/BuildPowerPlatform.Action.Test.ps1 @@ -0,0 +1,114 @@ +Get-Module TestActionsHelper | Remove-Module -Force +Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') +$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + +Describe "Build Power Platform Settings Action Tests" { + BeforeAll { + $actionName = "BuildPowerPlatform" + $scriptRoot = Join-Path $PSScriptRoot "..\Actions\$actionName" -Resolve + $scriptName = "$actionName.ps1" + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'scriptPath', Justification = 'False positive.')] + $scriptPath = Join-Path $scriptRoot $scriptName + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'actionScript', Justification = 'False positive.')] + $actionScript = GetActionScript -scriptRoot $scriptRoot -scriptName $scriptName + + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'testDataPath', Justification = 'False positive.')] + $testDataPath = Join-Path $PSScriptRoot "_TestData-PowerPlatform/*" + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'testDataTempPath', Justification = 'False positive.')] + $testDataTempPath = Join-Path $PSScriptRoot "_TestData-PowerPlatform_temp" + + Invoke-Expression $actionScript + } + + BeforeEach { + New-Item -Path $testDataTempPath -ItemType Directory -Force | Out-Null + Copy-Item -Path $testDataPath -Destination $testDataTempPath -Recurse -Force + } + + AfterEach { + Remove-Item -Path $testDataTempPath -Recurse -Force + } + + It 'Updates the solution file' { + # The old version is hardcoded in the test data + $oldVersionString = "1.0.0.0" + + $newBuildString = "222" + $newRevisionString = "999" + $newVersionString = "1.0.$newBuildString.$newRevisionString" + + $solutionPath = "$testDataTempPath/StandardSolution"; + + $testSolutionFileBeforeTest = [xml](Get-Content -Encoding UTF8 -Path (Join-Path $solutionPath 'other/Solution.xml')) + $versionNode = $testSolutionFileBeforeTest.SelectSingleNode("//Version") + $versionNodeText = $versionNode.'#text' + $versionNodeText | Should -Not -BeNullOrEmpty + $versionNodeText | Should -Contain $oldVersionString + + BuildPowerPlatform -solutionFolder $solutionPath -appBuild $newBuildString -appRevision $newRevisionString + + $testSolutionFileAfterTest = [xml](Get-Content -Encoding UTF8 -Path (Join-Path $solutionPath 'other/Solution.xml')) + $versionNode = $testSolutionFileAfterTest.SelectSingleNode("//Version") + $versionNodeText = $versionNode.'#text' + $versionNodeText | Should -Not -BeNullOrEmpty + $versionNodeText | Should -Not -Contain $oldVersionString + $versionNodeText | Should -Contain $newVersionString + } + + It 'Updates the Power App connections' { + # note: The old company name and environment name are hardcoded in the test data + $oldCompanyName = "TestCompanyId" + $oldEnvironmentName = "TestEnvironmentName" + + $newCompanyName = "NewCompanyName" + $newEnvironmentName = "NewEnvironmentName" + + $solutionPath = "$testDataTempPath/StandardSolution"; + + # Check file content before running the script + # NOTE: There are multiple connection files in the test data, but we only check one of them as a smoke test + $connectionFileContent = [string](Get-Content -Encoding UTF8 -Path (Join-Path $solutionPath 'CanvasApps/src/TestApp/Connections/Connections.json')) + $connectionFileContent | Should -Not -BeNullOrEmpty + $connectionFileContent | Should -Match $oldCompanyName + $connectionFileContent | Should -Match $oldEnvironmentName + $connectionFileContent | Should -Not -Match $newCompanyName + $connectionFileContent | Should -Not -Match $newEnvironmentName + + $workflowFileContent = [string](Get-Content -Encoding UTF8 -Path (Join-Path $solutionPath 'Workflows/TestWorkflow-ABA81736-12D9-ED11-A7C7-000D3A991110.json')) + $workflowFileContent | Should -Not -BeNullOrEmpty + $workflowFileContent | Should -Match $oldCompanyName + $workflowFileContent | Should -Match $oldEnvironmentName + $workflowFileContent | Should -Not -Match $newCompanyName + $workflowFileContent | Should -Not -Match $newEnvironmentName + + # Run the script + BuildPowerPlatform -solutionFolder $solutionPath -CompanyId $newCompanyName -EnvironmentName $newEnvironmentName + + # Check file content after running the script + $connectionFileContent = [string](Get-Content -Encoding UTF8 -Path (Join-Path $solutionPath 'CanvasApps/src/TestApp/Connections/Connections.json')) + $connectionFileContent | Should -Not -BeNullOrEmpty + $connectionFileContent | Should -Not -Match $oldCompanyName + $connectionFileContent | Should -Not -Match $oldEnvironmentName + $connectionFileContent | Should -Match $newCompanyName + $connectionFileContent | Should -Match $newEnvironmentName + + $workflowFileContent = [string](Get-Content -Encoding UTF8 -Path (Join-Path $solutionPath 'Workflows/TestWorkflow-ABA81736-12D9-ED11-A7C7-000D3A991110.json')) + $workflowFileContent | Should -Not -BeNullOrEmpty + $workflowFileContent | Should -Not -Match $oldCompanyName + $workflowFileContent | Should -Not -Match $oldEnvironmentName + $workflowFileContent | Should -Match $newCompanyName + $workflowFileContent | Should -Match $newEnvironmentName + } + + It 'Works with PowerApp Only' { + $solutionPath = "$testDataTempPath\PowerAppOnlySolution"; + + # Run the script + BuildPowerPlatform -solutionFolder $solutionPath -CompanyId "NewCompanyName" -EnvironmentName "NewEnvironmentName" + } + + It 'Works with Flow only solution' { + $solutionPath = "$testDataTempPath/FlowOnlySolution"; + BuildPowerPlatform -solutionFolder $solutionPath -CompanyId "NewCompanyName" -EnvironmentName "NewEnvironmentName" + } +} diff --git a/Tests/BuildReferenceDocumentation.Action.Test.ps1 b/Tests/BuildReferenceDocumentation.Action.Test.ps1 index a7a88044b..50b6ae012 100644 --- a/Tests/BuildReferenceDocumentation.Action.Test.ps1 +++ b/Tests/BuildReferenceDocumentation.Action.Test.ps1 @@ -1,5 +1,6 @@ Get-Module TestActionsHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') +$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 Describe "BuildReferenceDocumentation Action Tests" { BeforeAll { diff --git a/Tests/CalculateArtifactNames.Test.ps1 b/Tests/CalculateArtifactNames.Test.ps1 index 231af0740..8b237936f 100644 --- a/Tests/CalculateArtifactNames.Test.ps1 +++ b/Tests/CalculateArtifactNames.Test.ps1 @@ -1,5 +1,6 @@ Get-Module TestActionsHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') +$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 Describe 'CalculateArtifactNames Action Tests' { @@ -12,7 +13,7 @@ Describe 'CalculateArtifactNames Action Tests' { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'actionScript', Justification = 'False positive.')] $actionScript = GetActionScript -scriptRoot $scriptRoot -scriptName $scriptName - $env:Settings = '{ "appBuild": 123, "repoVersion": "22.0", "appRevision": 0,"repoName": "AL-GO"}' + $env:Settings = '{ "appBuild": 123, "repoVersion": "22.0", "appRevision": 0,"repoName": "AL-Go"}' [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'project', Justification = 'False positive.')] $project = "ALGOProject" } @@ -148,6 +149,7 @@ Describe 'CalculateArtifactNames Action Tests' { "ThisBuildDependenciesArtifactsName" = "Artifact name for dependencies of apps being built in the current workflow run" "ThisBuildTestAppsArtifactsName" = "Artifact name for test apps being built in the current workflow run" "AppsArtifactsName" = "Artifacts name for Apps" + "PowerPlatformSolutionArtifactsName" = "Artifacts name for PowerPlatform Solution" "DependenciesArtifactsName" = "Artifacts name for Dependencies" "TestAppsArtifactsName" = "Artifacts name for TestApps" "TestResultsArtifactsName" = "Artifacts name for TestResults" diff --git a/Tests/CheckForUpdates.Action.Test.ps1 b/Tests/CheckForUpdates.Action.Test.ps1 index c55876657..ea3cb62b7 100644 --- a/Tests/CheckForUpdates.Action.Test.ps1 +++ b/Tests/CheckForUpdates.Action.Test.ps1 @@ -1,5 +1,6 @@ Get-Module TestActionsHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') +$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 Describe "CheckForUpdates Action Tests" { BeforeAll { diff --git a/Tests/CreateApp.Action.Test.ps1 b/Tests/CreateApp.Action.Test.ps1 index 1b2c89857..1a9580395 100644 --- a/Tests/CreateApp.Action.Test.ps1 +++ b/Tests/CreateApp.Action.Test.ps1 @@ -1,5 +1,6 @@ Get-Module TestActionsHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') +$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 Describe "CreateApp Action Tests" { BeforeAll { diff --git a/Tests/CreateDevelopmentEnvironment.Action.Test.ps1 b/Tests/CreateDevelopmentEnvironment.Action.Test.ps1 index ec45cffa9..dc593fa87 100644 --- a/Tests/CreateDevelopmentEnvironment.Action.Test.ps1 +++ b/Tests/CreateDevelopmentEnvironment.Action.Test.ps1 @@ -1,5 +1,6 @@ Get-Module TestActionsHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') +$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 Describe "CreateDevelopmentEnvironment Action Tests" { BeforeAll { diff --git a/Tests/CreateReleaseNotes.Test.ps1 b/Tests/CreateReleaseNotes.Test.ps1 index 613f206aa..6da6168f7 100644 --- a/Tests/CreateReleaseNotes.Test.ps1 +++ b/Tests/CreateReleaseNotes.Test.ps1 @@ -4,6 +4,7 @@ Get-Module TestActionsHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') Get-Module TelemetryHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot '..\Actions\TelemetryHelper.psm1') +$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 Describe 'CreateReleaseNotes Tests' { BeforeAll { diff --git a/Tests/Deliver.Action.Test.ps1 b/Tests/Deliver.Action.Test.ps1 index 2b10d5103..d2fb59301 100644 --- a/Tests/Deliver.Action.Test.ps1 +++ b/Tests/Deliver.Action.Test.ps1 @@ -1,5 +1,6 @@ Get-Module TestActionsHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') +$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 Describe "Deliver Action Tests" { BeforeAll { diff --git a/Tests/Deploy.Action.Test.ps1 b/Tests/Deploy.Action.Test.ps1 index 97c8badef..73df39e54 100644 --- a/Tests/Deploy.Action.Test.ps1 +++ b/Tests/Deploy.Action.Test.ps1 @@ -1,5 +1,6 @@ Get-Module TestActionsHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') +$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 Describe "Deploy Action Tests" { BeforeAll { diff --git a/Tests/DetermineArtifactUrl.Test.ps1 b/Tests/DetermineArtifactUrl.Test.ps1 index 223394a0f..b8458eb36 100644 --- a/Tests/DetermineArtifactUrl.Test.ps1 +++ b/Tests/DetermineArtifactUrl.Test.ps1 @@ -110,7 +110,7 @@ Describe "DetermineArtifactUrl" { It 'Artifact setting wins over country setting' { $projectSettings.country = 'dk' $projectSettings.artifact = "///us/latest" - DetermineArtifactUrl -projectSettings $projectSettings | should -be 'https://bcartifacts/sandbox/22.1.12345.12345/us' + DetermineArtifactUrl -projectSettings $projectSettings -doNotIssueWarnings | should -be 'https://bcartifacts/sandbox/22.1.12345.12345/us' } It 'Artifact setting when using version = * and first' { diff --git a/Tests/DetermineDeploymentEnvironments.Test.ps1 b/Tests/DetermineDeploymentEnvironments.Test.ps1 index 840a25c82..24b21934b 100644 --- a/Tests/DetermineDeploymentEnvironments.Test.ps1 +++ b/Tests/DetermineDeploymentEnvironments.Test.ps1 @@ -60,13 +60,13 @@ Describe "DetermineDeploymentEnvironments Action Test" { . (Join-Path $scriptRoot $scriptName) -getEnvironments '*' -type 'CD' PassGeneratedOutput $EnvironmentsMatrixJson | ConvertFrom-Json | ConvertTo-HashTable -recurse | Should -MatchHashtable @{"matrix"=@{"include"=@(@{"environment"="another";"os"="[""ubuntu-latest""]";"shell"="pwsh"};@{"environment"="test";"os"="[""ubuntu-latest""]";"shell"="pwsh"})};"fail-fast"=$false} - $DeploymentEnvironmentsJson | ConvertFrom-Json | ConvertTo-HashTable -recurse | Should -MatchHashtable @{"test"=@{"EnvironmentType"="SaaS";"EnvironmentName"="test";"Branches"=@();"BranchesFromPolicy"=@();"Projects"="*";"SyncMode"=$null;"continuousDeployment"=$null;"runs-on"=@("ubuntu-latest");"shell"="pwsh"};"another"=@{"EnvironmentType"="SaaS";"EnvironmentName"="another";"Branches"=@();"BranchesFromPolicy"=@();"Projects"="*";"SyncMode"=$null;"continuousDeployment"=$null;"runs-on"=@("ubuntu-latest");"shell"="pwsh"}} + $DeploymentEnvironmentsJson | ConvertFrom-Json | ConvertTo-HashTable -recurse | Should -MatchHashtable @{"test"=@{"EnvironmentType"="SaaS";"EnvironmentName"="test";"Branches"=@();"BranchesFromPolicy"=@();"Projects"="*";"SyncMode"=$null;"continuousDeployment"=$null;"runs-on"=@("ubuntu-latest");"shell"="pwsh";"ppEnvironmentUrl"="";"companyId"=""};"another"=@{"EnvironmentType"="SaaS";"EnvironmentName"="another";"Branches"=@();"BranchesFromPolicy"=@();"Projects"="*";"SyncMode"=$null;"continuousDeployment"=$null;"runs-on"=@("ubuntu-latest");"shell"="pwsh";"ppEnvironmentUrl"="";"companyId"=""}} $EnvironmentCount | Should -Be 2 . (Join-Path $scriptRoot $scriptName) -getEnvironments 'test' -type 'CD' PassGeneratedOutput $EnvironmentsMatrixJson | ConvertFrom-Json | ConvertTo-HashTable -recurse | Should -MatchHashtable @{"matrix"=@{"include"=@(@{"environment"="test";"os"="[""ubuntu-latest""]";"shell"="pwsh"})};"fail-fast"=$false} - $DeploymentEnvironmentsJson | ConvertFrom-Json | ConvertTo-HashTable -recurse | Should -MatchHashtable @{"test"=@{"EnvironmentType"="SaaS";"EnvironmentName"="test";"Branches"=@();"BranchesFromPolicy"=@();"Projects"="*";"SyncMode"=$null;"continuousDeployment"=$null;"runs-on"=@("ubuntu-latest");"shell"="pwsh"}} + $DeploymentEnvironmentsJson | ConvertFrom-Json | ConvertTo-HashTable -recurse | Should -MatchHashtable @{"test"=@{"EnvironmentType"="SaaS";"EnvironmentName"="test";"Branches"=@();"BranchesFromPolicy"=@();"Projects"="*";"SyncMode"=$null;"continuousDeployment"=$null;"runs-on"=@("ubuntu-latest");"shell"="pwsh";"ppEnvironmentUrl"="";"companyId"=""}} $EnvironmentCount | Should -Be 1 } @@ -83,7 +83,7 @@ Describe "DetermineDeploymentEnvironments Action Test" { . (Join-Path $scriptRoot $scriptName) -getEnvironments '*' -type 'CD' PassGeneratedOutput $EnvironmentsMatrixJson | ConvertFrom-Json | ConvertTo-HashTable -recurse | Should -MatchHashtable @{"matrix"=@{"include"=@(@{"environment"="another";"os"="[""ubuntu-latest""]";"shell"="pwsh"})};"fail-fast"=$false} - $DeploymentEnvironmentsJson | ConvertFrom-Json | ConvertTo-HashTable -recurse | Should -MatchHashtable @{"another"=@{"EnvironmentType"="SaaS";"EnvironmentName"="another";"Branches"=@();"BranchesFromPolicy"=@();"Projects"="*";"SyncMode"=$null;"continuousDeployment"=$null;"runs-on"=@("ubuntu-latest");"shell"="pwsh"}} + $DeploymentEnvironmentsJson | ConvertFrom-Json | ConvertTo-HashTable -recurse | Should -MatchHashtable @{"another"=@{"EnvironmentType"="SaaS";"EnvironmentName"="another";"Branches"=@();"BranchesFromPolicy"=@();"Projects"="*";"SyncMode"=$null;"continuousDeployment"=$null;"runs-on"=@("ubuntu-latest");"shell"="pwsh";"ppEnvironmentUrl"="";"companyId"=""}} $EnvironmentCount | Should -Be 1 $env:GITHUB_REF_NAME = 'branch' @@ -109,7 +109,7 @@ Describe "DetermineDeploymentEnvironments Action Test" { . (Join-Path $scriptRoot $scriptName) -getEnvironments '*' -type 'CD' PassGeneratedOutput $EnvironmentsMatrixJson | ConvertFrom-Json | ConvertTo-HashTable -recurse | Should -MatchHashtable @{"matrix"=@{"include"=@(@{"environment"="another";"os"="[""ubuntu-latest""]";"shell"="pwsh"})};"fail-fast"=$false} - $DeploymentEnvironmentsJson | ConvertFrom-Json | ConvertTo-HashTable -recurse | Should -MatchHashtable @{"another"=@{"EnvironmentType"="SaaS";"EnvironmentName"="another";"Branches"=@();"BranchesFromPolicy"=@();"Projects"="*";"SyncMode"=$null;"continuousDeployment"=$null;"runs-on"=@("ubuntu-latest");"shell"="pwsh"}} + $DeploymentEnvironmentsJson | ConvertFrom-Json | ConvertTo-HashTable -recurse | Should -MatchHashtable @{"another"=@{"EnvironmentType"="SaaS";"EnvironmentName"="another";"Branches"=@();"BranchesFromPolicy"=@();"Projects"="*";"SyncMode"=$null;"continuousDeployment"=$null;"runs-on"=@("ubuntu-latest");"shell"="pwsh";"ppEnvironmentUrl"="";"companyId"=""}} $EnvironmentCount | Should -Be 1 ($EnvironmentsMatrixJson | ConvertFrom-Json | ConvertTo-HashTable -recurse).matrix.include.environment | Should -Contain "another" diff --git a/Tests/DetermineProjectsToBuild.Test.ps1 b/Tests/DetermineProjectsToBuild.Test.ps1 index c8d522baa..753ad81e5 100644 --- a/Tests/DetermineProjectsToBuild.Test.ps1 +++ b/Tests/DetermineProjectsToBuild.Test.ps1 @@ -17,7 +17,7 @@ Describe "Get-ProjectsToBuild" { New-Item -Path "$baseFolder/.AL-Go/settings.json" -type File -Force # Add AL-Go settings file - $alGoSettings = @{ alwaysBuildAllProjects = $false; projects = @(); useProjectDependencies = $false } + $alGoSettings = @{ alwaysBuildAllProjects = $false; projects = @(); powerPlatformSolutionFolder = ''; useProjectDependencies = $false } $env:Settings = ConvertTo-Json $alGoSettings -Depth 99 -Compress $allProjects, $projectsToBuild, $projectDependencies, $buildOrder = Get-ProjectsToBuild -baseFolder $baseFolder @@ -57,7 +57,7 @@ Describe "Get-ProjectsToBuild" { New-Item -Path "$baseFolder/Project2/.AL-Go/settings.json" -type File -Force # Add AL-Go settings file - $alGoSettings = @{ alwaysBuildAllProjects = $false; projects = @(); useProjectDependencies = $false } + $alGoSettings = @{ alwaysBuildAllProjects = $false; projects = @(); powerPlatformSolutionFolder = ''; useProjectDependencies = $false } $env:Settings = ConvertTo-Json $alGoSettings -Depth 99 -Compress $allProjects, $projectsToBuild, $projectDependencies, $buildOrder = Get-ProjectsToBuild -baseFolder $baseFolder @@ -385,7 +385,7 @@ Describe "Get-ProjectsToBuild" { New-Item -Path "$baseFolder/Project2/.AL-Go/settings.json" -type File -Force # Add AL-Go settings file - $alGoSettings = @{ alwaysBuildAllProjects = $false; projects = @(); useProjectDependencies = $false; fullBuildPatterns = @('.github') } + $alGoSettings = @{ alwaysBuildAllProjects = $false; projects = @(); powerPlatformSolutionFolder = ''; useProjectDependencies = $false; fullBuildPatterns = @('.github') } $env:Settings = ConvertTo-Json $alGoSettings -Depth 99 -Compress $modifiedFiles = @('Project1/.AL-Go/settings.json') @@ -415,7 +415,7 @@ Describe "Get-ProjectsToBuild" { New-Item -Path "$baseFolder/Project2/.AL-Go/settings.json" -type File -Force # Add AL-Go settings file - $alGoSettings = @{ alwaysBuildAllProjects = $false; projects = @(); useProjectDependencies = $false; fullBuildPatterns = @('Project1/*') } + $alGoSettings = @{ alwaysBuildAllProjects = $false; projects = @(); powerPlatformSolutionFolder = ''; useProjectDependencies = $false; fullBuildPatterns = @('Project1/*') } $env:Settings = ConvertTo-Json $alGoSettings -Depth 99 -Compress $modifiedFiles = @('Project1/.AL-Go/settings.json') @@ -445,7 +445,7 @@ Describe "Get-ProjectsToBuild" { New-Item -Path "$baseFolder/Project2/.AL-Go/settings.json" -type File -Force #Add settings file - $alGoSettings = @{ alwaysBuildAllProjects = $false; projects = @(); useProjectDependencies = $false } + $alGoSettings = @{ alwaysBuildAllProjects = $false; projects = @(); powerPlatformSolutionFolder = ''; useProjectDependencies = $false } $env:Settings = ConvertTo-Json $alGoSettings -Depth 99 -Compress $allProjects, $projectsToBuild, $projectDependencies, $buildOrder = Get-ProjectsToBuild -baseFolder $baseFolder @@ -495,7 +495,7 @@ Describe "Get-ProjectsToBuild" { New-Item -Path "$baseFolder/Project2/.AL-Go/settings.json" -type File -Force #Add settings file - $alGoSettings = @{ alwaysBuildAllProjects = $false; projects = @(); useProjectDependencies = $true } + $alGoSettings = @{ alwaysBuildAllProjects = $false; projects = @(); powerPlatformSolutionFolder = ''; useProjectDependencies = $true } $env:Settings = ConvertTo-Json $alGoSettings -Depth 99 -Compress $allProjects, $projectsToBuild, $projectDependencies, $buildOrder = Get-ProjectsToBuild -baseFolder $baseFolder @@ -550,7 +550,7 @@ Describe "Get-ProjectsToBuild" { New-Item -Path "$baseFolder/Project2/app/app.json" -Value (ConvertTo-Json $dependantAppFile -Depth 10) -type File -Force #Add settings file - $alGoSettings = @{ alwaysBuildAllProjects = $false; projects = @(); useProjectDependencies = $false } + $alGoSettings = @{ alwaysBuildAllProjects = $false; projects = @(); powerPlatformSolutionFolder = ''; useProjectDependencies = $false } $env:Settings = ConvertTo-Json $alGoSettings -Depth 99 -Compress $allProjects, $projectsToBuild, $projectDependencies, $buildOrder = Get-ProjectsToBuild -baseFolder $baseFolder @@ -605,7 +605,7 @@ Describe "Get-ProjectsToBuild" { New-Item -Path "$baseFolder/Project2/app/app.json" -Value (ConvertTo-Json $dependantAppFile -Depth 10) -type File -Force #Add settings file - $alGoSettings = @{ alwaysBuildAllProjects = $false; projects = @(); useProjectDependencies = $true } + $alGoSettings = @{ alwaysBuildAllProjects = $false; projects = @(); powerPlatformSolutionFolder = ''; useProjectDependencies = $true } $env:Settings = ConvertTo-Json $alGoSettings -Depth 99 -Compress $allProjects, $projectsToBuild, $projectDependencies, $buildOrder = Get-ProjectsToBuild -baseFolder $baseFolder @@ -672,7 +672,7 @@ Describe "Get-ProjectsToBuild" { New-Item -Path "$baseFolder/Project2/app/app.json" -Value (ConvertTo-Json $dependantAppFile -Depth 10) -type File -Force #Add settings file - $alGoSettings = @{ alwaysBuildAllProjects = $false; projects = @(); useProjectDependencies = $true } + $alGoSettings = @{ alwaysBuildAllProjects = $false; projects = @(); powerPlatformSolutionFolder = ''; useProjectDependencies = $true } $env:Settings = ConvertTo-Json $alGoSettings -Depth 99 -Compress { Get-ProjectsToBuild -baseFolder $baseFolder -maxBuildDepth 1 } | Should -Throw "The build depth is too deep, the maximum build depth is 1. You need to run 'Update AL-Go System Files' to update the workflows" diff --git a/Tests/IncrementVersionNumber.Action.Test.ps1 b/Tests/IncrementVersionNumber.Action.Test.ps1 index ebca505a1..2e829375e 100644 --- a/Tests/IncrementVersionNumber.Action.Test.ps1 +++ b/Tests/IncrementVersionNumber.Action.Test.ps1 @@ -1,5 +1,6 @@ Get-Module TestActionsHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') +$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 Describe "IncrementVersionNumber Action Tests" { BeforeAll { diff --git a/Tests/PipelineCleanup.Action.Test.ps1 b/Tests/PipelineCleanup.Action.Test.ps1 index c3059067d..6e9a29e55 100644 --- a/Tests/PipelineCleanup.Action.Test.ps1 +++ b/Tests/PipelineCleanup.Action.Test.ps1 @@ -1,5 +1,6 @@ Get-Module TestActionsHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') +$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 Describe "PipelineCleanup Action Tests" { BeforeAll { diff --git a/Tests/PullRequestStatusCheck.Test.ps1 b/Tests/PullRequestStatusCheck.Test.ps1 index 7c05bb30e..00d2738b8 100644 --- a/Tests/PullRequestStatusCheck.Test.ps1 +++ b/Tests/PullRequestStatusCheck.Test.ps1 @@ -1,5 +1,6 @@ Get-Module TestActionsHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') +$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 Describe "PullRequestStatusCheck Action Tests" { BeforeAll { diff --git a/Tests/ReadPowerPlatformSettings.Action.Test.ps1 b/Tests/ReadPowerPlatformSettings.Action.Test.ps1 new file mode 100644 index 000000000..0a508a814 --- /dev/null +++ b/Tests/ReadPowerPlatformSettings.Action.Test.ps1 @@ -0,0 +1,224 @@ +Get-Module TestActionsHelper | Remove-Module -Force +Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') +$ErrorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + +Describe "Read Power Platform Settings Action Tests" { + BeforeAll { + $actionName = "ReadPowerPlatformSettings" + $scriptRoot = Join-Path $PSScriptRoot "..\Actions\$actionName" -Resolve + $scriptName = "$actionName.ps1" + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'scriptPath', Justification = 'False positive.')] + $scriptPath = Join-Path $scriptRoot $scriptName + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'actionScript', Justification = 'False positive.')] + $actionScript = GetActionScript -scriptRoot $scriptRoot -scriptName $scriptName + + function ConvertToDeployToSettings { + param ( + [Parameter(Mandatory = $true)] + [hashtable] $deployToDevProperties + ) + return @{ + type = "PTE" + powerPlatformSolutionFolder = "CoffeMR" + DeployToDev = $deployToDevProperties + } | ConvertTo-Json + } + + function SetSecretsEnvVariable { + param ( + [Parameter(Mandatory = $true)] + [hashtable] $secretProperties + ) + $testSecret = $secretProperties | ConvertTo-Json + $env:Secrets = '{"DeployToDev-AuthContext": "' + [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($testSecret)) + '"}' + } + } + + BeforeEach { + Write-Host "Before test" + $env:GITHUB_OUTPUT = [System.IO.Path]::GetTempFileName() + $env:GITHUB_ENV = [System.IO.Path]::GetTempFileName() + + Write-Host $env:GITHUB_OUTPUT + Write-Host $env:GITHUB_ENV + + Invoke-Expression $actionScript + } + + AfterEach { + Write-Host "After test" + Remove-Item -Path $env:GITHUB_OUTPUT -Force + Remove-Item -Path $env:GITHUB_ENV -Force + + $env:Secrets = $null + } + + It 'Sets the correct GitHub environment variables - service principle auth' { + # Setup deploy to settings + $deployToDevProperties = @{ + environmentName = "sandbox" + companyId = "11111111-1111-1111-1111-111111111111" + ppEnvironmentUrl = "https://TestUrL.crm.dynamics.com" + } + $jsonInput = ConvertToDeployToSettings -deployToDevProperties $deployToDevProperties + + # Setup secrets as GitHub environment variable + $secretProperties = @{ + ppTenantId = "your-tenant-id" + ppApplicationId = "your-application-id" + ppClientSecret = "your-client-secret" + } + SetSecretsEnvVariable -secretProperties $secretProperties + + # Run the action + ReadPowerPlatformSettings -deploymentEnvironmentsJson $jsonInput -environmentName "DeployToDev" + + # Assert the GitHub environment variables are set correctly + $gitHubEnvPlaceholder = Get-Content -Path $env:GITHUB_OUTPUT + $gitHubEnvPlaceholder | Should -Contain ("ppEnvironmentUrl=" + $deployToDevProperties.ppEnvironmentUrl) + $gitHubEnvPlaceholder | Should -Contain ("companyId=" + $deployToDevProperties.companyId) + $gitHubEnvPlaceholder | Should -Contain ("environmentName=" + $deployToDevProperties.environmentName) + $gitHubEnvPlaceholder | Should -Contain ("ppTenantId=" + $secretProperties.ppTenantId) + $gitHubEnvPlaceholder | Should -Contain ("ppApplicationId=" + $secretProperties.ppApplicationId) + $gitHubEnvPlaceholder | Should -Contain ("ppClientSecret=" + $secretProperties.ppClientSecret) + } + + It 'Sets the correct GitHub environment variables - user auth' { + # Setup deploy to settings + $deployToDevProperties = @{ + environmentName = "sandbox" + companyId = "11111111-1111-1111-1111-111111111111" + ppEnvironmentUrl = "https://TestUrL.crm.dynamics.com" + } + $jsonInput = ConvertToDeployToSettings -deployToDevProperties $deployToDevProperties + + # Setup secrets as GitHub environment variable + $secretProperties = @{ + ppUserName = "your-username" + ppPassword = "your-password" + } + SetSecretsEnvVariable -secretProperties $secretProperties + + # Run the action + ReadPowerPlatformSettings -deploymentEnvironmentsJson $jsonInput -environmentName "DeployToDev" + + # Assert the GitHub environment variables are set correctly + $gitHubEnvPlaceholder = Get-Content -Path $env:GITHUB_OUTPUT + $gitHubEnvPlaceholder | Should -Contain ("ppEnvironmentUrl=" + $deployToDevProperties.ppEnvironmentUrl) + $gitHubEnvPlaceholder | Should -Contain ("companyId=" + $deployToDevProperties.companyId) + $gitHubEnvPlaceholder | Should -Contain ("environmentName=" + $deployToDevProperties.environmentName) + $gitHubEnvPlaceholder | Should -Contain ("ppUserName=" + $secretProperties.ppUserName) + $gitHubEnvPlaceholder | Should -Contain ("ppPassword=" + $secretProperties.ppPassword) + + } + + It 'Fails if required deployment settings are missing' { + function runMissingSettingsTest { + param ( + [hashtable] $deployToDevProperties + ) + # Convert hashtables to JSON strings + $jsonInput = ConvertToDeployToSettings -deployToDevProperties $deployToDevProperties + + $errorObject = $null + $HasThrownException = $false + # Run the action + try { + ReadPowerPlatformSettings -deploymentEnvironmentsJson $jsonInput -environmentName "DeployToDev" + } + catch { + $errorObject = $_ + $HasThrownException = $true + } + + $HasThrownException | Should -Be $true + return $errorObject.TargetObject + } + + # Test missing ppEnvironmentUrl + $deployToDevProperties = @{ + environmentName = "sandbox" + companyId = "11111111-1111-1111-1111-111111111111" + } + $errorMessage = runMissingSettingsTest -deployToDevProperties $deployToDevProperties + $errorMessage | Should -Be "DeployToDev setting must contain 'ppEnvironmentUrl' property" + + # Test missing companyId + $deployToDevProperties = @{ + environmentName = "sandbox" + ppEnvironmentUrl = "https://TestUrL.crm.dynamics.com" + } + $errorMessage = runMissingSettingsTest -deployToDevProperties $deployToDevProperties + $errorMessage | Should -Be "DeployToDev setting must contain 'companyId' property" + + # Test missing environmentName + $deployToDevProperties = @{ + companyId = "11111111-1111-1111-1111-111111111111" + ppEnvironmentUrl = "https://TestUrL.crm.dynamics.com" + } + $errorMessage = runMissingSettingsTest -deployToDevProperties $deployToDevProperties + $errorMessage | Should -Be "DeployToDev setting must contain 'environmentName' property" + + } + + It 'Fails if required secret settings are missing' { + function runMissingSecretsTest { + # Test missing ppEnvironmentUrl + $deployToDevProperties = @{ + environmentName = "sandbox" + companyId = "11111111-1111-1111-1111-111111111111" + ppEnvironmentUrl = "https://TestUrL.crm.dynamics.com" + } + # Convert hashtables to JSON strings + $jsonInput = ConvertToDeployToSettings -deployToDevProperties $deployToDevProperties + + $errorObject = $null + # Run the action + try { + ReadPowerPlatformSettings -deploymentEnvironmentsJson $jsonInput -environmentName "DeployToDev" + } + catch { + $errorObject = $_ + $HasThrownException = $true + } + + $HasThrownException | Should -Be $true + return $errorObject.TargetObject + } + + # Test secret missing ppTenantId + $secretProperties = @{ + ppApplicationId = "your-application-id" + ppClientSecret = "your-client-secret" + } + SetSecretsEnvVariable -secretProperties $secretProperties + $errorMessage = runMissingSecretsTest + $errorMessage | Should -Be "Secret DeployToDev-AuthContext must contain either 'ppUserName' and 'ppPassword' properties or 'ppApplicationId', 'ppClientSecret' and 'ppTenantId' properties" + + + # Test secret missing username + $secretProperties = @{ + username = "your-username" + } + SetSecretsEnvVariable -secretProperties $secretProperties + $errorMessage = runMissingSecretsTest + $errorMessage | Should -Be "Secret DeployToDev-AuthContext must contain either 'ppUserName' and 'ppPassword' properties or 'ppApplicationId', 'ppClientSecret' and 'ppTenantId' properties" + + } + + It 'Test action.yaml matches script' { + $permissions = [ordered]@{ + } + $outputs = [ordered]@{ + "ppEnvironmentUrl" = "Power Platform Environment URL" + "ppUserName" = "Power Platform Username" + "ppPassword" = "Power Platform Password" + "ppApplicationId" = "Power Platform Application Id" + "ppTenantId" = "Power Platform Tenant Id" + "ppClientSecret" = "Power Platform Client Secret" + "companyId" = "Business Central Company Id" + "environmentName" = "Business Central Environment Name" + } + YamlTest -scriptRoot $scriptRoot -actionName $actionName -actionScript $actionScript -permissions $permissions -outputs $outputs + } +} diff --git a/Tests/ReadSecrets.Action.Test.ps1 b/Tests/ReadSecrets.Action.Test.ps1 index 8eba15497..0fa6586db 100644 --- a/Tests/ReadSecrets.Action.Test.ps1 +++ b/Tests/ReadSecrets.Action.Test.ps1 @@ -1,5 +1,6 @@ Get-Module TestActionsHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') +$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 Describe "ReadSecrets Action Tests" { BeforeAll { diff --git a/Tests/ReadSettings.Action.Test.ps1 b/Tests/ReadSettings.Action.Test.ps1 index a803b9aab..ff883af15 100644 --- a/Tests/ReadSettings.Action.Test.ps1 +++ b/Tests/ReadSettings.Action.Test.ps1 @@ -1,5 +1,6 @@ Get-Module TestActionsHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') +$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 Describe "ReadSettings Action Tests" { BeforeAll { diff --git a/Tests/RunPipeline.Action.Test.ps1 b/Tests/RunPipeline.Action.Test.ps1 index 228232421..17f9dcfdc 100644 --- a/Tests/RunPipeline.Action.Test.ps1 +++ b/Tests/RunPipeline.Action.Test.ps1 @@ -1,5 +1,6 @@ Get-Module TestActionsHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') +$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 Describe "RunPipeline Action Tests" { BeforeAll { diff --git a/Tests/Sign.Test.ps1 b/Tests/Sign.Test.ps1 index e181fbafc..292a11d09 100644 --- a/Tests/Sign.Test.ps1 +++ b/Tests/Sign.Test.ps1 @@ -1,5 +1,6 @@ Get-Module TestActionsHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') +$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 Describe "Sign Action Tests" { BeforeAll { diff --git a/Tests/Troubleshooting.Test.ps1 b/Tests/Troubleshooting.Test.ps1 index 8b2b27b52..6d0c44a62 100644 --- a/Tests/Troubleshooting.Test.ps1 +++ b/Tests/Troubleshooting.Test.ps1 @@ -1,5 +1,6 @@ Get-Module TestActionsHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') +$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 Describe "Troubleshooting Action Tests" { BeforeAll { diff --git a/Tests/VerifyPRChanges.Action.Test.ps1 b/Tests/VerifyPRChanges.Action.Test.ps1 index 30f1c9575..d17abb5ed 100644 --- a/Tests/VerifyPRChanges.Action.Test.ps1 +++ b/Tests/VerifyPRChanges.Action.Test.ps1 @@ -1,5 +1,6 @@ Get-Module TestActionsHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') +$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 Describe 'VerifyPRChanges Action Tests' { diff --git a/Tests/WorkflowInitialize.Test.ps1 b/Tests/WorkflowInitialize.Test.ps1 index 8f93c146e..9bc279049 100644 --- a/Tests/WorkflowInitialize.Test.ps1 +++ b/Tests/WorkflowInitialize.Test.ps1 @@ -1,5 +1,6 @@ Get-Module TestActionsHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') +$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 Describe "WorkflowInitialize Action Tests" { BeforeAll { diff --git a/Tests/WorkflowPostProcess.Test.ps1 b/Tests/WorkflowPostProcess.Test.ps1 index 5717a882b..83892be34 100644 --- a/Tests/WorkflowPostProcess.Test.ps1 +++ b/Tests/WorkflowPostProcess.Test.ps1 @@ -1,5 +1,6 @@ Get-Module TestActionsHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') +$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 Describe "WorkflowPostProcess Action Tests" { BeforeAll { diff --git a/Tests/WorkflowSanitation/WorkflowFileContent.Test.ps1 b/Tests/WorkflowSanitation/WorkflowFileContent.Test.ps1 index 6e4e238f5..6fc53c4df 100644 --- a/Tests/WorkflowSanitation/WorkflowFileContent.Test.ps1 +++ b/Tests/WorkflowSanitation/WorkflowFileContent.Test.ps1 @@ -1,13 +1,21 @@ Get-Module TestActionsHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot '../TestActionsHelper.psm1') -Describe "All AL-GO workflows should have similar content" { +Describe "All AL-Go workflows should have similar content" { It 'All workflows existing in both templates should have similar content' { + . (Join-Path $PSScriptRoot '..\..\Actions\CheckForUpdates\yamlclass.ps1') + . (Join-Path $PSScriptRoot '..\..\Actions\CheckForUpdates\CheckForUpdates.HelperFunctions.ps1') $pteWorkflows = (Join-Path $PSScriptRoot "..\..\Templates\Per Tenant Extension\.github\workflows\" -Resolve) | GetWorkflowsInPath $appSourceWorkflows = ((Join-Path $PSScriptRoot "..\..\Templates\AppSource App\.github\workflows\" -Resolve) | GetWorkflowsInPath).Name $pteWorkflows | Where-Object { $appSourceWorkflows -contains $_.Name } | ForEach-Object { $pteWorkflowContent = (Get-Content -Path $_.FullName -Encoding UTF8 -Raw).Replace("`r", "").TrimEnd("`n") $appSourceWorkflowContent = (Get-Content -Path (Join-Path $PSScriptRoot "..\..\Templates\AppSource App\.github\workflows\$($_.Name)") -Encoding UTF8 -Raw).Replace("`r", "").TrimEnd("`n") + if ($_.Name -eq 'CICD.yaml') { + # CICD.yaml is a special case, as it contains a buildPP section that is not present in the AppSource App template + $yaml = [Yaml]::Load($_.FullName) + ModifyBuildWorkflows -yaml $yaml -depth 1 -includeBuildPP $false + $pteWorkflowContent = $yaml.content -join "`n" + } Write-Host "Comparing $($_.Name)" $pteWorkflowContent | Should -Be $appSourceWorkflowContent } diff --git a/Tests/WorkflowSanitation/WorkflowFileExtensions.Test.ps1 b/Tests/WorkflowSanitation/WorkflowFileExtensions.Test.ps1 index c41ad529d..073ccbf2b 100644 --- a/Tests/WorkflowSanitation/WorkflowFileExtensions.Test.ps1 +++ b/Tests/WorkflowSanitation/WorkflowFileExtensions.Test.ps1 @@ -1,7 +1,7 @@ Get-Module TestActionsHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot '../TestActionsHelper.psm1') -Describe "All AL-GO workflows should have the .yaml extension" { +Describe "All AL-Go workflows should have the .yaml extension" { It 'All PTE workflows should have the .yaml extension' { (Join-Path $PSScriptRoot "..\..\Templates\Per Tenant Extension\.github\workflows\" -Resolve) | GetWorkflowsInPath | ForEach-Object { $_.Extension | Should -Be '.yaml' diff --git a/Tests/WorkflowSanitation/WorkflowReferences.Test.ps1 b/Tests/WorkflowSanitation/WorkflowReferences.Test.ps1 index e727f733e..08dceff9c 100644 --- a/Tests/WorkflowSanitation/WorkflowReferences.Test.ps1 +++ b/Tests/WorkflowSanitation/WorkflowReferences.Test.ps1 @@ -1,7 +1,7 @@ Get-Module TestActionsHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot '../TestActionsHelper.psm1') -Describe "All AL-GO workflows should reference actions that come from the microsoft/AL-Go-Actions or actions/ (by GitHub)" { +Describe "All AL-Go workflows should reference actions that come from the microsoft/AL-Go-Actions or actions/ (by GitHub)" { It 'All PTE workflows are referencing actions that come from the microsoft/AL-Go-Actions or actions/ (by GitHub)' { (Join-Path $PSScriptRoot "..\..\Templates\Per Tenant Extension\.github\workflows\" -Resolve) | GetWorkflowsInPath | ForEach-Object { TestActionsReferences -YamlPath $_.FullName @@ -15,7 +15,7 @@ Describe "All AL-GO workflows should reference actions that come from the micros } } -Describe "All AL-GO workflows should reference reusable workflows from the same repository" { +Describe "All AL-Go workflows should reference reusable workflows from the same repository" { It 'All PTE workflows are referencing reusable workflows from the same repository ' { (Join-Path $PSScriptRoot "..\..\Templates\Per Tenant Extension\.github\workflows\" -Resolve) | GetWorkflowsInPath | ForEach-Object { TestWorkflowReferences -YamlPath $_.FullName diff --git a/Tests/_TestData-PowerPlatform/FlowOnlySolution/Workflows/Trigger-ABA81736-12D9-ED11-A7C7-000D3A991110.json b/Tests/_TestData-PowerPlatform/FlowOnlySolution/Workflows/Trigger-ABA81736-12D9-ED11-A7C7-000D3A991110.json new file mode 100644 index 000000000..9b03aadb3 --- /dev/null +++ b/Tests/_TestData-PowerPlatform/FlowOnlySolution/Workflows/Trigger-ABA81736-12D9-ED11-A7C7-000D3A991110.json @@ -0,0 +1,134 @@ +{ + "properties": { + "connectionReferences": { + "shared_dynamicssmbsaas_1": { + "runtimeSource": "invoker", + "connection": { + "connectionReferenceLogicalName": "cr301_BcConnection" + }, + "api": { + "name": "shared_dynamicssmbsaas" + } + } + }, + "definition": { + "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "$connections": { + "defaultValue": {}, + "type": "Object" + }, + "$authentication": { + "defaultValue": {}, + "type": "SecureObject" + } + }, + "triggers": { + "When_a_record_is_created_(V3)": { + "splitOn": "@triggerOutputs()?['body/value']", + "type": "OpenApiConnectionWebhook", + "inputs": { + "parameters": { + "bcenvironment": "PRODUCTION", + "company": "c381e6b6-b3d3-ee11-904f-6045bdac9aed", + "dataset": "v2.0", + "table": "dimensions" + }, + "host": { + "apiId": "/providers/Microsoft.PowerApps/apis/shared_dynamicssmbsaas", + "connectionName": "shared_dynamicssmbsaas", + "operationId": "CreateOnNewItemsSubscriptionV3" + }, + "authentication": "@parameters('$authentication')" + } + } + }, + "actions": { + "Create_record_(V3)": { + "runAfter": {}, + "metadata": { + "operationMetadataId": "a8fbd4cc-94d0-4e04-aa0a-8a3254ac8480" + }, + "type": "OpenApiConnection", + "inputs": { + "host": { + "connectionName": "shared_dynamicssmbsaas_1", + "operationId": "PostItemV3", + "apiId": "/providers/Microsoft.PowerApps/apis/shared_dynamicssmbsaas" + }, + "parameters": { + "bcenvironment": "TestEnvironmentName", + "company": "TestCompanyId", + "dataset": "v2.0", + "table": "documentAttachments", + "item/fileName": "Rendered3D.png", + "item/parentType": "2", + "item/parentId": "@triggerBody()['text']" + }, + "authentication": "@parameters('$authentication')" + } + }, + "Respond_to_a_PowerApp_or_flow": { + "runAfter": { + "Update_an_image,_file_or_document_(V3)": [ + "Succeeded" + ] + }, + "metadata": { + "operationMetadataId": "db725cb7-194e-4ca0-b5d5-4fc3559b9bf8" + }, + "type": "Response", + "kind": "PowerApp", + "inputs": { + "statusCode": 200, + "body": { + "status": "success" + }, + "schema": { + "type": "object", + "properties": { + "status": { + "title": "status", + "x-ms-dynamically-added": true, + "type": "string" + } + } + } + } + }, + "Update_an_image,_file_or_document_(V3)": { + "runAfter": { + "Create_record_(V3)": [ + "Succeeded" + ] + }, + "metadata": { + "operationMetadataId": "91343282-6ec2-40dc-81fd-80fb1942b37e" + }, + "type": "OpenApiConnection", + "inputs": { + "host": { + "connectionName": "shared_dynamicssmbsaas_1", + "operationId": "PatchBlobFromNavigationV3", + "apiId": "/providers/Microsoft.PowerApps/apis/shared_dynamicssmbsaas" + }, + "parameters": { + "bcenvironment": "sandbox", + "company": "5736fe89-41bc-ee11-907d-6045bdc8c244", + "dataset": "v2.0", + "blobnavigationpath": "salesOrders/documentAttachments/attachmentContent", + "pathParameters/salesOrders": "@outputs('Create_record_(V3)')?['body/parentId']", + "pathParameters/salesOrders~1documentAttachments": "@outputs('Create_record_(V3)')?['body/id']", + "pathParameters/$content": "@triggerBody()?['file']?['contentBytes']" + }, + "authentication": "@parameters('$authentication')" + } + } + }, + "outputs": {} + }, + "templateName": "" + }, + "schemaVersion": "1.0.0.0" +} \ No newline at end of file diff --git a/Tests/_TestData-PowerPlatform/FlowOnlySolution/other/Solution.xml b/Tests/_TestData-PowerPlatform/FlowOnlySolution/other/Solution.xml new file mode 100644 index 000000000..20ff5d530 --- /dev/null +++ b/Tests/_TestData-PowerPlatform/FlowOnlySolution/other/Solution.xml @@ -0,0 +1,137 @@ + + + + TestSOlutions + + + + + 1.0.0.0 + 0 + + Cr541ef + + + + + + + + + cr301 + 73543 + +
+ 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + +
+
\ No newline at end of file diff --git a/Tests/_TestData-PowerPlatform/PowerAppOnlySolution/CanvasApps/src/TestApp/Connections/Connections.json b/Tests/_TestData-PowerPlatform/PowerAppOnlySolution/CanvasApps/src/TestApp/Connections/Connections.json new file mode 100644 index 000000000..fc526cc00 --- /dev/null +++ b/Tests/_TestData-PowerPlatform/PowerAppOnlySolution/CanvasApps/src/TestApp/Connections/Connections.json @@ -0,0 +1,87 @@ +{ + "0ff5df45-9575-4f2b-8cac-8ae027644295": { + "appActions": [ + "providers/PowerPlatform.Governance/Operations/Read", + "providers/PowerPlatform.Governance/Operations/Write" + ], + "connectionRef": { + "apiTier": "Premium", + "displayName": "Dynamics 365 Business Central", + "iconUri": "https://connectoricons-prod.azureedge.net/releases/v1.0.1678/1.0.1678.3636/dynamicssmbsaas/icon.png", + "id": "/providers/microsoft.powerapps/apis/shared_dynamicssmbsaas", + "parameterHints": {}, + "parameterHintsV2": {} + }, + "datasets": { + "TestEnvironmentName,TestCompanyId": { + "dataSources": { + "coffeeItems (microsoft/powerApps/beta)": { + "tableName": "coffeeItems,microsoft%252FpowerApps%252Fbeta" + }, + "customers (v2.0)": { + "tableName": "customers,v2.0" + }, + "salesOrderLines (v2.0)": { + "tableName": "salesOrderLines,v2.0" + }, + "salesOrders (v2.0)": { + "tableName": "salesOrders,v2.0" + } + } + } + }, + "dataSources": [ + "salesOrderLines (v2.0)", + "salesOrders (v2.0)", + "customers (v2.0)", + "coffeeItems (microsoft/powerApps/beta)" + ], + "dependencies": {}, + "dependents": [ + "ea874b1a-7601-47f4-8e74-74b9da7b451c" + ], + "id": "0ff5df45-9575-4f2b-8cac-8ae027644295" + }, + "ea874b1a-7601-47f4-8e74-74b9da7b451c": { + "appActions": [ + "Run" + ], + "connectionRef": { + "apiTier": "Standard", + "displayName": "Logic flows", + "iconUri": "https://connectoricons-prod.azureedge.net/releases/v1.0.1664/1.0.1664.3477/logicflows/icon.png", + "id": "/providers/microsoft.powerapps/apis/shared_logicflows", + "parameterHints": { + "0ff5df45-9575-4f2b-8cac-8ae027644295": { + "value": "shared_dynamicssmbsaas_1" + }, + "workflowEntityId": { + "value": "aba81736-12d9-ed11-a7c7-000d3a991110" + }, + "workflowName": { + "value": "e6d4b0e2-4e50-44bc-a567-be8d48eac5ed" + } + }, + "parameterHintsV2": { + "shared_dynamicssmbsaas_1": { + "value": "0ff5df45-9575-4f2b-8cac-8ae027644295" + }, + "workflowEntityId": { + "value": "aba81736-12d9-ed11-a7c7-000d3a991110" + }, + "workflowName": { + "value": "e6d4b0e2-4e50-44bc-a567-be8d48eac5ed" + } + } + }, + "datasets": {}, + "dataSources": [ + "UploadImageToSalesOrder" + ], + "dependencies": { + "shared_dynamicssmbsaas_1": "0ff5df45-9575-4f2b-8cac-8ae027644295" + }, + "dependents": [], + "id": "ea874b1a-7601-47f4-8e74-74b9da7b451c" + } +} \ No newline at end of file diff --git a/Tests/_TestData-PowerPlatform/PowerAppOnlySolution/CanvasApps/src/TestApp/DataSources/customers %28v2.0%29.json b/Tests/_TestData-PowerPlatform/PowerAppOnlySolution/CanvasApps/src/TestApp/DataSources/customers %28v2.0%29.json new file mode 100644 index 000000000..2753689d0 --- /dev/null +++ b/Tests/_TestData-PowerPlatform/PowerAppOnlySolution/CanvasApps/src/TestApp/DataSources/customers %28v2.0%29.json @@ -0,0 +1,45 @@ +[ + { + "ApiId": "/providers/microsoft.powerapps/apis/shared_dynamicssmbsaas", + "CdpRevision": { + "BaseUrl": "/v3", + "LastChangedTimeString": "2023-12-20T19:25:08.8200000Z", + "RevisionNumber": 3 + }, + "ConnectedDataSourceInfoNameMapping": { + "addressLine1": "Address Line 1", + "addressLine2": "Address Line 2", + "balanceDue": "Balance Due", + "blocked": "Blocked", + "city": "City", + "country": "Country/Region Code", + "creditLimit": "Credit Limit", + "currencyCode": "Currency Code", + "currencyId": "Currency Id", + "displayName": "Display Name", + "email": "Email", + "id": "Id", + "lastModifiedDateTime": "Last Modified Date", + "number": "No.", + "paymentMethodId": "Payment Method Id", + "paymentTermsId": "Payment Terms Id", + "phoneNumber": "Phone No.", + "postalCode": "ZIP Code", + "salespersonCode": "Salesperson Code", + "shipmentMethodId": "Shipment Method Id", + "state": "State", + "taxAreaDisplayName": "Tax Area Display Name", + "taxAreaId": "Tax Area Id", + "taxLiable": "Tax Liable", + "taxRegistrationNumber": "Tax Registration No.", + "type": "Type", + "website": "Website" + }, + "DatasetName": "TestEnvironmentName,TestCompanyId", + "EncodeDataset": true, + "IsSampleData": false, + "IsWritable": true, + "Name": "customers (v2.0)", + "Type": "ConnectedDataSourceInfo" + } +] \ No newline at end of file diff --git a/Tests/_TestData-PowerPlatform/PowerAppOnlySolution/CanvasApps/src/TestApp/DataSources/salesOrderLines %28v2.0%29.json b/Tests/_TestData-PowerPlatform/PowerAppOnlySolution/CanvasApps/src/TestApp/DataSources/salesOrderLines %28v2.0%29.json new file mode 100644 index 000000000..8b69bc550 --- /dev/null +++ b/Tests/_TestData-PowerPlatform/PowerAppOnlySolution/CanvasApps/src/TestApp/DataSources/salesOrderLines %28v2.0%29.json @@ -0,0 +1,50 @@ +[ + { + "ApiId": "/providers/microsoft.powerapps/apis/shared_dynamicssmbsaas", + "CdpRevision": { + "BaseUrl": "/v3", + "LastChangedTimeString": "2023-12-20T19:25:08.8200000Z", + "RevisionNumber": 3 + }, + "ConnectedDataSourceInfoNameMapping": { + "accountId": "Account Id", + "amountExcludingTax": "Amount Excluding Tax", + "amountIncludingTax": "Amount Including Tax", + "description": "Description", + "description2": "Description 2", + "discountAmount": "Discount Amount", + "discountAppliedBeforeTax": "Discount Applied Before Tax", + "discountPercent": "Discount Percent", + "documentId": "Document Id", + "id": "Id", + "invoiceDiscountAllocation": "Invoice Discount Allocation", + "invoicedQuantity": "Invoiced Quantity", + "invoiceQuantity": "Invoice Quantity", + "itemId": "Item Id", + "itemVariantId": "Item Variant Id", + "lineObjectNumber": "Line Object No.", + "lineType": "Line Type", + "locationId": "Location Id", + "netAmount": "Net Amount", + "netAmountIncludingTax": "Net Amount Including Tax", + "netTaxAmount": "Net Tax Amount", + "quantity": "Quantity", + "sequence": "Sequence", + "shipmentDate": "Shipment Date", + "shippedQuantity": "Shipped Quantity", + "shipQuantity": "Ship Quantity", + "taxCode": "Tax Code", + "taxPercent": "Tax Percent", + "totalTaxAmount": "Total Tax Amount", + "unitOfMeasureCode": "Unit Of Measure Code", + "unitOfMeasureId": "Unit Of Measure Id", + "unitPrice": "Unit Price" + }, + "DatasetName": "TestEnvironmentName,TestCompanyId", + "EncodeDataset": true, + "IsSampleData": false, + "IsWritable": true, + "Name": "salesOrderLines (v2.0)", + "Type": "ConnectedDataSourceInfo" + } +] \ No newline at end of file diff --git a/Tests/_TestData-PowerPlatform/PowerAppOnlySolution/CanvasApps/src/TestApp/DataSources/salesOrders %28v2.0%29.json b/Tests/_TestData-PowerPlatform/PowerAppOnlySolution/CanvasApps/src/TestApp/DataSources/salesOrders %28v2.0%29.json new file mode 100644 index 000000000..9a072d488 --- /dev/null +++ b/Tests/_TestData-PowerPlatform/PowerAppOnlySolution/CanvasApps/src/TestApp/DataSources/salesOrders %28v2.0%29.json @@ -0,0 +1,69 @@ +[ + { + "ApiId": "/providers/microsoft.powerapps/apis/shared_dynamicssmbsaas", + "CdpRevision": { + "BaseUrl": "/v3", + "LastChangedTimeString": "2023-12-20T19:25:08.8200000Z", + "RevisionNumber": 3 + }, + "ConnectedDataSourceInfoNameMapping": { + "billToAddressLine1": "Bill-to Address Line 1", + "billToAddressLine2": "Bill-to Address Line 2", + "billToCity": "Bill-to City", + "billToCountry": "Bill-to Country/Region Code", + "billToCustomerId": "Bill-to Customer Id", + "billToCustomerNumber": "Bill-to Customer No.", + "billToName": "Bill-to Name", + "billToPostCode": "Bill-to ZIP Code", + "billToState": "BillTo State", + "currencyCode": "Currency Code", + "currencyId": "Currency Id", + "customerId": "Customer Id", + "customerName": "Customer Name", + "customerNumber": "Customer No.", + "discountAmount": "Discount Amount", + "discountAppliedBeforeTax": "Discount Applied Before Tax", + "email": "Email", + "externalDocumentNumber": "External Document No.", + "fullyShipped": "Fully Shipped", + "id": "Id", + "lastModifiedDateTime": "Last Modified Date", + "number": "No.", + "orderDate": "Order Date", + "partialShipping": "Partial Shipping", + "paymentTermsId": "Payment Terms Id", + "phoneNumber": "Phone No.", + "postingDate": "Posting Date", + "pricesIncludeTax": "Prices Include Tax", + "requestedDeliveryDate": "Requested Delivery Date", + "salesperson": "Salesperson", + "sellToAddressLine1": "Sell-to Address Line 1", + "sellToAddressLine2": "Sell-to Address Line 2", + "sellToCity": "Sell-to City", + "sellToCountry": "Sell-to Country/Region Code", + "sellToPostCode": "Sell-to ZIP Code", + "sellToState": "Sell-to State", + "shipmentMethodId": "Shipment Method Id", + "shipToAddressLine1": "Ship-to Address Line 1", + "shipToAddressLine2": "Ship-to Address Line 2", + "shipToCity": "Ship-to City", + "shipToContact": "Ship-to Contact", + "shipToCountry": "Ship-to Country/Region Code", + "shipToName": "Ship-to Name", + "shipToPostCode": "Ship-to ZIP Code", + "shipToState": "Ship-to State", + "shortcutDimension1Code": "Shortcut Dimension 1 Code", + "shortcutDimension2Code": "Shortcut Dimension 2 Code", + "status": "Status", + "totalAmountExcludingTax": "Total Amount Excluding Tax", + "totalAmountIncludingTax": "Total Amount Including Tax", + "totalTaxAmount": "Total Tax Amount" + }, + "DatasetName": "TestEnvironmentName,TestCompanyId", + "EncodeDataset": true, + "IsSampleData": false, + "IsWritable": true, + "Name": "salesOrders (v2.0)", + "Type": "ConnectedDataSourceInfo" + } +] \ No newline at end of file diff --git a/Tests/_TestData-PowerPlatform/PowerAppOnlySolution/other/Solution.xml b/Tests/_TestData-PowerPlatform/PowerAppOnlySolution/other/Solution.xml new file mode 100644 index 000000000..f8abbd333 --- /dev/null +++ b/Tests/_TestData-PowerPlatform/PowerAppOnlySolution/other/Solution.xml @@ -0,0 +1,137 @@ + + + + TestSOlutions + + + + + 1.0.0.0 + 0 + + Cr541ef + + + + + + + + + cr301 + 73543 + +
+ 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + +
+
\ No newline at end of file diff --git a/Tests/_TestData-PowerPlatform/StandardSolution/CanvasApps/src/TestApp/Connections/Connections.json b/Tests/_TestData-PowerPlatform/StandardSolution/CanvasApps/src/TestApp/Connections/Connections.json new file mode 100644 index 000000000..fc526cc00 --- /dev/null +++ b/Tests/_TestData-PowerPlatform/StandardSolution/CanvasApps/src/TestApp/Connections/Connections.json @@ -0,0 +1,87 @@ +{ + "0ff5df45-9575-4f2b-8cac-8ae027644295": { + "appActions": [ + "providers/PowerPlatform.Governance/Operations/Read", + "providers/PowerPlatform.Governance/Operations/Write" + ], + "connectionRef": { + "apiTier": "Premium", + "displayName": "Dynamics 365 Business Central", + "iconUri": "https://connectoricons-prod.azureedge.net/releases/v1.0.1678/1.0.1678.3636/dynamicssmbsaas/icon.png", + "id": "/providers/microsoft.powerapps/apis/shared_dynamicssmbsaas", + "parameterHints": {}, + "parameterHintsV2": {} + }, + "datasets": { + "TestEnvironmentName,TestCompanyId": { + "dataSources": { + "coffeeItems (microsoft/powerApps/beta)": { + "tableName": "coffeeItems,microsoft%252FpowerApps%252Fbeta" + }, + "customers (v2.0)": { + "tableName": "customers,v2.0" + }, + "salesOrderLines (v2.0)": { + "tableName": "salesOrderLines,v2.0" + }, + "salesOrders (v2.0)": { + "tableName": "salesOrders,v2.0" + } + } + } + }, + "dataSources": [ + "salesOrderLines (v2.0)", + "salesOrders (v2.0)", + "customers (v2.0)", + "coffeeItems (microsoft/powerApps/beta)" + ], + "dependencies": {}, + "dependents": [ + "ea874b1a-7601-47f4-8e74-74b9da7b451c" + ], + "id": "0ff5df45-9575-4f2b-8cac-8ae027644295" + }, + "ea874b1a-7601-47f4-8e74-74b9da7b451c": { + "appActions": [ + "Run" + ], + "connectionRef": { + "apiTier": "Standard", + "displayName": "Logic flows", + "iconUri": "https://connectoricons-prod.azureedge.net/releases/v1.0.1664/1.0.1664.3477/logicflows/icon.png", + "id": "/providers/microsoft.powerapps/apis/shared_logicflows", + "parameterHints": { + "0ff5df45-9575-4f2b-8cac-8ae027644295": { + "value": "shared_dynamicssmbsaas_1" + }, + "workflowEntityId": { + "value": "aba81736-12d9-ed11-a7c7-000d3a991110" + }, + "workflowName": { + "value": "e6d4b0e2-4e50-44bc-a567-be8d48eac5ed" + } + }, + "parameterHintsV2": { + "shared_dynamicssmbsaas_1": { + "value": "0ff5df45-9575-4f2b-8cac-8ae027644295" + }, + "workflowEntityId": { + "value": "aba81736-12d9-ed11-a7c7-000d3a991110" + }, + "workflowName": { + "value": "e6d4b0e2-4e50-44bc-a567-be8d48eac5ed" + } + } + }, + "datasets": {}, + "dataSources": [ + "UploadImageToSalesOrder" + ], + "dependencies": { + "shared_dynamicssmbsaas_1": "0ff5df45-9575-4f2b-8cac-8ae027644295" + }, + "dependents": [], + "id": "ea874b1a-7601-47f4-8e74-74b9da7b451c" + } +} \ No newline at end of file diff --git a/Tests/_TestData-PowerPlatform/StandardSolution/CanvasApps/src/TestApp/DataSources/customers %28v2.0%29.json b/Tests/_TestData-PowerPlatform/StandardSolution/CanvasApps/src/TestApp/DataSources/customers %28v2.0%29.json new file mode 100644 index 000000000..2753689d0 --- /dev/null +++ b/Tests/_TestData-PowerPlatform/StandardSolution/CanvasApps/src/TestApp/DataSources/customers %28v2.0%29.json @@ -0,0 +1,45 @@ +[ + { + "ApiId": "/providers/microsoft.powerapps/apis/shared_dynamicssmbsaas", + "CdpRevision": { + "BaseUrl": "/v3", + "LastChangedTimeString": "2023-12-20T19:25:08.8200000Z", + "RevisionNumber": 3 + }, + "ConnectedDataSourceInfoNameMapping": { + "addressLine1": "Address Line 1", + "addressLine2": "Address Line 2", + "balanceDue": "Balance Due", + "blocked": "Blocked", + "city": "City", + "country": "Country/Region Code", + "creditLimit": "Credit Limit", + "currencyCode": "Currency Code", + "currencyId": "Currency Id", + "displayName": "Display Name", + "email": "Email", + "id": "Id", + "lastModifiedDateTime": "Last Modified Date", + "number": "No.", + "paymentMethodId": "Payment Method Id", + "paymentTermsId": "Payment Terms Id", + "phoneNumber": "Phone No.", + "postalCode": "ZIP Code", + "salespersonCode": "Salesperson Code", + "shipmentMethodId": "Shipment Method Id", + "state": "State", + "taxAreaDisplayName": "Tax Area Display Name", + "taxAreaId": "Tax Area Id", + "taxLiable": "Tax Liable", + "taxRegistrationNumber": "Tax Registration No.", + "type": "Type", + "website": "Website" + }, + "DatasetName": "TestEnvironmentName,TestCompanyId", + "EncodeDataset": true, + "IsSampleData": false, + "IsWritable": true, + "Name": "customers (v2.0)", + "Type": "ConnectedDataSourceInfo" + } +] \ No newline at end of file diff --git a/Tests/_TestData-PowerPlatform/StandardSolution/CanvasApps/src/TestApp/DataSources/salesOrderLines %28v2.0%29.json b/Tests/_TestData-PowerPlatform/StandardSolution/CanvasApps/src/TestApp/DataSources/salesOrderLines %28v2.0%29.json new file mode 100644 index 000000000..8b69bc550 --- /dev/null +++ b/Tests/_TestData-PowerPlatform/StandardSolution/CanvasApps/src/TestApp/DataSources/salesOrderLines %28v2.0%29.json @@ -0,0 +1,50 @@ +[ + { + "ApiId": "/providers/microsoft.powerapps/apis/shared_dynamicssmbsaas", + "CdpRevision": { + "BaseUrl": "/v3", + "LastChangedTimeString": "2023-12-20T19:25:08.8200000Z", + "RevisionNumber": 3 + }, + "ConnectedDataSourceInfoNameMapping": { + "accountId": "Account Id", + "amountExcludingTax": "Amount Excluding Tax", + "amountIncludingTax": "Amount Including Tax", + "description": "Description", + "description2": "Description 2", + "discountAmount": "Discount Amount", + "discountAppliedBeforeTax": "Discount Applied Before Tax", + "discountPercent": "Discount Percent", + "documentId": "Document Id", + "id": "Id", + "invoiceDiscountAllocation": "Invoice Discount Allocation", + "invoicedQuantity": "Invoiced Quantity", + "invoiceQuantity": "Invoice Quantity", + "itemId": "Item Id", + "itemVariantId": "Item Variant Id", + "lineObjectNumber": "Line Object No.", + "lineType": "Line Type", + "locationId": "Location Id", + "netAmount": "Net Amount", + "netAmountIncludingTax": "Net Amount Including Tax", + "netTaxAmount": "Net Tax Amount", + "quantity": "Quantity", + "sequence": "Sequence", + "shipmentDate": "Shipment Date", + "shippedQuantity": "Shipped Quantity", + "shipQuantity": "Ship Quantity", + "taxCode": "Tax Code", + "taxPercent": "Tax Percent", + "totalTaxAmount": "Total Tax Amount", + "unitOfMeasureCode": "Unit Of Measure Code", + "unitOfMeasureId": "Unit Of Measure Id", + "unitPrice": "Unit Price" + }, + "DatasetName": "TestEnvironmentName,TestCompanyId", + "EncodeDataset": true, + "IsSampleData": false, + "IsWritable": true, + "Name": "salesOrderLines (v2.0)", + "Type": "ConnectedDataSourceInfo" + } +] \ No newline at end of file diff --git a/Tests/_TestData-PowerPlatform/StandardSolution/CanvasApps/src/TestApp/DataSources/salesOrders %28v2.0%29.json b/Tests/_TestData-PowerPlatform/StandardSolution/CanvasApps/src/TestApp/DataSources/salesOrders %28v2.0%29.json new file mode 100644 index 000000000..9a072d488 --- /dev/null +++ b/Tests/_TestData-PowerPlatform/StandardSolution/CanvasApps/src/TestApp/DataSources/salesOrders %28v2.0%29.json @@ -0,0 +1,69 @@ +[ + { + "ApiId": "/providers/microsoft.powerapps/apis/shared_dynamicssmbsaas", + "CdpRevision": { + "BaseUrl": "/v3", + "LastChangedTimeString": "2023-12-20T19:25:08.8200000Z", + "RevisionNumber": 3 + }, + "ConnectedDataSourceInfoNameMapping": { + "billToAddressLine1": "Bill-to Address Line 1", + "billToAddressLine2": "Bill-to Address Line 2", + "billToCity": "Bill-to City", + "billToCountry": "Bill-to Country/Region Code", + "billToCustomerId": "Bill-to Customer Id", + "billToCustomerNumber": "Bill-to Customer No.", + "billToName": "Bill-to Name", + "billToPostCode": "Bill-to ZIP Code", + "billToState": "BillTo State", + "currencyCode": "Currency Code", + "currencyId": "Currency Id", + "customerId": "Customer Id", + "customerName": "Customer Name", + "customerNumber": "Customer No.", + "discountAmount": "Discount Amount", + "discountAppliedBeforeTax": "Discount Applied Before Tax", + "email": "Email", + "externalDocumentNumber": "External Document No.", + "fullyShipped": "Fully Shipped", + "id": "Id", + "lastModifiedDateTime": "Last Modified Date", + "number": "No.", + "orderDate": "Order Date", + "partialShipping": "Partial Shipping", + "paymentTermsId": "Payment Terms Id", + "phoneNumber": "Phone No.", + "postingDate": "Posting Date", + "pricesIncludeTax": "Prices Include Tax", + "requestedDeliveryDate": "Requested Delivery Date", + "salesperson": "Salesperson", + "sellToAddressLine1": "Sell-to Address Line 1", + "sellToAddressLine2": "Sell-to Address Line 2", + "sellToCity": "Sell-to City", + "sellToCountry": "Sell-to Country/Region Code", + "sellToPostCode": "Sell-to ZIP Code", + "sellToState": "Sell-to State", + "shipmentMethodId": "Shipment Method Id", + "shipToAddressLine1": "Ship-to Address Line 1", + "shipToAddressLine2": "Ship-to Address Line 2", + "shipToCity": "Ship-to City", + "shipToContact": "Ship-to Contact", + "shipToCountry": "Ship-to Country/Region Code", + "shipToName": "Ship-to Name", + "shipToPostCode": "Ship-to ZIP Code", + "shipToState": "Ship-to State", + "shortcutDimension1Code": "Shortcut Dimension 1 Code", + "shortcutDimension2Code": "Shortcut Dimension 2 Code", + "status": "Status", + "totalAmountExcludingTax": "Total Amount Excluding Tax", + "totalAmountIncludingTax": "Total Amount Including Tax", + "totalTaxAmount": "Total Tax Amount" + }, + "DatasetName": "TestEnvironmentName,TestCompanyId", + "EncodeDataset": true, + "IsSampleData": false, + "IsWritable": true, + "Name": "salesOrders (v2.0)", + "Type": "ConnectedDataSourceInfo" + } +] \ No newline at end of file diff --git a/Tests/_TestData-PowerPlatform/StandardSolution/Workflows/TestWorkflow-ABA81736-12D9-ED11-A7C7-000D3A991110.json b/Tests/_TestData-PowerPlatform/StandardSolution/Workflows/TestWorkflow-ABA81736-12D9-ED11-A7C7-000D3A991110.json new file mode 100644 index 000000000..a7c687000 --- /dev/null +++ b/Tests/_TestData-PowerPlatform/StandardSolution/Workflows/TestWorkflow-ABA81736-12D9-ED11-A7C7-000D3A991110.json @@ -0,0 +1,134 @@ +{ + "properties": { + "connectionReferences": { + "shared_dynamicssmbsaas_1": { + "runtimeSource": "invoker", + "connection": { + "connectionReferenceLogicalName": "cr301_BcConnection" + }, + "api": { + "name": "shared_dynamicssmbsaas" + } + } + }, + "definition": { + "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "$connections": { + "defaultValue": {}, + "type": "Object" + }, + "$authentication": { + "defaultValue": {}, + "type": "SecureObject" + } + }, + "triggers": { + "When_a_record_is_created_(V3)": { + "splitOn": "@triggerOutputs()?['body/value']", + "type": "OpenApiConnectionWebhook", + "inputs": { + "parameters": { + "bcenvironment": "TestEnvironmentName", + "company": "TestCompanyId", + "dataset": "v2.0", + "table": "dimensions" + }, + "host": { + "apiId": "/providers/Microsoft.PowerApps/apis/shared_dynamicssmbsaas", + "connectionName": "shared_dynamicssmbsaas", + "operationId": "CreateOnNewItemsSubscriptionV3" + }, + "authentication": "@parameters('$authentication')" + } + } + }, + "actions": { + "Create_record_(V3)": { + "runAfter": {}, + "metadata": { + "operationMetadataId": "a8fbd4cc-94d0-4e04-aa0a-8a3254ac8480" + }, + "type": "OpenApiConnection", + "inputs": { + "host": { + "connectionName": "shared_dynamicssmbsaas_1", + "operationId": "PostItemV3", + "apiId": "/providers/Microsoft.PowerApps/apis/shared_dynamicssmbsaas" + }, + "parameters": { + "bcenvironment": "TestEnvironmentName", + "company": "TestCompanyId", + "dataset": "v2.0", + "table": "documentAttachments", + "item/fileName": "Rendered3D.png", + "item/parentType": "2", + "item/parentId": "@triggerBody()['text']" + }, + "authentication": "@parameters('$authentication')" + } + }, + "Respond_to_a_PowerApp_or_flow": { + "runAfter": { + "Update_an_image,_file_or_document_(V3)": [ + "Succeeded" + ] + }, + "metadata": { + "operationMetadataId": "db725cb7-194e-4ca0-b5d5-4fc3559b9bf8" + }, + "type": "Response", + "kind": "PowerApp", + "inputs": { + "statusCode": 200, + "body": { + "status": "success" + }, + "schema": { + "type": "object", + "properties": { + "status": { + "title": "status", + "x-ms-dynamically-added": true, + "type": "string" + } + } + } + } + }, + "Update_an_image,_file_or_document_(V3)": { + "runAfter": { + "Create_record_(V3)": [ + "Succeeded" + ] + }, + "metadata": { + "operationMetadataId": "91343282-6ec2-40dc-81fd-80fb1942b37e" + }, + "type": "OpenApiConnection", + "inputs": { + "host": { + "connectionName": "shared_dynamicssmbsaas_1", + "operationId": "PatchBlobFromNavigationV3", + "apiId": "/providers/Microsoft.PowerApps/apis/shared_dynamicssmbsaas" + }, + "parameters": { + "bcenvironment": "sandbox", + "company": "5736fe89-41bc-ee11-907d-6045bdc8c244", + "dataset": "v2.0", + "blobnavigationpath": "salesOrders/documentAttachments/attachmentContent", + "pathParameters/salesOrders": "@outputs('Create_record_(V3)')?['body/parentId']", + "pathParameters/salesOrders~1documentAttachments": "@outputs('Create_record_(V3)')?['body/id']", + "pathParameters/$content": "@triggerBody()?['file']?['contentBytes']" + }, + "authentication": "@parameters('$authentication')" + } + } + }, + "outputs": {} + }, + "templateName": "" + }, + "schemaVersion": "1.0.0.0" +} \ No newline at end of file diff --git a/Tests/_TestData-PowerPlatform/StandardSolution/other/Solution.xml b/Tests/_TestData-PowerPlatform/StandardSolution/other/Solution.xml new file mode 100644 index 000000000..217594907 --- /dev/null +++ b/Tests/_TestData-PowerPlatform/StandardSolution/other/Solution.xml @@ -0,0 +1,138 @@ + + + + TestSOlutions + + + + + 1.0.0.0 + 0 + + Cr541ef + + + + + + + + + cr301 + 73543 + +
+ 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + + +
+
\ No newline at end of file diff --git a/Workshop/AddAnApp.md b/Workshop/AddAnApp.md index b470a03cc..e9c13b2ca 100644 --- a/Workshop/AddAnApp.md +++ b/Workshop/AddAnApp.md @@ -3,7 +3,7 @@ There are several ways you can add an app to your repository: - You can use the **Create a new app** workflow in AL-Go for GitHub to create a new app and start coding. - You can use the **Add existing app or test app** workflow to upload an .app file (or multiple) and have AL-Go for GitHub extract the source. - You can **upload the source files** directly into GitHub -- You can **clone** the repository and add your source files from **VS Code** (possibly using the AL:Go) +- You can **clone** the repository and add your source files from **VS Code** (possibly using the AL:Go in VS Code) All in all - it is just a matter of adding the source files of your app to the repository. diff --git a/e2eTests/Test-AL-Go-Upgrade.ps1 b/e2eTests/Test-AL-Go-Upgrade.ps1 index 825f7490f..dcdb59849 100644 --- a/e2eTests/Test-AL-Go-Upgrade.ps1 +++ b/e2eTests/Test-AL-Go-Upgrade.ps1 @@ -103,7 +103,7 @@ $runs++ # Update AL-Go System Files SetRepositorySecret -repository $repository -name 'GHTOKENWORKFLOW' -value $token -RunUpdateAlGoSystemFiles -templateUrl $template -wait -branch $branch | Out-Null +RunUpdateAlGoSystemFiles -templateUrl $template -wait -repository $repository -branch $branch | Out-Null # Expected Run: Update AL-Go System Files triggered on workflow_dispatch $runs++ diff --git a/e2eTests/Test-AL-Go.ps1 b/e2eTests/Test-AL-Go.ps1 index 57fbda15b..13445574d 100644 --- a/e2eTests/Test-AL-Go.ps1 +++ b/e2eTests/Test-AL-Go.ps1 @@ -231,7 +231,7 @@ Test-ArtifactsFromRun -runid $run.id -expectedArtifacts @{"Apps"=3;"TestApps"=2} # Update AL-Go System Files $repoSettings = Get-Content ".github\AL-Go-Settings.json" -Encoding UTF8 | ConvertFrom-Json SetRepositorySecret -repository $repository -name 'GHTOKENWORKFLOW' -value $token -RunUpdateAlGoSystemFiles -templateUrl $repoSettings.templateUrl -wait -branch $branch | Out-Null +RunUpdateAlGoSystemFiles -templateUrl $repoSettings.templateUrl -wait -repository $repository -branch $branch | Out-Null $runs++ MergePRandPull -branch $branch -wait | Out-Null $runs += 2 diff --git a/e2eTests/e2eTestHelper.psm1 b/e2eTests/e2eTestHelper.psm1 index 405e710e4..b99a77a15 100644 --- a/e2eTests/e2eTestHelper.psm1 +++ b/e2eTests/e2eTestHelper.psm1 @@ -101,6 +101,26 @@ function Add-PropertiesToJsonFile { } } +function Remove-PropertiesFromJsonFile { + Param( + [string] $path, + [string[]] $properties + ) + + Write-Host -ForegroundColor Yellow "`nRemove Properties from $([System.IO.Path]::GetFileName($path))" + Write-Host "Properties" + $properties | Out-Host + + $json = Get-Content $path -Encoding UTF8 | ConvertFrom-Json | ConvertTo-HashTable -recurse + $keys = @($json.Keys) + $keys | ForEach-Object { + $key = $_ + if ($properties | Where-Object { $key -like $_ }) { + $json.Remove($key) + } + } + $json | Set-JsonContentLF -path $path +} function DisplayTokenAndRepository { Write-Host "Token: $token" @@ -358,7 +378,7 @@ function CreateAlGoRepository { $templateRepo = $template.Split('@')[0] $tempPath = [System.IO.Path]::GetTempPath() - $path = Join-Path $tempPath ([GUID]::NewGuid().ToString()) + $path = Join-Path $tempPath ( [System.IO.Path]::GetFileNameWithoutExtension([System.IO.Path]::GetTempFileName())) New-Item $path -ItemType Directory | Out-Null Set-Location $path if ($waitMinutes) { @@ -554,13 +574,22 @@ function RemoveRepository { $repository = $defaultRepository } if ($repository) { - Write-Host -ForegroundColor Yellow "`nRemoving repository $repository" try { + $user = invoke-gh api user -silent -returnValue | ConvertFrom-Json + Write-Host -ForegroundColor Yellow "`nRemoving repository $repository (user $($user.login))" $owner = $repository.Split("/")[0] - @((invoke-gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /orgs/$owner/packages?package_type=nuget -silent -returnvalue -ErrorAction SilentlyContinue | ConvertFrom-Json)) | Where-Object { $_.PSObject.Properties.Name -eq 'repository' } | Where-Object { $_.repository.full_name -eq $repository } | ForEach-Object { + if ($owner -eq $user.login) { + # Package belongs to a user + $ownerStr = "users/$owner" + } + else { + # Package belongs to an organization + $ownerStr = "orgs/$owner" + } + @((invoke-gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /$ownerStr/packages?package_type=nuget -silent -returnvalue -ErrorAction SilentlyContinue | ConvertFrom-Json)) | Where-Object { $_.PSObject.Properties.Name -eq 'repository' } | Where-Object { $_.repository.full_name -eq $repository } | ForEach-Object { Write-Host "+ package $($_.name)" # Pipe empty string into GH API --METHOD DELETE due to https://github.com/cli/cli/issues/3937 - '' | invoke-gh api --method DELETE -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /orgs/$owner/packages/nuget/$($_.name) --input + '' | invoke-gh api --method DELETE -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /$ownerStr/packages/nuget/$($_.name) --input } } catch { diff --git a/e2eTests/scenarios/IncludeDependencies/runtest.ps1 b/e2eTests/scenarios/IncludeDependencies/runtest.ps1 index 58ff35bf3..ba6122416 100644 --- a/e2eTests/scenarios/IncludeDependencies/runtest.ps1 +++ b/e2eTests/scenarios/IncludeDependencies/runtest.ps1 @@ -111,7 +111,7 @@ $repoPath = (Get-Location).Path CommitAndPush -commitMessage 'Shift to Linux' # Upgrade AL-Go System Files - RunUpdateAlGoSystemFiles -directCommit -commitMessage 'Update system files' -wait -templateUrl $template -ghTokenWorkflow $token | Out-Null + RunUpdateAlGoSystemFiles -directCommit -wait -templateUrl $template -ghTokenWorkflow $token -repository $repository | Out-Null } } diff --git a/e2eTests/scenarios/PowerPlatform/runtest.ps1 b/e2eTests/scenarios/PowerPlatform/runtest.ps1 new file mode 100644 index 000000000..2067f29fa --- /dev/null +++ b/e2eTests/scenarios/PowerPlatform/runtest.ps1 @@ -0,0 +1,126 @@ +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '', Justification = 'Global vars used for local test execution only.')] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Justification = 'All scenario tests have equal parameter set.')] +Param( + [switch] $github, + [string] $githubOwner = $global:E2EgithubOwner, + [string] $repoName = [System.IO.Path]::GetFileNameWithoutExtension([System.IO.Path]::GetTempFileName()), + [string] $token = ($Global:SecureE2EPAT | Get-PlainText), + [string] $pteTemplate = $global:pteTemplate, + [string] $appSourceTemplate = $global:appSourceTemplate, + [string] $adminCenterApiToken = ($global:SecureAdminCenterApiToken | Get-PlainText), + [string] $licenseFileUrl = ($global:SecureLicenseFileUrl | Get-PlainText) +) + +Write-Host -ForegroundColor Yellow @' +# _____ _____ _ _ __ _____ _ _ _ +# | __ \ | __ \| | | | / _| / ____| | | | | (_) +# | |__) |____ _____ _ __| |__) | | __ _| |_| |_ ___ _ __ _ __ ___ | (___ ___ | |_ _| |_ _ ___ _ __ +# | ___/ _ \ \ /\ / / _ \ '__| ___/| |/ _` | __| _/ _ \| '__| '_ ` _ \ \___ \ / _ \| | | | | __| |/ _ \| '_ \ +# | | | (_) \ V V / __/ | | | | | (_| | |_| || (_) | | | | | | | | ____) | (_) | | |_| | |_| | (_) | | | | +# |_| \___/ \_/\_/ \___|_| |_| |_|\__,_|\__|_| \___/|_| |_| |_| |_| |_____/ \___/|_|\__,_|\__|_|\___/|_| |_| +# +# +# This test tests the following scenario: +# +# - For the three bcsamples repositories: microsoft/bcsamples-takeorder, microsoft/bcsamples-CoffeeMR and microsoft/bcsampels-WarehouseHelper do the following: +# - Create a new repository with the same content as the repository +# - Run the Update AL-Go System Files with the latest AL-Go version (main) +# - Run the Update AL-Go System Files with the test version +# - Check that PP workflows exists +# - Run the "CI/CD" workflow +# - Check that artifacts are created for the app and the PowerPlatform solution +# - Remove the repository +# +'@ + +$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + +Remove-Module e2eTestHelper -ErrorAction SilentlyContinue +Import-Module (Join-Path $PSScriptRoot "..\..\e2eTestHelper.psm1") -DisableNameChecking + +$branch = "main" +$template = "https://github.com/$pteTemplate" +$repoPath = (Get-Location).Path + +$repositories = @('bcsamples-WarehouseHelper', 'bcsamples-takeorder', 'bcsamples-CoffeeMR') +$repoVars = @{} + +foreach($sourceRepo in $repositories) { + $repoName = [System.IO.Path]::GetFileNameWithoutExtension([System.IO.Path]::GetTempFileName()) + Push-Location + $repository = "$githubOwner/$repoName" + + # Login + SetTokenAndRepository -github:$github -githubOwner $githubOwner -token $token -repository $repository + + # Create repository1 + CreateAlGoRepository ` + -github:$github ` + -template "https://github.com/microsoft/$sourceRepo" ` + -repository $repository ` + -branch $branch ` + -contentScript { + Param([string] $path) + Remove-PropertiesFromJsonFile -path (Join-Path $path '.github/AL-Go-Settings.json') -properties @('environments','DeployTo*') + } + + $repoPath = (Get-Location).Path + Write-Host "Repo Path: $repoPath" + + $settings = Get-Content -Path '.github/AL-Go-Settings.json' -Raw | ConvertFrom-Json + Write-Host "PowerPlatform Solution Folder: $($settings.powerPlatformSolutionFolder)" + + SetRepositorySecret -repository $repository -name 'GHTOKENWORKFLOW' -value $token + + if ($settings.templateUrl -eq 'https://github.com/Microsoft/AL-Go-PTE@PPPreview') { + # Upgrade AL-Go System Files from PPPreview to main (PPPreview branch still uses Y/N prompt and doesn't support direct AL-Go development - i.e. freddydk/AL-Go@branch) + $parameters = @{ + "templateUrl" = 'https://github.com/microsoft/AL-Go-PTE@main' + "directCommit" = 'Y' + } + RunWorkflow -name 'Update AL-Go System Files' -parameters $parameters -wait -branch $branch -repository $repository + } + + # Upgrade AL-Go System Files to test version + RunUpdateAlGoSystemFiles -directCommit -wait -templateUrl $template -ghTokenWorkflow $token -repository $repository | Out-Null + + CancelAllWorkflows -repository $repository + + # Pull and test workflows + Pull + Test-Path -Path '.github/workflows/PullPowerPlatformChanges.yaml' | Should -Be $true -Because "PullPowerPlatformChanges workflow exists" + Test-Path -Path '.github/workflows/PushPowerPlatformChanges.yaml' | Should -Be $true -Because "PushPowerPlatformChanges workflow exists" + Test-Path -Path '.github/workflows/_BuildPowerPlatformSolution.yaml' | Should -Be $true -Because "_BuildPowerPlatformSolution workflow exists" + + $run = RunCICD -repository $repository -branch $branch + Pop-Location + + $repoVars."$sourceRepo" = @{ + "run" = $run + "repoPath" = $repoPath + "repoName" = $repoName + "settings" = $settings + } +} + +foreach($sourceRepo in $repositories) { + $repoVar = $repoVars."$sourceRepo" + $run = $repoVar.run + $repoPath = $repoVar.repoPath + $repoName = $repoVar.repoName + $settings = $repoVar.settings + + $repository = "$githubOwner/$repoName" + + Push-Location $repoPath + WaitWorkflow -repository $repository -runid $run.id + + # Test artifacts generated + Test-ArtifactsFromRun -runid $run.id -folder 'artifacts' -expectedArtifacts @{"Apps"=1} -repoVersion '*.*' -appVersion '*.*' + + # Test PowerPlatform solution artifact + Test-Path -Path "./artifacts/$($settings.powerPlatformSolutionFolder)-$branch-PowerPlatformSolution-*.*.*.*/$($settings.powerPlatformSolutionFolder).zip" | Should -Be $true -Because "PowerPlatform solution artifact exists" + + Pop-Location + RemoveRepository -repository $repository -path $repoPath +} diff --git a/e2eTests/scenarios/ReferenceDocumentation/runtest.ps1 b/e2eTests/scenarios/ReferenceDocumentation/runtest.ps1 index 2daa0b3cf..89968975f 100644 --- a/e2eTests/scenarios/ReferenceDocumentation/runtest.ps1 +++ b/e2eTests/scenarios/ReferenceDocumentation/runtest.ps1 @@ -104,7 +104,7 @@ Add-PropertiesToJsonFile -path '.github/AL-Go-Settings.json' -properties @{ "run CommitAndPush -commitMessage 'Shift to Linux' # Upgrade AL-Go System Files -RunUpdateAlGoSystemFiles -directCommit -commitMessage 'Update system files' -wait -templateUrl $template -ghTokenWorkflow $token | Out-Null +RunUpdateAlGoSystemFiles -directCommit -wait -templateUrl $template -ghTokenWorkflow $token -repository $repository | Out-Null # Cancel all running workflows CancelAllWorkflows -repository $repository diff --git a/e2eTests/scenarios/SpecialCharacters/runtest.ps1 b/e2eTests/scenarios/SpecialCharacters/runtest.ps1 index f2df2794c..dd5002150 100644 --- a/e2eTests/scenarios/SpecialCharacters/runtest.ps1 +++ b/e2eTests/scenarios/SpecialCharacters/runtest.ps1 @@ -88,7 +88,7 @@ Add-PropertiesToJsonFile -path '.github/AL-Go-Settings.json' -properties @{ "run CommitAndPush -commitMessage 'Shift to Linux' # Upgrade AL-Go System Files -RunUpdateAlGoSystemFiles -directCommit -commitMessage 'Update system files' -wait -templateUrl $template -ghTokenWorkflow $token | Out-Null +RunUpdateAlGoSystemFiles -directCommit -wait -templateUrl $template -ghTokenWorkflow $token -repository $repository | Out-Null $run = RunCICD -repository $repository -branch $branch -wait diff --git a/e2eTests/scenarios/UseProjectDependencies/runtest.ps1 b/e2eTests/scenarios/UseProjectDependencies/runtest.ps1 index ccec7ccb3..64aa73de9 100644 --- a/e2eTests/scenarios/UseProjectDependencies/runtest.ps1 +++ b/e2eTests/scenarios/UseProjectDependencies/runtest.ps1 @@ -82,7 +82,7 @@ CreateAlGoRepository ` $repoPath = (Get-Location).Path # Update AL-Go System Files to uptake UseProjectDependencies setting -RunUpdateAlGoSystemFiles -templateUrl $template -wait -branch $branch -directCommit -ghTokenWorkflow $token | Out-Null +RunUpdateAlGoSystemFiles -directCommit -wait -templateUrl $template -ghTokenWorkflow $token -repository $repository -branch $branch | Out-Null # Run CI/CD workflow $run = RunCICD -branch $branch