feat: NodeJS pg-info function; funcs endpoint: filter + created_at/last_built_at; operator v0.1.32
This commit is contained in:
@@ -1,30 +1,53 @@
|
||||
# 2026-03-18
|
||||
# funcs_list.py — HTTP-функция, возвращает список всех функций пользователя.
|
||||
# Вызывает внутренний REST API оператора (http://sless-operator.sless.svc.cluster.local:9090).
|
||||
# Объединяет данные функций и их триггеров в один ответ.
|
||||
# 2026-03-18 (обновлено: фильтрация SLESS_EXCLUDE, читаемый вывод через "#"-ключ)
|
||||
# funcs_list.py — HTTP-функция: список всех пользовательских функций с их статусами.
|
||||
# Вызывает внутренний REST API оператора (ClusterIP, без TLS).
|
||||
# Объединяет данные функций и триггеров в один ответ; скрывает служебные функции.
|
||||
#
|
||||
# Env vars (обязательные):
|
||||
# SLESS_API_URL — URL оператора внутри кластера
|
||||
# SLESS_NAMESPACE — namespace пользователя (sless-{hex16})
|
||||
# SLESS_TOKEN — JWT токен для авторизации в /v1/ API
|
||||
# Env vars:
|
||||
# SLESS_API_URL — URL оператора (http://sless-operator.sless.svc.cluster.local:9090)
|
||||
# SLESS_NAMESPACE — namespace пользователя (sless-{hex16})
|
||||
# SLESS_TOKEN — JWT токен для /v1/ API
|
||||
# SLESS_EXTERNAL_URL — публичный базовый URL (https://sless.kube5s.ru), для корректных ссылок
|
||||
# SLESS_EXCLUDE — comma-separated имена функций, которые не надо показывать
|
||||
# Пример: "funcs,event-writer,event-monitor,event-cleaner"
|
||||
#
|
||||
# Поля в ответе:
|
||||
# name, runtime, phase, image_ref, message — из /v1/namespaces/{ns}/functions
|
||||
# triggers[].type/enabled/active/url — из /v1/namespaces/{ns}/triggers
|
||||
#
|
||||
# Что ПОКА не возвращается (фиксировано оператором):
|
||||
# created_at — metav1.CreationTimestamp, не включён в fnToResponse (нужно v0.1.32+)
|
||||
# last_built_at — FunctionStatus.LastBuiltAt, не включён в fnToResponse (нужно v0.1.32+)
|
||||
# Формат вывода: JSON-объект, где каждая функция содержит поле "#" — краткий комментарий.
|
||||
# При pretty-print (python3 -m json.tool) выглядит как читаемый список с аннотациями.
|
||||
|
||||
import os
|
||||
import json
|
||||
import requests
|
||||
|
||||
|
||||
def _short_comment(fn, http_triggers, cron_triggers):
|
||||
"""Генерирует однострочный комментарий-описание функции по её метаданным."""
|
||||
phase = fn.get("phase", "")
|
||||
runtime = fn.get("runtime", "")
|
||||
|
||||
if http_triggers:
|
||||
active_str = "активна" if http_triggers[0].get("active") else "неактивна"
|
||||
return f"HTTP endpoint ({runtime}) — {phase}, {active_str}"
|
||||
elif cron_triggers:
|
||||
schedule = cron_triggers[0].get("schedule", "?")
|
||||
active_str = "активна" if cron_triggers[0].get("active") else "неактивна"
|
||||
return f"Cron '{schedule}' ({runtime}) — {phase}, {active_str}"
|
||||
else:
|
||||
return f"Job/runner без триггера ({runtime}) — {phase}"
|
||||
|
||||
|
||||
def list_all(event):
|
||||
api_url = os.environ["SLESS_API_URL"].rstrip("/")
|
||||
api_url = os.environ["SLESS_API_URL"].rstrip("/")
|
||||
namespace = os.environ["SLESS_NAMESPACE"]
|
||||
token = os.environ["SLESS_TOKEN"]
|
||||
token = os.environ["SLESS_TOKEN"]
|
||||
ext_url = os.environ.get("SLESS_EXTERNAL_URL", "").rstrip("/")
|
||||
|
||||
# Имена функций, которые не должны присутствовать в выводе.
|
||||
# Включает саму себя ("funcs") и служебные функции других примеров.
|
||||
exclude = {
|
||||
n.strip()
|
||||
for n in os.environ.get("SLESS_EXCLUDE", "").split(",")
|
||||
if n.strip()
|
||||
}
|
||||
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
fns_resp = requests.get(
|
||||
@@ -33,7 +56,6 @@ def list_all(event):
|
||||
timeout=10,
|
||||
)
|
||||
fns_resp.raise_for_status()
|
||||
functions = fns_resp.json()
|
||||
|
||||
trs_resp = requests.get(
|
||||
f"{api_url}/v1/namespaces/{namespace}/triggers",
|
||||
@@ -41,43 +63,67 @@ def list_all(event):
|
||||
timeout=10,
|
||||
)
|
||||
trs_resp.raise_for_status()
|
||||
triggers = trs_resp.json()
|
||||
|
||||
# Индексируем триггеры по имени функции (поле "function" = functionRef).
|
||||
# Индекс триггеров по имени функции
|
||||
triggers_by_fn = {}
|
||||
for tr in triggers:
|
||||
for tr in trs_resp.json():
|
||||
fn_name = tr.get("function") or tr.get("functionRef")
|
||||
if fn_name:
|
||||
triggers_by_fn.setdefault(fn_name, []).append(tr)
|
||||
|
||||
result = []
|
||||
for fn in functions:
|
||||
for fn in fns_resp.json():
|
||||
name = fn["name"]
|
||||
fn_triggers = [
|
||||
{
|
||||
"name": tr.get("name"),
|
||||
"type": tr.get("type"),
|
||||
"enabled": tr.get("enabled", True),
|
||||
"active": tr.get("active", False),
|
||||
"url": tr.get("url", ""),
|
||||
"schedule": tr.get("schedule", ""),
|
||||
}
|
||||
for tr in triggers_by_fn.get(name, [])
|
||||
if name in exclude:
|
||||
continue
|
||||
|
||||
http_triggers = [
|
||||
t for t in triggers_by_fn.get(name, []) if t.get("type") == "http"
|
||||
]
|
||||
cron_triggers = [
|
||||
t for t in triggers_by_fn.get(name, []) if t.get("type") == "cron"
|
||||
]
|
||||
is_active = any(
|
||||
t.get("enabled", True) and t.get("active", False)
|
||||
for t in triggers_by_fn.get(name, [])
|
||||
)
|
||||
|
||||
result.append({
|
||||
"name": name,
|
||||
entry = {
|
||||
# "#" — первый ключ: служит визуальным комментарием при pretty-print
|
||||
"#": _short_comment(fn, http_triggers, cron_triggers),
|
||||
"name": name,
|
||||
"runtime": fn.get("runtime"),
|
||||
"phase": fn.get("phase"),
|
||||
# active = у функции есть хотя бы один включённый и активный триггер
|
||||
"active": any(t["enabled"] and t["active"] for t in fn_triggers),
|
||||
"image_ref": fn.get("image_ref", ""),
|
||||
"message": fn.get("message", ""),
|
||||
"triggers": fn_triggers,
|
||||
# TODO: добавить created_at и last_built_at после обновления оператора до v0.1.32+
|
||||
})
|
||||
"phase": fn.get("phase"),
|
||||
"active": is_active,
|
||||
}
|
||||
|
||||
# Сортируем: сначала активные, потом по имени
|
||||
# URL вычисляем из SLESS_EXTERNAL_URL если задан — state может хранить старый домен
|
||||
if http_triggers:
|
||||
if ext_url:
|
||||
entry["url"] = f"{ext_url}/fn/{namespace}/{name}"
|
||||
else:
|
||||
entry["url"] = http_triggers[0].get("url", "")
|
||||
|
||||
if cron_triggers:
|
||||
entry["cron"] = cron_triggers[0].get("schedule", "")
|
||||
|
||||
if fn.get("message"):
|
||||
entry["message"] = fn["message"]
|
||||
|
||||
# created_at и last_built_at — доступны после обновления оператора до v0.1.32+
|
||||
if fn.get("created_at"):
|
||||
entry["created_at"] = fn["created_at"]
|
||||
if fn.get("last_built_at"):
|
||||
entry["last_built_at"] = fn["last_built_at"]
|
||||
|
||||
result.append(entry)
|
||||
|
||||
# Сортировка: активные вверх, затем по имени
|
||||
result.sort(key=lambda f: (not f["active"], f["name"]))
|
||||
|
||||
return {"functions": result, "count": len(result)}
|
||||
return {
|
||||
"namespace": namespace,
|
||||
"count": len(result),
|
||||
"functions": result,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user