# ================================================================ # Harbor 镜像清理脚本 # 功能:清理 Harbor Registry 中的旧镜像和未使用的层 # ================================================================ param( [string]$HarborUrl = "harbor.flnxi.com", [string]$HarborUsername = "admin", [string]$HarborPassword = "Harbor12345", [string]$ProjectName = "flnxi", [string]$ImageName = "flnxi-material", [int]$KeepCount = 5, [switch]$DryRun, [switch]$Help ) # 颜色输出函数 function Write-ColorOutput { param( [string]$Message, [ConsoleColor]$ForegroundColor = "White" ) Write-Host $Message -ForegroundColor $ForegroundColor } function Write-Success { param([string]$Message) Write-ColorOutput "[OK] $Message" "Green" } function Write-Warning { param([string]$Message) Write-ColorOutput "[WARN] $Message" "Yellow" } function Write-ErrorColored { param([string]$Message) Write-ColorOutput "[ERROR] $Message" "Red" } function Write-Info { param([string]$Message) Write-ColorOutput "[INFO] $Message" "Cyan" } if ($Help) { Write-ColorOutput "Harbor 镜像清理脚本" "Green" Write-ColorOutput "========================================" "Green" Write-ColorOutput "用法:" "White" Write-ColorOutput " .\scripts\cleanup-harbor.ps1 [参数]" "Yellow" Write-ColorOutput "" "White" Write-ColorOutput "参数:" "White" Write-ColorOutput " -HarborUrl [地址] Harbor地址 (默认: harbor.flnxi.com)" "Gray" Write-ColorOutput " -HarborUsername [用户] Harbor用户名 (默认: admin)" "Gray" Write-ColorOutput " -HarborPassword [密码] Harbor密码 (默认: Harbor12345)" "Gray" Write-ColorOutput " -ProjectName [名称] Harbor项目名称 (默认: flnxi)" "Gray" Write-ColorOutput " -ImageName [名称] 镜像名称 (默认: flnxi-material)" "Gray" Write-ColorOutput " -KeepCount [N] 保留的镜像数量 (默认: 5)" "Gray" Write-ColorOutput " -DryRun 仅显示将要执行的操作,不实际删除" "Gray" Write-ColorOutput " -Help 显示此帮助信息" "Gray" exit 0 } Write-ColorOutput "========================================" "Magenta" Write-ColorOutput " Harbor 镜像清理脚本" "Magenta" Write-ColorOutput "========================================" "Magenta" Write-ColorOutput "" # 1. 获取 Harbor API Token Write-Info "正在获取 Harbor API Token..." $authUrl = "https://$HarborUrl/api/v2.0/users/login" $authBody = @{ principal = $HarborUsername password = $HarborPassword } | ConvertTo-Json try { $authResponse = Invoke-RestMethod -Uri $authUrl -Method Post -Body $authBody -ContentType "application/json" -SkipCertificateCheck $token = $authResponse.token Write-Success "Harbor 登录成功" } catch { Write-ErrorColored "Harbor 登录失败: $_" exit 1 } # 2. 获取项目ID Write-Info "正在获取项目信息..." $projectUrl = "https://$HarborUrl/api/v2.0/projects?name=$ProjectName" $headers = @{ "Authorization" = "Bearer $token" } try { $projects = Invoke-RestMethod -Uri $projectUrl -Method Get -Headers $headers -SkipCertificateCheck if ($projects.Count -eq 0) { Write-ErrorColored "项目 $ProjectName 不存在" exit 1 } $projectId = $projects[0].project_id Write-Success "找到项目: $ProjectName (ID: $projectId)" } catch { Write-ErrorColored "获取项目信息失败: $_" exit 1 } # 3. 获取仓库信息 Write-Info "正在获取仓库信息..." $repoUrl = "https://$HarborUrl/api/v2.0/projects/$ProjectName/repositories/$ImageName" try { $repo = Invoke-RestMethod -Uri $repoUrl -Method Get -Headers $headers -SkipCertificateCheck Write-Success "找到仓库: $ProjectName/$ImageName" } catch { Write-ErrorColored "仓库 $ProjectName/$ImageName 不存在或无法访问: $_" exit 1 } # 4. 获取所有标签 Write-Info "正在获取镜像标签列表..." $tagsUrl = "https://$HarborUrl/api/v2.0/projects/$ProjectName/repositories/$ImageName/artifacts" try { $artifacts = Invoke-RestMethod -Uri $tagsUrl -Method Get -Headers $headers -SkipCertificateCheck Write-Info "找到 $($artifacts.Count) 个镜像版本" # 按创建时间排序(最新的在前) $sortedArtifacts = $artifacts | Sort-Object { [DateTime]::Parse($_.push_time) } -Descending # 保留最新的几个 $toKeep = $sortedArtifacts | Select-Object -First $KeepCount $toRemove = $sortedArtifacts | Select-Object -Skip $KeepCount Write-Info "将保留 $($toKeep.Count) 个最新版本" Write-Info "将删除 $($toRemove.Count) 个旧版本" if ($toKeep.Count -gt 0) { Write-ColorOutput "" "White" Write-ColorOutput "保留的版本:" "Cyan" foreach ($artifact in $toKeep) { $tags = if ($artifact.tags) { ($artifact.tags | ForEach-Object { $_.name }) -join ", " } else { "<无标签>" } $size = [Math]::Round($artifact.size / 1MB, 2) Write-ColorOutput " - $tags (大小: ${size}MB, 创建时间: $($artifact.push_time))" "Gray" } } if ($toRemove.Count -gt 0) { Write-ColorOutput "" "White" Write-ColorOutput "将删除的版本:" "Yellow" foreach ($artifact in $toRemove) { $tags = if ($artifact.tags) { ($artifact.tags | ForEach-Object { $_.name }) -join ", " } else { "<无标签>" } $size = [Math]::Round($artifact.size / 1MB, 2) Write-ColorOutput " - $tags (大小: ${size}MB, 创建时间: $($artifact.push_time))" "Gray" } if ($DryRun) { Write-ColorOutput "" "Yellow" Write-Warning "这是模拟运行,不会实际删除镜像" Write-Info "使用不带 -DryRun 参数来实际执行删除" } else { Write-ColorOutput "" "White" Write-Info "开始删除旧版本..." $deletedCount = 0 $failedCount = 0 foreach ($artifact in $toRemove) { $digest = $artifact.digest $tags = if ($artifact.tags) { ($artifact.tags | ForEach-Object { $_.name }) -join ", " } else { "<无标签>" } try { $deleteUrl = "https://$HarborUrl/api/v2.0/projects/$ProjectName/repositories/$ImageName/artifacts/$digest" Invoke-RestMethod -Uri $deleteUrl -Method Delete -Headers $headers -SkipCertificateCheck | Out-Null Write-Success "已删除: $tags (digest: $digest)" $deletedCount++ } catch { Write-Warning "删除失败: $tags - $_" $failedCount++ } } Write-ColorOutput "" "White" if ($deletedCount -gt 0) { Write-Success "成功删除 $deletedCount 个版本" } if ($failedCount -gt 0) { Write-Warning "$failedCount 个版本删除失败" } # 5. 触发垃圾回收(清理未使用的层) Write-ColorOutput "" "White" Write-Info "建议在 Harbor Web UI 中手动触发垃圾回收以清理未使用的层" Write-Info "路径: 项目 -> $ProjectName -> 仓库 -> $ImageName -> 垃圾回收" } } else { Write-Info "没有需要删除的旧版本" } } catch { Write-ErrorColored "获取标签列表失败: $_" exit 1 } Write-ColorOutput "" "Magenta" Write-ColorOutput "========================================" "Magenta" Write-ColorOutput " 清理完成" "Magenta" Write-ColorOutput "========================================" "Magenta"