Compare commits

...
2 Commits
Author SHA1 Message Date
dresberandClaude Sonnet 5 467e60627b fix(python-checks): don't fail private-index probe on expected 404 at bare /simple/ root
Gitea's PyPI registry only resolves /simple/<package>/, not the bare
/simple/ index root, so the v1.11.2 probe added to give visible
diagnostics for the private-index setup step was always 404ing even
when the index and credentials were correct. This broke installs of
private Python packages during builds. The probe now only fails hard
on 401/403 (auth rejected) or an unreachable host; a plain 404 is
logged and left to pip's own resolution to confirm.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 04:49:00 +00:00
dresber a7d2e552af fix private pip installation and notification of healed build 2026-09-03 21:03:12 +00:00
4 changed files with 128 additions and 64 deletions
+4 -2
View File
@@ -38,8 +38,10 @@ on:
# whose Dockerfile installs the in-house BB* packages. The Dockerfile must consume it
# via `RUN --mount=type=secret,id=pip_extra_index`, never as an ARG or ENV, so the
# token is not baked into an image layer.
type: boolean
default: false
# Gitea 1.27.x does not reliably propagate boolean inputs through
# reusable workflows. Callers pass the literal string "true".
type: string
default: "false"
secrets:
REGISTRY_USERNAME: { required: true }
REGISTRY_PASSWORD: { required: true }
+59 -55
View File
@@ -43,72 +43,76 @@ jobs:
current_event = "${{ gitea.event_name }}"
token = os.environ["API_GITEA_TOKEN"]
url = (
f"{server}/api/v1/repos/{repo}/actions/runs"
f"?page=1&limit=5"
)
print(f"Fetching workflow runs from: {url}")
print(f"Current run number: {current_run_number}")
print(f"Current branch: {current_branch}")
print(f"Current event: {current_event}")
req = urllib.request.Request(
url,
headers={
"Authorization": f"token {token}",
"Accept": "application/json",
},
)
with urllib.request.urlopen(req) as response:
data = json.loads(response.read().decode("utf-8"))
runs = data.get("workflow_runs", [])
print(f"Received {len(runs)} workflow runs")
previous = "unknown"
for run in runs:
run_number = run.get("run_number")
status = run.get("status")
conclusion = run.get("conclusion")
branch = run.get("head_branch")
event = run.get("event")
print(
f"Inspecting run #{run_number}: "
f"status={status}, "
f"conclusion={conclusion}, "
f"branch={branch}, "
f"event={event}"
# Scheduled runs can push the prior run for this branch/event beyond the
# first page. Scan a bounded history so a succeeding push after a failure
# is still reported as healed.
for page in range(1, 11):
url = f"{server}/api/v1/repos/{repo}/actions/runs?page={page}&limit=100"
print(f"Fetching workflow runs page {page}: {url}")
req = urllib.request.Request(
url,
headers={
"Authorization": f"token {token}",
"Accept": "application/json",
},
)
with urllib.request.urlopen(req) as response:
data = json.loads(response.read().decode("utf-8"))
# aktuellen Run überspringen
if int(run_number) == current_run_number:
print(" -> skipping current run")
continue
runs = data.get("workflow_runs", [])
if not isinstance(runs, list):
print("Received an invalid workflow-runs payload; stopping lookup.")
break
print(f"Received {len(runs)} workflow runs on page {page}")
if not runs:
break
# nur abgeschlossene Runs
if status != "completed":
print(" -> skipping non-completed run")
continue
for run in runs:
run_number = run.get("run_number")
status = run.get("status")
conclusion = run.get("conclusion")
branch = run.get("head_branch")
event = run.get("event")
# nur gleicher Branch
if branch != current_branch:
print(" -> skipping different branch")
continue
print(
f"Inspecting run #{run_number}: "
f"status={status}, "
f"conclusion={conclusion}, "
f"branch={branch}, "
f"event={event}"
)
# nur gleiches Event
if event != current_event:
print(" -> skipping different event")
continue
# aktuellen Run überspringen
if str(run_number) == str(current_run_number):
print(" -> skipping current run")
continue
previous = conclusion or "unknown"
# nur abgeschlossene Runs
if status != "completed":
print(" -> skipping non-completed run")
continue
print(f" -> selected previous conclusion: {previous}")
break
# nur gleicher Branch
if branch != current_branch:
print(" -> skipping different branch")
continue
# nur gleiches Event
if event != current_event:
print(" -> skipping different event")
continue
previous = conclusion or "unknown"
print(f" -> selected previous conclusion: {previous}")
break
if previous != "unknown" or len(runs) < 100:
break
print(f"Previous conclusion final: {previous}")
@@ -170,4 +174,4 @@ jobs:
-H "Authorization: Bearer ${{ secrets.NTFY_TOKEN }}" \
-H "Content-Type: application/json" \
-d @/tmp/ntfy-payload.json \
"${{ secrets.NTFY_SERVER }}"
"${{ secrets.NTFY_SERVER }}"
+61 -5
View File
@@ -22,8 +22,10 @@ on:
type: boolean
default: true
use_private_index:
type: boolean
default: false
# Gitea 1.27.x does not reliably propagate boolean inputs through
# reusable workflows. Keep this a string until that regression is fixed.
type: string
default: "false"
jobs:
check:
@@ -38,15 +40,69 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Configure private package index
if: ${{ inputs.use_private_index }}
# Keep this step unconditional. A Gitea Actions update can otherwise silently
# skip it when a boolean workflow_call input is not propagated as expected;
# pip then misleadingly reports that the private package does not exist.
- name: Configure and verify private package index
env:
PRIVATE_INDEX_ENABLED: ${{ inputs.use_private_index }}
PRIVATE_INDEX_URL: ${{ secrets.PIP_EXTRA_INDEX_URL }}
run: |
echo "Private package index requested: ${PRIVATE_INDEX_ENABLED:-<empty>}"
if [ "${PRIVATE_INDEX_ENABLED}" != "true" ]; then
echo "Private package index is not requested for this workflow call."
exit 0
fi
if [ -z "$PRIVATE_INDEX_URL" ]; then
echo "use_private_index=true but the PIP_EXTRA_INDEX_URL secret is empty or not set"
exit 1
fi
# Probe the configured simple index before pip resolves dependencies. The
# probe deliberately prints only scheme/host/path, never URL credentials.
# NOTE: Gitea's PyPI package registry does not serve a browsable listing at
# the bare /simple/ root (only /simple/<package>/ resolves per PEP 503), so a
# 404 against the configured URL is expected there and is not itself a fault.
# This probe therefore only fails hard on auth rejection (401/403) or an
# unreachable/erroring host; a plain 404 is logged and deferred to pip's own
# resolution, which is the real signal for whether the package is installable.
python - "$PRIVATE_INDEX_URL" <<'PY'
import base64
import sys
from urllib.error import HTTPError, URLError
from urllib.parse import unquote, urlsplit, urlunsplit
from urllib.request import Request, urlopen
configured_url = sys.argv[1]
parsed = urlsplit(configured_url)
hostname = parsed.hostname or "<missing host>"
port = f":{parsed.port}" if parsed.port else ""
safe_url = urlunsplit((parsed.scheme, f"{hostname}{port}", parsed.path, parsed.query, ""))
request = Request(safe_url, headers={"Accept": "application/vnd.pypi.simple.v1+json"})
if parsed.username is not None:
credentials = f"{unquote(parsed.username)}:{unquote(parsed.password or '')}".encode()
request.add_header("Authorization", "Basic " + base64.b64encode(credentials).decode())
try:
with urlopen(request, timeout=15) as response: # nosec B310 -- configured CI package index
print(f"Private package-index probe: HTTP {response.status} at {safe_url}")
except HTTPError as error:
print(f"Private package-index probe: HTTP {error.code} at {safe_url}")
if error.code in {401, 403}:
print("Authentication was rejected by the private package index.")
raise SystemExit(1)
if error.code == 404:
print(
"Bare index root returned 404; Gitea's PyPI registry does not "
"serve a listing at /simple/ (only /simple/<package>/ resolves "
"per PEP 503). Treating this as expected and deferring to pip's "
"own dependency resolution to confirm the index actually works."
)
else:
raise SystemExit(1)
except URLError as error:
print(f"Private package-index probe could not reach {safe_url}: {error.reason}")
raise SystemExit(1)
PY
pip config --site set global.extra-index-url "$PRIVATE_INDEX_URL"
- name: Install Tools & Deps
@@ -86,4 +142,4 @@ jobs:
with:
name: coverage-xml
path: coverage.xml
if-no-files-found: warn
if-no-files-found: warn
+4 -2
View File
@@ -17,8 +17,10 @@ on:
type: string
use_private_index:
# build backends that need the in-house BB* packages to resolve build dependencies
type: boolean
default: false
# Gitea 1.27.x does not reliably propagate boolean inputs through
# reusable workflows. Callers pass the literal string "true".
type: string
default: "false"
secrets:
REGISTRY_USERNAME: { required: true }
REGISTRY_PASSWORD: { required: true }