fix(updates): neem het webadres van je releases ook aan
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 4m23s
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 4m23s
Het adres uit de adresbalk van Forgejo of GitHub geeft HTML terug, en de enige melding was een JSONDecodeError. Dat adres wordt nu omgerekend naar /api/v1/repos/...; komt er toch HTML, dan zegt de melding wat er mis is. Een https://-schema voor het imageveld wordt weggehaald, want een image-adres heeft er nooit een. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H9Vcwu7owJZ3nj5dCf4VFT
This commit is contained in:
parent
b4a41afc1c
commit
5068997599
6 changed files with 181 additions and 7 deletions
17
CHANGELOG.md
17
CHANGELOG.md
|
|
@ -1,7 +1,18 @@
|
|||
# v0.8.30-beta — In ontwikkeling
|
||||
# v0.8.30-beta — De updater neemt genoegen met het adres uit je adresbalk
|
||||
|
||||
Nog niets uitgebracht sinds v0.8.29. Wat hier onder komt te staan gaat mee in
|
||||
de volgende uitgave; bij het uitbrengen wordt deze kop de releaseversie.
|
||||
Wie in Forgejo naar zijn releases keek en dat adres in de instellingen plakte,
|
||||
kreeg een webpagina in plaats van JSON — en als enige melding
|
||||
`JSONDecodeError: Expecting value: line 1 column 1 (char 0)`. Daar leidt
|
||||
niemand uit af dat er `/api/v1/repos/` in het adres had moeten staan.
|
||||
|
||||
- **Het adres wordt omgerekend.** `https://git.example.nl/bes-r/server-up` en
|
||||
`.../releases` worden allebei het juiste API-adres; GitHub krijgt zijn eigen
|
||||
`api.github.com`. Een adres dat al klopt of dat we niet herkennen blijft staan.
|
||||
- **Een begrijpelijke melding** als er tóch HTML terugkomt, met wat er dan wél
|
||||
in het veld hoort.
|
||||
- **`https://` voor het imageveld wordt weggehaald.** Een image-adres heeft
|
||||
nooit een schema, dus `docker pull https://…` faalde op iets wat er in het
|
||||
veld goed uitzag.
|
||||
|
||||
# v0.8.29 — UniFi start weer op een Raspberry Pi 4
|
||||
|
||||
|
|
|
|||
|
|
@ -82,6 +82,15 @@ def image_repo(info: dict | None = None) -> str:
|
|||
"""
|
||||
expliciet = (cfg.load().get("UPDATE_IMAGE") or "").strip()
|
||||
if expliciet:
|
||||
# Een image-adres heeft nooit een schema. Wie het uit zijn adresbalk
|
||||
# plakt neemt `https://` mee, en dan faalt `docker pull` op iets wat er
|
||||
# in het veld goed uitziet. Weghalen is hier eenduidig: er bestaat geen
|
||||
# geldig image dat zo begint.
|
||||
for schema in ("https://", "http://"):
|
||||
if expliciet.lower().startswith(schema):
|
||||
expliciet = expliciet[len(schema):]
|
||||
break
|
||||
expliciet = expliciet.strip("/")
|
||||
return expliciet.rsplit(":", 1)[0] if _has_tag(expliciet) else expliciet
|
||||
img = (info or inspect_self()).get("image", "")
|
||||
if not img:
|
||||
|
|
|
|||
|
|
@ -78,11 +78,46 @@ def image_tag(release_tag: str) -> str:
|
|||
return tag[1:] if tag.startswith("v") and _parse(tag) else tag
|
||||
|
||||
|
||||
def api_url(url: str) -> str:
|
||||
"""Maak van een geplakt adres het API-adres.
|
||||
|
||||
Wie in Forgejo of GitHub naar zijn releases kijkt en dat adres uit de
|
||||
adresbalk plakt, geeft het adres van een wébpagina op. Dat levert HTML op,
|
||||
en de enige melding die je daarna kreeg was `JSONDecodeError: Expecting
|
||||
value: line 1 column 1` — waaruit niemand opmaakt dat er `/api/v1/` had
|
||||
moeten staan. Dus rekenen we het om in plaats van erover te vallen.
|
||||
|
||||
https://git.voorbeeld.nl/bes-r/server-up/releases
|
||||
→ https://git.voorbeeld.nl/api/v1/repos/bes-r/server-up/releases
|
||||
https://github.com/bes-r/server-up/releases
|
||||
→ https://api.github.com/repos/bes-r/server-up/releases
|
||||
"""
|
||||
schoon = (url or "").strip().rstrip("/")
|
||||
if not schoon or "/api/" in schoon:
|
||||
return schoon
|
||||
m = re.match(r"^(https?)://([^/]+)/([^/]+)/([^/]+?)(?:/releases.*|\.git)?$",
|
||||
schoon)
|
||||
if not m:
|
||||
return schoon
|
||||
schema, host, eigenaar, repo = m.groups()
|
||||
if host.lower() in ("github.com", "www.github.com"):
|
||||
return f"https://api.github.com/repos/{eigenaar}/{repo}/releases"
|
||||
return f"{schema}://{host}/api/v1/repos/{eigenaar}/{repo}/releases"
|
||||
|
||||
|
||||
def _releases(url: str) -> list[dict]:
|
||||
req = urllib.request.Request(
|
||||
url, headers={"Accept": "application/json", "User-Agent": "server-up"})
|
||||
with urllib.request.urlopen(req, timeout=8) as r:
|
||||
data = json.loads(r.read().decode("utf-8"))
|
||||
rauw = r.read().decode("utf-8", "replace")
|
||||
try:
|
||||
data = json.loads(rauw)
|
||||
except ValueError:
|
||||
# Bijna altijd een webpagina in plaats van de API. Zeg dát, in plaats
|
||||
# van de gebruiker met een JSON-fout te laten zitten.
|
||||
raise ValueError(
|
||||
f"{url} geeft geen JSON terug maar een webpagina. Vul het "
|
||||
"API-adres in, met /api/v1/repos/ erin.") from None
|
||||
return data if isinstance(data, list) else [data]
|
||||
|
||||
|
||||
|
|
@ -98,7 +133,7 @@ def check(current: str, force: bool = False) -> dict:
|
|||
return dict(_CACHE)
|
||||
|
||||
c = cfg.load()
|
||||
url = (c.get("UPDATE_API_URL") or "").strip()
|
||||
url = api_url(c.get("UPDATE_API_URL") or "")
|
||||
ch = channel()
|
||||
res = {"enabled": bool(url), "current": current, "channel": ch,
|
||||
"current_is_prerelease": is_prerelease(current),
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@
|
|||
"release_notes": "Release notes",
|
||||
"view_release": "View release",
|
||||
"update_api_url": "Releases API URL",
|
||||
"update_api_hint": "Forgejo/GitHub Releases API returning the latest release. Empty = disabled.",
|
||||
"update_api_hint": "Address of your releases. You may paste the address from your browser; Server Up converts it to the API address with /api/v1/repos/ itself. Empty = disabled.",
|
||||
"include_prerelease": "Include pre-releases (beta)",
|
||||
"login": "Sign in",
|
||||
"login_failed": "Sign-in failed",
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@
|
|||
"release_notes": "Release notes",
|
||||
"view_release": "Bekijk release",
|
||||
"update_api_url": "Releases-API URL",
|
||||
"update_api_hint": "Forgejo/GitHub Releases-API die de laatste release teruggeeft. Leeg = uitgeschakeld.",
|
||||
"update_api_hint": "Adres van je releases. Je mag het adres uit de adresbalk plakken; Server Up rekent het zelf om naar het API-adres met /api/v1/repos/. Leeg = uitgeschakeld.",
|
||||
"include_prerelease": "Pre-releases (beta) meenemen",
|
||||
"login": "Inloggen",
|
||||
"login_failed": "Inloggen mislukt",
|
||||
|
|
|
|||
119
tests/test_update_adres.py
Normal file
119
tests/test_update_adres.py
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
"""Het adres van de releases-API en van het image, zoals mensen ze invullen.
|
||||
|
||||
Wie in Forgejo naar zijn releases kijkt en dat adres uit de adresbalk plakt,
|
||||
geeft het adres van een webpagina op. De enige melding die daarop volgde was
|
||||
`JSONDecodeError: Expecting value: line 1 column 1 (char 0)`, en daar leidt
|
||||
niemand uit af dat er `/api/v1/repos/` in had moeten staan.
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
WORTEL = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(WORTEL / "server-up"))
|
||||
|
||||
|
||||
# ── Het releases-adres ───────────────────────────────────────────────────────
|
||||
|
||||
@pytest.mark.parametrize("ingevuld,verwacht", [
|
||||
# Wat je uit de adresbalk van Forgejo plakt.
|
||||
("https://git.voorbeeld.nl/bes-r/server-up/releases",
|
||||
"https://git.voorbeeld.nl/api/v1/repos/bes-r/server-up/releases"),
|
||||
# Zonder /releases erachter.
|
||||
("https://git.voorbeeld.nl/bes-r/server-up",
|
||||
"https://git.voorbeeld.nl/api/v1/repos/bes-r/server-up/releases"),
|
||||
# Met poort, en http.
|
||||
("http://10.0.20.22:3000/bes-r/server-up/releases",
|
||||
"http://10.0.20.22:3000/api/v1/repos/bes-r/server-up/releases"),
|
||||
# De kloon-URL.
|
||||
("https://git.voorbeeld.nl/bes-r/server-up.git",
|
||||
"https://git.voorbeeld.nl/api/v1/repos/bes-r/server-up/releases"),
|
||||
# GitHub heeft zijn API op een eigen host.
|
||||
("https://github.com/bes-r/server-up/releases",
|
||||
"https://api.github.com/repos/bes-r/server-up/releases"),
|
||||
])
|
||||
def test_webadres_wordt_een_apiadres(env, ingevuld, verwacht):
|
||||
from core import updater
|
||||
assert updater.api_url(ingevuld) == verwacht
|
||||
|
||||
|
||||
@pytest.mark.parametrize("al_goed", [
|
||||
"https://git.voorbeeld.nl/api/v1/repos/bes-r/server-up/releases",
|
||||
"https://api.github.com/repos/bes-r/server-up/releases",
|
||||
])
|
||||
def test_een_apiadres_blijft_ongemoeid(env, al_goed):
|
||||
from core import updater
|
||||
assert updater.api_url(al_goed) == al_goed
|
||||
|
||||
|
||||
def test_leeg_blijft_leeg(env):
|
||||
from core import updater
|
||||
assert updater.api_url("") == ""
|
||||
assert updater.api_url(" ") == ""
|
||||
|
||||
|
||||
def test_onherkenbaar_adres_blijft_staan(env):
|
||||
"""Niet slimmer doen dan we zijn: wat we niet herkennen laten we met rust,
|
||||
zodat een eigen proxy-adres blijft werken."""
|
||||
from core import updater
|
||||
raar = "https://voorbeeld.nl/een/diep/pad/met/releases.json"
|
||||
assert updater.api_url(raar) == raar
|
||||
|
||||
|
||||
def test_html_geeft_een_begrijpelijke_fout(env, monkeypatch):
|
||||
"""De melding moet zeggen wát er mis is, niet dat JSON niet parseert."""
|
||||
from core import updater
|
||||
|
||||
class _Antwoord:
|
||||
def read(self):
|
||||
return b"<!DOCTYPE html>\n<html lang=\"en-US\">"
|
||||
def __enter__(self):
|
||||
return self
|
||||
def __exit__(self, *a):
|
||||
return False
|
||||
|
||||
monkeypatch.setattr(updater.urllib.request, "urlopen",
|
||||
lambda *a, **k: _Antwoord())
|
||||
with pytest.raises(ValueError) as fout:
|
||||
updater._releases("https://git.voorbeeld.nl/bes-r/server-up/releases")
|
||||
melding = str(fout.value)
|
||||
assert "webpagina" in melding
|
||||
assert "/api/v1/repos/" in melding
|
||||
assert "JSONDecode" not in melding
|
||||
|
||||
|
||||
def test_check_gebruikt_het_omgerekende_adres(env, monkeypatch):
|
||||
from core import updater
|
||||
env["core"].patch({"UPDATE_API_URL":
|
||||
"https://git.voorbeeld.nl/bes-r/server-up/releases"})
|
||||
gebruikt = []
|
||||
monkeypatch.setattr(updater, "_releases",
|
||||
lambda u: gebruikt.append(u) or [
|
||||
{"tag_name": "v9.9.99", "prerelease": False}])
|
||||
res = updater.check("0.8.28", force=True)
|
||||
assert gebruikt == [
|
||||
"https://git.voorbeeld.nl/api/v1/repos/bes-r/server-up/releases"]
|
||||
assert res["update_available"] is True
|
||||
|
||||
|
||||
# ── Het imageadres ───────────────────────────────────────────────────────────
|
||||
|
||||
def test_schema_voor_het_image_wordt_weggehaald(env):
|
||||
"""`docker pull https://…` bestaat niet, maar in het veld ziet het er goed
|
||||
uit. Er is geen geldig image dat met een schema begint, dus weghalen kan."""
|
||||
from core import selfupdate
|
||||
env["core"].patch({"UPDATE_IMAGE": "https://git.voorbeeld.nl/bes-r/server-up"})
|
||||
assert selfupdate.image_repo({}) == "git.voorbeeld.nl/bes-r/server-up"
|
||||
|
||||
env["core"].patch({"UPDATE_IMAGE": "http://10.0.20.22:3000/bes-r/server-up/"})
|
||||
assert selfupdate.image_repo({}) == "10.0.20.22:3000/bes-r/server-up"
|
||||
|
||||
|
||||
def test_image_zonder_schema_blijft_gelijk(env):
|
||||
from core import selfupdate
|
||||
env["core"].patch({"UPDATE_IMAGE": "git.voorbeeld.nl/bes-r/server-up"})
|
||||
assert selfupdate.image_repo({}) == "git.voorbeeld.nl/bes-r/server-up"
|
||||
# Een tag hoort er niet in; die komt van de release.
|
||||
env["core"].patch({"UPDATE_IMAGE": "git.voorbeeld.nl/bes-r/server-up:0.8.28"})
|
||||
assert selfupdate.image_repo({}) == "git.voorbeeld.nl/bes-r/server-up"
|
||||
Loading…
Reference in a new issue