Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 0
🌟 [Major]: Fixed test secret inputs replaced by TestData#365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
fdb8ae5772d65fe5dfca393ca6f2c501e355ae2f4cea0193506d6d8539864d1cdfd9ae4dc521fFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| if ([string]::IsNullOrWhiteSpace($env:PSMODULE_TEST_DATA)) { | ||
| Write-Output 'No test data was provided by the calling workflow.' | ||
| return | ||
| } | ||
| try { | ||
| $data = $env:PSMODULE_TEST_DATA | ConvertFrom-Json -ErrorAction Stop | ||
| } catch { | ||
| throw "The 'TestData' secret must be valid JSON with 'secrets' and/or 'variables' maps." | ||
| } | ||
| if ($null -eq $data -or $data -isnot [pscustomobject]) { | ||
| throw "The 'TestData' secret must be a JSON object with 'secrets' and/or 'variables' maps." | ||
| } | ||
| $allowedTopLevelKeys = @('secrets', 'variables') | ||
| foreach ($propertyName in $data.PSObject.Properties.Name) { | ||
| if ($allowedTopLevelKeys -notcontains $propertyName) { | ||
| throw "The 'TestData' secret only supports 'secrets' and 'variables' maps." | ||
| } | ||
| } | ||
| $reservedNames = @('CI', 'HOME', 'PATH', 'PWD', 'SHELL', 'PSMODULE_TEST_DATA') | ||
| $reservedPrefixes = @('GITHUB_', 'RUNNER_', 'ACTIONS_') | ||
| function Assert-EnvironmentName { | ||
| <# | ||
| .SYNOPSIS | ||
| Validates that a TestData key can safely be written to GITHUB_ENV. | ||
| #> | ||
| param([string] $Name) | ||
| if ($Name -notmatch '^[A-Za-z_][A-Za-z0-9_]*$') { | ||
| throw 'TestData keys must be valid environment variable names.' | ||
| } | ||
| $normalized = $Name.ToUpperInvariant() | ||
| if ($reservedNames -contains $normalized) { | ||
| throw 'TestData keys must not override reserved environment variables.' | ||
| } | ||
| foreach ($prefix in $reservedPrefixes) { | ||
| if ($normalized.StartsWith($prefix)) { | ||
| throw 'TestData keys must not override reserved environment variables.' | ||
| } | ||
| } | ||
| } | ||
| function Assert-Map { | ||
| <# | ||
| .SYNOPSIS | ||
| Validates that a TestData section is a JSON object map. | ||
| #> | ||
| param( | ||
| [object] $Map, | ||
| [string] $Name | ||
| ) | ||
| if ($null -eq $Map) { return } | ||
| if ($Map -isnot [pscustomobject]) { | ||
| throw "The 'TestData.$Name' value must be a JSON object." | ||
| } | ||
| } | ||
| function Get-EnvironmentValue { | ||
| <# | ||
| .SYNOPSIS | ||
| Converts a scalar TestData value to an environment variable value. | ||
| #> | ||
| param( | ||
| [object] $Value, | ||
| [string] $Name | ||
| ) | ||
| if ($null -eq $Value) { return '' } | ||
| if ( | ||
| $Value -is [pscustomobject] -or | ||
| ($Value -is [System.Collections.IEnumerable] -and $Value -isnot [string]) | ||
| ) { | ||
| throw "Values in 'TestData.$Name' must be scalar values." | ||
| } | ||
| return [string]$Value | ||
| } | ||
| function Add-EnvFromMap { | ||
| <# | ||
| .SYNOPSIS | ||
| Writes validated TestData entries to GITHUB_ENV. | ||
| #> | ||
| param( | ||
| [object] $Map, | ||
| [string] $Name, | ||
| [switch] $Mask | ||
| ) | ||
| Assert-Map -Map $Map -Name $Name | ||
| if ($null -eq $Map) { return } | ||
| $count = 0 | ||
| foreach ($item in $Map.PSObject.Properties) { | ||
| $name = $item.Name | ||
| Assert-EnvironmentName -Name $name | ||
| $value = Get-EnvironmentValue -Value $item.Value -Name $Name | ||
| if ($Mask) { | ||
| foreach ($line in ($value -split "`n")) { | ||
| $line = $line.TrimEnd("`r") | ||
| if ($line.Length -gt 0) { | ||
| Write-Output "::add-mask::$line" | ||
| } | ||
| } | ||
| } | ||
| do { | ||
| $delimiter = "GHENV_$([guid]::NewGuid().ToString('N'))" | ||
| } while ($value.Contains($delimiter)) | ||
| Add-Content -Path $env:GITHUB_ENV -Value "$name<<$delimiter" -Encoding utf8 | ||
| Add-Content -Path $env:GITHUB_ENV -Value $value -Encoding utf8 | ||
| Add-Content -Path $env:GITHUB_ENV -Value $delimiter -Encoding utf8 | ||
| $count++ | ||
| } | ||
| if ($count -gt 0) { | ||
| if ($Mask) { | ||
| Write-Output "Exposed $count secret value(s) as environment variables." | ||
| } else { | ||
| Write-Output "Exposed $count variable value(s) as environment variables." | ||
| } | ||
| } | ||
| } | ||
| Assert-Map -Map $data.secrets -Name 'secrets' | ||
| Assert-Map -Map $data.variables -Name 'variables' | ||
| $secretNames = @() | ||
| if ($null -ne $data.secrets) { | ||
| $secretNames = @($data.secrets.PSObject.Properties.Name) | ||
| } | ||
| $variableNames = @() | ||
| if ($null -ne $data.variables) { | ||
| $variableNames = @($data.variables.PSObject.Properties.Name) | ||
| } | ||
| $secretNameSet = [System.Collections.Generic.HashSet[string]]::new( | ||
| [System.StringComparer]::OrdinalIgnoreCase | ||
| ) | ||
| foreach ($secretName in $secretNames) { | ||
| [void] $secretNameSet.Add($secretName) | ||
| } | ||
| foreach ($variableName in $variableNames) { | ||
| if ($secretNameSet.Contains($variableName)) { | ||
| throw 'TestData keys must not be duplicated across secrets and variables.' | ||
| } | ||
| } | ||
| Add-EnvFromMap -Map $data.secrets -Name 'secrets' -Mask | ||
| Add-EnvFromMap -Map $data.variables -Name 'variables' | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -3,42 +3,18 @@ name: AfterAll-ModuleLocal | ||
| on: | ||
| workflow_call: | ||
| secrets: | ||
| TEST_APP_ENT_CLIENT_ID: | ||
| description: The client ID of an Enterprise GitHub App for running tests. | ||
| required: false | ||
| TEST_APP_ENT_PRIVATE_KEY: | ||
| description: The private key of an Enterprise GitHub App for running tests. | ||
| required: false | ||
| TEST_APP_ORG_CLIENT_ID: | ||
| description: The client ID of an Organization GitHub App for running tests. | ||
| required: false | ||
| TEST_APP_ORG_PRIVATE_KEY: | ||
| description: The private key of an Organization GitHub App for running tests. | ||
| required: false | ||
| TEST_USER_ORG_FG_PAT: | ||
| description: The fine-grained personal access token with org access for running tests. | ||
| required: false | ||
| TEST_USER_USER_FG_PAT: | ||
| description: The fine-grained personal access token with user account access for running tests. | ||
| required: false | ||
| TEST_USER_PAT: | ||
| description: The classic personal access token for running tests. | ||
| TestData: | ||
| description: | | ||
| Optional single-line JSON object with 'secrets' and 'variables' maps. Each entry is exposed | ||
| as an environment variable available to the AfterAll teardown script; 'secrets' values are | ||
| masked in the logs, 'variables' values are not. | ||
| required: false | ||
| inputs: | ||
| Settings: | ||
| type: string | ||
| description: The complete settings object including test suites. | ||
| required: true | ||
| env: | ||
| TEST_APP_ENT_CLIENT_ID: ${{ secrets.TEST_APP_ENT_CLIENT_ID }} | ||
| TEST_APP_ENT_PRIVATE_KEY: ${{ secrets.TEST_APP_ENT_PRIVATE_KEY }} | ||
| TEST_APP_ORG_CLIENT_ID: ${{ secrets.TEST_APP_ORG_CLIENT_ID }} | ||
| TEST_APP_ORG_PRIVATE_KEY: ${{ secrets.TEST_APP_ORG_PRIVATE_KEY }} | ||
| TEST_USER_ORG_FG_PAT: ${{ secrets.TEST_USER_ORG_FG_PAT }} | ||
| TEST_USER_USER_FG_PAT: ${{ secrets.TEST_USER_USER_FG_PAT }} | ||
| TEST_USER_PAT: ${{ secrets.TEST_USER_PAT }} | ||
| permissions: | ||
| contents: read # to checkout the repo | ||
| @@ -55,6 +31,13 @@ jobs: | ||
| persist-credentials: false | ||
| fetch-depth: 0 | ||
| - name: Expose caller-provided test data | ||
| shell: pwsh | ||
| env: | ||
| PSMODULE_TEST_DATA: ${{ secrets.TestData }} | ||
| run: | | ||
MariusStorhaug marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| ./.github/scripts/Expose-TestData.ps1 | ||
| - name: Run AfterAll Teardown Scripts | ||
| if: always() | ||
| uses: PSModule/GitHub-Script@1ee97bbc652d19c38ae12f6e1e47e9d9fbd12d0a # v1.8.0 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -3,42 +3,18 @@ name: BeforeAll-ModuleLocal | ||
| on: | ||
| workflow_call: | ||
| secrets: | ||
| TEST_APP_ENT_CLIENT_ID: | ||
| description: The client ID of an Enterprise GitHub App for running tests. | ||
| required: false | ||
| TEST_APP_ENT_PRIVATE_KEY: | ||
| description: The private key of an Enterprise GitHub App for running tests. | ||
| required: false | ||
| TEST_APP_ORG_CLIENT_ID: | ||
| description: The client ID of an Organization GitHub App for running tests. | ||
| required: false | ||
| TEST_APP_ORG_PRIVATE_KEY: | ||
| description: The private key of an Organization GitHub App for running tests. | ||
| required: false | ||
| TEST_USER_ORG_FG_PAT: | ||
| description: The fine-grained personal access token with org access for running tests. | ||
| required: false | ||
| TEST_USER_USER_FG_PAT: | ||
| description: The fine-grained personal access token with user account access for running tests. | ||
| required: false | ||
| TEST_USER_PAT: | ||
| description: The classic personal access token for running tests. | ||
| TestData: | ||
| description: | | ||
| Optional single-line JSON object with 'secrets' and 'variables' maps. Each entry is exposed | ||
| as an environment variable available to the BeforeAll setup script; 'secrets' values are | ||
| masked in the logs, 'variables' values are not. | ||
| required: false | ||
| inputs: | ||
| Settings: | ||
| type: string | ||
| description: The complete settings object including test suites. | ||
| required: true | ||
| env: | ||
| TEST_APP_ENT_CLIENT_ID: ${{ secrets.TEST_APP_ENT_CLIENT_ID }} | ||
| TEST_APP_ENT_PRIVATE_KEY: ${{ secrets.TEST_APP_ENT_PRIVATE_KEY }} | ||
| TEST_APP_ORG_CLIENT_ID: ${{ secrets.TEST_APP_ORG_CLIENT_ID }} | ||
| TEST_APP_ORG_PRIVATE_KEY: ${{ secrets.TEST_APP_ORG_PRIVATE_KEY }} | ||
| TEST_USER_ORG_FG_PAT: ${{ secrets.TEST_USER_ORG_FG_PAT }} | ||
| TEST_USER_USER_FG_PAT: ${{ secrets.TEST_USER_USER_FG_PAT }} | ||
| TEST_USER_PAT: ${{ secrets.TEST_USER_PAT }} | ||
| permissions: | ||
| contents: read # to checkout the repo | ||
| @@ -55,6 +31,13 @@ jobs: | ||
| persist-credentials: false | ||
| fetch-depth: 0 | ||
| - name: Expose caller-provided test data | ||
| shell: pwsh | ||
| env: | ||
| PSMODULE_TEST_DATA: ${{ secrets.TestData }} | ||
| run: | | ||
MariusStorhaug marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| ./.github/scripts/Expose-TestData.ps1 | ||
| - name: Run BeforeAll Setup Scripts | ||
| uses: PSModule/GitHub-Script@1ee97bbc652d19c38ae12f6e1e47e9d9fbd12d0a # v1.8.0 | ||
| with: | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -3,26 +3,11 @@ name: Test-ModuleLocal | ||
| on: | ||
| workflow_call: | ||
| secrets: | ||
| TEST_APP_ENT_CLIENT_ID: | ||
| description: The client ID of an Enterprise GitHub App for running tests. | ||
| required: false | ||
| TEST_APP_ENT_PRIVATE_KEY: | ||
| description: The private key of an Enterprise GitHub App for running tests. | ||
| required: false | ||
| TEST_APP_ORG_CLIENT_ID: | ||
| description: The client ID of an Organization GitHub App for running tests. | ||
| required: false | ||
| TEST_APP_ORG_PRIVATE_KEY: | ||
| description: The private key of an Organization GitHub App for running tests. | ||
| required: false | ||
| TEST_USER_ORG_FG_PAT: | ||
| description: The fine-grained personal access token with org access for running tests. | ||
| required: false | ||
| TEST_USER_USER_FG_PAT: | ||
| description: The fine-grained personal access token with user account access for running tests. | ||
| required: false | ||
| TEST_USER_PAT: | ||
| description: The classic personal access token for running tests. | ||
| TestData: | ||
| description: | | ||
| Optional single-line JSON object with 'secrets' and 'variables' maps. Each entry is exposed | ||
| as an environment variable the module's Pester tests read via $env:<name>; 'secrets' values | ||
| are masked in the logs, 'variables' values are not. | ||
| required: false | ||
| inputs: | ||
| Settings: | ||
| @@ -34,13 +19,6 @@ permissions: | ||
| contents: read # to checkout the repo and create releases on the repo | ||
| env: | ||
| TEST_APP_ENT_CLIENT_ID: ${{ secrets.TEST_APP_ENT_CLIENT_ID }} | ||
| TEST_APP_ENT_PRIVATE_KEY: ${{ secrets.TEST_APP_ENT_PRIVATE_KEY }} | ||
| TEST_APP_ORG_CLIENT_ID: ${{ secrets.TEST_APP_ORG_CLIENT_ID }} | ||
| TEST_APP_ORG_PRIVATE_KEY: ${{ secrets.TEST_APP_ORG_PRIVATE_KEY }} | ||
| TEST_USER_ORG_FG_PAT: ${{ secrets.TEST_USER_ORG_FG_PAT }} | ||
| TEST_USER_USER_FG_PAT: ${{ secrets.TEST_USER_USER_FG_PAT }} | ||
| TEST_USER_PAT: ${{ secrets.TEST_USER_PAT }} | ||
| GITHUB_TOKEN: ${{ github.token }} | ||
| jobs: | ||
| @@ -58,6 +36,13 @@ jobs: | ||
| persist-credentials: false | ||
| fetch-depth: 0 | ||
| - name: Expose caller-provided test data | ||
| shell: pwsh | ||
| env: | ||
| PSMODULE_TEST_DATA: ${{ secrets.TestData }} | ||
| run: | | ||
MariusStorhaug marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| ./.github/scripts/Expose-TestData.ps1 | ||
| - name: Download module artifact | ||
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | ||
| with: | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -25,16 +25,21 @@ permissions: | ||
| jobs: | ||
| WorkflowTestDefault: | ||
| if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} | ||
| uses: ./.github/workflows/workflow.yml | ||
| secrets: | ||
| APIKey: ${{ secrets.APIKey }} | ||
| TEST_APP_ENT_CLIENT_ID: ${{ secrets.TEST_APP_ENT_CLIENT_ID }} | ||
| TEST_APP_ENT_PRIVATE_KEY: ${{ secrets.TEST_APP_ENT_PRIVATE_KEY }} | ||
| TEST_APP_ORG_CLIENT_ID: ${{ secrets.TEST_APP_ORG_CLIENT_ID }} | ||
| TEST_APP_ORG_PRIVATE_KEY: ${{ secrets.TEST_APP_ORG_PRIVATE_KEY }} | ||
| TEST_USER_ORG_FG_PAT: ${{ secrets.TEST_USER_ORG_FG_PAT }} | ||
| TEST_USER_USER_FG_PAT: ${{ secrets.TEST_USER_USER_FG_PAT }} | ||
| TEST_USER_PAT: ${{ secrets.TEST_USER_PAT }} | ||
| # Self-test only: a dedicated, NON-SENSITIVE repository secret + variable exist purely to prove | ||
| # the TestData plumbing end to end - the "secrets" entry is masked and the "variables" entry is | ||
| # not, and both are exposed as $env:<name>. Their known values are asserted (value + length) in | ||
| # tests/.../Environment.Tests.ps1. | ||
| # Secrets use the direct "${{ secrets.X }}" form (CodeQL-clean; avoids toJSON(secrets.*)), which | ||
| # requires single-line secret values with no embedded quotes or backslashes; variables use | ||
| # toJSON(vars.X) so any characters are encoded safely. The folded '>-' scalar keeps the whole blob | ||
| # on ONE line so GitHub registers a single mask instead of one per line. | ||
| TestData: >- | ||
| { "secrets": { "PSMODULE_TEST_SINGLELINE_SECRET": "${{ secrets.PSMODULE_TEST_SINGLELINE_SECRET }}" }, | ||
| "variables": { "PSMODULE_TEST_VARIABLE": ${{ toJSON(vars.PSMODULE_TEST_VARIABLE) }} } } | ||
MariusStorhaug marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| with: | ||
| WorkingDirectory: tests/srcTestRepo | ||
| ImportantFilePatterns: | | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.