diff --git a/examples/POSTGRES/code/funcs-list/funcs_list.py b/examples/POSTGRES/code/funcs-list/funcs_list.py new file mode 100644 index 0000000..350ede6 --- /dev/null +++ b/examples/POSTGRES/code/funcs-list/funcs_list.py @@ -0,0 +1,83 @@ +# 2026-03-18 +# funcs_list.py — HTTP-функция, возвращает список всех функций пользователя. +# Вызывает внутренний REST API оператора (http://sless-operator.sless.svc.cluster.local:9090). +# Объединяет данные функций и их триггеров в один ответ. +# +# Env vars (обязательные): +# SLESS_API_URL — URL оператора внутри кластера +# SLESS_NAMESPACE — namespace пользователя (sless-{hex16}) +# SLESS_TOKEN — JWT токен для авторизации в /v1/ API +# +# Поля в ответе: +# 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+) + +import os +import json +import requests + + +def list_all(event): + api_url = os.environ["SLESS_API_URL"].rstrip("/") + namespace = os.environ["SLESS_NAMESPACE"] + token = os.environ["SLESS_TOKEN"] + headers = {"Authorization": f"Bearer {token}"} + + fns_resp = requests.get( + f"{api_url}/v1/namespaces/{namespace}/functions", + headers=headers, + timeout=10, + ) + fns_resp.raise_for_status() + functions = fns_resp.json() + + trs_resp = requests.get( + f"{api_url}/v1/namespaces/{namespace}/triggers", + headers=headers, + timeout=10, + ) + trs_resp.raise_for_status() + triggers = trs_resp.json() + + # Индексируем триггеры по имени функции (поле "function" = functionRef). + triggers_by_fn = {} + for tr in triggers: + 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: + 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, []) + ] + + result.append({ + "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+ + }) + + # Сортируем: сначала активные, потом по имени + result.sort(key=lambda f: (not f["active"], f["name"])) + + return {"functions": result, "count": len(result)} diff --git a/examples/POSTGRES/code/funcs-list/requirements.txt b/examples/POSTGRES/code/funcs-list/requirements.txt new file mode 100644 index 0000000..2c24336 --- /dev/null +++ b/examples/POSTGRES/code/funcs-list/requirements.txt @@ -0,0 +1 @@ +requests==2.31.0 diff --git a/examples/POSTGRES/resources.tf b/examples/POSTGRES/resources.tf index d11679a..cacc410 100644 --- a/examples/POSTGRES/resources.tf +++ b/examples/POSTGRES/resources.tf @@ -130,3 +130,44 @@ output "table_reader_url" { value = sless_trigger.postgres_table_reader_http.url } +# Namespace пользователя — извлекаем из уже известного URL триггера. +# URL формат: https://sless.kube5s.ru/fn/sless-{hex16}/{name} +# split("/") → ["https:", "", "sless.kube5s.ru", "fn", "sless-{hex16}", "{name}"] +# element(..., 4) → "sless-{hex16}" +locals { + user_namespace = element(split("/", sless_trigger.postgres_table_reader_http.url), 4) +} + +# HTTP-функция возвращает список всех функций пользователя с их статусами и триггерами. +# Вызывает внутренний API оператора — не проходит через внешний Ingress. +# Доступна по URL: https://sless.kube5s.ru/fn//funcs +resource "sless_function" "funcs_list" { + name = "funcs" + runtime = "python3.11" + entrypoint = "funcs_list.list_all" + memory_mb = 128 + timeout_sec = 15 + + env_vars = { + # Внутренний ClusterIP сервис оператора — без TLS, без DNS-overhead. + SLESS_API_URL = "http://sless-operator.sless.svc.cluster.local:9090" + SLESS_NAMESPACE = local.user_namespace + SLESS_TOKEN = var.api_token + } + + source_dir = "${path.module}/code/funcs-list" + + depends_on = [sless_trigger.postgres_table_reader_http] +} + +resource "sless_trigger" "funcs_list_http" { + name = "funcs-http" + type = "http" + function = sless_function.funcs_list.name + enabled = true +} + +output "funcs_url" { + value = sless_trigger.funcs_list_http.url +} +