From 3bfc40b4600d7ab52a0a5d5a4f5e2d80482fd10e Mon Sep 17 00:00:00 2001 From: Zaine Date: Fri, 8 May 2026 15:52:56 +0100 Subject: [PATCH] testing the pipeline --- gitea-build-monitor.ps1 | 184 +++++++++++++++--- ....sync-conflict-20260417-233429-VT6366A.org | 16 -- posts/posts-list.org | 2 +- sitemap.org | 2 +- 4 files changed, 161 insertions(+), 43 deletions(-) delete mode 100755 home/categories.sync-conflict-20260417-233429-VT6366A.org diff --git a/gitea-build-monitor.ps1 b/gitea-build-monitor.ps1 index b03e7fe..13d530a 100755 --- a/gitea-build-monitor.ps1 +++ b/gitea-build-monitor.ps1 @@ -1,11 +1,11 @@ <# .SYNOPSIS - Monitor recent Gitea Actions build jobs and notify Discord about failures, warnings, errors, or slow builds. + Monitor recent Gitea Actions build runs and notify Discord about build status. .DESCRIPTION This script is intended to run after the main build pipeline. It queries a self-hosted Gitea instance, - analyzes recent Actions jobs for one or more repositories, parses logs where available, caches processed - job IDs locally, and sends grouped Discord embed notifications. + analyzes recent Actions runs for one or more repositories, parses logs where available, caches processed + run IDs locally, and sends grouped Discord embed notifications. Required token permissions depend on your Gitea version and repository visibility. For private repos, use a personal access token that can read repository Actions/jobs and logs. @@ -36,9 +36,11 @@ param( Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' +$GiteaApiToken = if (-not [string]::IsNullOrWhiteSpace($env:API_KEY)) { $env:API_KEY } else { $env:GITEA_API_TOKEN } + $Config = @{ GiteaBaseUrl = 'http://127.0.0.1:3000' - ApiToken = $env:API_KEY + ApiToken = $GiteaApiToken DiscordWebhookUrl = $env:DISCORD_WEBHOOK_URL # Supports multiple repositories. @@ -55,7 +57,7 @@ $Config = @{ MaxDiscordEmbeds = 10 RetryCount = 3 RetryDelaySeconds = 2 - AuthoringTestsEnabled = $true + AuthoringTestsEnabled = $false AuthoringTestsPattern = 'test_authoring_server.py' AuthoringTestsDirectory = Join-Path $PSScriptRoot 'tests' AuthoringTestTimeoutSec = 120 @@ -71,6 +73,9 @@ $Config = @{ '(?im)\bdeprecated\b' ) + # When true, completed successful runs are reported too. + NotifySuccessfulBuilds = $true + # When true, successful but slow/warned jobs are reported too. # Keep this false if Discord should only receive notifications for failed/cancelled/timed-out jobs. NotifyWarningsAndSlow = $false @@ -244,11 +249,31 @@ function Get-BuildRuns { $owner = [uri]::EscapeDataString((Get-RepositoryOwner -Repository $Repository)) $repo = [uri]::EscapeDataString((Get-RepositoryName -Repository $Repository)) - $path = "/api/v1/repos/$owner/$repo/actions/jobs?page=1&limit=$($Config.MaxJobsPerRepository)" - $response = Invoke-GiteaApi -Path $path + $limit = [int]$Config.MaxJobsPerRepository - $jobs = Get-ObjectValue -Object $response -Name 'jobs' + try { + $runsResponse = Invoke-GiteaApi -Path "/api/v1/repos/$owner/$repo/actions/runs?page=1&limit=$limit" + $runs = if ($runsResponse -is [array]) { $runsResponse } else { Get-ObjectValue -Object $runsResponse -Name 'workflow_runs' } + if (-not $runs) { + $runs = Get-ObjectValue -Object $runsResponse -Name 'runs' + } + if ($runs) { + foreach ($run in @($runs)) { + Add-Member -InputObject $run -NotePropertyName '_monitor_kind' -NotePropertyValue 'run' -Force + } + return @($runs) + } + } + catch { + Write-Log -Level 'WARN' -Message "Could not fetch workflow runs for $(Get-RepositorySlug -Repository $Repository); falling back to jobs endpoint: $($_.Exception.Message)" + } + + $jobsResponse = Invoke-GiteaApi -Path "/api/v1/repos/$owner/$repo/actions/jobs?page=1&limit=$limit" + $jobs = if ($jobsResponse -is [array]) { $jobsResponse } else { Get-ObjectValue -Object $jobsResponse -Name 'jobs' } if ($jobs) { + foreach ($job in @($jobs)) { + Add-Member -InputObject $job -NotePropertyName '_monitor_kind' -NotePropertyValue 'job' -Force + } return @($jobs) } @@ -311,6 +336,55 @@ function Get-JobLogText { } } +function Get-BuildLogText { + param( + [Parameter(Mandatory)][hashtable]$Repository, + [Parameter(Mandatory)][object]$Build + ) + + $kind = [string](Get-ObjectValue -Object $Build -Name '_monitor_kind' -Default 'job') + if ($kind -ne 'run') { + return Get-JobLogText -Repository $Repository -Job $Build + } + + try { + $owner = [uri]::EscapeDataString((Get-RepositoryOwner -Repository $Repository)) + $repo = [uri]::EscapeDataString((Get-RepositoryName -Repository $Repository)) + $runId = [uri]::EscapeDataString([string](Get-ObjectValue -Object $Build -Name 'id')) + $response = Invoke-GiteaApi -Path "/api/v1/repos/$owner/$repo/actions/runs/$runId/jobs" + $jobs = Get-ObjectValue -Object $response -Name 'jobs' + if (-not $jobs) { + return '' + } + + $logParts = New-Object System.Collections.Generic.List[string] + foreach ($job in @($jobs)) { + $conclusion = Get-ObjectValue -Object $job -Name 'conclusion' + $jobStatus = Get-ObjectValue -Object $job -Name 'status' + $status = if ($conclusion) { [string]$conclusion } elseif ($jobStatus) { [string]$jobStatus } else { 'unknown' } + if ($status.ToLowerInvariant() -notin @('failure', 'failed', 'cancelled', 'canceled', 'timed_out')) { + continue + } + + $jobName = [string](Get-ObjectValue -Object $job -Name 'name' -Default (Get-ObjectValue -Object $job -Name 'id')) + $jobLog = Get-JobLogText -Repository $Repository -Job $job + if (-not [string]::IsNullOrWhiteSpace($jobLog)) { + $logParts.Add(("Job {0}:`n{1}" -f $jobName, $jobLog)) + } + } + + $text = ($logParts -join "`n`n") + if ($text.Length -gt [int]$Config.MaxLogBytesToAnalyze) { + return $text.Substring([Math]::Max(0, $text.Length - [int]$Config.MaxLogBytesToAnalyze)) + } + return $text + } + catch { + Write-Log -Level 'WARN' -Message "Could not fetch logs for run $(Get-ObjectValue -Object $Build -Name 'id'): $($_.Exception.Message)" + return '' + } +} + function Get-Duration { param([Parameter(Mandatory)][object]$Job) @@ -383,6 +457,12 @@ function Test-IsSuccessfulStatus { return $Status.ToLowerInvariant() -in @('success', 'succeeded', 'skipped', 'neutral') } +function Test-IsCompletedStatus { + param([Parameter(Mandatory)][string]$Status) + + return $Status.ToLowerInvariant() -in @('success', 'succeeded', 'skipped', 'neutral', 'failure', 'failed', 'cancelled', 'canceled', 'timed_out', 'completed') +} + function Get-LogSummary { param( [Parameter(Mandatory)][string]$LogText, @@ -455,7 +535,7 @@ function Get-Severity { ) $normalized = $Status.ToLowerInvariant() - if ($normalized -in @('failure', 'failed', 'cancelled', 'timed_out')) { + if ($normalized -in @('failure', 'failed', 'cancelled', 'canceled', 'timed_out')) { return 'Critical' } if ((-not (Test-IsSuccessfulStatus -Status $Status)) -and $HasErrors) { @@ -481,9 +561,9 @@ function Analyze-Build { $isSlow = $duration.TotalMinutes -ge [double]$Config.SlowBuildThresholdMins $logText = '' - $definitelyProblematicStatus = $status.ToLowerInvariant() -in @('failure', 'failed', 'cancelled', 'timed_out') + $definitelyProblematicStatus = $status.ToLowerInvariant() -in @('failure', 'failed', 'cancelled', 'canceled', 'timed_out') if ($definitelyProblematicStatus -or $Config.NotifyWarningsAndSlow) { - $logText = Get-JobLogText -Repository $Repository -Job $Job + $logText = Get-BuildLogText -Repository $Repository -Build $Job } $logSummary = Get-LogSummary -LogText $logText -IgnoreErrors:$isSuccessfulStatus @@ -493,6 +573,9 @@ function Analyze-Build { if (-not $Config.NotifyWarningsAndSlow -and $severity -eq 'Warning') { $shouldAlert = $false } + if (-not $shouldAlert -and $Config.NotifySuccessfulBuilds -and (Test-IsCompletedStatus -Status $status)) { + $shouldAlert = $true + } $sha = [string](Get-ObjectValue -Object $Job -Name 'head_sha' -Default '') $commitMessage = Get-CommitMessage -Repository $Repository -Sha $sha @@ -530,16 +613,27 @@ function Analyze-Build { function New-DiscordEmbed { param([Parameter(Mandatory)][object]$Analysis) - $emoji = switch ($Analysis.Severity) { - 'Critical' { '❌' } - 'Warning' { '⚠️' } - default { 'ℹ️' } + $isSuccess = Test-IsSuccessfulStatus -Status ([string]$Analysis.Status) + $emoji = if ($isSuccess) { + '✅' + } + else { + switch ($Analysis.Severity) { + 'Critical' { '❌' } + 'Warning' { '⚠️' } + default { 'ℹ️' } + } } - $color = switch ($Analysis.Severity) { - 'Critical' { 15158332 } - 'Warning' { 16776960 } - default { 3447003 } + $color = if ($isSuccess) { + 3066993 + } + else { + switch ($Analysis.Severity) { + 'Critical' { 15158332 } + 'Warning' { 16776960 } + default { 3447003 } + } } $shortSha = if ($Analysis.CommitSha.Length -ge 8) { $Analysis.CommitSha.Substring(0, 8) } else { $Analysis.CommitSha } @@ -554,7 +648,14 @@ function New-DiscordEmbed { if ($Analysis.HasErrors) { $flags += 'errors found in logs' } if ($Analysis.HasWarnings) { $flags += 'warnings found in logs' } if ($Analysis.IsSlow) { $flags += "duration exceeded $($Config.SlowBuildThresholdMins)m" } - if ($flags.Count -eq 0) { $flags += 'problematic status' } + if ($flags.Count -eq 0) { + if ($isSuccess) { + $flags += 'build completed successfully' + } + else { + $flags += 'build status changed' + } + } $summary = $Analysis.ErrorSummary if ($summary.Length -gt 950) { @@ -562,7 +663,7 @@ function New-DiscordEmbed { } return @{ - title = "$emoji $($Analysis.Severity): $($Analysis.Repository) build #$($Analysis.RunId)" + title = "$emoji $($Analysis.Status): $($Analysis.Repository) build #$($Analysis.RunId)" url = $Analysis.BuildUrl color = $color description = ($flags -join ', ') @@ -597,10 +698,23 @@ function Send-DiscordNotification { $chunk = @($alerting | Select-Object -Skip $offset -First $maxEmbeds) $criticalCount = @($chunk | Where-Object { $_.Severity -eq 'Critical' }).Count $warningCount = @($chunk | Where-Object { $_.Severity -eq 'Warning' }).Count + $successCount = @($chunk | Where-Object { Test-IsSuccessfulStatus -Status ([string]$_.Status) }).Count + $completedCount = $chunk.Count $embeds = @($chunk | ForEach-Object { New-DiscordEmbed -Analysis $_ }) $payload = @{ username = 'Gitea Build Monitor' - content = if ($criticalCount -gt 0) { "❌ Gitea build monitor found $criticalCount critical issue(s) and $warningCount warning(s)." } else { "⚠️ Gitea build monitor found $warningCount warning(s)." } + content = if ($criticalCount -gt 0) { + "❌ Gitea build monitor found $criticalCount failed build(s), $warningCount warning(s), and $successCount successful build(s)." + } + elseif ($warningCount -gt 0) { + "⚠️ Gitea build monitor found $warningCount warning(s) and $successCount successful build(s)." + } + elseif ($successCount -gt 0) { + "✅ Gitea build monitor found $successCount successful build(s)." + } + else { + "ℹ️ Gitea build monitor found $completedCount completed build(s)." + } embeds = $embeds } @@ -776,18 +890,35 @@ function Test-IsRecentBuild { return $false } +function Test-IsCompletedBuild { + param([Parameter(Mandatory)][object]$Build) + + $conclusion = Get-ObjectValue -Object $Build -Name 'conclusion' + if ($conclusion) { + return Test-IsCompletedStatus -Status ([string]$conclusion) + } + + $status = Get-ObjectValue -Object $Build -Name 'status' + if ($status) { + return Test-IsCompletedStatus -Status ([string]$status) + } + + return $false +} + function Get-CacheKey { param( [Parameter(Mandatory)][hashtable]$Repository, [Parameter(Mandatory)][object]$Job ) + $kind = Get-ObjectValue -Object $Job -Name '_monitor_kind' -Default 'job' $jobId = Get-ObjectValue -Object $Job -Name 'id' $conclusion = Get-ObjectValue -Object $Job -Name 'conclusion' $status = Get-ObjectValue -Object $Job -Name 'status' -Default 'unknown' $state = if ($conclusion) { $conclusion } else { $status } - return "$(Get-RepositorySlug -Repository $Repository):job:$jobId`:state:$state" + return "$(Get-RepositorySlug -Repository $Repository):$kind`:$jobId`:state:$state" } function Start-BuildMonitor { @@ -812,13 +943,13 @@ function Start-BuildMonitor { $analyses = New-Object System.Collections.Generic.List[object] foreach ($repository in $Config.Repositories) { - Write-Log -Message "Fetching recent Gitea Actions jobs for $(Get-RepositorySlug -Repository $repository)." + Write-Log -Message "Fetching recent Gitea Actions runs for $(Get-RepositorySlug -Repository $repository)." try { $jobs = Get-BuildRuns -Repository $repository } catch { - Write-Log -Level 'ERROR' -Message "Failed to retrieve jobs for $(Get-RepositorySlug -Repository $repository): $($_.Exception.Message)" + Write-Log -Level 'ERROR' -Message "Failed to retrieve builds for $(Get-RepositorySlug -Repository $repository): $($_.Exception.Message)" continue } @@ -826,6 +957,9 @@ function Start-BuildMonitor { if (-not (Test-IsRecentBuild -Job $job)) { continue } + if (-not (Test-IsCompletedBuild -Build $job)) { + continue + } $cacheKey = Get-CacheKey -Repository $repository -Job $job if ($cache.ContainsKey($cacheKey)) { diff --git a/home/categories.sync-conflict-20260417-233429-VT6366A.org b/home/categories.sync-conflict-20260417-233429-VT6366A.org deleted file mode 100755 index 023e4cd..0000000 --- a/home/categories.sync-conflict-20260417-233429-VT6366A.org +++ /dev/null @@ -1,16 +0,0 @@ -#+TITLE: Categories -#+OPTIONS: toc:nil num:nil title:nil - -* Categories (Includes both blogs and posts) -- [[file:../tags/education.org][@@html:education@@]] (1) -- [[file:../tags/emacs.org][@@html:emacs@@]] (2) -- [[file:../tags/insights.org][@@html:insights@@]] (4) -- [[file:../tags/introduction.org][@@html:introduction@@]] (3) -- [[file:../tags/learning.org][@@html:learning@@]] (16) -- [[file:../tags/life.org][@@html:life@@]] (21) -- [[file:../tags/maths.org][@@html:maths@@]] (1) -- [[file:../tags/notes.org][@@html:notes@@]] (17) -- [[file:../tags/reading.org][@@html:reading@@]] (1) -- [[file:../tags/review.org][@@html:review@@]] (26) -- [[file:../tags/update.org][@@html:update@@]] (2) -- [[file:../tags/website.org][@@html:website@@]] (2) \ No newline at end of file diff --git a/posts/posts-list.org b/posts/posts-list.org index f7dd9c8..c02ae25 100755 --- a/posts/posts-list.org +++ b/posts/posts-list.org @@ -4,7 +4,7 @@ See the categories: @@html:Categories@@ * Posts: -- [[file:career/career-list.org][Career List]] @@html:08-05-2026 15:43@@ +- [[file:career/career-list.org][Career List]] @@html:08-05-2026 15:52@@ - [[file:posts-list.sync-conflict-20260417-233432-VT6366A.org][Posts List]] @@html:14-04-2026 16:36@@ - [[file:career/ha-dr.org][High Availability, Disaster Recovery and Business Continuity]] @@html:11-03-2026 17:18@@ @@html:@@ @@html:@@ - [[file:career/javascript.org][Understands the Javascript language]] @@html:11-03-2026 16:52@@ @@html:@@ @@html:@@ diff --git a/sitemap.org b/sitemap.org index a3e59e4..69e2620 100755 --- a/sitemap.org +++ b/sitemap.org @@ -12,10 +12,10 @@ - [[file:tags/review.org][Tag: review]] - [[file:tags/website.org][Tag: website]] - [[file:tags/life.org][Tag: life]] - - [[file:tags/education.org][Tag: education]] - [[file:tags/update.org][Tag: update]] - [[file:tags/insights.org][Tag: insights]] - [[file:tags/emacs.org][Tag: emacs]] + - [[file:tags/education.org][Tag: education]] - [[file:tags/reading.org][Tag: reading]] - [[file:tags/maths.org][Tag: maths]] - home