Skip to content

Commit fdb8ae5

Browse files
🚀 [Feature]: Modules can expose caller-selected secrets to test jobs via TestSecrets
Replaces the seven hard-coded TEST_* secrets with a single optional TestSecrets JSON input. The calling workflow decides which org/repo secrets to expose by building a JSON object with toJSON(secrets.<name>); each entry is expanded into an environment variable (multi-line safe and masked) in the BeforeAll/Test/AfterAll ModuleLocal jobs, so tests read them via $env:<name>. No secret names are hard-coded in the shared workflow and secrets: inherit is not required. Closes #52.
1 parent 8b75537 commit fdb8ae5

9 files changed

Lines changed: 201 additions & 152 deletions

File tree

‎.github/workflows/AfterAll-ModuleLocal.yml‎

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,17 @@ name: AfterAll-ModuleLocal
33
on:
44
workflow_call:
55
secrets:
6-
TEST_APP_ENT_CLIENT_ID:
7-
description: The client ID of an Enterprise GitHub App for running tests.
8-
required: false
9-
TEST_APP_ENT_PRIVATE_KEY:
10-
description: The private key of an Enterprise GitHub App for running tests.
11-
required: false
12-
TEST_APP_ORG_CLIENT_ID:
13-
description: The client ID of an Organization GitHub App for running tests.
14-
required: false
15-
TEST_APP_ORG_PRIVATE_KEY:
16-
description: The private key of an Organization GitHub App for running tests.
17-
required: false
18-
TEST_USER_ORG_FG_PAT:
19-
description: The fine-grained personal access token with org access for running tests.
20-
required: false
21-
TEST_USER_USER_FG_PAT:
22-
description: The fine-grained personal access token with user account access for running tests.
23-
required: false
24-
TEST_USER_PAT:
25-
description: The classic personal access token for running tests.
6+
TestSecrets:
7+
description: |
8+
Optional JSON object mapping environment variable names to secret values. Each entry is
9+
exposed as an environment variable available to the AfterAll teardown script.
2610
required: false
2711
inputs:
2812
Settings:
2913
type: string
3014
description: The complete settings object including test suites.
3115
required: true
3216

33-
env:
34-
TEST_APP_ENT_CLIENT_ID: ${{ secrets.TEST_APP_ENT_CLIENT_ID }}
35-
TEST_APP_ENT_PRIVATE_KEY: ${{ secrets.TEST_APP_ENT_PRIVATE_KEY }}
36-
TEST_APP_ORG_CLIENT_ID: ${{ secrets.TEST_APP_ORG_CLIENT_ID }}
37-
TEST_APP_ORG_PRIVATE_KEY: ${{ secrets.TEST_APP_ORG_PRIVATE_KEY }}
38-
TEST_USER_ORG_FG_PAT: ${{ secrets.TEST_USER_ORG_FG_PAT }}
39-
TEST_USER_USER_FG_PAT: ${{ secrets.TEST_USER_USER_FG_PAT }}
40-
TEST_USER_PAT: ${{ secrets.TEST_USER_PAT }}
41-
4217
permissions:
4318
contents: read # to checkout the repo
4419

@@ -55,6 +30,36 @@ jobs:
5530
persist-credentials: false
5631
fetch-depth: 0
5732

33+
- name: Expose caller-provided test secrets
34+
shell: pwsh
35+
env:
36+
PSMODULE_TEST_SECRETS: ${{ secrets.TestSecrets }}
37+
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"
54+
}
55+
}
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."
61+
}
62+
5863
- name: Run AfterAll Teardown Scripts
5964
if: always()
6065
uses: PSModule/GitHub-Script@1ee97bbc652d19c38ae12f6e1e47e9d9fbd12d0a # v1.8.0

‎.github/workflows/BeforeAll-ModuleLocal.yml‎

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,17 @@ name: BeforeAll-ModuleLocal
33
on:
44
workflow_call:
55
secrets:
6-
TEST_APP_ENT_CLIENT_ID:
7-
description: The client ID of an Enterprise GitHub App for running tests.
8-
required: false
9-
TEST_APP_ENT_PRIVATE_KEY:
10-
description: The private key of an Enterprise GitHub App for running tests.
11-
required: false
12-
TEST_APP_ORG_CLIENT_ID:
13-
description: The client ID of an Organization GitHub App for running tests.
14-
required: false
15-
TEST_APP_ORG_PRIVATE_KEY:
16-
description: The private key of an Organization GitHub App for running tests.
17-
required: false
18-
TEST_USER_ORG_FG_PAT:
19-
description: The fine-grained personal access token with org access for running tests.
20-
required: false
21-
TEST_USER_USER_FG_PAT:
22-
description: The fine-grained personal access token with user account access for running tests.
23-
required: false
24-
TEST_USER_PAT:
25-
description: The classic personal access token for running tests.
6+
TestSecrets:
7+
description: |
8+
Optional JSON object mapping environment variable names to secret values. Each entry is
9+
exposed as an environment variable available to the BeforeAll setup script.
2610
required: false
2711
inputs:
2812
Settings:
2913
type: string
3014
description: The complete settings object including test suites.
3115
required: true
3216

33-
env:
34-
TEST_APP_ENT_CLIENT_ID: ${{ secrets.TEST_APP_ENT_CLIENT_ID }}
35-
TEST_APP_ENT_PRIVATE_KEY: ${{ secrets.TEST_APP_ENT_PRIVATE_KEY }}
36-
TEST_APP_ORG_CLIENT_ID: ${{ secrets.TEST_APP_ORG_CLIENT_ID }}
37-
TEST_APP_ORG_PRIVATE_KEY: ${{ secrets.TEST_APP_ORG_PRIVATE_KEY }}
38-
TEST_USER_ORG_FG_PAT: ${{ secrets.TEST_USER_ORG_FG_PAT }}
39-
TEST_USER_USER_FG_PAT: ${{ secrets.TEST_USER_USER_FG_PAT }}
40-
TEST_USER_PAT: ${{ secrets.TEST_USER_PAT }}
41-
4217
permissions:
4318
contents: read # to checkout the repo
4419

@@ -55,6 +30,36 @@ jobs:
5530
persist-credentials: false
5631
fetch-depth: 0
5732

33+
- name: Expose caller-provided test secrets
34+
shell: pwsh
35+
env:
36+
PSMODULE_TEST_SECRETS: ${{ secrets.TestSecrets }}
37+
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"
54+
}
55+
}
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."
61+
}
62+
5863
- name: Run BeforeAll Setup Scripts
5964
uses: PSModule/GitHub-Script@1ee97bbc652d19c38ae12f6e1e47e9d9fbd12d0a # v1.8.0
6065
with:

‎.github/workflows/Test-ModuleLocal.yml‎

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,10 @@ name: Test-ModuleLocal
33
on:
44
workflow_call:
55
secrets:
6-
TEST_APP_ENT_CLIENT_ID:
7-
description: The client ID of an Enterprise GitHub App for running tests.
8-
required: false
9-
TEST_APP_ENT_PRIVATE_KEY:
10-
description: The private key of an Enterprise GitHub App for running tests.
11-
required: false
12-
TEST_APP_ORG_CLIENT_ID:
13-
description: The client ID of an Organization GitHub App for running tests.
14-
required: false
15-
TEST_APP_ORG_PRIVATE_KEY:
16-
description: The private key of an Organization GitHub App for running tests.
17-
required: false
18-
TEST_USER_ORG_FG_PAT:
19-
description: The fine-grained personal access token with org access for running tests.
20-
required: false
21-
TEST_USER_USER_FG_PAT:
22-
description: The fine-grained personal access token with user account access for running tests.
23-
required: false
24-
TEST_USER_PAT:
25-
description: The classic personal access token for running tests.
6+
TestSecrets:
7+
description: |
8+
Optional JSON object mapping environment variable names to secret values. Each entry is
9+
exposed as an environment variable that the module's Pester tests read via $env:<name>.
2610
required: false
2711
inputs:
2812
Settings:
@@ -34,13 +18,6 @@ permissions:
3418
contents: read # to checkout the repo and create releases on the repo
3519

3620
env:
37-
TEST_APP_ENT_CLIENT_ID: ${{ secrets.TEST_APP_ENT_CLIENT_ID }}
38-
TEST_APP_ENT_PRIVATE_KEY: ${{ secrets.TEST_APP_ENT_PRIVATE_KEY }}
39-
TEST_APP_ORG_CLIENT_ID: ${{ secrets.TEST_APP_ORG_CLIENT_ID }}
40-
TEST_APP_ORG_PRIVATE_KEY: ${{ secrets.TEST_APP_ORG_PRIVATE_KEY }}
41-
TEST_USER_ORG_FG_PAT: ${{ secrets.TEST_USER_ORG_FG_PAT }}
42-
TEST_USER_USER_FG_PAT: ${{ secrets.TEST_USER_USER_FG_PAT }}
43-
TEST_USER_PAT: ${{ secrets.TEST_USER_PAT }}
4421
GITHUB_TOKEN: ${{ github.token }}
4522

4623
jobs:
@@ -58,6 +35,36 @@ jobs:
5835
persist-credentials: false
5936
fetch-depth: 0
6037

38+
- name: Expose caller-provided test secrets
39+
shell: pwsh
40+
env:
41+
PSMODULE_TEST_SECRETS: ${{ secrets.TestSecrets }}
42+
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"
59+
}
60+
}
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."
66+
}
67+
6168
- name: Download module artifact
6269
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
6370
with:

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

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,20 @@ jobs:
2828
uses: ./.github/workflows/workflow.yml
2929
secrets:
3030
APIKey: ${{ secrets.APIKey }}
31-
TEST_APP_ENT_CLIENT_ID: ${{ secrets.TEST_APP_ENT_CLIENT_ID }}
32-
TEST_APP_ENT_PRIVATE_KEY: ${{ secrets.TEST_APP_ENT_PRIVATE_KEY }}
33-
TEST_APP_ORG_CLIENT_ID: ${{ secrets.TEST_APP_ORG_CLIENT_ID }}
34-
TEST_APP_ORG_PRIVATE_KEY: ${{ secrets.TEST_APP_ORG_PRIVATE_KEY }}
35-
TEST_USER_ORG_FG_PAT: ${{ secrets.TEST_USER_ORG_FG_PAT }}
36-
TEST_USER_USER_FG_PAT: ${{ secrets.TEST_USER_USER_FG_PAT }}
37-
TEST_USER_PAT: ${{ secrets.TEST_USER_PAT }}
31+
# The calling workflow chooses which secrets to expose to the test jobs. Names are
32+
# caller-defined and read by the module's Pester tests via $env:<name>. CUSTOM_TEST_ENV_VAR
33+
# is a plain literal that proves arbitrary, caller-defined names flow through end to end.
34+
TestSecrets: |
35+
{
36+
"TEST_APP_ENT_CLIENT_ID": ${{ toJSON(secrets.TEST_APP_ENT_CLIENT_ID) }},
37+
"TEST_APP_ENT_PRIVATE_KEY": ${{ toJSON(secrets.TEST_APP_ENT_PRIVATE_KEY) }},
38+
"TEST_APP_ORG_CLIENT_ID": ${{ toJSON(secrets.TEST_APP_ORG_CLIENT_ID) }},
39+
"TEST_APP_ORG_PRIVATE_KEY": ${{ toJSON(secrets.TEST_APP_ORG_PRIVATE_KEY) }},
40+
"TEST_USER_ORG_FG_PAT": ${{ toJSON(secrets.TEST_USER_ORG_FG_PAT) }},
41+
"TEST_USER_USER_FG_PAT": ${{ toJSON(secrets.TEST_USER_USER_FG_PAT) }},
42+
"TEST_USER_PAT": ${{ toJSON(secrets.TEST_USER_PAT) }},
43+
"CUSTOM_TEST_ENV_VAR": "caller-provided-value"
44+
}
3845
with:
3946
WorkingDirectory: tests/srcTestRepo
4047
ImportantFilePatterns: |

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

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,20 @@ jobs:
2828
uses: ./.github/workflows/workflow.yml
2929
secrets:
3030
APIKey: ${{ secrets.APIKey }}
31-
TEST_APP_ENT_CLIENT_ID: ${{ secrets.TEST_APP_ENT_CLIENT_ID }}
32-
TEST_APP_ENT_PRIVATE_KEY: ${{ secrets.TEST_APP_ENT_PRIVATE_KEY }}
33-
TEST_APP_ORG_CLIENT_ID: ${{ secrets.TEST_APP_ORG_CLIENT_ID }}
34-
TEST_APP_ORG_PRIVATE_KEY: ${{ secrets.TEST_APP_ORG_PRIVATE_KEY }}
35-
TEST_USER_ORG_FG_PAT: ${{ secrets.TEST_USER_ORG_FG_PAT }}
36-
TEST_USER_USER_FG_PAT: ${{ secrets.TEST_USER_USER_FG_PAT }}
37-
TEST_USER_PAT: ${{ secrets.TEST_USER_PAT }}
31+
# The calling workflow chooses which secrets to expose to the test jobs. Names are
32+
# caller-defined and read by the module's Pester tests via $env:<name>. CUSTOM_TEST_ENV_VAR
33+
# is a plain literal that proves arbitrary, caller-defined names flow through end to end.
34+
TestSecrets: |
35+
{
36+
"TEST_APP_ENT_CLIENT_ID": ${{ toJSON(secrets.TEST_APP_ENT_CLIENT_ID) }},
37+
"TEST_APP_ENT_PRIVATE_KEY": ${{ toJSON(secrets.TEST_APP_ENT_PRIVATE_KEY) }},
38+
"TEST_APP_ORG_CLIENT_ID": ${{ toJSON(secrets.TEST_APP_ORG_CLIENT_ID) }},
39+
"TEST_APP_ORG_PRIVATE_KEY": ${{ toJSON(secrets.TEST_APP_ORG_PRIVATE_KEY) }},
40+
"TEST_USER_ORG_FG_PAT": ${{ toJSON(secrets.TEST_USER_ORG_FG_PAT) }},
41+
"TEST_USER_USER_FG_PAT": ${{ toJSON(secrets.TEST_USER_USER_FG_PAT) }},
42+
"TEST_USER_PAT": ${{ toJSON(secrets.TEST_USER_PAT) }},
43+
"CUSTOM_TEST_ENV_VAR": "caller-provided-value"
44+
}
3845
with:
3946
WorkingDirectory: tests/srcWithManifestTestRepo
4047
ImportantFilePatterns: |

‎.github/workflows/workflow.yml‎

Lines changed: 12 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,15 @@ on:
66
APIKey:
77
description: The API key for the PowerShell Gallery.
88
required: true
9-
TEST_APP_ENT_CLIENT_ID:
10-
description: The client ID of an Enterprise GitHub App for running tests.
11-
required: false
12-
TEST_APP_ENT_PRIVATE_KEY:
13-
description: The private key of an Enterprise GitHub App for running tests.
14-
required: false
15-
TEST_APP_ORG_CLIENT_ID:
16-
description: The client ID of an Organization GitHub App for running tests.
17-
required: false
18-
TEST_APP_ORG_PRIVATE_KEY:
19-
description: The private key of an Organization GitHub App for running tests.
20-
required: false
21-
TEST_USER_ORG_FG_PAT:
22-
description: The fine-grained personal access token with org access for running tests.
23-
required: false
24-
TEST_USER_USER_FG_PAT:
25-
description: The fine-grained personal access token with user account access for running tests.
26-
required: false
27-
TEST_USER_PAT:
28-
description: The classic personal access token for running tests.
9+
TestSecrets:
10+
description: |
11+
Optional JSON object mapping environment variable names to secret values that are exposed to
12+
the module test jobs (BeforeAll-ModuleLocal, Test-ModuleLocal, AfterAll-ModuleLocal).
13+
The calling workflow decides which secrets to provide and builds the JSON using
14+
toJSON(secrets.<name>) so that quoting and multi-line values are encoded correctly. Each
15+
entry is exposed as an environment variable that the module's Pester tests read via
16+
$env:<name>. Values are masked in the logs. Omit it entirely when the module needs no
17+
secrets. See the README for a complete example.
2918
required: false
3019
inputs:
3120
SettingsPath:
@@ -168,13 +157,7 @@ jobs:
168157
if: fromJson(needs.Get-Settings.outputs.Settings).Run.BeforeAllModuleLocal && needs.Build-Module.result == 'success' && !cancelled()
169158
uses: ./.github/workflows/BeforeAll-ModuleLocal.yml
170159
secrets:
171-
TEST_APP_ENT_CLIENT_ID: ${{ secrets.TEST_APP_ENT_CLIENT_ID }}
172-
TEST_APP_ENT_PRIVATE_KEY: ${{ secrets.TEST_APP_ENT_PRIVATE_KEY }}
173-
TEST_APP_ORG_CLIENT_ID: ${{ secrets.TEST_APP_ORG_CLIENT_ID }}
174-
TEST_APP_ORG_PRIVATE_KEY: ${{ secrets.TEST_APP_ORG_PRIVATE_KEY }}
175-
TEST_USER_ORG_FG_PAT: ${{ secrets.TEST_USER_ORG_FG_PAT }}
176-
TEST_USER_USER_FG_PAT: ${{ secrets.TEST_USER_USER_FG_PAT }}
177-
TEST_USER_PAT: ${{ secrets.TEST_USER_PAT }}
160+
TestSecrets: ${{ secrets.TestSecrets }}
178161
needs:
179162
- Build-Module
180163
- Get-Settings
@@ -194,13 +177,7 @@ jobs:
194177
- BeforeAll-ModuleLocal
195178
uses: ./.github/workflows/Test-ModuleLocal.yml
196179
secrets:
197-
TEST_APP_ENT_CLIENT_ID: ${{ secrets.TEST_APP_ENT_CLIENT_ID }}
198-
TEST_APP_ENT_PRIVATE_KEY: ${{ secrets.TEST_APP_ENT_PRIVATE_KEY }}
199-
TEST_APP_ORG_CLIENT_ID: ${{ secrets.TEST_APP_ORG_CLIENT_ID }}
200-
TEST_APP_ORG_PRIVATE_KEY: ${{ secrets.TEST_APP_ORG_PRIVATE_KEY }}
201-
TEST_USER_ORG_FG_PAT: ${{ secrets.TEST_USER_ORG_FG_PAT }}
202-
TEST_USER_USER_FG_PAT: ${{ secrets.TEST_USER_USER_FG_PAT }}
203-
TEST_USER_PAT: ${{ secrets.TEST_USER_PAT }}
180+
TestSecrets: ${{ secrets.TestSecrets }}
204181
with:
205182
Settings: ${{ needs.Get-Settings.outputs.Settings }}
206183

@@ -213,13 +190,7 @@ jobs:
213190
if: fromJson(needs.Get-Settings.outputs.Settings).Run.AfterAllModuleLocal && needs.Test-ModuleLocal.result != 'skipped' && always()
214191
uses: ./.github/workflows/AfterAll-ModuleLocal.yml
215192
secrets:
216-
TEST_APP_ENT_CLIENT_ID: ${{ secrets.TEST_APP_ENT_CLIENT_ID }}
217-
TEST_APP_ENT_PRIVATE_KEY: ${{ secrets.TEST_APP_ENT_PRIVATE_KEY }}
218-
TEST_APP_ORG_CLIENT_ID: ${{ secrets.TEST_APP_ORG_CLIENT_ID }}
219-
TEST_APP_ORG_PRIVATE_KEY: ${{ secrets.TEST_APP_ORG_PRIVATE_KEY }}
220-
TEST_USER_ORG_FG_PAT: ${{ secrets.TEST_USER_ORG_FG_PAT }}
221-
TEST_USER_USER_FG_PAT: ${{ secrets.TEST_USER_USER_FG_PAT }}
222-
TEST_USER_PAT: ${{ secrets.TEST_USER_PAT }}
193+
TestSecrets: ${{ secrets.TestSecrets }}
223194
needs:
224195
- Get-Settings
225196
- Test-ModuleLocal

0 commit comments

Comments
 (0)