diff --git a/Actions/CheckAuthContext/CheckAuthContext.ps1 b/Actions/CheckAuthContext/CheckAuthContext.ps1 new file mode 100644 index 000000000..8790c35f4 --- /dev/null +++ b/Actions/CheckAuthContext/CheckAuthContext.ps1 @@ -0,0 +1,45 @@ +Param( + [Parameter(HelpMessage = "Name of the secret to check (e.g., 'adminCenterApiCredentials' or comma-separated list to check multiple)", Mandatory = $true)] + [string] $secretName, + [Parameter(HelpMessage = "Environment name (for error messages)", Mandatory = $false)] + [string] $environmentName = '' +) + +. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) + +$settings = $env:Settings | ConvertFrom-Json +$secrets = $env:Secrets | ConvertFrom-Json | ConvertTo-HashTable + +# Check each secret name in order +$authContext = $null +$foundSecretName = '' +foreach ($name in ($secretName -split ',')) { + $name = $name.Trim() + if ($secrets.ContainsKey($name) -and $secrets."$name") { + Write-Host "Using $name secret" + $authContext = $secrets."$name" + $foundSecretName = $name + break + } +} + +if ($authContext) { + Write-Host "AuthContext provided in secret $foundSecretName!" + Add-Content -Encoding UTF8 -Path $ENV:GITHUB_STEP_SUMMARY -Value "AuthContext was provided in a secret called $foundSecretName. Using this information for authentication." +} +else { + Write-Host "No AuthContext provided, initiating Device Code flow" + DownloadAndImportBcContainerHelper + $authContext = New-BcAuthContext -includeDeviceLogin -deviceLoginTimeout ([TimeSpan]::FromSeconds(0)) + + # Build appropriate error message + if ($environmentName) { + $message = "AL-Go needs access to the Business Central Environment $($environmentName.Split(' ')[0]) and could not locate a secret called $($secretName -replace ',', ' or ')" + } + else { + $message = "AL-Go needs access to the Business Central Admin Center Api and could not locate a secret called $($settings.adminCenterApiCredentialsSecretName) (https://aka.ms/ALGoSettings#AdminCenterApiCredentialsSecretName)" + } + + Add-Content -Encoding UTF8 -Path $ENV:GITHUB_STEP_SUMMARY -Value "$message`n`n$($authContext.message)" + Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "deviceCode=$($authContext.deviceCode)" +} diff --git a/Actions/CheckAuthContext/README.md b/Actions/CheckAuthContext/README.md new file mode 100644 index 000000000..adafed8c3 --- /dev/null +++ b/Actions/CheckAuthContext/README.md @@ -0,0 +1,32 @@ +# Check Auth Context + +Check if Admin Center Api Credentials / AuthContext are provided in secrets. If not, initiate device code flow for authentication. + +## INPUT + +### ENV variables + +| Name | Description | +| :-- | :-- | +| Settings | env.Settings must be set by a prior call to the ReadSettings Action | +| Secrets | env.Secrets must be set to the secrets output from 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 | +| secretName | Yes | Name of the secret to check (comma-separated list to check multiple in order) | | +| environmentName | | Environment name (for error messages when deploying to environments) | | + +## OUTPUT + +### ENV variables + +none + +### OUTPUT variables + +| Name | Description | +| :-- | :-- | +| deviceCode | Device code for authentication (only set if device login is required) | diff --git a/Actions/CheckAuthContext/action.yaml b/Actions/CheckAuthContext/action.yaml new file mode 100644 index 000000000..e55d7f89f --- /dev/null +++ b/Actions/CheckAuthContext/action.yaml @@ -0,0 +1,34 @@ +name: Check Auth Context +author: Microsoft Corporation +inputs: + shell: + description: Shell in which you want to run the action (powershell or pwsh) + required: false + default: powershell + secretName: + description: Name of the secret to check (e.g., 'adminCenterApiCredentials' or comma-separated list to check multiple) + required: true + environmentName: + description: Environment name (for error messages) + required: false + default: '' +outputs: + deviceCode: + description: Device code for authentication (if device login is required) + value: ${{ steps.CheckAuthContext.outputs.deviceCode }} +runs: + using: composite + steps: + - name: run + shell: ${{ inputs.shell }} + id: CheckAuthContext + env: + _secretName: ${{ inputs.secretName }} + _environmentName: ${{ inputs.environmentName }} + run: | + ${{ github.action_path }}/../Invoke-AlGoAction.ps1 -ActionName "CheckAuthContext" -Action { + ${{ github.action_path }}/CheckAuthContext.ps1 -secretName $ENV:_secretName -environmentName $ENV:_environmentName + } +branding: + icon: terminal + color: blue diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 1f7b9dec7..c4b05b74e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,5 +1,6 @@ ### Issues +- Issue 2113 Using the action "Create Online Dev. Environment" fails in Initialization phase - Issue 1915 CICD fails on releases/26.x branch - '26.x' cannot be recognized as a semantic version string ## v8.2 diff --git a/Templates/AppSource App/.github/workflows/CreateOnlineDevelopmentEnvironment.yaml b/Templates/AppSource App/.github/workflows/CreateOnlineDevelopmentEnvironment.yaml index 174741887..2c52fa88a 100644 --- a/Templates/AppSource App/.github/workflows/CreateOnlineDevelopmentEnvironment.yaml +++ b/Templates/AppSource App/.github/workflows/CreateOnlineDevelopmentEnvironment.yaml @@ -79,24 +79,12 @@ jobs: - name: Check AdminCenterApiCredentials / Initiate Device Login (open to see code) id: authenticate - run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - $settings = $env:Settings | ConvertFrom-Json - if ('${{ fromJson(steps.ReadSecrets.outputs.Secrets).adminCenterApiCredentials }}') { - Write-Host "AdminCenterApiCredentials provided in secret $($settings.adminCenterApiCredentialsSecretName)!" - Add-Content -Encoding UTF8 -path $ENV:GITHUB_STEP_SUMMARY -value "Admin Center Api Credentials was provided in a secret called $($settings.adminCenterApiCredentialsSecretName). Using this information for authentication." - } - else { - Write-Host "AdminCenterApiCredentials not provided, initiating Device Code flow" - $ALGoHelperPath = "$([System.IO.Path]::GetTempFileName()).ps1" - $webClient = New-Object System.Net.WebClient - $webClient.DownloadFile('https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/AL-Go-Helper.ps1', $ALGoHelperPath) - . $ALGoHelperPath - DownloadAndImportBcContainerHelper - $authContext = New-BcAuthContext -includeDeviceLogin -deviceLoginTimeout ([TimeSpan]::FromSeconds(0)) - Add-Content -Encoding UTF8 -path $ENV:GITHUB_STEP_SUMMARY -value "AL-Go needs access to the Business Central Admin Center Api and could not locate a secret called $($settings.adminCenterApiCredentialsSecretName) (https://aka.ms/ALGoSettings#AdminCenterApiCredentialsSecretName)`n`n$($authContext.message)" - Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "deviceCode=$($authContext.deviceCode)" - } + uses: microsoft/AL-Go-Actions/CheckAuthContext@main + env: + Secrets: '${{ steps.ReadSecrets.outputs.Secrets }}' + with: + shell: powershell + secretName: 'adminCenterApiCredentials' CreateDevelopmentEnvironment: needs: [ Initialization ] diff --git a/Templates/AppSource App/.github/workflows/PublishToEnvironment.yaml b/Templates/AppSource App/.github/workflows/PublishToEnvironment.yaml index 710d40119..3d7cfc7ff 100644 --- a/Templates/AppSource App/.github/workflows/PublishToEnvironment.yaml +++ b/Templates/AppSource App/.github/workflows/PublishToEnvironment.yaml @@ -92,35 +92,13 @@ jobs: - name: Authenticate id: Authenticate if: steps.DetermineDeploymentEnvironments.outputs.UnknownEnvironment == 1 - run: | - $envName = '${{ steps.envName.outputs.envName }}' - $secretName = '' - $secrets = '${{ steps.ReadSecrets.outputs.Secrets }}' | ConvertFrom-Json - $authContext = $null - "$($envName)-AuthContext", "$($envName)_AuthContext", "AuthContext" | ForEach-Object { - if (!($authContext)) { - if ($secrets."$_") { - Write-Host "Using $_ secret as AuthContext" - $authContext = $secrets."$_" - $secretName = $_ - } - } - } - if ($authContext) { - Write-Host "AuthContext provided in secret $secretName!" - Add-Content -Encoding UTF8 -path $ENV:GITHUB_STEP_SUMMARY -value "AuthContext was provided in a secret called $secretName. Using this information for authentication." - } - else { - Write-Host "No AuthContext provided for $envName, initiating Device Code flow" - $ALGoHelperPath = "$([System.IO.Path]::GetTempFileName()).ps1" - $webClient = New-Object System.Net.WebClient - $webClient.DownloadFile('https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/AL-Go-Helper.ps1', $ALGoHelperPath) - . $ALGoHelperPath - DownloadAndImportBcContainerHelper - $authContext = New-BcAuthContext -includeDeviceLogin -deviceLoginTimeout ([TimeSpan]::FromSeconds(0)) - Add-Content -Encoding UTF8 -path $ENV:GITHUB_STEP_SUMMARY -value "AL-Go needs access to the Business Central Environment $('${{ steps.envName.outputs.envName }}'.Split(' ')[0]) and could not locate a secret called ${{ steps.envName.outputs.envName }}_AuthContext`n`n$($authContext.message)" - Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "deviceCode=$($authContext.deviceCode)" - } + uses: microsoft/AL-Go-Actions/CheckAuthContext@main + env: + Secrets: '${{ steps.ReadSecrets.outputs.Secrets }}' + with: + shell: powershell + secretName: '${{ steps.envName.outputs.envName }}-AuthContext,${{ steps.envName.outputs.envName }}_AuthContext,AuthContext' + environmentName: ${{ steps.envName.outputs.envName }} Deploy: needs: [ Initialization ] diff --git a/Templates/Per Tenant Extension/.github/workflows/CreateOnlineDevelopmentEnvironment.yaml b/Templates/Per Tenant Extension/.github/workflows/CreateOnlineDevelopmentEnvironment.yaml index 174741887..2c52fa88a 100644 --- a/Templates/Per Tenant Extension/.github/workflows/CreateOnlineDevelopmentEnvironment.yaml +++ b/Templates/Per Tenant Extension/.github/workflows/CreateOnlineDevelopmentEnvironment.yaml @@ -79,24 +79,12 @@ jobs: - name: Check AdminCenterApiCredentials / Initiate Device Login (open to see code) id: authenticate - run: | - $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - $settings = $env:Settings | ConvertFrom-Json - if ('${{ fromJson(steps.ReadSecrets.outputs.Secrets).adminCenterApiCredentials }}') { - Write-Host "AdminCenterApiCredentials provided in secret $($settings.adminCenterApiCredentialsSecretName)!" - Add-Content -Encoding UTF8 -path $ENV:GITHUB_STEP_SUMMARY -value "Admin Center Api Credentials was provided in a secret called $($settings.adminCenterApiCredentialsSecretName). Using this information for authentication." - } - else { - Write-Host "AdminCenterApiCredentials not provided, initiating Device Code flow" - $ALGoHelperPath = "$([System.IO.Path]::GetTempFileName()).ps1" - $webClient = New-Object System.Net.WebClient - $webClient.DownloadFile('https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/AL-Go-Helper.ps1', $ALGoHelperPath) - . $ALGoHelperPath - DownloadAndImportBcContainerHelper - $authContext = New-BcAuthContext -includeDeviceLogin -deviceLoginTimeout ([TimeSpan]::FromSeconds(0)) - Add-Content -Encoding UTF8 -path $ENV:GITHUB_STEP_SUMMARY -value "AL-Go needs access to the Business Central Admin Center Api and could not locate a secret called $($settings.adminCenterApiCredentialsSecretName) (https://aka.ms/ALGoSettings#AdminCenterApiCredentialsSecretName)`n`n$($authContext.message)" - Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "deviceCode=$($authContext.deviceCode)" - } + uses: microsoft/AL-Go-Actions/CheckAuthContext@main + env: + Secrets: '${{ steps.ReadSecrets.outputs.Secrets }}' + with: + shell: powershell + secretName: 'adminCenterApiCredentials' CreateDevelopmentEnvironment: needs: [ Initialization ] diff --git a/Templates/Per Tenant Extension/.github/workflows/PublishToEnvironment.yaml b/Templates/Per Tenant Extension/.github/workflows/PublishToEnvironment.yaml index 710d40119..3d7cfc7ff 100644 --- a/Templates/Per Tenant Extension/.github/workflows/PublishToEnvironment.yaml +++ b/Templates/Per Tenant Extension/.github/workflows/PublishToEnvironment.yaml @@ -92,35 +92,13 @@ jobs: - name: Authenticate id: Authenticate if: steps.DetermineDeploymentEnvironments.outputs.UnknownEnvironment == 1 - run: | - $envName = '${{ steps.envName.outputs.envName }}' - $secretName = '' - $secrets = '${{ steps.ReadSecrets.outputs.Secrets }}' | ConvertFrom-Json - $authContext = $null - "$($envName)-AuthContext", "$($envName)_AuthContext", "AuthContext" | ForEach-Object { - if (!($authContext)) { - if ($secrets."$_") { - Write-Host "Using $_ secret as AuthContext" - $authContext = $secrets."$_" - $secretName = $_ - } - } - } - if ($authContext) { - Write-Host "AuthContext provided in secret $secretName!" - Add-Content -Encoding UTF8 -path $ENV:GITHUB_STEP_SUMMARY -value "AuthContext was provided in a secret called $secretName. Using this information for authentication." - } - else { - Write-Host "No AuthContext provided for $envName, initiating Device Code flow" - $ALGoHelperPath = "$([System.IO.Path]::GetTempFileName()).ps1" - $webClient = New-Object System.Net.WebClient - $webClient.DownloadFile('https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/AL-Go-Helper.ps1', $ALGoHelperPath) - . $ALGoHelperPath - DownloadAndImportBcContainerHelper - $authContext = New-BcAuthContext -includeDeviceLogin -deviceLoginTimeout ([TimeSpan]::FromSeconds(0)) - Add-Content -Encoding UTF8 -path $ENV:GITHUB_STEP_SUMMARY -value "AL-Go needs access to the Business Central Environment $('${{ steps.envName.outputs.envName }}'.Split(' ')[0]) and could not locate a secret called ${{ steps.envName.outputs.envName }}_AuthContext`n`n$($authContext.message)" - Add-Content -Encoding UTF8 -Path $env:GITHUB_OUTPUT -Value "deviceCode=$($authContext.deviceCode)" - } + uses: microsoft/AL-Go-Actions/CheckAuthContext@main + env: + Secrets: '${{ steps.ReadSecrets.outputs.Secrets }}' + with: + shell: powershell + secretName: '${{ steps.envName.outputs.envName }}-AuthContext,${{ steps.envName.outputs.envName }}_AuthContext,AuthContext' + environmentName: ${{ steps.envName.outputs.envName }} Deploy: needs: [ Initialization ] diff --git a/Tests/CheckAuthContext.Action.Test.ps1 b/Tests/CheckAuthContext.Action.Test.ps1 new file mode 100644 index 000000000..5df078f3a --- /dev/null +++ b/Tests/CheckAuthContext.Action.Test.ps1 @@ -0,0 +1,107 @@ +Get-Module TestActionsHelper | Remove-Module -Force +Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') +$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + +Describe "CheckAuthContext Action Tests" { + BeforeAll { + $actionName = "CheckAuthContext" + $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 + } + + It 'Compile Action' { + Invoke-Expression $actionScript + } + + It 'Test action.yaml matches script' { + $outputs = [ordered]@{ + "deviceCode" = "Device code for authentication (if device login is required)" + } + YamlTest -scriptRoot $scriptRoot -actionName $actionName -actionScript $actionScript -outputs $outputs + } + + It 'Should find first matching secret' { + $env:Settings = '{"adminCenterApiCredentialsSecretName": "adminCenterApiCredentials"}' + $env:Secrets = '{"adminCenterApiCredentials": "someCredentials"}' + $env:GITHUB_STEP_SUMMARY = [System.IO.Path]::GetTempFileName() + $env:GITHUB_OUTPUT = [System.IO.Path]::GetTempFileName() + + Invoke-Expression $actionScript + CheckAuthContext -secretName 'adminCenterApiCredentials' + + # Should NOT output deviceCode when secret is found + $output = Get-Content $env:GITHUB_OUTPUT -Raw + $output | Should -Not -Match "deviceCode=" + + # Should write to step summary + $summary = Get-Content $env:GITHUB_STEP_SUMMARY -Raw + $summary | Should -Match "AuthContext was provided in a secret called adminCenterApiCredentials" + + Remove-Item $env:GITHUB_STEP_SUMMARY -ErrorAction SilentlyContinue + Remove-Item $env:GITHUB_OUTPUT -ErrorAction SilentlyContinue + } + + It 'Should find secret when checking multiple names - first match wins' { + $env:Settings = '{"adminCenterApiCredentialsSecretName": "adminCenterApiCredentials"}' + $env:Secrets = '{"TestEnv-AuthContext": "firstSecret", "AuthContext": "secondSecret"}' + $env:GITHUB_STEP_SUMMARY = [System.IO.Path]::GetTempFileName() + $env:GITHUB_OUTPUT = [System.IO.Path]::GetTempFileName() + + Invoke-Expression $actionScript + CheckAuthContext -secretName 'TestEnv-AuthContext,TestEnv_AuthContext,AuthContext' + + $summary = Get-Content $env:GITHUB_STEP_SUMMARY -Raw + $summary | Should -Match "AuthContext was provided in a secret called TestEnv-AuthContext" + + Remove-Item $env:GITHUB_STEP_SUMMARY -ErrorAction SilentlyContinue + Remove-Item $env:GITHUB_OUTPUT -ErrorAction SilentlyContinue + } + + It 'Should find fallback secret when primary not found' { + $env:Settings = '{"adminCenterApiCredentialsSecretName": "adminCenterApiCredentials"}' + $env:Secrets = '{"AuthContext": "fallbackSecret"}' + $env:GITHUB_STEP_SUMMARY = [System.IO.Path]::GetTempFileName() + $env:GITHUB_OUTPUT = [System.IO.Path]::GetTempFileName() + + Invoke-Expression $actionScript + CheckAuthContext -secretName 'TestEnv-AuthContext,TestEnv_AuthContext,AuthContext' + + $summary = Get-Content $env:GITHUB_STEP_SUMMARY -Raw + $summary | Should -Match "AuthContext was provided in a secret called AuthContext" + + Remove-Item $env:GITHUB_STEP_SUMMARY -ErrorAction SilentlyContinue + Remove-Item $env:GITHUB_OUTPUT -ErrorAction SilentlyContinue + } + + It 'Should initiate device login when no secret is found' { + $env:Settings = '{"adminCenterApiCredentialsSecretName": "adminCenterApiCredentials"}' + $env:Secrets = '{}' + $env:GITHUB_STEP_SUMMARY = [System.IO.Path]::GetTempFileName() + $env:GITHUB_OUTPUT = [System.IO.Path]::GetTempFileName() + + # Import AL-Go-Helper to get the functions defined, then mock them + . (Join-Path $scriptRoot "..\AL-Go-Helper.ps1") + Mock DownloadAndImportBcContainerHelper { } + Mock New-BcAuthContext { + return @{ deviceCode = "TESTDEVICECODE"; message = "Enter code to authenticate" } + } + + Invoke-Expression $actionScript + CheckAuthContext -secretName 'nonExistentSecret' + + # Should output deviceCode when no secret is found + $output = Get-Content $env:GITHUB_OUTPUT -Raw + $output | Should -Match "deviceCode=TESTDEVICECODE" + + # Should write device login message to step summary + $summary = Get-Content $env:GITHUB_STEP_SUMMARY -Raw + $summary | Should -Match "could not locate a secret" + + Remove-Item $env:GITHUB_STEP_SUMMARY -ErrorAction SilentlyContinue + Remove-Item $env:GITHUB_OUTPUT -ErrorAction SilentlyContinue + } +}