added tzdata and net-tools

This commit is contained in:
2026-01-15 11:26:28 +01:00
parent 016a249153
commit b6b2e95993
+62 -80
View File
@@ -46,6 +46,8 @@ spec:
value: /data value: /data
- name: GLANCE_HELPER_KEY - name: GLANCE_HELPER_KEY
value: oplQqnLnJK2vErRVYJpvVUcSDBOSbCHZSbsYY2bwSifgTMfT value: oplQqnLnJK2vErRVYJpvVUcSDBOSbCHZSbsYY2bwSifgTMfT
- name: TZ
value: Europe/Budapest
ports: ports:
- containerPort: 8000 - containerPort: 8000
command: command:
@@ -54,18 +56,18 @@ spec:
args: args:
- 'set -eux; - 'set -eux;
export DEBIAN_FRONTEND=noninteractive;
apt-get update; apt-get update;
apt-get install -y --no-install-recommends curl ca-certificates iputils-ping apt-get install -y --no-install-recommends curl ca-certificates iputils-ping
dnsutils; dnsutils tzdata net-tools;
rm -rf /var/lib/apt/lists/*; rm -rf /var/lib/apt/lists/*;
pip install --no-cache-dir fastapi uvicorn requests beautifulsoup4 prometheus-client; 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: volumeMounts:
- name: app - name: app
mountPath: /app mountPath: /app
@@ -183,60 +185,62 @@ data:
\ media_type=\"application/json; charset=utf-8\")\n\n\n@APP.get(\"/metrics\")\n\ \ 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\ def metrics():\n return Response(generate_latest(), media_type=CONTENT_TYPE_LATEST)\n\
\n\n# -------------------------------\n# Tandoor helpers\n# -------------------------------\n\ \n\n# -------------------------------\n# Tandoor helpers\n# -------------------------------\n\
def _today_str() -> str:\n # Use Europe/Budapest for \"day\" boundaries\n \ def _today_str() -> str:\n # Use Europe/Budapest for \"day\" boundaries (fallback\
\ return datetime.now(tz=ZoneInfo(\"Europe/Budapest\")).date().isoformat()\n\ \ to UTC if tzdata missing)\n try:\n return datetime.now(tz=ZoneInfo(\"\
\ndef _load_json(path: Path, default):\n try:\n with path.open(\"r\"\ Europe/Budapest\")).date().isoformat()\n except Exception:\n return\
, encoding=\"utf-8\") as f:\n return json.load(f)\n except Exception:\n\ \ datetime.utcnow().date().isoformat()\n\ndef _load_json(path: Path, default):\n\
\ return default\n\ndef _save_json(path: Path, data) -> None:\n tmp\ \ try:\n with path.open(\"r\", encoding=\"utf-8\") as f:\n \
\ = path.with_suffix(path.suffix + \".tmp\")\n with tmp.open(\"w\", encoding=\"\ \ return json.load(f)\n except Exception:\n return default\n\ndef\
utf-8\") as f:\n json.dump(data, f, ensure_ascii=False, indent=2)\n \ \ _save_json(path: Path, data) -> None:\n tmp = path.with_suffix(path.suffix\
\ tmp.replace(path)\n\ndef _tandoor_headers() -> Dict[str, str]:\n token =\ \ + \".tmp\")\n with tmp.open(\"w\", encoding=\"utf-8\") as f:\n json.dump(data,\
\ os.getenv(\"TANDOOR_TOKEN\", \"\")\n if not token:\n return {\"Accept\"\ \ f, ensure_ascii=False, indent=2)\n tmp.replace(path)\n\ndef _tandoor_headers()\
: \"application/json\"}\n return {\"Accept\": \"application/json\", \"Authorization\"\ \ -> Dict[str, str]:\n token = os.getenv(\"TANDOOR_TOKEN\", \"\")\n if not\
: f\"Bearer {token}\"}\n\ndef _rewrite_to_public(maybe_url: Optional[str]) ->\ \ token:\n return {\"Accept\": \"application/json\"}\n return {\"Accept\"\
\ Optional[str]:\n if not maybe_url:\n return None\n\n # Relative\ : \"application/json\", \"Authorization\": f\"Bearer {token}\"}\n\ndef _rewrite_to_public(maybe_url:\
\ path -> public\n if maybe_url.startswith(\"/\"):\n return TANDOOR_PUBLIC_URL\ \ Optional[str]) -> Optional[str]:\n if not maybe_url:\n return None\n\
\ + maybe_url\n\n # If the API returns internal host URLs, rewrite scheme+host\ \n # Relative path -> public\n if maybe_url.startswith(\"/\"):\n \
\ to public\n try:\n u = urlparse(maybe_url)\n pub = urlparse(TANDOOR_PUBLIC_URL)\n\ \ return TANDOOR_PUBLIC_URL + maybe_url\n\n # If the API returns internal host\
\ internal = urlparse(TANDOOR_INTERNAL_URL)\n if u.netloc and internal.netloc\ \ URLs, rewrite scheme+host to public\n try:\n u = urlparse(maybe_url)\n\
\ and u.netloc == internal.netloc:\n u = u._replace(scheme=pub.scheme,\ \ pub = urlparse(TANDOOR_PUBLIC_URL)\n internal = urlparse(TANDOOR_INTERNAL_URL)\n\
\ netloc=pub.netloc)\n return urlunparse(u)\n except Exception:\n\ \ if u.netloc and internal.netloc and u.netloc == internal.netloc:\n \
\ pass\n\n return maybe_url\n\ndef _fetch_recipes_flat() -> List[Dict[str,\ \ u = u._replace(scheme=pub.scheme, netloc=pub.netloc)\n return\
\ Any]]:\n # Prefer /api/recipe/flat/ because it's already {id,name,image}\ \ urlunparse(u)\n except Exception:\n pass\n\n return maybe_url\n\
\ list\n flat_url = f\"{TANDOOR_INTERNAL_URL}/api/recipe/flat/\"\n r = requests.get(flat_url,\ \ndef _fetch_recipes_flat() -> List[Dict[str, Any]]:\n # Prefer /api/recipe/flat/\
\ headers=_tandoor_headers(), timeout=15)\n if r.status_code == 200:\n \ \ because it's already {id,name,image} list\n flat_url = f\"{TANDOOR_INTERNAL_URL}/api/recipe/flat/\"\
\ data = r.json()\n # Expected: list\n if isinstance(data, list):\n\ \n r = requests.get(flat_url, headers=_tandoor_headers(), timeout=15)\n \
\ out = []\n for x in data:\n out.append({\n\ \ if r.status_code == 200:\n data = r.json()\n # Expected: list\n\
\ \"id\": int(x.get(\"id\", 0)),\n \"name\"\ \ if isinstance(data, list):\n out = []\n for x in\
: str(x.get(\"name\", \"\")),\n \"image\": _rewrite_to_public(x.get(\"\ \ data:\n out.append({\n \"id\": int(x.get(\"\
image\")),\n })\n return [x for x in out if x[\"id\"\ id\", 0)),\n \"name\": str(x.get(\"name\", \"\")),\n \
] and x[\"name\"]]\n\n # Fallback: paginated /api/recipe/\n list_url = f\"\ \ \"image\": _rewrite_to_public(x.get(\"image\")),\n \
{TANDOOR_INTERNAL_URL}/api/recipe/?page_size=250\"\n r = requests.get(list_url,\ \ })\n return [x for x in out if x[\"id\"] and x[\"name\"]]\n\n\
\ headers=_tandoor_headers(), timeout=15)\n r.raise_for_status()\n data\ \ # Fallback: paginated /api/recipe/\n list_url = f\"{TANDOOR_INTERNAL_URL}/api/recipe/?page_size=250\"\
\ = r.json()\n items = data.get(\"results\", []) if isinstance(data, dict)\ \n r = requests.get(list_url, headers=_tandoor_headers(), timeout=15)\n \
\ else []\n out = []\n for x in items:\n out.append({\n \ \ r.raise_for_status()\n data = r.json()\n items = data.get(\"results\"\
\ \"id\": int(x.get(\"id\", 0)),\n \"name\": str(x.get(\"name\",\ , []) if isinstance(data, dict) else []\n out = []\n for x in items:\n \
\ \"\")),\n \"image\": _rewrite_to_public(x.get(\"image\")),\n \ \ out.append({\n \"id\": int(x.get(\"id\", 0)),\n \
\ })\n return [x for x in out if x[\"id\"] and x[\"name\"]]\n\ndef _get_cooked_for_today()\ \ \"name\": str(x.get(\"name\", \"\")),\n \"image\": _rewrite_to_public(x.get(\"\
\ -> List[int]:\n today = _today_str()\n cooked = _load_json(COOKED_PATH,\ image\")),\n })\n return [x for x in out if x[\"id\"] and x[\"name\"\
\ {})\n ids = cooked.get(today, [])\n # normalize\n try:\n return\ ]]\n\ndef _get_cooked_for_today() -> List[int]:\n today = _today_str()\n \
\ [int(i) for i in ids]\n except Exception:\n return []\n\ndef _set_cooked_today(ids:\ \ cooked = _load_json(COOKED_PATH, {})\n ids = cooked.get(today, [])\n \
\ List[int]) -> None:\n today = _today_str()\n cooked = _load_json(COOKED_PATH,\ \ # normalize\n try:\n return [int(i) for i in ids]\n except Exception:\n\
\ {})\n cooked[today] = sorted(list({int(i) for i in ids}))\n # Optional\ \ return []\n\ndef _set_cooked_today(ids: List[int]) -> None:\n today\
\ cleanup: keep only last 14 days\n try:\n keys = sorted(cooked.keys())\n\ \ = _today_str()\n cooked = _load_json(COOKED_PATH, {})\n cooked[today]\
\ if len(keys) > 14:\n for k in keys[:-14]:\n \ \ = sorted(list({int(i) for i in ids}))\n # Optional cleanup: keep only last\
\ cooked.pop(k, None)\n except Exception:\n pass\n _save_json(COOKED_PATH,\ \ 14 days\n try:\n keys = sorted(cooked.keys())\n if len(keys)\
\ cooked)\n\ndef _get_picks_today() -> List[int]:\n today = _today_str()\n\ \ > 14:\n for k in keys[:-14]:\n cooked.pop(k, None)\n\
\ picks = _load_json(PICKS_PATH, {})\n ids = picks.get(today, [])\n try:\n\ \ except Exception:\n pass\n _save_json(COOKED_PATH, cooked)\n\n\
\ return [int(i) for i in ids]\n except Exception:\n return []\n\ def _get_picks_today() -> List[int]:\n today = _today_str()\n picks = _load_json(PICKS_PATH,\
\ndef _set_picks_today(ids: List[int]) -> None:\n today = _today_str()\n \ \ {})\n ids = picks.get(today, [])\n try:\n return [int(i) for i\
\ picks = _load_json(PICKS_PATH, {})\n picks[today] = [int(i) for i in ids\ \ in ids]\n except Exception:\n return []\n\ndef _set_picks_today(ids:\
\ if int(i) > 0]\n # cleanup old days\n try:\n keys = sorted(picks.keys())\n\ \ List[int]) -> None:\n today = _today_str()\n picks = _load_json(PICKS_PATH,\
\ if len(keys) > 14:\n for k in keys[:-14]:\n \ \ {})\n picks[today] = [int(i) for i in ids if int(i) > 0]\n # cleanup old\
\ picks.pop(k, None)\n except Exception:\n pass\n _save_json(PICKS_PATH,\ \ days\n try:\n keys = sorted(picks.keys())\n if len(keys) >\
\ picks)\n\ndef _ensure_daily_picks(recipes: List[Dict[str, Any]], count: int)\ \ 14:\n for k in keys[:-14]:\n picks.pop(k, None)\n\
\ -> List[int]:\n cooked = set(_get_cooked_for_today())\n picks = _get_picks_today()\n\ \ except Exception:\n pass\n _save_json(PICKS_PATH, picks)\n\ndef\
\n # Remove picks that are cooked today\n picks = [i for i in picks if i\ \ _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)\ \ 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\ \ < 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\ \ cooked and r[\"id\"] not in picks]\n # If everything is cooked (or too\
@@ -305,25 +309,3 @@ kind: Ingress
metadata: metadata:
name: glance-helper name: glance-helper
namespace: glance-system 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