fix: deploy test gate uses a per-run pytest basetemp

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
gti
2026-09-24 10:59:25 -03:00
co-authored by Claude Sonnet 5
parent 40a4d835fe
commit d5d313375c
+80 -78
View File
@@ -1,78 +1,80 @@
# Official production deploy of the GTI Fiscal Hub (10.0.10.117). Run by the OPERATOR, outside Claude Code: # Official production deploy of the GTI Fiscal Hub (10.0.10.117). Run by the OPERATOR, outside Claude Code:
# powershell -ExecutionPolicy Bypass -File .\scripts\deploy_hub.ps1 [-PublishAgent] [-Version 1.1.5] [-SkipTests] [-DryRun] # powershell -ExecutionPolicy Bypass -File .\scripts\deploy_hub.ps1 [-PublishAgent] [-Version 1.1.5] [-SkipTests] [-DryRun]
# SSH password: env GTI_SSH_PASSWORD, else GTI_SSH_PASSWORD=... in .env.local (gitignored). Never printed or logged. # SSH password: env GTI_SSH_PASSWORD, else GTI_SSH_PASSWORD=... in .env.local (gitignored). Never printed or logged.
[CmdletBinding()] [CmdletBinding()]
param( param(
[string]$Version = "", [string]$Version = "",
[switch]$PublishAgent, [switch]$PublishAgent,
[switch]$SkipTests, [switch]$SkipTests,
[switch]$DryRun, [switch]$DryRun,
[string]$Python = "python" [string]$Python = "python"
) )
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
$Root = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path $Root = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
Set-Location $Root Set-Location $Root
$LogDir = Join-Path $Root "logs"; New-Item -ItemType Directory -Force $LogDir | Out-Null $LogDir = Join-Path $Root "logs"; New-Item -ItemType Directory -Force $LogDir | Out-Null
$Log = Join-Path $LogDir ("deploy-{0}.log" -f (Get-Date -Format "yyyyMMdd-HHmmss")) $Log = Join-Path $LogDir ("deploy-{0}.log" -f (Get-Date -Format "yyyyMMdd-HHmmss"))
Start-Transcript -Path $Log | Out-Null Start-Transcript -Path $Log | Out-Null
try { try {
if (-not $Version) { $Version = (Get-Content (Join-Path $Root "release\VERSION") -Raw).Trim() } if (-not $Version) { $Version = (Get-Content (Join-Path $Root "release\VERSION") -Raw).Trim() }
# 3. credential: process env wins, then .env.local # 3. credential: process env wins, then .env.local
$src = "env" $src = "env"
if (-not $env:GTI_SSH_PASSWORD) { if (-not $env:GTI_SSH_PASSWORD) {
$envFile = Join-Path $Root ".env.local" $envFile = Join-Path $Root ".env.local"
if (Test-Path $envFile) { if (Test-Path $envFile) {
$line = Get-Content $envFile | Where-Object { $_ -match '^\s*GTI_SSH_PASSWORD\s*=' } | Select-Object -First 1 $line = Get-Content $envFile | Where-Object { $_ -match '^\s*GTI_SSH_PASSWORD\s*=' } | Select-Object -First 1
if ($line) { $env:GTI_SSH_PASSWORD = ($line -replace '^\s*GTI_SSH_PASSWORD\s*=\s*', '').Trim().Trim('"').Trim("'"); $src = ".env.local" } if ($line) { $env:GTI_SSH_PASSWORD = ($line -replace '^\s*GTI_SSH_PASSWORD\s*=\s*', '').Trim().Trim('"').Trim("'"); $src = ".env.local" }
} }
} }
if (-not $env:GTI_SSH_PASSWORD) { throw "GTI_SSH_PASSWORD not found in the environment or .env.local" } if (-not $env:GTI_SSH_PASSWORD) { throw "GTI_SSH_PASSWORD not found in the environment or .env.local" }
Write-Host "SSH_AUTH_SOURCE=$src PASSWORD_LOGGED=false" Write-Host "SSH_AUTH_SOURCE=$src PASSWORD_LOGGED=false"
# 2. local pre-checks # 2. local pre-checks
git check-ignore -q .env.local git check-ignore -q .env.local
if ($LASTEXITCODE -ne 0 -and (Test-Path ".env.local")) { throw ".env.local is not gitignored" } if ($LASTEXITCODE -ne 0 -and (Test-Path ".env.local")) { throw ".env.local is not gitignored" }
$dirty = git status --porcelain $dirty = git status --porcelain
if ($dirty) { throw "working tree is not clean - commit or stash first:`n$dirty" } if ($dirty) { throw "working tree is not clean - commit or stash first:`n$dirty" }
$branch = git rev-parse --abbrev-ref HEAD; $head = git rev-parse --short HEAD $branch = git rev-parse --abbrev-ref HEAD; $head = git rev-parse --short HEAD
Write-Host "BRANCH=$branch HEAD=$head VERSION=$Version" Write-Host "BRANCH=$branch HEAD=$head VERSION=$Version"
$fileVersion = (Get-Content (Join-Path $Root "release\VERSION") -Raw).Trim() $fileVersion = (Get-Content (Join-Path $Root "release\VERSION") -Raw).Trim()
if ($fileVersion -ne $Version) { throw "release/VERSION is $fileVersion but -Version is $Version" } if ($fileVersion -ne $Version) { throw "release/VERSION is $fileVersion but -Version is $Version" }
if ($PublishAgent) { if ($PublishAgent) {
$exe = Join-Path $Root "dist\installer\GTIFiscalAgent-Setup-$Version.exe" $exe = Join-Path $Root "dist\installer\GTIFiscalAgent-Setup-$Version.exe"
if (-not (Test-Path $exe)) { throw "installer not built: $exe (run scripts\build_windows_installer.ps1)" } if (-not (Test-Path $exe)) { throw "installer not built: $exe (run scripts\build_windows_installer.ps1)" }
} }
if (-not $SkipTests) { if (-not $SkipTests) {
& $Python -m pytest tests central/tests agent/tests -q -p no:cacheprovider # per-run basetemp: some tests ACL-lock their tmp dirs, which breaks pytest's shared default temp root
if ($LASTEXITCODE -ne 0) { throw "tests failed - deploy aborted" } $base = Join-Path $Root (".pytmp␍un-" + (Get-Date -Format "yyyyMMddHHmmss"))
} & $Python -m pytest tests central/tests agent/tests -q -p no:cacheprovider --basetemp=$base
if ($LASTEXITCODE -ne 0) { throw "tests failed - deploy aborted" }
# 4. route/SSH recovery: reconnect the existing Windows VPN profile "GTI" (saved credentials) and retry }
$reachable = $false
for ($i = 1; $i -le 3 -and -not $reachable; $i++) { # 4. route/SSH recovery: reconnect the existing Windows VPN profile "GTI" (saved credentials) and retry
$reachable = (Test-NetConnection 10.0.10.117 -Port 22 -WarningAction SilentlyContinue).TcpTestSucceeded $reachable = $false
if (-not $reachable) { for ($i = 1; $i -le 3 -and -not $reachable; $i++) {
Write-Host "SSH_UNREACHABLE attempt=$i - reconnecting VPN profile GTI" $reachable = (Test-NetConnection 10.0.10.117 -Port 22 -WarningAction SilentlyContinue).TcpTestSucceeded
rasdial GTI | Out-Null if (-not $reachable) {
Start-Sleep -Seconds (10 * $i) Write-Host "SSH_UNREACHABLE attempt=$i - reconnecting VPN profile GTI"
} rasdial GTI | Out-Null
} Start-Sleep -Seconds (10 * $i)
if (-not $reachable) { throw "FAILED_STAGE=network REMOTE_ERROR=10.0.10.117:22 unreachable after 3 attempts (VPN profile GTI)" } }
Write-Host "SSH_ROUTE_OK" }
Remove-Item (Join-Path $LogDir "last_deploy_result.json") -ErrorAction SilentlyContinue if (-not $reachable) { throw "FAILED_STAGE=network REMOTE_ERROR=10.0.10.117:22 unreachable after 3 attempts (VPN profile GTI)" }
Write-Host "SSH_ROUTE_OK"
$deployArgs = @("scripts\deploy_hub_remote.py", "--version", $Version) Remove-Item (Join-Path $LogDir "last_deploy_result.json") -ErrorAction SilentlyContinue
if ($DryRun) { $deployArgs += "--dry-run" }
if ($PublishAgent) { $deployArgs += "--publish-agent" } $deployArgs = @("scripts\deploy_hub_remote.py", "--version", $Version)
& $Python @deployArgs if ($DryRun) { $deployArgs += "--dry-run" }
if ($LASTEXITCODE -ne 0) { if ($PublishAgent) { $deployArgs += "--publish-agent" }
$res = Join-Path $LogDir "last_deploy_result.json" & $Python @deployArgs
$detail = if (Test-Path $res) { $j = Get-Content $res -Raw | ConvertFrom-Json; "FAILED_STAGE/ERROR=$($j.error) ROLLBACK_REQUIRED=$($j.rollback_required)" } else { "no result file (see $Log)" } if ($LASTEXITCODE -ne 0) {
throw "remote deploy failed: $detail" $res = Join-Path $LogDir "last_deploy_result.json"
} $detail = if (Test-Path $res) { $j = Get-Content $res -Raw | ConvertFrom-Json; "FAILED_STAGE/ERROR=$($j.error) ROLLBACK_REQUIRED=$($j.rollback_required)" } else { "no result file (see $Log)" }
Write-Host "DEPLOY_SCRIPT_OK log=$Log" throw "remote deploy failed: $detail"
} finally { }
Remove-Item Env:GTI_SSH_PASSWORD -ErrorAction SilentlyContinue Write-Host "DEPLOY_SCRIPT_OK log=$Log"
Stop-Transcript | Out-Null } finally {
} Remove-Item Env:GTI_SSH_PASSWORD -ErrorAction SilentlyContinue
Stop-Transcript | Out-Null
}