Sync all scripts from website downloads — 352 scripts total
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
This commit is contained in:
@@ -0,0 +1,864 @@
|
||||
###############################################################################
|
||||
# gitlab-smoke-tests.ps1 - Verify GitLab instance health after upgrades
|
||||
#
|
||||
# PowerShell port of gitlab-smoke-tests.sh. Zero external dependencies
|
||||
# beyond PowerShell 5.1+ and git. Runs on Windows, Linux, and macOS.
|
||||
#
|
||||
# Author: Phil Connor
|
||||
# Contact: contact@mylinux.work
|
||||
# License: MIT
|
||||
# Version 1.00
|
||||
#
|
||||
# Usage:
|
||||
# $env:GITLAB_URL = "https://gitlab.example.com"
|
||||
# $env:GITLAB_TOKEN = "glpat-xxxxxxxxxxxx"
|
||||
# .\gitlab-smoke-tests.ps1
|
||||
# .\gitlab-smoke-tests.ps1 -SkipGit -SkipRegistry
|
||||
# .\gitlab-smoke-tests.ps1 -Insecure -Format junit
|
||||
# .\gitlab-smoke-tests.ps1 -Format tap
|
||||
###############################################################################
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$GitLabUrl = $env:GITLAB_URL,
|
||||
[string]$GitLabToken = $env:GITLAB_TOKEN,
|
||||
[string]$GitLabUser = $(if ($env:GITLAB_USER) { $env:GITLAB_USER } else { "root" }),
|
||||
[string]$HealthToken = $env:GITLAB_HEALTH_TOKEN,
|
||||
[string]$ProjectPrefix = $(if ($env:SMOKE_PROJECT_PREFIX) { $env:SMOKE_PROJECT_PREFIX } else { "smoke-test" }),
|
||||
[int]$Timeout = $(if ($env:CURL_TIMEOUT) { [int]$env:CURL_TIMEOUT } else { 10 }),
|
||||
[switch]$Insecure,
|
||||
[switch]$SkipGit,
|
||||
[switch]$SkipRegistry,
|
||||
[switch]$SkipCleanup,
|
||||
[ValidateSet("text","tap","junit")]
|
||||
[string]$Format = "text",
|
||||
[string]$JunitFile = "smoke-results.xml",
|
||||
[switch]$NoColor
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
# ============================================================================
|
||||
# STATE
|
||||
# ============================================================================
|
||||
|
||||
$script:Pass = 0
|
||||
$script:Fail = 0
|
||||
$script:Skip = 0
|
||||
$script:Total = 0
|
||||
$script:Results = @()
|
||||
$script:CleanupProjectId = ""
|
||||
$script:TmpDir = ""
|
||||
$script:StartTime = $null
|
||||
$script:GitCloneOk = $false
|
||||
|
||||
# ============================================================================
|
||||
# COLORS
|
||||
# ============================================================================
|
||||
|
||||
function Write-Color {
|
||||
param([string]$Text, [string]$Color = "White")
|
||||
if ($NoColor) {
|
||||
Write-Host $Text
|
||||
} else {
|
||||
Write-Host $Text -ForegroundColor $Color
|
||||
}
|
||||
}
|
||||
|
||||
function Write-Log { param([string]$Msg) Write-Color "[INFO] $Msg" "Cyan" }
|
||||
function Write-Warn { param([string]$Msg) Write-Color "[WARN] $Msg" "Yellow" }
|
||||
function Write-Err { param([string]$Msg) Write-Color "[ERROR] $Msg" "Red" }
|
||||
|
||||
# ============================================================================
|
||||
# TEST RESULT RECORDING
|
||||
# ============================================================================
|
||||
|
||||
function Record-Pass {
|
||||
param([string]$Name, [string]$Detail = "")
|
||||
$script:Pass++
|
||||
$script:Total++
|
||||
$script:Results += [PSCustomObject]@{ Status="PASS"; Name=$Name; Detail=$Detail }
|
||||
if ($Format -eq "tap") {
|
||||
Write-Host "ok $($script:Total) - $Name"
|
||||
} else {
|
||||
$msg = " $(if($NoColor){'[PASS]'}else{[char]0x2713}) $Name"
|
||||
if ($Detail) { $msg += " - $Detail" }
|
||||
Write-Color $msg "Green"
|
||||
}
|
||||
}
|
||||
|
||||
function Record-Fail {
|
||||
param([string]$Name, [string]$Detail = "")
|
||||
$script:Fail++
|
||||
$script:Total++
|
||||
$script:Results += [PSCustomObject]@{ Status="FAIL"; Name=$Name; Detail=$Detail }
|
||||
if ($Format -eq "tap") {
|
||||
Write-Host "not ok $($script:Total) - $Name"
|
||||
if ($Detail) { Write-Host " # $Detail" }
|
||||
} else {
|
||||
$msg = " $(if($NoColor){'[FAIL]'}else{[char]0x2717}) $Name"
|
||||
if ($Detail) { $msg += " - $Detail" }
|
||||
Write-Color $msg "Red"
|
||||
}
|
||||
}
|
||||
|
||||
function Record-Skip {
|
||||
param([string]$Name, [string]$Reason = "")
|
||||
$script:Skip++
|
||||
$script:Total++
|
||||
$script:Results += [PSCustomObject]@{ Status="SKIP"; Name=$Name; Detail=$Reason }
|
||||
if ($Format -eq "tap") {
|
||||
Write-Host "ok $($script:Total) - $Name # SKIP $Reason"
|
||||
} else {
|
||||
$msg = " $(if($NoColor){'[SKIP]'}else{[char]0x2298}) $Name"
|
||||
if ($Reason) { $msg += " - $Reason" }
|
||||
Write-Color $msg "Yellow"
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# HTTP HELPERS
|
||||
# ============================================================================
|
||||
|
||||
function Invoke-GitLabApi {
|
||||
param(
|
||||
[string]$Method,
|
||||
[string]$Endpoint,
|
||||
[string]$Body = $null,
|
||||
[switch]$StatusOnly
|
||||
)
|
||||
|
||||
$uri = "$GitLabUrl/api/v4$Endpoint"
|
||||
$headers = @{ "Content-Type" = "application/json" }
|
||||
if ($GitLabToken) { $headers["PRIVATE-TOKEN"] = $GitLabToken }
|
||||
|
||||
$params = @{
|
||||
Uri = $uri
|
||||
Method = $Method
|
||||
Headers = $headers
|
||||
TimeoutSec = $Timeout
|
||||
UseBasicParsing = $true
|
||||
ErrorAction = "Stop"
|
||||
}
|
||||
|
||||
if ($Insecure -and $PSVersionTable.PSVersion.Major -ge 7) {
|
||||
$params["SkipCertificateCheck"] = $true
|
||||
}
|
||||
|
||||
if ($Body) {
|
||||
$params["Body"] = $Body
|
||||
}
|
||||
|
||||
try {
|
||||
if ($StatusOnly) {
|
||||
$response = Invoke-WebRequest @params
|
||||
return [int]$response.StatusCode
|
||||
} else {
|
||||
return Invoke-RestMethod @params
|
||||
}
|
||||
} catch {
|
||||
if ($StatusOnly) {
|
||||
if ($_.Exception.Response) {
|
||||
return [int]$_.Exception.Response.StatusCode
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-HealthCheck {
|
||||
param([string]$Path)
|
||||
|
||||
$uri = "$GitLabUrl$Path"
|
||||
if ($HealthToken) { $uri += "?token=$HealthToken" }
|
||||
|
||||
$params = @{
|
||||
Uri = $uri
|
||||
Method = "GET"
|
||||
TimeoutSec = $Timeout
|
||||
UseBasicParsing = $true
|
||||
ErrorAction = "Stop"
|
||||
}
|
||||
|
||||
if ($Insecure -and $PSVersionTable.PSVersion.Major -ge 7) {
|
||||
$params["SkipCertificateCheck"] = $true
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Invoke-WebRequest @params
|
||||
return [int]$response.StatusCode
|
||||
} catch {
|
||||
if ($_.Exception.Response) {
|
||||
return [int]$_.Exception.Response.StatusCode
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# TLS HELPER
|
||||
# ============================================================================
|
||||
|
||||
function Get-TlsCertExpiry {
|
||||
param([string]$HostName, [int]$Port = 443)
|
||||
|
||||
try {
|
||||
$tcpClient = New-Object System.Net.Sockets.TcpClient
|
||||
$tcpClient.ReceiveTimeout = $Timeout * 1000
|
||||
$tcpClient.SendTimeout = $Timeout * 1000
|
||||
$tcpClient.Connect($HostName, $Port)
|
||||
|
||||
$sslStream = New-Object System.Net.Security.SslStream(
|
||||
$tcpClient.GetStream(), $false,
|
||||
{ param($s,$c,$ch,$e) return $true }
|
||||
)
|
||||
$sslStream.AuthenticateAsClient($HostName)
|
||||
|
||||
$cert = $sslStream.RemoteCertificate
|
||||
$expiry = [DateTime]$cert.GetExpirationDateString()
|
||||
|
||||
$sslStream.Dispose()
|
||||
$tcpClient.Dispose()
|
||||
|
||||
return $expiry
|
||||
} catch {
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# TEST SUITES
|
||||
# ============================================================================
|
||||
|
||||
# -- 1. Connectivity --------------------------------------------------------
|
||||
|
||||
function Test-Connectivity {
|
||||
Write-Host ""
|
||||
Write-Color "Connectivity" "White"
|
||||
|
||||
# 1a. Health endpoint
|
||||
$code = Invoke-HealthCheck "/-/health"
|
||||
if ($code -eq 200) {
|
||||
Record-Pass "GitLab health endpoint reachable" "HTTP $code"
|
||||
} else {
|
||||
Record-Fail "GitLab health endpoint reachable" "HTTP $code"
|
||||
}
|
||||
|
||||
# 1b. Readiness
|
||||
$code = Invoke-HealthCheck "/-/readiness"
|
||||
if ($code -eq 200) {
|
||||
Record-Pass "GitLab readiness check" "HTTP $code"
|
||||
} else {
|
||||
Record-Fail "GitLab readiness check" "HTTP $code"
|
||||
}
|
||||
|
||||
# 1c. Liveness
|
||||
$code = Invoke-HealthCheck "/-/liveness"
|
||||
if ($code -eq 200) {
|
||||
Record-Pass "GitLab liveness check" "HTTP $code"
|
||||
} else {
|
||||
Record-Fail "GitLab liveness check" "HTTP $code"
|
||||
}
|
||||
|
||||
# 1d. TLS certificate
|
||||
if ($GitLabUrl -match "^https://") {
|
||||
$hostPart = $GitLabUrl -replace "^https://", "" -replace "/.*", "" -replace ":.*", ""
|
||||
$portPart = 443
|
||||
if ($GitLabUrl -match ":(\d+)") { $portPart = [int]$Matches[1] }
|
||||
|
||||
$expiry = Get-TlsCertExpiry -HostName $hostPart -Port $portPart
|
||||
if ($expiry) {
|
||||
$daysLeft = [math]::Floor(($expiry - (Get-Date)).TotalDays)
|
||||
if ($daysLeft -gt 30) {
|
||||
Record-Pass "TLS certificate valid" "$daysLeft days remaining"
|
||||
} elseif ($daysLeft -gt 0) {
|
||||
Record-Pass "TLS certificate valid" "$daysLeft days remaining (renew soon)"
|
||||
} else {
|
||||
Record-Fail "TLS certificate valid" "expired or expiring in $daysLeft days"
|
||||
}
|
||||
} else {
|
||||
Record-Skip "TLS certificate check" "could not retrieve certificate"
|
||||
}
|
||||
} else {
|
||||
Record-Skip "TLS certificate check" "not using HTTPS"
|
||||
}
|
||||
}
|
||||
|
||||
# -- 2. API ----------------------------------------------------------------
|
||||
|
||||
function Test-Api {
|
||||
Write-Host ""
|
||||
Write-Color "API" "White"
|
||||
|
||||
# 2a. Version
|
||||
$versionData = Invoke-GitLabApi -Method GET -Endpoint "/version"
|
||||
if ($versionData -and $versionData.version) {
|
||||
Record-Pass "API version endpoint" "GitLab $($versionData.version) ($($versionData.revision))"
|
||||
} else {
|
||||
Record-Fail "API version endpoint" "no version returned"
|
||||
}
|
||||
|
||||
# 2b. Authentication
|
||||
$authStatus = Invoke-GitLabApi -Method GET -Endpoint "/user" -StatusOnly
|
||||
if ($authStatus -eq 200) {
|
||||
$userData = Invoke-GitLabApi -Method GET -Endpoint "/user"
|
||||
Record-Pass "API authentication" "authenticated as $($userData.username)"
|
||||
} elseif ($authStatus -eq 401) {
|
||||
Record-Fail "API authentication" "token rejected (HTTP 401)"
|
||||
} else {
|
||||
Record-Fail "API authentication" "HTTP $authStatus"
|
||||
}
|
||||
|
||||
# 2c. List projects
|
||||
$projStatus = Invoke-GitLabApi -Method GET -Endpoint "/projects?per_page=1" -StatusOnly
|
||||
if ($projStatus -eq 200) {
|
||||
Record-Pass "API list projects" "database responding"
|
||||
} else {
|
||||
Record-Fail "API list projects" "HTTP $projStatus"
|
||||
}
|
||||
|
||||
# 2d. List users
|
||||
$userStatus = Invoke-GitLabApi -Method GET -Endpoint "/users?per_page=1" -StatusOnly
|
||||
if ($userStatus -eq 200) {
|
||||
Record-Pass "API list users" "user directory accessible"
|
||||
} else {
|
||||
Record-Fail "API list users" "HTTP $userStatus"
|
||||
}
|
||||
|
||||
# 2e. Sidekiq
|
||||
$sidekiq = Invoke-GitLabApi -Method GET -Endpoint "/sidekiq/compound_metrics"
|
||||
if ($sidekiq -and -not $sidekiq.error) {
|
||||
$procCount = 0
|
||||
if ($sidekiq.processes) { $procCount = @($sidekiq.processes).Count }
|
||||
Record-Pass "Sidekiq running" "$procCount process(es) responding"
|
||||
} else {
|
||||
Record-Fail "Sidekiq running" "could not query Sidekiq metrics"
|
||||
}
|
||||
|
||||
# 2f. Runners
|
||||
$runnerStatus = Invoke-GitLabApi -Method GET -Endpoint "/runners/all?per_page=1" -StatusOnly
|
||||
if ($runnerStatus -eq 200) {
|
||||
Record-Pass "API runners endpoint" "runner management accessible"
|
||||
} elseif ($runnerStatus -eq 403) {
|
||||
Record-Skip "API runners endpoint" "token lacks admin scope"
|
||||
} else {
|
||||
Record-Fail "API runners endpoint" "HTTP $runnerStatus"
|
||||
}
|
||||
|
||||
# 2g. Search
|
||||
$searchStatus = Invoke-GitLabApi -Method GET -Endpoint "/search?scope=projects&search=test" -StatusOnly
|
||||
if ($searchStatus -eq 200) {
|
||||
Record-Pass "API search" "search index responding"
|
||||
} elseif ($searchStatus -eq 403) {
|
||||
Record-Skip "API search" "search disabled or token lacks scope"
|
||||
} else {
|
||||
Record-Fail "API search" "HTTP $searchStatus"
|
||||
}
|
||||
}
|
||||
|
||||
# -- 3. Git Operations -----------------------------------------------------
|
||||
|
||||
function Test-Git {
|
||||
if ($SkipGit) {
|
||||
Write-Host ""
|
||||
Write-Color "Git Operations" "White"
|
||||
Record-Skip "Git clone" "SkipGit specified"
|
||||
Record-Skip "Git push" "SkipGit specified"
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Color "Git Operations" "White"
|
||||
|
||||
# Create test project
|
||||
$projectName = "$ProjectPrefix-$([DateTimeOffset]::UtcNow.ToUnixTimeSeconds())"
|
||||
$body = @{ name = $projectName; visibility = "private"; initialize_with_readme = $true } | ConvertTo-Json
|
||||
$project = Invoke-GitLabApi -Method POST -Endpoint "/projects" -Body $body
|
||||
|
||||
if (-not $project -or -not $project.id) {
|
||||
Record-Fail "Create test project" "API returned no project ID"
|
||||
Record-Skip "Git clone" "no test project"
|
||||
Record-Skip "Git push" "no test project"
|
||||
return
|
||||
}
|
||||
|
||||
$script:CleanupProjectId = $project.id
|
||||
Record-Pass "Create test project" "$projectName (ID: $($project.id))"
|
||||
|
||||
# Build clone URL
|
||||
$httpUrl = $project.http_url_to_repo
|
||||
if (-not $httpUrl) {
|
||||
$httpUrl = "$GitLabUrl/$GitLabUser/$projectName.git"
|
||||
}
|
||||
|
||||
# Rewrite origin if API returns an internal hostname
|
||||
$apiOrigin = if ($httpUrl -match "^(https?://[^/]+)") { $Matches[1] } else { "" }
|
||||
if ($apiOrigin -and $apiOrigin -ne $GitLabUrl) {
|
||||
$httpUrl = $httpUrl -replace [regex]::Escape($apiOrigin), $GitLabUrl
|
||||
}
|
||||
|
||||
# Inject token
|
||||
if ($httpUrl -match "^https://") {
|
||||
$cloneUrl = $httpUrl -replace "^https://", "https://oauth2:${GitLabToken}@"
|
||||
} elseif ($httpUrl -match "^http://") {
|
||||
$cloneUrl = $httpUrl -replace "^http://", "http://oauth2:${GitLabToken}@"
|
||||
} else {
|
||||
$cloneUrl = $httpUrl
|
||||
}
|
||||
|
||||
# Temp directory
|
||||
$script:TmpDir = Join-Path ([System.IO.Path]::GetTempPath()) "gitlab-smoke-$([guid]::NewGuid().ToString('N').Substring(0,8))"
|
||||
New-Item -ItemType Directory -Path $script:TmpDir -Force | Out-Null
|
||||
|
||||
# Wait for repo init
|
||||
Start-Sleep -Seconds 2
|
||||
|
||||
# Clone
|
||||
$gitArgs = @("clone")
|
||||
if ($Insecure) { $env:GIT_SSL_NO_VERIFY = "true" }
|
||||
|
||||
$repoDir = Join-Path $script:TmpDir "repo"
|
||||
$cloneOutput = & git clone $cloneUrl $repoDir 2>&1
|
||||
$cloneRc = $LASTEXITCODE
|
||||
|
||||
if ($cloneRc -eq 0) {
|
||||
$script:GitCloneOk = $true
|
||||
Record-Pass "Git clone (HTTPS)" "Gitaly responding"
|
||||
} else {
|
||||
$shortErr = ($cloneOutput | Select-String -Pattern "fatal|error" | Select-Object -First 1) -replace [regex]::Escape($GitLabToken), "[REDACTED]"
|
||||
Record-Fail "Git clone (HTTPS)" "$shortErr"
|
||||
return
|
||||
}
|
||||
|
||||
# Push
|
||||
Push-Location $repoDir
|
||||
try {
|
||||
& git config user.email "smoke-test@example.com"
|
||||
& git config user.name "Smoke Test"
|
||||
"smoke test $(Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ')" | Out-File -FilePath "smoke-test.txt" -Encoding utf8
|
||||
|
||||
& git add smoke-test.txt
|
||||
& git commit -m "smoke test commit" 2>&1 | Out-Null
|
||||
|
||||
$pushOutput = & git push origin main 2>&1
|
||||
$pushRc = $LASTEXITCODE
|
||||
if ($pushRc -ne 0) {
|
||||
$pushOutput = & git push origin master 2>&1
|
||||
$pushRc = $LASTEXITCODE
|
||||
}
|
||||
|
||||
if ($pushRc -eq 0) {
|
||||
Record-Pass "Git push (HTTPS)" "write to Gitaly succeeded"
|
||||
} else {
|
||||
Record-Fail "Git push (HTTPS)" "push failed"
|
||||
}
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
|
||||
# -- 4. Container Registry -------------------------------------------------
|
||||
|
||||
function Test-Registry {
|
||||
if ($SkipRegistry) {
|
||||
Write-Host ""
|
||||
Write-Color "Container Registry" "White"
|
||||
Record-Skip "Registry API" "SkipRegistry specified"
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Color "Container Registry" "White"
|
||||
|
||||
# Check if registry is enabled
|
||||
$registryEnabled = ""
|
||||
$settings = Invoke-GitLabApi -Method GET -Endpoint "/application/settings"
|
||||
if ($settings) {
|
||||
$registryEnabled = $settings.container_registry_enabled
|
||||
}
|
||||
|
||||
if ($registryEnabled -eq $false) {
|
||||
Record-Skip "Registry API reachable" "container registry disabled in application settings"
|
||||
Record-Skip "Registry project endpoint" "container registry disabled in application settings"
|
||||
return
|
||||
}
|
||||
|
||||
# Try registry v2 API
|
||||
$hostPart = $GitLabUrl -replace "^https?://", "" -replace "/.*", ""
|
||||
$registryStatus = 0
|
||||
|
||||
$registryUrls = @(
|
||||
"$GitLabUrl`:5050/v2/",
|
||||
"https://${hostPart}:5050/v2/",
|
||||
"https://registry.${hostPart}/v2/"
|
||||
)
|
||||
|
||||
foreach ($regUrl in $registryUrls) {
|
||||
try {
|
||||
$params = @{
|
||||
Uri = $regUrl
|
||||
Method = "GET"
|
||||
TimeoutSec = $Timeout
|
||||
UseBasicParsing = $true
|
||||
ErrorAction = "Stop"
|
||||
}
|
||||
if ($Insecure -and $PSVersionTable.PSVersion.Major -ge 7) {
|
||||
$params["SkipCertificateCheck"] = $true
|
||||
}
|
||||
$response = Invoke-WebRequest @params
|
||||
$registryStatus = [int]$response.StatusCode
|
||||
break
|
||||
} catch {
|
||||
if ($_.Exception.Response) {
|
||||
$registryStatus = [int]$_.Exception.Response.StatusCode
|
||||
if ($registryStatus -eq 401) { break }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($registryStatus -eq 200 -or $registryStatus -eq 401) {
|
||||
Record-Pass "Registry API reachable" "HTTP $registryStatus"
|
||||
} elseif ($registryStatus -eq 0) {
|
||||
if ($registryEnabled -eq $true) {
|
||||
Record-Fail "Registry API reachable" "enabled in settings but not reachable at standard ports/hosts"
|
||||
} else {
|
||||
Record-Skip "Registry API reachable" "not found at standard ports/hosts (settings unreadable - may need admin token)"
|
||||
}
|
||||
} else {
|
||||
Record-Fail "Registry API reachable" "HTTP $registryStatus"
|
||||
}
|
||||
|
||||
# Project-level registry
|
||||
if ($script:CleanupProjectId) {
|
||||
$regStatus = Invoke-GitLabApi -Method GET -Endpoint "/projects/$($script:CleanupProjectId)/registry/repositories" -StatusOnly
|
||||
if ($regStatus -eq 200) {
|
||||
Record-Pass "Registry project endpoint" "project registry accessible"
|
||||
} elseif ($regStatus -eq 404) {
|
||||
Record-Skip "Registry project endpoint" "container registry not enabled for project"
|
||||
} else {
|
||||
Record-Fail "Registry project endpoint" "HTTP $regStatus"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# -- 5. CI/CD --------------------------------------------------------------
|
||||
|
||||
function Test-CICD {
|
||||
Write-Host ""
|
||||
Write-Color "CI/CD" "White"
|
||||
|
||||
# Runners
|
||||
$runners = Invoke-GitLabApi -Method GET -Endpoint "/runners/all?per_page=100"
|
||||
if ($runners -is [array]) {
|
||||
$runnerCount = $runners.Count
|
||||
$onlineCount = @($runners | Where-Object { $_.status -eq "online" }).Count
|
||||
|
||||
if ($onlineCount -gt 0) {
|
||||
Record-Pass "CI/CD runners online" "$onlineCount/$runnerCount runners online"
|
||||
} elseif ($runnerCount -gt 0) {
|
||||
Record-Fail "CI/CD runners online" "0/$runnerCount runners online"
|
||||
} else {
|
||||
Record-Skip "CI/CD runners online" "no runners registered"
|
||||
}
|
||||
} else {
|
||||
Record-Skip "CI/CD runners" "could not query runners (admin token required)"
|
||||
}
|
||||
|
||||
# CI/CD settings
|
||||
$cicdStatus = Invoke-GitLabApi -Method GET -Endpoint "/application/settings" -StatusOnly
|
||||
if ($cicdStatus -eq 200) {
|
||||
Record-Pass "CI/CD settings accessible" "application settings readable"
|
||||
} elseif ($cicdStatus -eq 403) {
|
||||
Record-Skip "CI/CD settings accessible" "admin token required"
|
||||
} else {
|
||||
Record-Fail "CI/CD settings accessible" "HTTP $cicdStatus"
|
||||
}
|
||||
}
|
||||
|
||||
# -- 6. Background Migrations ----------------------------------------------
|
||||
|
||||
function Test-Migrations {
|
||||
Write-Host ""
|
||||
Write-Color "Background Migrations" "White"
|
||||
|
||||
$migrations = Invoke-GitLabApi -Method GET -Endpoint "/admin/batched_background_migrations?database=main"
|
||||
|
||||
if ($migrations -is [array]) {
|
||||
$totalMig = $migrations.Count
|
||||
$failedMig = @($migrations | Where-Object { $_.status -eq "failed" }).Count
|
||||
$activeMig = @($migrations | Where-Object { $_.status -eq "active" }).Count
|
||||
$pausedMig = @($migrations | Where-Object { $_.status -eq "paused" }).Count
|
||||
$finishedMig = @($migrations | Where-Object { $_.status -eq "finished" }).Count
|
||||
|
||||
if ($failedMig -gt 0) {
|
||||
Record-Fail "Background migrations" "$failedMig failed, $activeMig active, $pausedMig paused, $finishedMig finished of $totalMig"
|
||||
} elseif ($pausedMig -gt 0) {
|
||||
Record-Fail "Background migrations" "$pausedMig paused, $activeMig active, $finishedMig finished of $totalMig"
|
||||
} elseif ($activeMig -gt 0) {
|
||||
Record-Pass "Background migrations" "$activeMig active, $finishedMig finished of $totalMig (in progress)"
|
||||
} else {
|
||||
Record-Pass "Background migrations" "all $totalMig finished"
|
||||
}
|
||||
} else {
|
||||
$migStatus = Invoke-GitLabApi -Method GET -Endpoint "/admin/batched_background_migrations?database=main" -StatusOnly
|
||||
if ($migStatus -eq 403) {
|
||||
Record-Skip "Background migrations" "admin token required"
|
||||
} else {
|
||||
Record-Skip "Background migrations" "could not query (HTTP $migStatus)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# -- 7. Components ---------------------------------------------------------
|
||||
|
||||
function Test-Components {
|
||||
Write-Host ""
|
||||
Write-Color "Components" "White"
|
||||
|
||||
# Metadata
|
||||
$metadata = Invoke-GitLabApi -Method GET -Endpoint "/metadata"
|
||||
if ($metadata -and $metadata.version) {
|
||||
$edition = if ($metadata.enterprise -eq $true) { "EE" } else { "CE" }
|
||||
Record-Pass "GitLab metadata" "$($metadata.version) $edition"
|
||||
} elseif ($metadata) {
|
||||
Record-Pass "GitLab metadata" "endpoint reachable"
|
||||
} else {
|
||||
Record-Skip "GitLab metadata" "metadata endpoint not available"
|
||||
}
|
||||
|
||||
# Statistics
|
||||
$stats = Invoke-GitLabApi -Method GET -Endpoint "/application/statistics"
|
||||
if ($stats -and $stats.active_users) {
|
||||
Record-Pass "Instance statistics" "$($stats.active_users) users, $($stats.projects) projects, $($stats.groups) groups"
|
||||
} elseif ($stats) {
|
||||
Record-Pass "Instance statistics" "endpoint reachable"
|
||||
} else {
|
||||
Record-Skip "Instance statistics" "admin token required"
|
||||
}
|
||||
|
||||
# Gitaly (inferred)
|
||||
if ($script:GitCloneOk) {
|
||||
Record-Pass "Gitaly storage" "project created and cloned successfully"
|
||||
} elseif ($script:CleanupProjectId) {
|
||||
Record-Skip "Gitaly storage" "project created but clone was not tested or failed"
|
||||
}
|
||||
|
||||
# PostgreSQL (inferred)
|
||||
$pgStatus = Invoke-GitLabApi -Method GET -Endpoint "/projects?per_page=1&order_by=updated_at" -StatusOnly
|
||||
if ($pgStatus -eq 200) {
|
||||
Record-Pass "PostgreSQL" "database queries succeeding"
|
||||
} else {
|
||||
Record-Fail "PostgreSQL" "sorted query failed (HTTP $pgStatus)"
|
||||
}
|
||||
|
||||
# Redis (inferred)
|
||||
$redisStatus = Invoke-GitLabApi -Method GET -Endpoint "/user" -StatusOnly
|
||||
if ($redisStatus -eq 200) {
|
||||
Record-Pass "Redis" "session/cache operational (auth succeeded)"
|
||||
} else {
|
||||
Record-Skip "Redis" "cannot verify independently"
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# OUTPUT
|
||||
# ============================================================================
|
||||
|
||||
function Write-Summary {
|
||||
$duration = [math]::Floor(((Get-Date) - $script:StartTime).TotalSeconds)
|
||||
|
||||
Write-Host ""
|
||||
$separator = [string]::new([char]0x2500, 40)
|
||||
Write-Color $separator "White"
|
||||
Write-Color "Summary $GitLabUrl" "White"
|
||||
|
||||
$summaryLine = " $($script:Pass) passed $($script:Fail) failed $($script:Skip) skipped (${duration}s)"
|
||||
Write-Host $summaryLine
|
||||
Write-Color $separator "White"
|
||||
|
||||
if ($script:Fail -eq 0) {
|
||||
Write-Color "All tests passed." "Green"
|
||||
} else {
|
||||
Write-Color "$($script:Fail) test(s) failed." "Red"
|
||||
}
|
||||
}
|
||||
|
||||
function Write-TapHeader {
|
||||
Write-Host "TAP version 13"
|
||||
}
|
||||
|
||||
function Write-TapFooter {
|
||||
Write-Host "1..$($script:Total)"
|
||||
Write-Host "# pass $($script:Pass)"
|
||||
Write-Host "# fail $($script:Fail)"
|
||||
Write-Host "# skip $($script:Skip)"
|
||||
}
|
||||
|
||||
function Write-JunitReport {
|
||||
$duration = [math]::Floor(((Get-Date) - $script:StartTime).TotalSeconds)
|
||||
|
||||
$xml = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuites tests="$($script:Total)" failures="$($script:Fail)" skipped="$($script:Skip)" time="$duration">
|
||||
<testsuite name="gitlab-smoke-tests" tests="$($script:Total)" failures="$($script:Fail)" skipped="$($script:Skip)" time="$duration">
|
||||
"@
|
||||
|
||||
foreach ($r in $script:Results) {
|
||||
$safeName = $r.Name -replace '&','&' -replace '<','<' -replace '>','>' -replace '"','"'
|
||||
$safeDetail = $r.Detail -replace '&','&' -replace '<','<' -replace '>','>' -replace '"','"'
|
||||
|
||||
switch ($r.Status) {
|
||||
"PASS" {
|
||||
$xml += "`n <testcase name=`"$safeName`" classname=`"smoke`">"
|
||||
if ($r.Detail) { $xml += "`n <system-out>$safeDetail</system-out>" }
|
||||
$xml += "`n </testcase>"
|
||||
}
|
||||
"FAIL" {
|
||||
$xml += "`n <testcase name=`"$safeName`" classname=`"smoke`">"
|
||||
$xml += "`n <failure message=`"$safeDetail`">FAILED: $safeName - $safeDetail</failure>"
|
||||
$xml += "`n </testcase>"
|
||||
}
|
||||
"SKIP" {
|
||||
$xml += "`n <testcase name=`"$safeName`" classname=`"smoke`">"
|
||||
$xml += "`n <skipped message=`"$safeDetail`"/>"
|
||||
$xml += "`n </testcase>"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$xml += "`n </testsuite>"
|
||||
$xml += "`n</testsuites>"
|
||||
|
||||
$xml | Out-File -FilePath $JunitFile -Encoding utf8
|
||||
Write-Log "JUnit report written to $JunitFile"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# CLEANUP
|
||||
# ============================================================================
|
||||
|
||||
function Invoke-Cleanup {
|
||||
if ($script:CleanupProjectId -and -not $SkipCleanup) {
|
||||
try {
|
||||
Invoke-GitLabApi -Method DELETE -Endpoint "/projects/$($script:CleanupProjectId)" | Out-Null
|
||||
} catch { }
|
||||
}
|
||||
|
||||
if ($script:TmpDir -and (Test-Path $script:TmpDir)) {
|
||||
Remove-Item -Recurse -Force $script:TmpDir -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
if ($env:GIT_SSL_NO_VERIFY) {
|
||||
Remove-Item Env:\GIT_SSL_NO_VERIFY -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# MAIN
|
||||
# ============================================================================
|
||||
|
||||
function Show-Usage {
|
||||
@"
|
||||
Usage: .\gitlab-smoke-tests.ps1 [OPTIONS]
|
||||
|
||||
Smoke-test a GitLab instance. PowerShell 5.1+, git only.
|
||||
Designed for air-gapped environments.
|
||||
|
||||
Required environment variables:
|
||||
GITLAB_URL GitLab base URL (https://gitlab.example.com)
|
||||
GITLAB_TOKEN Personal access token (api scope; admin for full coverage)
|
||||
|
||||
Optional environment variables:
|
||||
GITLAB_HEALTH_TOKEN Health check access token
|
||||
GITLAB_USER Username for git operations (default: root)
|
||||
|
||||
Parameters:
|
||||
-SkipGit Skip git clone/push tests
|
||||
-SkipRegistry Skip container registry tests
|
||||
-SkipCleanup Don't delete the test project after run
|
||||
-Insecure Allow self-signed TLS certificates
|
||||
-Timeout N HTTP timeout in seconds (default: 10)
|
||||
-Format FORMAT Output: text (default), tap, junit
|
||||
-JunitFile FILE JUnit output path (default: smoke-results.xml)
|
||||
-NoColor Disable colored output
|
||||
-Verbose Show debug output
|
||||
|
||||
Examples:
|
||||
`$env:GITLAB_URL = "https://gitlab.example.com"
|
||||
`$env:GITLAB_TOKEN = "glpat-xxxxxxxxxxxx"
|
||||
.\gitlab-smoke-tests.ps1
|
||||
|
||||
.\gitlab-smoke-tests.ps1 -Insecure -Format junit
|
||||
.\gitlab-smoke-tests.ps1 -SkipGit -SkipRegistry
|
||||
.\gitlab-smoke-tests.ps1 -Format tap
|
||||
"@
|
||||
}
|
||||
|
||||
# Handle PS 5.1 TLS and self-signed certs
|
||||
if ($PSVersionTable.PSVersion.Major -lt 7) {
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
|
||||
if ($Insecure) {
|
||||
Add-Type @"
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
public class TrustAll : ICertificatePolicy {
|
||||
public bool CheckValidationResult(ServicePoint sp, X509Certificate cert,
|
||||
WebRequest req, int problem) { return true; }
|
||||
}
|
||||
"@ -ErrorAction SilentlyContinue
|
||||
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAll
|
||||
}
|
||||
}
|
||||
|
||||
# Validate
|
||||
if (-not $GitLabUrl) {
|
||||
Write-Err "GITLAB_URL is required"
|
||||
Write-Host ""
|
||||
Show-Usage
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not $GitLabToken) {
|
||||
Write-Err "GITLAB_TOKEN is required"
|
||||
Write-Host ""
|
||||
Show-Usage
|
||||
exit 1
|
||||
}
|
||||
|
||||
$GitLabUrl = $GitLabUrl.TrimEnd("/")
|
||||
$script:StartTime = Get-Date
|
||||
|
||||
if ($Format -eq "tap") {
|
||||
Write-TapHeader
|
||||
} else {
|
||||
Write-Host ""
|
||||
Write-Color "GitLab Smoke Tests" "White"
|
||||
Write-Host "Target: $GitLabUrl"
|
||||
Write-Host "Time: $(Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ')"
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
try {
|
||||
Test-Connectivity
|
||||
Test-Api
|
||||
Test-Git
|
||||
Test-Registry
|
||||
Test-CICD
|
||||
Test-Migrations
|
||||
Test-Components
|
||||
} finally {
|
||||
Invoke-Cleanup
|
||||
}
|
||||
|
||||
if ($Format -eq "tap") {
|
||||
Write-TapFooter
|
||||
} elseif ($Format -eq "junit") {
|
||||
Write-Summary
|
||||
Write-JunitReport
|
||||
} else {
|
||||
Write-Summary
|
||||
}
|
||||
|
||||
if ($script:Fail -eq 0) { exit 0 } else { exit 1 }
|
||||
Reference in New Issue
Block a user