diff --git a/.github/workflows/Process-PSModule.yml b/.github/workflows/Process-PSModule.yml index 0aee102..9c157c3 100644 --- a/.github/workflows/Process-PSModule.yml +++ b/.github/workflows/Process-PSModule.yml @@ -27,15 +27,14 @@ permissions: jobs: Process-PSModule: - uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@d4020f3c9c3621cebae5d8bf4ffaf10f55c1784a # v6.1.2 + # SPIKE: pinned to the Process-PSModule branch that adds the 'Environment' input. + # MariusStorhaug (personal) -> PSModule (org) is CROSS-OWNER, and `secrets: inherit` + # only works within the same org/enterprise, so we must pass APIKey explicitly. That + # closes the secrets context, so environment SECRETS cannot flow here (same-org + # consumers using secrets: inherit do get them). Environment VARIABLES still flow via + # the vars context once the test jobs bind to the environment. + uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@spike/environment-support secrets: APIKey: ${{ secrets.APIKey }} - TestData: >- - { - "secrets": { - "TEST_SECRET": "${{ secrets.TEST_SECRET }}" - }, - "variables": { - "TEST_VARIABLE": "${{ vars.TEST_VARIABLE }}" - } - } + with: + Environment: Testing diff --git a/.github/workflows/env-probe-mid.yml b/.github/workflows/env-probe-mid.yml new file mode 100644 index 0000000..78a94bb --- /dev/null +++ b/.github/workflows/env-probe-mid.yml @@ -0,0 +1,16 @@ +name: env-probe-mid + +# SPIKE probe: intermediate reusable workflow that does NOT bind an environment; it +# only forwards secrets down with `secrets: inherit`. Mirrors Process-PSModule's +# workflow.yml sitting between the caller and the environment-bound leaf job. + +on: + workflow_call: + +permissions: + contents: read + +jobs: + mid: + uses: ./.github/workflows/env-probe-reusable.yml + secrets: inherit diff --git a/.github/workflows/env-probe-reusable.yml b/.github/workflows/env-probe-reusable.yml new file mode 100644 index 0000000..0b9641a --- /dev/null +++ b/.github/workflows/env-probe-reusable.yml @@ -0,0 +1,35 @@ +name: env-probe-reusable + +# SPIKE probe: a reusable workflow whose job binds to the `Testing` environment. +# Determines whether an environment SECRET is reachable inside a reusable workflow, +# both by enumeration (toJSON(secrets)) and by explicit declared name. + +on: + workflow_call: + secrets: + TEST_ENV_SECRET: + description: Declared so it can be referenced by name; the environment value should override. + required: false + +permissions: + contents: read + +jobs: + reusable-probe: + runs-on: ubuntu-latest + environment: Testing + steps: + - name: Probe contexts inside a reusable workflow bound to the environment + shell: pwsh + env: + ALL_SECRETS: ${{ toJSON(secrets) }} + ALL_VARS: ${{ toJSON(vars) }} + NAMED_DECLARED_SECRET: ${{ secrets.TEST_ENV_SECRET }} + NAMED_ENV_VAR: ${{ vars.TEST_ENV_VARIABLE }} + run: | + $s = $env:ALL_SECRETS | ConvertFrom-Json + $v = $env:ALL_VARS | ConvertFrom-Json + Write-Host "REUSABLE secrets keys: $((@($s.PSObject.Properties.Name) | Sort-Object) -join ', ')" + Write-Host "REUSABLE vars keys: $((@($v.PSObject.Properties.Name) | Sort-Object) -join ', ')" + Write-Host "REUSABLE named declared secret (TEST_ENV_SECRET) present: $(-not [string]::IsNullOrEmpty($env:NAMED_DECLARED_SECRET))" + Write-Host "REUSABLE named env var (TEST_ENV_VARIABLE) present: $(-not [string]::IsNullOrEmpty($env:NAMED_ENV_VAR))" diff --git a/.github/workflows/env-probe.yml b/.github/workflows/env-probe.yml new file mode 100644 index 0000000..a29319a --- /dev/null +++ b/.github/workflows/env-probe.yml @@ -0,0 +1,53 @@ +name: env-probe + +# SPIKE probe: compares how an environment's secret/variable are seen by a DIRECT +# job vs a REUSABLE workflow job, both bound to the `Testing` environment. +# Runs on push to the spike branch (workflow_dispatch needs the file on the default branch). + +on: + push: + branches: + - spike/environment-testdata + workflow_dispatch: + +permissions: + contents: read + +jobs: + # Baseline: a normal (non-reusable) job binds to an environment directly. + direct-probe: + runs-on: ubuntu-latest + environment: Testing + steps: + - name: Probe contexts in a direct job bound to the environment + shell: pwsh + env: + ALL_SECRETS: ${{ toJSON(secrets) }} + ALL_VARS: ${{ toJSON(vars) }} + NAMED_SECRET: ${{ secrets.TEST_ENV_SECRET }} + NAMED_VAR: ${{ vars.TEST_ENV_VARIABLE }} + run: | + $s = $env:ALL_SECRETS | ConvertFrom-Json + $v = $env:ALL_VARS | ConvertFrom-Json + Write-Host "DIRECT secrets keys: $((@($s.PSObject.Properties.Name) | Sort-Object) -join ', ')" + Write-Host "DIRECT vars keys: $((@($v.PSObject.Properties.Name) | Sort-Object) -join ', ')" + Write-Host "DIRECT named env secret present: $(-not [string]::IsNullOrEmpty($env:NAMED_SECRET))" + Write-Host "DIRECT named env var present: $(-not [string]::IsNullOrEmpty($env:NAMED_VAR))" + + # The real question: does a reusable workflow bound to the environment get the env + # secret WITHOUT the caller passing it (the caller cannot, being a `uses:` job)? + reusable-no-pass: + uses: ./.github/workflows/env-probe-reusable.yml + # deliberately passes nothing + + # Does secrets: inherit change anything? (The caller job cannot bind to an + # environment, so inherited secrets should NOT include the environment secret.) + reusable-inherit: + uses: ./.github/workflows/env-probe-reusable.yml + secrets: inherit + + # 3-level nested chain mirroring Process-PSModule (caller -> workflow.yml -> + # Test-ModuleLocal): inherit at every hop, environment bound only on the leaf. + reusable-3level: + uses: ./.github/workflows/env-probe-mid.yml + secrets: inherit diff --git a/src/functions/public/Get-PSModuleTest.ps1 b/src/functions/public/Get-PSModuleTest.ps1 index fe4d4cc..78451b3 100644 --- a/src/functions/public/Get-PSModuleTest.ps1 +++ b/src/functions/public/Get-PSModuleTest.ps1 @@ -22,5 +22,7 @@ [Parameter(Mandatory)] [string] $Name ) + # SPIKE: touched so the change matches the ^src/ important-file pattern and the + # module build + local test jobs run for this pull request. Write-Output "Hello, $Name!" } diff --git a/tests/Environment.Tests.ps1 b/tests/Environment.Tests.ps1 index 3b90a6b..164d8ab 100644 --- a/tests/Environment.Tests.ps1 +++ b/tests/Environment.Tests.ps1 @@ -13,21 +13,29 @@ [CmdletBinding()] param() -Describe 'TestData is pushed into the module tests' { - It 'Exposes the secret from the "secrets" map' { - $actual = [System.Environment]::GetEnvironmentVariable('TEST_SECRET') +Describe 'Environment-scoped test data reaches the module tests' { + # This module is called CROSS-OWNER (MariusStorhaug personal account -> PSModule org). + # GitHub only allows `secrets: inherit` within the same org/enterprise, so environment + # SECRETS cannot flow to a cross-owner consumer. Environment VARIABLES flow regardless, + # via the vars context, once the reusable workflow binds the test job to the environment. + # TEST_ENV_VARIABLE exists ONLY in the "Testing" environment (not at repo level), so + # seeing it here proves it was sourced from the environment. + It 'Exposes the environment variable as $env:TEST_ENV_VARIABLE' { + $actual = [System.Environment]::GetEnvironmentVariable('TEST_ENV_VARIABLE') $actual | Should -Not -BeNullOrEmpty - $actual | Should -BeExactly 'mariustestmodule-secret-fixture-value' + $actual | Should -BeExactly 'env-scoped-variable-fixture-value' } - It 'Exposes the variable from the "variables" map' { - $actual = [System.Environment]::GetEnvironmentVariable('TEST_VARIABLE') - $actual | Should -Not -BeNullOrEmpty - $actual | Should -BeExactly 'mariustestmodule-variable-fixture-value' + # Environment secrets require `secrets: inherit` end-to-end, which is unavailable + # cross-owner. A SAME-ORG consumer would drop the -Skip and this would pass (proven by + # the env-probe* workflows in this repo, which show the secret reaching a reusable + # workflow that is called with secrets: inherit and bound to the environment). + It 'Exposes the environment secret as $env:TEST_ENV_SECRET (same-org callers only)' -Skip { + $actual = [System.Environment]::GetEnvironmentVariable('TEST_ENV_SECRET') + $actual | Should -BeExactly 'env-scoped-secret-fixture-value' } - It 'Masks the secret in the log even when a test prints it in plain text' { - Write-Host "Secret in plain text: $env:TEST_SECRET" - Write-Host "Variable in plain text: $env:TEST_VARIABLE" + It 'Shows the environment variable in the log' { + Write-Host "Variable in plain text: $env:TEST_ENV_VARIABLE" } }