feat: Enhance asset upload process with Python script for reliability and retry logic

This commit is contained in:
claudi 2026-03-12 13:59:33 +01:00
parent ef96184dc3
commit 1de604e7e2

View file

@ -165,24 +165,123 @@ if (Test-Path $manifestOutput) {
$artifactPaths.Add($manifestOutput)
$assetMap = Get-AssetMap -Assets $releaseInfo.assets
$curlAuth = "$ForgejoUser`:$ForgejoPW"
$uploadUrl = "$ForgejoUrl/api/v1/repos/$Repo/releases/$releaseId/assets"
$artifactsToUpload = New-Object System.Collections.Generic.List[string]
foreach ($artifact in $artifactPaths) {
$assetName = [System.IO.Path]::GetFileName($artifact)
if ($assetMap.ContainsKey($assetName)) {
$existingAsset = $assetMap[$assetName]
Invoke-RestMethod -Uri "$ForgejoUrl/api/v1/repos/$Repo/releases/$releaseId/assets/$($existingAsset.id)" -Method DELETE -Headers $headers -TimeoutSec 30 | Out-Null
Write-Host "[OK] Replaced existing asset $assetName" -ForegroundColor Yellow
$extension = [System.IO.Path]::GetExtension($artifact).ToLowerInvariant()
if ($extension -eq ".msi" -and $assetMap.ContainsKey($assetName)) {
$localSize = (Get-Item $artifact).Length
$remoteSize = [int64]$assetMap[$assetName].size
if ($localSize -eq $remoteSize) {
Write-Host "[OK] Skipping already uploaded MSI $assetName ($([math]::Round($localSize / 1MB, 2)) MB)" -ForegroundColor Cyan
continue
}
}
$response = curl.exe -s -X POST -u $curlAuth -F "attachment=@$artifact" $uploadUrl
if ($response -like "*error*" -or $response -like "*404*") {
Write-Host "WARNING: Could not upload $artifact : $response" -ForegroundColor Yellow
$artifactsToUpload.Add($artifact)
}
else {
Write-Host "[OK] Uploaded $assetName" -ForegroundColor Green
if ($artifactsToUpload.Count -eq 0) {
Write-Host "[OK] All release assets already uploaded." -ForegroundColor Green
Write-Host "View at: $ForgejoUrl/$Repo/releases/tag/v$Version" -ForegroundColor Cyan
exit 0
}
# Use Python requests library for more reliable large file uploads
$pythonUploadScript = @"
import sys
import requests
from requests.auth import HTTPBasicAuth
from pathlib import Path
import time
upload_url = sys.argv[1]
artifacts = sys.argv[2:]
username = '$ForgejoUser'
password = '$ForgejoPW'
delete_url_template = '${ForgejoUrl}/api/v1/repos/${Repo}/releases/$releaseId/assets/{}'
release_info_url = '${ForgejoUrl}/api/v1/repos/${Repo}/releases/$releaseId'
session = requests.Session()
session.auth = HTTPBasicAuth(username, password)
session.headers.update({'Connection': 'close'})
def upload_with_retry(artifact_path, max_retries=3):
asset_name = Path(artifact_path).name
# Check if asset already exists and delete it
try:
release_response = session.get(release_info_url, timeout=30)
release_response.raise_for_status()
for asset in release_response.json().get('assets', []):
if asset['name'] == asset_name:
delete_resp = session.delete(delete_url_template.format(asset['id']), timeout=30)
delete_resp.raise_for_status()
print(f'[OK] Replaced existing asset {asset_name}', file=sys.stderr)
break
except Exception as e:
print(f'Warning checking existing assets: {e}', file=sys.stderr)
# Upload file with streaming and retries
retryable_status_codes = {429, 502, 503, 504}
for attempt in range(max_retries):
try:
if attempt > 0:
print(f' Retry {attempt} of {max_retries}...', file=sys.stderr)
time.sleep(min(10, 2 * attempt))
with open(artifact_path, 'rb') as f:
files = {'attachment': (asset_name, f)}
response = session.post(
upload_url,
files=files,
timeout=900, # 15 minute timeout
stream=False
)
if response.status_code in [200, 201]:
print(f'[OK] Uploaded {asset_name}')
return True
if response.status_code in retryable_status_codes:
if attempt >= max_retries - 1:
print(f'ERROR uploading {asset_name} (HTTP {response.status_code} after {max_retries} retries)')
print(response.text)
sys.exit(1)
continue
print(f'ERROR uploading {asset_name} (HTTP {response.status_code})')
print(response.text)
sys.exit(1)
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e:
if attempt >= max_retries - 1:
print(f'ERROR uploading {asset_name}: Connection failed after {max_retries} retries')
print(str(e))
sys.exit(1)
time.sleep(min(10, 2 * (attempt + 1)))
except Exception as e:
print(f'ERROR uploading {asset_name}: {e}')
sys.exit(1)
for artifact_path in artifacts:
upload_with_retry(artifact_path)
print(f'[OK] All files uploaded successfully')
"@
$uploadScriptPath = ([System.IO.Path]::GetTempFileName() -replace 'tmp$', 'py')
Set-Content -Path $uploadScriptPath -Value $pythonUploadScript -Encoding UTF8
try {
$uploadUrl = "$ForgejoUrl/api/v1/repos/$Repo/releases/$releaseId/assets"
& $pythonExe $uploadScriptPath $uploadUrl @artifactsToUpload
if ($LASTEXITCODE -ne 0) {
exit 1
}
}
finally {
Remove-Item $uploadScriptPath -ErrorAction SilentlyContinue
}
Write-Host "`n[OK] Release complete!" -ForegroundColor Green