From 1de604e7e24919fdec69496c4e0a68d2c0f988ec Mon Sep 17 00:00:00 2001 From: claudi Date: Thu, 12 Mar 2026 13:59:33 +0100 Subject: [PATCH] feat: Enhance asset upload process with Python script for reliability and retry logic --- build/scripts/create_release.ps1 | 125 +++++++++++++++++++++++++++---- 1 file changed, 112 insertions(+), 13 deletions(-) diff --git a/build/scripts/create_release.ps1 b/build/scripts/create_release.ps1 index 488c9bb..187c9aa 100644 --- a/build/scripts/create_release.ps1 +++ b/build/scripts/create_release.ps1 @@ -165,25 +165,124 @@ 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 - } - else { - Write-Host "[OK] Uploaded $assetName" -ForegroundColor Green + $artifactsToUpload.Add($artifact) +} + +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 Write-Host "View at: $ForgejoUrl/$Repo/releases/tag/v$Version" -ForegroundColor Cyan