ci-hygiene: needs-docker skips workflows disabled on Windy Git
deploy/release workflows run on the target host (real daemon) and are disabled here (repo_unit DisabledWorkflows); one bounded query per process, flag everything if it fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -139,3 +139,17 @@ def test_docker_in_ci_is_flagged_with_the_fix(text):
|
|||||||
])
|
])
|
||||||
def test_docker_not_flagged_outside_ci_steps(path, text):
|
def test_docker_not_flagged_outside_ci_steps(path, text):
|
||||||
assert [k for k, _ in hy.scan_line(path, text) if k == "needs docker"] == []
|
assert [k for k, _ in hy.scan_line(path, text) if k == "needs docker"] == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_needs_docker_skips_workflows_disabled_on_windy_git(monkeypatch):
|
||||||
|
"""deploy.yml runs on the target host (a real daemon); Gitea has it disabled here."""
|
||||||
|
F = hy.cg.Finding
|
||||||
|
monkeypatch.setattr(hy, "_DISABLED", {"eternitas": {"deploy.yml"}})
|
||||||
|
got = hy._runs_here("Eternitas", [
|
||||||
|
F(".github/workflows/deploy.yml", 70, "needs docker", "docker compose in CI"),
|
||||||
|
F(".github/workflows/ci.yml", 176, "needs docker", "docker compose in CI"),
|
||||||
|
F(".github/workflows/deploy.yml", 12, "floating install", "npm install"),
|
||||||
|
])
|
||||||
|
assert [(f.path.rsplit("/", 1)[1], f.kind) for f in got] == [
|
||||||
|
("ci.yml", "needs docker"), ("deploy.yml", "floating install")]
|
||||||
|
assert hy._runs_here("eternitas", None) is None
|
||||||
|
|||||||
@@ -28,9 +28,11 @@ Exceptions: ci/ci-hygiene-allow.yml, one reason per entry.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -156,7 +158,49 @@ def scan_line(path: str, text: str) -> list[tuple[str, str]]:
|
|||||||
return hits
|
return hits
|
||||||
|
|
||||||
|
|
||||||
|
_DISABLED: dict[str, set[str]] | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def disabled_workflows() -> dict[str, set[str]]:
|
||||||
|
"""Workflow file names Gitea has DISABLED per repo (lowercased repo name).
|
||||||
|
|
||||||
|
Deploy/release workflows are disabled on Windy Git: they run on the target
|
||||||
|
host, where a Docker daemon really exists, so "needs docker" must not flag
|
||||||
|
them. One bounded query per process; on any failure nothing is excused
|
||||||
|
(flag rather than hide).
|
||||||
|
"""
|
||||||
|
global _DISABLED
|
||||||
|
if _DISABLED is not None:
|
||||||
|
return _DISABLED
|
||||||
|
_DISABLED = {}
|
||||||
|
query = ("select coalesce(json_object_agg(r.lower_name, u.config::json->'DisabledWorkflows'), '{}'::json)"
|
||||||
|
" from repo_unit u join repository r on r.id = u.repo_id"
|
||||||
|
" where u.type = 10 and u.config like '%DisabledWorkflows%';")
|
||||||
|
try:
|
||||||
|
out = subprocess.run(
|
||||||
|
["docker", "exec", "-i", "windy-git-db-1", "sh", "-c",
|
||||||
|
'psql -U "$POSTGRES_USER" -d gitea -At -v ON_ERROR_STOP=1'],
|
||||||
|
input=query, capture_output=True, text=True, check=True, timeout=30,
|
||||||
|
).stdout.strip()
|
||||||
|
_DISABLED = {k: set(v or []) for k, v in json.loads(out or "{}").items()}
|
||||||
|
except (subprocess.SubprocessError, OSError, ValueError):
|
||||||
|
pass
|
||||||
|
return _DISABLED
|
||||||
|
|
||||||
|
|
||||||
|
def _runs_here(repo: str, findings):
|
||||||
|
"""Drop "needs docker" hits in workflows that never run on Windy Git."""
|
||||||
|
if findings is None:
|
||||||
|
return None
|
||||||
|
off = disabled_workflows().get(repo.lower(), set())
|
||||||
|
return [f for f in findings if not (f.kind == "needs docker" and Path(f.path).name in off)]
|
||||||
|
|
||||||
|
|
||||||
def check(repo: str, sha: str, default_branch: str, is_default_head: bool):
|
def check(repo: str, sha: str, default_branch: str, is_default_head: bool):
|
||||||
|
return _runs_here(repo, _check(repo, sha, default_branch, is_default_head))
|
||||||
|
|
||||||
|
|
||||||
|
def _check(repo: str, sha: str, default_branch: str, is_default_head: bool):
|
||||||
bare = cg.WORK / f"{repo}.git"
|
bare = cg.WORK / f"{repo}.git"
|
||||||
if not bare.is_dir() or not cg.fetched(bare, sha): # pushed after the fetch: next cycle
|
if not bare.is_dir() or not cg.fetched(bare, sha): # pushed after the fetch: next cycle
|
||||||
return None
|
return None
|
||||||
|
|||||||
Reference in New Issue
Block a user