Skip to content

Commit a0610ec

Browse files
committed
Updating masking for integration tests
1 parent 6018e00 commit a0610ec

4 files changed

Lines changed: 184 additions & 57 deletions

File tree

tests/integration/all-resource-types/Deploy-SourceApim.ps1

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -164,15 +164,16 @@ $azReplacements = @{
164164
$ResourceGroupName = Protect-ResourceGroupName -Value $ResourceGroupName
165165
}
166166

167-
$raw = Invoke-MaskedAzCommand -Replacements $azReplacements -Command {
168-
az deployment group create `
169-
--resource-group $ResourceGroupName `
170-
--name $deploymentName `
171-
--template-file $bicepFile `
172-
--parameters skuName=$SkuName location=$Location publisherEmail=$PublisherEmail `
173-
--output json `
174-
@azVerbosity
175-
}
167+
$azArgs = @(
168+
'deployment', 'group', 'create',
169+
'--resource-group', $ResourceGroupName,
170+
'--name', $deploymentName,
171+
'--template-file', $bicepFile,
172+
'--parameters', "skuName=$SkuName", "location=$Location", "publisherEmail=$PublisherEmail",
173+
'--output', 'json'
174+
) + $azVerbosity
175+
176+
$raw = Invoke-MaskedAzCommand -Replacements $azReplacements -Arguments $azArgs
176177

177178
$result = $raw | ConvertFrom-Json
178179

tests/integration/all-resource-types/Deploy-TargetApim.ps1

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,16 @@ $azReplacements = @{
7979
$ResourceGroupName = Protect-ResourceGroupName -Value $ResourceGroupName
8080
}
8181

82-
$raw = Invoke-MaskedAzCommand -Replacements $azReplacements -Command {
83-
az deployment group create `
84-
--resource-group $ResourceGroupName `
85-
--name "target-apim-$(Get-Date -Format 'yyyyMMddHHmmss')" `
86-
--template-file $bicepFile `
87-
--parameters skuName=$SkuName location=$Location publisherEmail=$PublisherEmail `
88-
--output json `
89-
@azVerbosity
90-
}
82+
$azArgs = @(
83+
'deployment', 'group', 'create',
84+
'--resource-group', $ResourceGroupName,
85+
'--name', "target-apim-$(Get-Date -Format 'yyyyMMddHHmmss')",
86+
'--template-file', $bicepFile,
87+
'--parameters', "skuName=$SkuName", "location=$Location", "publisherEmail=$PublisherEmail",
88+
'--output', 'json'
89+
) + $azVerbosity
90+
91+
$raw = Invoke-MaskedAzCommand -Replacements $azReplacements -Arguments $azArgs
9192

9293
if ($LASTEXITCODE -ne 0) {
9394
throw "Target APIM deployment failed"

tests/integration/all-resource-types/MaskingHelpers.psm1

Lines changed: 147 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -137,49 +137,174 @@ function Protect-LogLine {
137137
return $protectedLine
138138
}
139139

140+
# Native-process invocation with masked output. Uses System.Diagnostics.Process
141+
# with redirected stdout/stderr so the child's raw bytes bypass PowerShell's
142+
# ErrorRecord promotion (which would otherwise leak unmasked to Start-Transcript).
143+
144+
# Windows npm shims (e.g. npx.cmd) need cmd.exe; *nix shebang scripts launch directly.
145+
function Resolve-NativeExecutable {
146+
param([string]$Name)
147+
148+
$resolved = Get-Command -Name $Name -CommandType Application -ErrorAction Stop |
149+
Select-Object -First 1
150+
151+
$exePath = $resolved.Source
152+
$prefix = @()
153+
154+
if ($IsWindows -and ($exePath -like '*.cmd' -or $exePath -like '*.bat')) {
155+
$prefix = @('/c', $exePath)
156+
$exePath = $env:ComSpec
157+
}
158+
159+
return [pscustomobject]@{ FilePath = $exePath; Prefix = $prefix }
160+
}
161+
162+
# Stderr is always masked + streamed. Stdout is either captured (and returned)
163+
# or masked + streamed. Sets $global:LASTEXITCODE.
164+
function Invoke-MaskedProcess {
165+
[CmdletBinding()]
166+
param(
167+
[Parameter(Mandatory)][string]$FilePath,
168+
[string[]]$Arguments = @(),
169+
[hashtable]$Replacements,
170+
[switch]$CaptureStdout
171+
)
172+
173+
$exe = Resolve-NativeExecutable -Name $FilePath
174+
$finalArgs = @() + $exe.Prefix + $Arguments
175+
176+
$psi = [System.Diagnostics.ProcessStartInfo]::new()
177+
$psi.FileName = $exe.FilePath
178+
$psi.RedirectStandardOutput = $true
179+
$psi.RedirectStandardError = $true
180+
$psi.RedirectStandardInput = $false
181+
$psi.UseShellExecute = $false
182+
$psi.CreateNoWindow = $true
183+
# Default OEM encoding on Windows mangles `az --debug` output.
184+
$psi.StandardOutputEncoding = [System.Text.Encoding]::UTF8
185+
$psi.StandardErrorEncoding = [System.Text.Encoding]::UTF8
186+
187+
foreach ($a in $finalArgs) {
188+
[void]$psi.ArgumentList.Add([string]$a)
189+
}
190+
191+
$stdoutQueue = [System.Collections.Concurrent.ConcurrentQueue[string]]::new()
192+
$stderrQueue = [System.Collections.Concurrent.ConcurrentQueue[string]]::new()
193+
194+
$proc = [System.Diagnostics.Process]::new()
195+
$proc.StartInfo = $psi
196+
197+
# Allocate explicitly — `$x = if (...) { [List[T]]::new() }` returns $null
198+
# because PowerShell enumerates the empty list.
199+
$stdoutBuffer = $null
200+
if ($CaptureStdout) {
201+
$stdoutBuffer = [System.Collections.Generic.List[string]]::new()
202+
}
203+
204+
# Use ThreadJob readers rather than Register-ObjectEvent: Action handlers
205+
# don't drain reliably while the main runspace is in Start-Sleep.
206+
$readerScript = {
207+
param([System.IO.StreamReader]$Reader,
208+
[System.Collections.Concurrent.ConcurrentQueue[string]]$Queue)
209+
try {
210+
while ($null -ne ($line = $Reader.ReadLine())) {
211+
[void]$Queue.Enqueue($line)
212+
}
213+
} catch {
214+
# IO errors on process teardown are expected; main thread owns exit.
215+
}
216+
}
217+
218+
$outJob = $null
219+
$errJob = $null
220+
221+
try {
222+
[void]$proc.Start()
223+
224+
$outJob = Start-ThreadJob -ScriptBlock $readerScript -ArgumentList $proc.StandardOutput, $stdoutQueue
225+
$errJob = Start-ThreadJob -ScriptBlock $readerScript -ArgumentList $proc.StandardError, $stderrQueue
226+
227+
$line = $null
228+
while (-not $proc.HasExited -or
229+
$outJob.State -eq 'Running' -or
230+
$errJob.State -eq 'Running') {
231+
Start-Sleep -Milliseconds 100
232+
while ($stderrQueue.TryDequeue([ref]$line)) {
233+
Write-Host (Protect-LogLine -Line $line -Replacements $Replacements)
234+
}
235+
while ($stdoutQueue.TryDequeue([ref]$line)) {
236+
if ($CaptureStdout) {
237+
[void]$stdoutBuffer.Add($line)
238+
} else {
239+
Write-Host (Protect-LogLine -Line $line -Replacements $Replacements)
240+
}
241+
}
242+
}
243+
244+
# Readers exit on EOF once the child closes its pipes.
245+
Wait-Job -Job $outJob, $errJob | Out-Null
246+
247+
while ($stderrQueue.TryDequeue([ref]$line)) {
248+
Write-Host (Protect-LogLine -Line $line -Replacements $Replacements)
249+
}
250+
while ($stdoutQueue.TryDequeue([ref]$line)) {
251+
if ($CaptureStdout) {
252+
[void]$stdoutBuffer.Add($line)
253+
} else {
254+
Write-Host (Protect-LogLine -Line $line -Replacements $Replacements)
255+
}
256+
}
257+
258+
$global:LASTEXITCODE = $proc.ExitCode
259+
}
260+
finally {
261+
if ($outJob) { Remove-Job -Job $outJob -Force -ErrorAction SilentlyContinue }
262+
if ($errJob) { Remove-Job -Job $errJob -Force -ErrorAction SilentlyContinue }
263+
$proc.Dispose()
264+
}
265+
266+
if ($CaptureStdout) {
267+
return ($stdoutBuffer -join "`n")
268+
}
269+
}
270+
271+
# Run `npx apiops <args>` masking ALL output streams. Returns the exit code.
140272
function Invoke-MaskedApiopsCommand {
273+
[CmdletBinding()]
141274
param(
142-
[scriptblock]$Command,
275+
[Parameter(Mandatory)][string[]]$Arguments,
143276
[hashtable]$Replacements
144277
)
145278

146-
& $Command 2>&1 | ForEach-Object {
147-
$message = $_.ToString()
148-
Write-Host (Protect-LogLine -Line $message -Replacements $Replacements)
149-
}
279+
Invoke-MaskedProcess -FilePath 'npx' `
280+
-Arguments (@('apiops') + $Arguments) `
281+
-Replacements $Replacements
150282

151283
return $LASTEXITCODE
152284
}
153285

154-
# Invoke a native command (typically `az`) capturing its stdout for return while
155-
# streaming stderr through Protect-LogLine so verbose/debug output cannot leak
156-
# secrets (subscription id, resource group name, etc.). Returns stdout as a
286+
# Run `az <args>` capturing stdout (typically JSON) for the caller while
287+
# masking and streaming stderr (verbose/debug output). Returns stdout as a
157288
# single string. Sets/preserves $LASTEXITCODE for the caller.
158289
function Invoke-MaskedAzCommand {
290+
[CmdletBinding()]
159291
param(
160-
[scriptblock]$Command,
292+
[Parameter(Mandatory)][string[]]$Arguments,
161293
[hashtable]$Replacements
162294
)
163295

164-
$stdoutLines = New-Object System.Collections.Generic.List[string]
165-
166-
& $Command 2>&1 | ForEach-Object {
167-
if ($_ -is [System.Management.Automation.ErrorRecord]) {
168-
$message = $_.Exception.Message
169-
if ([string]::IsNullOrEmpty($message)) { $message = $_.ToString() }
170-
Write-Host (Protect-LogLine -Line $message -Replacements $Replacements)
171-
} else {
172-
$stdoutLines.Add([string]$_)
173-
}
174-
}
175-
176-
return ($stdoutLines -join "`n")
296+
return Invoke-MaskedProcess -FilePath 'az' `
297+
-Arguments $Arguments `
298+
-Replacements $Replacements `
299+
-CaptureStdout
177300
}
178301

179302
Export-ModuleMember -Function `
180303
Protect-Identifier, `
181304
Protect-SubscriptionId, `
182305
Protect-ResourceGroupName, `
183306
Protect-LogLine, `
307+
Resolve-NativeExecutable, `
308+
Invoke-MaskedProcess, `
184309
Invoke-MaskedApiopsCommand, `
185310
Invoke-MaskedAzCommand

tests/integration/all-resource-types/run-roundtrip-test.ps1

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -471,14 +471,14 @@ try {
471471
$extractExitCode = Invoke-MaskedApiopsCommand -Replacements @{
472472
$sourceSubId = Protect-SubscriptionId -Value $sourceSubId
473473
$sourceRg = Protect-ResourceGroupName -Value $sourceRg
474-
} -Command {
475-
npx apiops extract `
476-
--subscription-id $sourceSubId `
477-
--resource-group $sourceRg `
478-
--service-name $sourceName `
479-
--output $ExtractOutputDir `
480-
--log-level $apiopsLogLevel
481-
}
474+
} -Arguments @(
475+
'extract',
476+
'--subscription-id', $sourceSubId,
477+
'--resource-group', $sourceRg,
478+
'--service-name', $sourceName,
479+
'--output', $ExtractOutputDir,
480+
'--log-level', $apiopsLogLevel
481+
)
482482

483483
if ($extractExitCode -ne 0) {
484484
Write-Host "❌ Extract failed (exit code $extractExitCode)"
@@ -606,15 +606,15 @@ loggers:
606606
$publishExitCode = Invoke-MaskedApiopsCommand -Replacements @{
607607
$targetSubId = Protect-SubscriptionId -Value $targetSubId
608608
$targetRg = Protect-ResourceGroupName -Value $targetRg
609-
} -Command {
610-
npx apiops publish `
611-
--subscription-id $targetSubId `
612-
--resource-group $targetRg `
613-
--service-name $targetName `
614-
--source $ExtractOutputDir `
615-
--overrides $overrideFile `
616-
--log-level $apiopsLogLevel
617-
}
609+
} -Arguments @(
610+
'publish',
611+
'--subscription-id', $targetSubId,
612+
'--resource-group', $targetRg,
613+
'--service-name', $targetName,
614+
'--source', $ExtractOutputDir,
615+
'--overrides', $overrideFile,
616+
'--log-level', $apiopsLogLevel
617+
)
618618

619619
if ($publishExitCode -ne 0) {
620620
Write-Host "❌ Publish failed (exit code $publishExitCode)"

0 commit comments

Comments
 (0)