added tzdata and net-tools
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user