Work in progress
All checks were successful
Build Antora container image for Fedora Docs / build (x86_64, latest, buildroot) (push) Successful in 54s
Build Antora container image for Fedora Docs / merge (x86_64, latest, buildroot) (push) Successful in 27s
Build buildroot container image for Fedora Docs / build (x86_64, latest, buildroot) (push) Successful in 1m26s
Build buildroot container image for Fedora Docs / merge (x86_64, latest, buildroot) (push) Successful in 46s
Build container image for testing the Fedora Council Docs / build (push) Successful in 6m2s

Signed-off-by: Hristo Marinov <hricky@fedoraproject.org>
This commit is contained in:
Hristo Marinov 2026-08-12 17:45:43 +03:00
commit d7450432f0
No known key found for this signature in database

116
ci/monitor-timestamps.py Executable file
View file

@ -0,0 +1,116 @@
#! /usr/bin/env python3
import re
import socket
import urllib.error
import urllib.request
from datetime import datetime, timedelta, timezone
TIMEOUT = 10
UTC = timezone.utc
STALE_THRESHOLD_MINUTES = 100
URLS = {
"production": "https://docs.fedoraproject.org/en-US/docs/",
"staging": "https://docs.stg.fedoraproject.org/en-US/docs/",
}
def fetch_page(url):
"""Fetch the webpage and return its content."""
try:
request = urllib.request.Request(url)
with urllib.request.urlopen(request, timeout=TIMEOUT) as response:
return response.read().decode('utf-8')
except socket.timeout:
raise RuntimeError(f"Timeout fetching {url}")
except urllib.error.HTTPError as e:
raise RuntimeError(f"HTTP error {e.code} from {url}")
except urllib.error.URLError as e:
raise RuntimeError(f"URL error from {url}: {e.reason}")
def extract_build_timestamp(html):
"""Extract the build timestamp from the footer."""
footer_match = re.search(r"<footer[^>]*>.*?</footer>", html, re.DOTALL)
if not footer_match:
raise ValueError("Could not find footer in HTML")
footer_content = footer_match.group(0)
timestamp_match = re.search(
r"Last build:\s*(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\s+UTC",
footer_content
)
if not timestamp_match:
raise ValueError("Could not find build timestamp in footer")
timestamp_str = timestamp_match.group(1)
dt = datetime.strptime(timestamp_str, "%Y-%m-%d %H:%M:%S")
return dt.replace(tzinfo=UTC)
def check_staleness(url):
"""Check if the build timestamp is stale (older than STALE_THRESHOLD_MINUTES)."""
html = fetch_page(url)
build_timestamp = extract_build_timestamp(html)
current_time = datetime.now(UTC)
time_diff = current_time - build_timestamp
is_stale = time_diff.total_seconds() > STALE_THRESHOLD_MINUTES * 60
return {
'is_stale': is_stale,
'build_timestamp': build_timestamp,
'current_time': current_time,
'time_diff': time_diff,
}
def format_time_diff(total_seconds):
"""Format time difference in human-readable form with proper pluralization."""
days = int(total_seconds // 86400)
hours = int((total_seconds % 86400) // 3600)
minutes = int((total_seconds % 3600) // 60)
parts = []
if days > 0:
parts.append(f"{days} day{'s' if days != 1 else ''}")
if hours > 0:
parts.append(f"{hours} hour{'s' if hours != 1 else ''}")
if minutes > 0 and days == 0:
parts.append(f"{minutes} minute{'s' if minutes != 1 else ''}")
return " and ".join(parts) if parts else "0 minutes"
def main():
results = {}
for instance, url in URLS.items():
try:
staleness_result = check_staleness(url)
results[instance] = {
'status': "STALE" if staleness_result['is_stale'] else "RECENT",
'build_timestamp': staleness_result['build_timestamp'],
'current_time': staleness_result['current_time'],
'time_diff': staleness_result['time_diff'],
}
except (RuntimeError, ValueError) as e:
results[instance] = {'status': "ERROR", 'error': str(e)}
print("Build Status:")
for instance, result in results.items():
if result['status'] != "ERROR":
time_str = format_time_diff(result['time_diff'].total_seconds())
build_ts = result['build_timestamp'].replace(microsecond=0)
build_ts_str = build_ts.strftime('%Y-%m-%d %H:%M:%S UTC')
print(f" {instance.capitalize():11} {result['status']:6} {build_ts_str} ({time_str} ago)")
else:
print(f" {instance.capitalize():11} {result['status']:6} {result['error']}")
stale_instances = [instance.capitalize() for instance, result in results.items() if result['status'] != "RECENT"]
return 1 if stale_instances else 0
if __name__ == "__main__":
raise SystemExit(main())