Skip to content

Commit 5ae2f4c

Browse files
🚀 [Feature]: Add TestVariables input for non-secret test configuration
Adds a TestVariables workflow input (symmetric to TestSecrets) that exposes caller-selected NON-SECRET values as environment variables in the module test jobs, without masking. The expose step is refactored into a shared helper that masks secrets but not variables. Self-tests prove it via a dedicated PSMODULE_TEST_VARIABLE repo variable (asserted value + length). README documents both.
1 parent c501e35 commit 5ae2f4c

9 files changed

Lines changed: 199 additions & 93 deletions

File tree

.github/workflows/AfterAll-ModuleLocal.yml

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ on:
1313
type: string
1414
description: The complete settings object including test suites.
1515
required: true
16+
TestVariables:
17+
type: string
18+
description: |
19+
Optional JSON object mapping environment variable names to NON-SECRET values, exposed as
20+
environment variables (not masked) to the AfterAll teardown script. Built from toJSON(vars.<name>).
21+
required: false
22+
default: ''
1623

1724
permissions:
1825
contents: read # to checkout the repo
@@ -30,34 +37,48 @@ jobs:
3037
persist-credentials: false
3138
fetch-depth: 0
3239

33-
- name: Expose caller-provided test secrets
40+
- name: Expose caller-provided test secrets and variables
3441
shell: pwsh
3542
env:
3643
PSMODULE_TEST_SECRETS: ${{ secrets.TestSecrets }}
44+
PSMODULE_TEST_VARIABLES: ${{ inputs.TestVariables }}
3745
run: |
38-
if ([string]::IsNullOrWhiteSpace($env:PSMODULE_TEST_SECRETS)) {
39-
Write-Host 'No test secrets were provided by the calling workflow.'
40-
return
41-
}
42-
try {
43-
$secrets = $env:PSMODULE_TEST_SECRETS | ConvertFrom-Json -ErrorAction Stop
44-
} catch {
45-
throw "The 'TestSecrets' secret must be a JSON object mapping names to values. $_"
46-
}
47-
foreach ($secret in $secrets.PSObject.Properties) {
48-
$name = $secret.Name
49-
$value = [string]$secret.Value
50-
foreach ($line in ($value -split "`n")) {
51-
$line = $line.TrimEnd("`r")
52-
if ($line.Length -gt 0) {
53-
Write-Host "::add-mask::$line"
46+
function Add-EnvFromJson {
47+
param(
48+
[string] $Json,
49+
[string] $Source,
50+
[switch] $Mask
51+
)
52+
if ([string]::IsNullOrWhiteSpace($Json)) { return }
53+
try {
54+
$items = $Json | ConvertFrom-Json -ErrorAction Stop
55+
} catch {
56+
throw "The '$Source' value must be a JSON object mapping names to values. $_"
57+
}
58+
foreach ($item in $items.PSObject.Properties) {
59+
$name = $item.Name
60+
$value = [string]$item.Value
61+
if ($Mask) {
62+
foreach ($line in ($value -split "`n")) {
63+
$line = $line.TrimEnd("`r")
64+
if ($line.Length -gt 0) {
65+
Write-Host "::add-mask::$line"
66+
}
67+
}
5468
}
69+
$delimiter = "GHENV_$([guid]::NewGuid().ToString('N'))"
70+
Add-Content -Path $env:GITHUB_ENV -Value "$name<<$delimiter"
71+
Add-Content -Path $env:GITHUB_ENV -Value $value
72+
Add-Content -Path $env:GITHUB_ENV -Value $delimiter
73+
Write-Host "Exposed [$name] as an environment variable."
5574
}
56-
$delimiter = "GHENV_$([guid]::NewGuid().ToString('N'))"
57-
Add-Content -Path $env:GITHUB_ENV -Value "$name<<$delimiter"
58-
Add-Content -Path $env:GITHUB_ENV -Value $value
59-
Add-Content -Path $env:GITHUB_ENV -Value $delimiter
60-
Write-Host "Exposed [$name] as an environment variable."
75+
}
76+
77+
Add-EnvFromJson -Json $env:PSMODULE_TEST_SECRETS -Source 'TestSecrets' -Mask
78+
Add-EnvFromJson -Json $env:PSMODULE_TEST_VARIABLES -Source 'TestVariables'
79+
80+
if ([string]::IsNullOrWhiteSpace($env:PSMODULE_TEST_SECRETS) -and [string]::IsNullOrWhiteSpace($env:PSMODULE_TEST_VARIABLES)) {
81+
Write-Host 'No test secrets or variables were provided by the calling workflow.'
6182
}
6283
6384
- name: Run AfterAll Teardown Scripts

.github/workflows/BeforeAll-ModuleLocal.yml

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ on:
1313
type: string
1414
description: The complete settings object including test suites.
1515
required: true
16+
TestVariables:
17+
type: string
18+
description: |
19+
Optional JSON object mapping environment variable names to NON-SECRET values, exposed as
20+
environment variables (not masked) to the BeforeAll setup script. Built from toJSON(vars.<name>).
21+
required: false
22+
default: ''
1623

1724
permissions:
1825
contents: read # to checkout the repo
@@ -30,34 +37,48 @@ jobs:
3037
persist-credentials: false
3138
fetch-depth: 0
3239

33-
- name: Expose caller-provided test secrets
40+
- name: Expose caller-provided test secrets and variables
3441
shell: pwsh
3542
env:
3643
PSMODULE_TEST_SECRETS: ${{ secrets.TestSecrets }}
44+
PSMODULE_TEST_VARIABLES: ${{ inputs.TestVariables }}
3745
run: |
38-
if ([string]::IsNullOrWhiteSpace($env:PSMODULE_TEST_SECRETS)) {
39-
Write-Host 'No test secrets were provided by the calling workflow.'
40-
return
41-
}
42-
try {
43-
$secrets = $env:PSMODULE_TEST_SECRETS | ConvertFrom-Json -ErrorAction Stop
44-
} catch {
45-
throw "The 'TestSecrets' secret must be a JSON object mapping names to values. $_"
46-
}
47-
foreach ($secret in $secrets.PSObject.Properties) {
48-
$name = $secret.Name
49-
$value = [string]$secret.Value
50-
foreach ($line in ($value -split "`n")) {
51-
$line = $line.TrimEnd("`r")
52-
if ($line.Length -gt 0) {
53-
Write-Host "::add-mask::$line"
46+
function Add-EnvFromJson {
47+
param(
48+
[string] $Json,
49+
[string] $Source,
50+
[switch] $Mask
51+
)
52+
if ([string]::IsNullOrWhiteSpace($Json)) { return }
53+
try {
54+
$items = $Json | ConvertFrom-Json -ErrorAction Stop
55+
} catch {
56+
throw "The '$Source' value must be a JSON object mapping names to values. $_"
57+
}
58+
foreach ($item in $items.PSObject.Properties) {
59+
$name = $item.Name
60+
$value = [string]$item.Value
61+
if ($Mask) {
62+
foreach ($line in ($value -split "`n")) {
63+
$line = $line.TrimEnd("`r")
64+
if ($line.Length -gt 0) {
65+
Write-Host "::add-mask::$line"
66+
}
67+
}
5468
}
69+
$delimiter = "GHENV_$([guid]::NewGuid().ToString('N'))"
70+
Add-Content -Path $env:GITHUB_ENV -Value "$name<<$delimiter"
71+
Add-Content -Path $env:GITHUB_ENV -Value $value
72+
Add-Content -Path $env:GITHUB_ENV -Value $delimiter
73+
Write-Host "Exposed [$name] as an environment variable."
5574
}
56-
$delimiter = "GHENV_$([guid]::NewGuid().ToString('N'))"
57-
Add-Content -Path $env:GITHUB_ENV -Value "$name<<$delimiter"
58-
Add-Content -Path $env:GITHUB_ENV -Value $value
59-
Add-Content -Path $env:GITHUB_ENV -Value $delimiter
60-
Write-Host "Exposed [$name] as an environment variable."
75+
}
76+
77+
Add-EnvFromJson -Json $env:PSMODULE_TEST_SECRETS -Source 'TestSecrets' -Mask
78+
Add-EnvFromJson -Json $env:PSMODULE_TEST_VARIABLES -Source 'TestVariables'
79+
80+
if ([string]::IsNullOrWhiteSpace($env:PSMODULE_TEST_SECRETS) -and [string]::IsNullOrWhiteSpace($env:PSMODULE_TEST_VARIABLES)) {
81+
Write-Host 'No test secrets or variables were provided by the calling workflow.'
6182
}
6283
6384
- name: Run BeforeAll Setup Scripts

.github/workflows/Test-ModuleLocal.yml

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ on:
1313
type: string
1414
description: The settings object as a JSON string.
1515
required: true
16+
TestVariables:
17+
type: string
18+
description: |
19+
Optional JSON object mapping environment variable names to NON-SECRET values, exposed as
20+
environment variables (not masked). The caller builds it from toJSON(vars.<name>).
21+
required: false
22+
default: ''
1623

1724
permissions:
1825
contents: read # to checkout the repo and create releases on the repo
@@ -35,34 +42,48 @@ jobs:
3542
persist-credentials: false
3643
fetch-depth: 0
3744

38-
- name: Expose caller-provided test secrets
45+
- name: Expose caller-provided test secrets and variables
3946
shell: pwsh
4047
env:
4148
PSMODULE_TEST_SECRETS: ${{ secrets.TestSecrets }}
49+
PSMODULE_TEST_VARIABLES: ${{ inputs.TestVariables }}
4250
run: |
43-
if ([string]::IsNullOrWhiteSpace($env:PSMODULE_TEST_SECRETS)) {
44-
Write-Host 'No test secrets were provided by the calling workflow.'
45-
return
46-
}
47-
try {
48-
$secrets = $env:PSMODULE_TEST_SECRETS | ConvertFrom-Json -ErrorAction Stop
49-
} catch {
50-
throw "The 'TestSecrets' secret must be a JSON object mapping names to values. $_"
51-
}
52-
foreach ($secret in $secrets.PSObject.Properties) {
53-
$name = $secret.Name
54-
$value = [string]$secret.Value
55-
foreach ($line in ($value -split "`n")) {
56-
$line = $line.TrimEnd("`r")
57-
if ($line.Length -gt 0) {
58-
Write-Host "::add-mask::$line"
51+
function Add-EnvFromJson {
52+
param(
53+
[string] $Json,
54+
[string] $Source,
55+
[switch] $Mask
56+
)
57+
if ([string]::IsNullOrWhiteSpace($Json)) { return }
58+
try {
59+
$items = $Json | ConvertFrom-Json -ErrorAction Stop
60+
} catch {
61+
throw "The '$Source' value must be a JSON object mapping names to values. $_"
62+
}
63+
foreach ($item in $items.PSObject.Properties) {
64+
$name = $item.Name
65+
$value = [string]$item.Value
66+
if ($Mask) {
67+
foreach ($line in ($value -split "`n")) {
68+
$line = $line.TrimEnd("`r")
69+
if ($line.Length -gt 0) {
70+
Write-Host "::add-mask::$line"
71+
}
72+
}
5973
}
74+
$delimiter = "GHENV_$([guid]::NewGuid().ToString('N'))"
75+
Add-Content -Path $env:GITHUB_ENV -Value "$name<<$delimiter"
76+
Add-Content -Path $env:GITHUB_ENV -Value $value
77+
Add-Content -Path $env:GITHUB_ENV -Value $delimiter
78+
Write-Host "Exposed [$name] as an environment variable."
6079
}
61-
$delimiter = "GHENV_$([guid]::NewGuid().ToString('N'))"
62-
Add-Content -Path $env:GITHUB_ENV -Value "$name<<$delimiter"
63-
Add-Content -Path $env:GITHUB_ENV -Value $value
64-
Add-Content -Path $env:GITHUB_ENV -Value $delimiter
65-
Write-Host "Exposed [$name] as an environment variable."
80+
}
81+
82+
Add-EnvFromJson -Json $env:PSMODULE_TEST_SECRETS -Source 'TestSecrets' -Mask
83+
Add-EnvFromJson -Json $env:PSMODULE_TEST_VARIABLES -Source 'TestVariables'
84+
85+
if ([string]::IsNullOrWhiteSpace($env:PSMODULE_TEST_SECRETS) -and [string]::IsNullOrWhiteSpace($env:PSMODULE_TEST_VARIABLES)) {
86+
Write-Host 'No test secrets or variables were provided by the calling workflow.'
6687
}
6788
6889
- name: Download module artifact

.github/workflows/Workflow-Test-Default.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ jobs:
4141
}
4242
with:
4343
WorkingDirectory: tests/srcTestRepo
44+
# Non-secret configuration flows via TestVariables (built from repo/org variables, not masked).
45+
TestVariables: >-
46+
{ "PSMODULE_TEST_VARIABLE": ${{ toJSON(vars.PSMODULE_TEST_VARIABLE) }} }
4447
ImportantFilePatterns: |
4548
^src/
4649
^README\.md$

.github/workflows/Workflow-Test-WithManifest.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ jobs:
4141
}
4242
with:
4343
WorkingDirectory: tests/srcWithManifestTestRepo
44+
# Non-secret configuration flows via TestVariables (built from repo/org variables, not masked).
45+
TestVariables: >-
46+
{ "PSMODULE_TEST_VARIABLE": ${{ toJSON(vars.PSMODULE_TEST_VARIABLE) }} }
4447
ImportantFilePatterns: |
4548
^src/
4649
^README\.md$

.github/workflows/workflow.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,16 @@ on:
5959
default: |
6060
^src/
6161
^README\.md$
62+
TestVariables:
63+
type: string
64+
description: |
65+
Optional JSON object mapping environment variable names to NON-SECRET values that are exposed
66+
to the module test jobs (BeforeAll-ModuleLocal, Test-ModuleLocal, AfterAll-ModuleLocal).
67+
The calling workflow builds it from repository/organization variables using
68+
toJSON(vars.<name>). Unlike TestSecrets, these values are NOT masked. Omit it when the module
69+
needs no non-secret test configuration. See the README for a complete example.
70+
required: false
71+
default: ''
6272

6373
permissions:
6474
contents: write # to checkout the repo and create releases on the repo
@@ -165,6 +175,7 @@ jobs:
165175
- Get-Settings
166176
with:
167177
Settings: ${{ needs.Get-Settings.outputs.Settings }}
178+
TestVariables: ${{ inputs.TestVariables }}
168179

169180
# Runs on:
170181
# - ✅ Open/Updated PR - Tests module in local environment
@@ -182,6 +193,7 @@ jobs:
182193
TestSecrets: ${{ secrets.TestSecrets }}
183194
with:
184195
Settings: ${{ needs.Get-Settings.outputs.Settings }}
196+
TestVariables: ${{ inputs.TestVariables }}
185197

186198
# Runs on:
187199
# - ✅ Open/Updated PR - Runs teardown scripts after local module tests
@@ -198,6 +210,7 @@ jobs:
198210
- Test-ModuleLocal
199211
with:
200212
Settings: ${{ needs.Get-Settings.outputs.Settings }}
213+
TestVariables: ${{ inputs.TestVariables }}
201214

202215
# Runs on:
203216
# - ✅ Open/Updated PR - Collects and reports test results

README.md

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ jobs:
394394
| `Prerelease` | `boolean` | Whether to use a prerelease version of the 'GitHub' module. | `false` | `false` |
395395
| `WorkingDirectory` | `string` | The path to the root of the repo. | `false` | `'.'` |
396396
| `ImportantFilePatterns` | `string` | Newline-separated list of regular expression patterns that identify important files. Changes matching these patterns trigger build, test, and publish stages. When set, fully replaces the defaults. | `false` | `^src/\n^README\.md$` |
397+
| `TestVariables` | `string` | A JSON object mapping environment variable names to non-secret values, exposed (unmasked) as environment variables to the module test jobs. Built by the caller from `toJSON(vars.<name>)`. | `false` | `''` |
397398

398399
### Secrets
399400

@@ -405,13 +406,16 @@ credentials that are exposed. `secrets: inherit` is intentionally not required.
405406
| `APIKey` | GitHub secrets | The API key for the PowerShell Gallery, used to publish the module. | Yes |
406407
| `TestSecrets` | GitHub secrets | A JSON object mapping environment variable names to secret values, exposed to the module test jobs. | No |
407408

408-
#### Passing secrets to the tests
409+
#### Passing secrets and variables to the tests
409410

410-
`TestSecrets` lets a module expose any number of caller-defined secrets to its test jobs
411-
(`BeforeAll-ModuleLocal`, `Test-ModuleLocal` and `AfterAll-ModuleLocal`) without changing the shared
412-
workflow. The calling workflow decides exactly which secrets are passed by building a single-line JSON object.
413-
Use `toJSON(secrets.<name>)` so that quoting and multi-line values (such as private keys) are encoded
414-
correctly. A folded `>-` block keeps the source readable while producing a single-line value:
411+
`TestSecrets` (a secret) and `TestVariables` (a regular input) let a module expose any number of
412+
caller-defined values to its test jobs (`BeforeAll-ModuleLocal`, `Test-ModuleLocal` and
413+
`AfterAll-ModuleLocal`) without changing the shared workflow. Use `TestSecrets` for sensitive values
414+
(masked in the logs) and `TestVariables` for non-secret configuration such as URLs, usernames and
415+
identifiers (not masked). The calling workflow decides exactly what is passed by building single-line
416+
JSON objects. Use `toJSON(secrets.<name>)` / `toJSON(vars.<name>)` so that quoting and multi-line
417+
values (such as private keys) are encoded correctly. A folded `>-` block keeps the source readable
418+
while producing a single-line value:
415419

416420
```yaml
417421
jobs:
@@ -421,30 +425,36 @@ jobs:
421425
APIKey: ${{ secrets.APIKEY }}
422426
TestSecrets: >-
423427
{
424-
"TEST_USER_PAT": ${{ toJSON(secrets.TEST_USER_PAT) }},
425-
"CONFLUENCE_API_KEY": ${{ toJSON(secrets.CONFLUENCE_API_KEY) }}
428+
"CONFLUENCE_API_TOKEN": ${{ toJSON(secrets.CONFLUENCE_API_TOKEN) }}
429+
}
430+
with:
431+
TestVariables: >-
432+
{
433+
"CONFLUENCE_API_BASE_URI": ${{ toJSON(vars.CONFLUENCE_API_BASE_URI) }},
434+
"CONFLUENCE_USERNAME": ${{ toJSON(vars.CONFLUENCE_USERNAME) }},
435+
"CONFLUENCE_SPACE_KEY": ${{ toJSON(vars.CONFLUENCE_SPACE_KEY) }}
426436
}
427437
```
428438

429439
Each entry becomes an environment variable in the test jobs, so the module's Pester tests read the
430440
values directly:
431441

432442
```powershell
433-
$env:TEST_USER_PAT
434-
$env:CONFLUENCE_API_KEY
443+
$env:CONFLUENCE_API_TOKEN # from TestSecrets (masked in logs)
444+
$env:CONFLUENCE_API_BASE_URI # from TestVariables (not masked)
435445
```
436446

437447
Notes:
438448

439-
- The names are entirely caller-defined; no secret names are hard-coded in the shared workflow.
440-
- Every value is masked in the logs (`::add-mask::`).
441-
- Provide the object as a single-line value (the folded `>-` block above does this). Avoid a literal
449+
- The names are entirely caller-defined; no secret or variable names are hard-coded in the shared workflow.
450+
- `TestSecrets` values are masked in the logs (`::add-mask::`); `TestVariables` values are not masked.
451+
- Provide each object as a single-line value (the folded `>-` block above does this). Avoid a literal
442452
`|` block: GitHub registers every line of a multi-line secret as its own mask, which over-masks
443453
unrelated log output.
444-
- Omit `TestSecrets` entirely when the module needs no secrets.
445-
- Because `secrets: inherit` is not used, only the secrets you list are ever exposed.
446-
- Organization and repository secrets are supported. Secrets stored in a GitHub *Environment* are not
447-
exposed by this mechanism.
454+
- Omit `TestSecrets` / `TestVariables` entirely when the module needs no secrets / no non-secret config.
455+
- Because `secrets: inherit` is not used, only the values you list are ever exposed.
456+
- Organization and repository secrets and variables are supported. Secrets stored in a GitHub
457+
*Environment* are not exposed by this mechanism.
448458

449459
### Permissions
450460

0 commit comments

Comments
 (0)