added tzdata and net-tools

This commit is contained in:
2026-01-15 11:26:28 +01:00
parent 016a249153
commit b6b2e95993
+63 -81
View File
@@ -46,6 +46,8 @@ spec:
value: /data
- name: GLANCE_HELPER_KEY
value: oplQqnLnJK2vErRVYJpvVUcSDBOSbCHZSbsYY2bwSifgTMfT
- name: TZ
value: Europe/Budapest
ports:
- containerPort: 8000
command:
@@ -54,18 +56,18 @@ spec:
args:
- 'set -eux;
export DEBIAN_FRONTEND=noninteractive;
apt-get update;
apt-get install -y --no-install-recommends curl ca-certificates iputils-ping
dnsutils;
dnsutils tzdata net-tools;
rm -rf /var/lib/apt/lists/*;
pip install --no-cache-dir fastapi uvicorn requests beautifulsoup4 prometheus-client;
python -c "import uvicorn; uvicorn.run(''app:APP'', host=''0.0.0.0'', port=8000)"
'
python -c "import uvicorn; uvicorn.run(''app:APP'', host=''0.0.0.0'', port=8000)"'
volumeMounts:
- name: app
mountPath: /app
@@ -183,60 +185,62 @@ data:
\ media_type=\"application/json; charset=utf-8\")\n\n\n@APP.get(\"/metrics\")\n\
def metrics():\n return Response(generate_latest(), media_type=CONTENT_TYPE_LATEST)\n\
\n\n# -------------------------------\n# Tandoor helpers\n# -------------------------------\n\
def _today_str() -> str:\n # Use Europe/Budapest for \"day\" boundaries\n \
\ return datetime.now(tz=ZoneInfo(\"Europe/Budapest\")).date().isoformat()\n\
\ndef _load_json(path: Path, default):\n try:\n with path.open(\"r\"\
, encoding=\"utf-8\") as f:\n return json.load(f)\n except Exception:\n\
\ return default\n\ndef _save_json(path: Path, data) -> None:\n tmp\
\ = path.with_suffix(path.suffix + \".tmp\")\n with tmp.open(\"w\", encoding=\"\
utf-8\") as f:\n json.dump(data, f, ensure_ascii=False, indent=2)\n \
\ tmp.replace(path)\n\ndef _tandoor_headers() -> Dict[str, str]:\n token =\
\ os.getenv(\"TANDOOR_TOKEN\", \"\")\n if not token:\n return {\"Accept\"\
: \"application/json\"}\n return {\"Accept\": \"application/json\", \"Authorization\"\
: f\"Bearer {token}\"}\n\ndef _rewrite_to_public(maybe_url: Optional[str]) ->\
\ Optional[str]:\n if not maybe_url:\n return None\n\n # Relative\
\ path -> public\n if maybe_url.startswith(\"/\"):\n return TANDOOR_PUBLIC_URL\
\ + maybe_url\n\n # If the API returns internal host URLs, rewrite scheme+host\
\ to public\n try:\n u = urlparse(maybe_url)\n pub = urlparse(TANDOOR_PUBLIC_URL)\n\
\ internal = urlparse(TANDOOR_INTERNAL_URL)\n if u.netloc and internal.netloc\
\ and u.netloc == internal.netloc:\n u = u._replace(scheme=pub.scheme,\
\ netloc=pub.netloc)\n return urlunparse(u)\n except Exception:\n\
\ pass\n\n return maybe_url\n\ndef _fetch_recipes_flat() -> List[Dict[str,\
\ Any]]:\n # Prefer /api/recipe/flat/ because it's already {id,name,image}\
\ list\n flat_url = f\"{TANDOOR_INTERNAL_URL}/api/recipe/flat/\"\n r = requests.get(flat_url,\
\ headers=_tandoor_headers(), timeout=15)\n if r.status_code == 200:\n \
\ data = r.json()\n # Expected: list\n if isinstance(data, list):\n\
\ out = []\n for x in data:\n out.append({\n\
\ \"id\": int(x.get(\"id\", 0)),\n \"name\"\
: str(x.get(\"name\", \"\")),\n \"image\": _rewrite_to_public(x.get(\"\
image\")),\n })\n return [x for x in out if x[\"id\"\
] and x[\"name\"]]\n\n # Fallback: paginated /api/recipe/\n list_url = f\"\
{TANDOOR_INTERNAL_URL}/api/recipe/?page_size=250\"\n r = requests.get(list_url,\
\ headers=_tandoor_headers(), timeout=15)\n r.raise_for_status()\n data\
\ = r.json()\n items = data.get(\"results\", []) if isinstance(data, dict)\
\ else []\n out = []\n for x in items:\n out.append({\n \
\ \"id\": int(x.get(\"id\", 0)),\n \"name\": str(x.get(\"name\",\
\ \"\")),\n \"image\": _rewrite_to_public(x.get(\"image\")),\n \
\ })\n return [x for x in out if x[\"id\"] and x[\"name\"]]\n\ndef _get_cooked_for_today()\
\ -> List[int]:\n today = _today_str()\n cooked = _load_json(COOKED_PATH,\
\ {})\n ids = cooked.get(today, [])\n # normalize\n try:\n return\
\ [int(i) for i in ids]\n except Exception:\n return []\n\ndef _set_cooked_today(ids:\
\ List[int]) -> None:\n today = _today_str()\n cooked = _load_json(COOKED_PATH,\
\ {})\n cooked[today] = sorted(list({int(i) for i in ids}))\n # Optional\
\ cleanup: keep only last 14 days\n try:\n keys = sorted(cooked.keys())\n\
\ if len(keys) > 14:\n for k in keys[:-14]:\n \
\ cooked.pop(k, None)\n except Exception:\n pass\n _save_json(COOKED_PATH,\
\ cooked)\n\ndef _get_picks_today() -> List[int]:\n today = _today_str()\n\
\ picks = _load_json(PICKS_PATH, {})\n ids = picks.get(today, [])\n try:\n\
\ return [int(i) for i in ids]\n except Exception:\n return []\n\
\ndef _set_picks_today(ids: List[int]) -> None:\n today = _today_str()\n \
\ picks = _load_json(PICKS_PATH, {})\n picks[today] = [int(i) for i in ids\
\ if int(i) > 0]\n # cleanup old days\n try:\n keys = sorted(picks.keys())\n\
\ if len(keys) > 14:\n for k in keys[:-14]:\n \
\ picks.pop(k, None)\n except Exception:\n pass\n _save_json(PICKS_PATH,\
\ picks)\n\ndef _ensure_daily_picks(recipes: List[Dict[str, Any]], count: int)\
\ -> List[int]:\n cooked = set(_get_cooked_for_today())\n picks = _get_picks_today()\n\
\n # Remove picks that are cooked today\n picks = [i for i in picks if i\
def _today_str() -> str:\n # Use Europe/Budapest for \"day\" boundaries (fallback\
\ to UTC if tzdata missing)\n try:\n return datetime.now(tz=ZoneInfo(\"\
Europe/Budapest\")).date().isoformat()\n except Exception:\n return\
\ datetime.utcnow().date().isoformat()\n\ndef _load_json(path: Path, default):\n\
\ try:\n with path.open(\"r\", encoding=\"utf-8\") as f:\n \
\ return json.load(f)\n except Exception:\n return default\n\ndef\
\ _save_json(path: Path, data) -> None:\n tmp = path.with_suffix(path.suffix\
\ + \".tmp\")\n with tmp.open(\"w\", encoding=\"utf-8\") as f:\n json.dump(data,\
\ f, ensure_ascii=False, indent=2)\n tmp.replace(path)\n\ndef _tandoor_headers()\
\ -> Dict[str, str]:\n token = os.getenv(\"TANDOOR_TOKEN\", \"\")\n if not\
\ token:\n return {\"Accept\": \"application/json\"}\n return {\"Accept\"\
: \"application/json\", \"Authorization\": f\"Bearer {token}\"}\n\ndef _rewrite_to_public(maybe_url:\
\ Optional[str]) -> Optional[str]:\n if not maybe_url:\n return None\n\
\n # Relative path -> public\n if maybe_url.startswith(\"/\"):\n \
\ return TANDOOR_PUBLIC_URL + maybe_url\n\n # If the API returns internal host\
\ URLs, rewrite scheme+host to public\n try:\n u = urlparse(maybe_url)\n\
\ pub = urlparse(TANDOOR_PUBLIC_URL)\n internal = urlparse(TANDOOR_INTERNAL_URL)\n\
\ if u.netloc and internal.netloc and u.netloc == internal.netloc:\n \
\ u = u._replace(scheme=pub.scheme, netloc=pub.netloc)\n return\
\ urlunparse(u)\n except Exception:\n pass\n\n return maybe_url\n\
\ndef _fetch_recipes_flat() -> List[Dict[str, Any]]:\n # Prefer /api/recipe/flat/\
\ because it's already {id,name,image} list\n flat_url = f\"{TANDOOR_INTERNAL_URL}/api/recipe/flat/\"\
\n r = requests.get(flat_url, headers=_tandoor_headers(), timeout=15)\n \
\ if r.status_code == 200:\n data = r.json()\n # Expected: list\n\
\ if isinstance(data, list):\n out = []\n for x in\
\ data:\n out.append({\n \"id\": int(x.get(\"\
id\", 0)),\n \"name\": str(x.get(\"name\", \"\")),\n \
\ \"image\": _rewrite_to_public(x.get(\"image\")),\n \
\ })\n return [x for x in out if x[\"id\"] and x[\"name\"]]\n\n\
\ # Fallback: paginated /api/recipe/\n list_url = f\"{TANDOOR_INTERNAL_URL}/api/recipe/?page_size=250\"\
\n r = requests.get(list_url, headers=_tandoor_headers(), timeout=15)\n \
\ r.raise_for_status()\n data = r.json()\n items = data.get(\"results\"\
, []) if isinstance(data, dict) else []\n out = []\n for x in items:\n \
\ out.append({\n \"id\": int(x.get(\"id\", 0)),\n \
\ \"name\": str(x.get(\"name\", \"\")),\n \"image\": _rewrite_to_public(x.get(\"\
image\")),\n })\n return [x for x in out if x[\"id\"] and x[\"name\"\
]]\n\ndef _get_cooked_for_today() -> List[int]:\n today = _today_str()\n \
\ cooked = _load_json(COOKED_PATH, {})\n ids = cooked.get(today, [])\n \
\ # normalize\n try:\n return [int(i) for i in ids]\n except Exception:\n\
\ return []\n\ndef _set_cooked_today(ids: List[int]) -> None:\n today\
\ = _today_str()\n cooked = _load_json(COOKED_PATH, {})\n cooked[today]\
\ = sorted(list({int(i) for i in ids}))\n # Optional cleanup: keep only last\
\ 14 days\n try:\n keys = sorted(cooked.keys())\n if len(keys)\
\ > 14:\n for k in keys[:-14]:\n cooked.pop(k, None)\n\
\ except Exception:\n pass\n _save_json(COOKED_PATH, cooked)\n\n\
def _get_picks_today() -> List[int]:\n today = _today_str()\n picks = _load_json(PICKS_PATH,\
\ {})\n ids = picks.get(today, [])\n try:\n return [int(i) for i\
\ in ids]\n except Exception:\n return []\n\ndef _set_picks_today(ids:\
\ List[int]) -> None:\n today = _today_str()\n picks = _load_json(PICKS_PATH,\
\ {})\n picks[today] = [int(i) for i in ids if int(i) > 0]\n # cleanup old\
\ days\n try:\n keys = sorted(picks.keys())\n if len(keys) >\
\ 14:\n for k in keys[:-14]:\n picks.pop(k, None)\n\
\ except Exception:\n pass\n _save_json(PICKS_PATH, picks)\n\ndef\
\ _ensure_daily_picks(recipes: List[Dict[str, Any]], count: int) -> List[int]:\n\
\ cooked = set(_get_cooked_for_today())\n picks = _get_picks_today()\n\n\
\ # Remove picks that are cooked today\n picks = [i for i in picks if i\
\ not in cooked]\n\n # Top up to requested count if needed\n if len(picks)\
\ < count:\n available = [r[\"id\"] for r in recipes if r[\"id\"] not in\
\ cooked and r[\"id\"] not in picks]\n # If everything is cooked (or too\
@@ -304,26 +308,4 @@ apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: glance-helper
namespace: glance-system
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
external-dns.alpha.kubernetes.io/hostname: glance-helper.dooplex.hu,glance-helper.home
nginx.ingress.kubernetes.io/ssl-redirect: '"true"'
nginx.ingress.kubernetes.io/proxy-body-size: 10m
spec:
ingressClassName: nginx-internal
rules:
- host: glance-helper.dooplex.hu
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: glance-helper
port:
number: 8000
tls:
- hosts:
- glance-helper.dooplex.hu
secretName: glance-helper-tls
namespace: glance-system