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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions .github/workflows/Process-PSModule.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
16 changes: 16 additions & 0 deletions .github/workflows/env-probe-mid.yml
Original file line number Diff line number Diff line change
@@ -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
35 changes: 35 additions & 0 deletions .github/workflows/env-probe-reusable.yml
Original file line number Diff line number Diff line change
@@ -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))"
53 changes: 53 additions & 0 deletions .github/workflows/env-probe.yml
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions src/functions/public/Get-PSModuleTest.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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!"
}
30 changes: 19 additions & 11 deletions tests/Environment.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
Loading