From d32d0b976648d0e29ff4d1591b8147fb9c8970b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Sat, 7 Mar 2026 10:30:50 +0400 Subject: [PATCH] feat: add pg-query example + upload.go supports requirements.txt - examples/pg-query/handler.py: Python function querying PostgreSQL invocations table - examples/pg-query/requirements.txt: psycopg2-binary==2.9.9 - examples/pg-query/main.tf: terraform config for sless_function + sless_trigger - internal/api/handler/upload.go: generateDockerfile() now accepts hasRequirements bool - scans zip for requirements.txt at upload time - adds RUN pip install --no-cache-dir to Dockerfile when requirements.txt present - doc/progress.md: updated status for pg-query e2e task --- pg-query/handler.py | 39 ++++++++++++++++++++++++ pg-query/main.tf | 64 +++++++++++++++++++++++++++++++++++++++ pg-query/requirements.txt | 1 + 3 files changed, 104 insertions(+) create mode 100644 pg-query/handler.py create mode 100644 pg-query/main.tf create mode 100644 pg-query/requirements.txt diff --git a/pg-query/handler.py b/pg-query/handler.py new file mode 100644 index 0000000..8007e2a --- /dev/null +++ b/pg-query/handler.py @@ -0,0 +1,39 @@ +# 2026-03-07 +# handler.py — пример serverless функции, работающей с PostgreSQL. +# Подключается к postgres.sless.svc.cluster.local:5432 (внутри кластера). +# DSN берётся из env переменной PG_DSN (задаётся через env_vars ресурса sless_function). +# handle(event) — принимает JSON event, возвращает список записей из таблицы invocations. +import os +import json + +def handle(event): + # psycopg2 входит в python:3.11-slim через pip install ниже, + # либо нужно добавить в Dockerfile — для этого примера устанавливаем через requirements.txt. + try: + import psycopg2 + import psycopg2.extras + except ImportError: + return {"error": "psycopg2 not installed — add requirements.txt with psycopg2-binary"} + + dsn = os.environ.get("PG_DSN", "") + if not dsn: + return {"error": "PG_DSN env variable is not set"} + + limit = event.get("limit", 5) + + conn = psycopg2.connect(dsn) + try: + cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + cur.execute("SELECT id, namespace, function_name, status, started_at FROM invocations ORDER BY started_at DESC LIMIT %s", (limit,)) + rows = cur.fetchall() + # RealDictCursor возвращает объекты, сериализуем вручную + result = [] + for row in rows: + r = dict(row) + # datetime → str + if r.get("started_at"): + r["started_at"] = str(r["started_at"]) + result.append(r) + return {"invocations": result, "count": len(result)} + finally: + conn.close() diff --git a/pg-query/main.tf b/pg-query/main.tf new file mode 100644 index 0000000..a545b2f --- /dev/null +++ b/pg-query/main.tf @@ -0,0 +1,64 @@ +# 2026-03-07 +# main.tf — e2e тест: создать serverless функцию, которая читает из PostgreSQL. +# +# Использование: +# 1. Убедиться что оператор запущен локально: source hack/local.env && go run main.go +# 2. zip handler.py + requirements.txt: +# zip handler.zip handler.py requirements.txt +# 3. terraform init && terraform apply +# 4. После apply (ждёт ~2 мин пока kaniko соберёт образ): +# curl -s -X POST -d '{}' +# +# Функция подключается к postgres.sless.svc.cluster.local:5432 изнутри кластера. +# PG_DSN задаётся как env_var ресурса sless_function. + +terraform { + required_providers { + sless = { + source = "terra.k8c.ru/naeel/sless" + version = "~> 0.1" + } + } +} + +provider "sless" { + endpoint = "http://localhost:9090" + token = "dev-token-change-me" +} + +resource "sless_function" "pg_query" { + namespace = "default" + name = "pg-query" + runtime = "python3.11" + entrypoint = "handler.handle" + memory_mb = 128 + timeout_sec = 30 + + # DSN для подключения к postgres внутри кластера + # Хост: postgres.sless.svc.cluster.local (Service в namespace sless) + env_vars = { + PG_DSN = "postgres://sless:sless-pg-password@postgres.sless.svc.cluster.local:5432/sless?sslmode=disable" + } + + code_path = "${path.module}/handler.zip" + code_hash = filemd5("${path.module}/handler.zip") +} + +resource "sless_trigger" "pg_query_http" { + namespace = "default" + name = "pg-query-http" + type = "http" + function = sless_function.pg_query.name +} + +output "function_phase" { + value = sless_function.pg_query.phase +} + +output "function_image" { + value = sless_function.pg_query.image_ref +} + +output "trigger_url" { + value = sless_trigger.pg_query_http.url +} diff --git a/pg-query/requirements.txt b/pg-query/requirements.txt new file mode 100644 index 0000000..58ab769 --- /dev/null +++ b/pg-query/requirements.txt @@ -0,0 +1 @@ +psycopg2-binary==2.9.9