diff --git a/POSTGRES/code/funcs-list/funcs_list.py b/POSTGRES/code/funcs-list/funcs_list.py
new file mode 100644
index 0000000..68157d2
--- /dev/null
+++ b/POSTGRES/code/funcs-list/funcs_list.py
@@ -0,0 +1,94 @@
+# 2026-03-18 (обновлено: plain text вывод; фильтрация SLESS_EXCLUDE)
+# funcs_list.py — HTTP-функция: список пользовательских функций, человекочитаемый plain text.
+# Вызывает внутренний REST API оператора (ClusterIP, без TLS).
+# Возвращает str → python runtime отдаёт text/plain напрямую без json.dumps.
+#
+# 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 имена функций, которые не показывать
+
+import os
+import requests
+
+SEP = "─" * 52
+
+
+def _comment(fn, http_trigs, cron_trigs):
+ phase = fn.get("phase", "?")
+ runtime = fn.get("runtime", "?")
+ if http_trigs:
+ active = "активна" if http_trigs[0].get("active") else "неактивна"
+ return f"HTTP endpoint ({runtime}) — {phase}, {active}"
+ elif cron_trigs:
+ schedule = cron_trigs[0].get("schedule", "?")
+ active = "активна" if cron_trigs[0].get("active") else "неактивна"
+ return f"Cron '{schedule}' ({runtime}) — {phase}, {active}"
+ else:
+ return f"Job/runner без триггера ({runtime}) — {phase}"
+
+
+def list_all(event):
+ api_url = os.environ["SLESS_API_URL"].rstrip("/")
+ namespace = os.environ["SLESS_NAMESPACE"]
+ token = os.environ["SLESS_TOKEN"]
+ ext_url = os.environ.get("SLESS_EXTERNAL_URL", "").rstrip("/")
+ exclude = {n.strip() for n in os.environ.get("SLESS_EXCLUDE", "").split(",") if n.strip()}
+
+ headers = {"Authorization": f"Bearer {token}"}
+ fns = requests.get(f"{api_url}/v1/namespaces/{namespace}/functions", headers=headers, timeout=10)
+ trs = requests.get(f"{api_url}/v1/namespaces/{namespace}/triggers", headers=headers, timeout=10)
+ fns.raise_for_status()
+ trs.raise_for_status()
+
+ trig_idx = {}
+ for tr in trs.json():
+ fn_name = tr.get("function") or tr.get("functionRef")
+ if fn_name:
+ trig_idx.setdefault(fn_name, []).append(tr)
+
+ items = []
+ for fn in fns.json():
+ name = fn["name"]
+ if name in exclude:
+ continue
+ http_t = [t for t in trig_idx.get(name, []) if t.get("type") == "http"]
+ cron_t = [t for t in trig_idx.get(name, []) if t.get("type") == "cron"]
+ is_active = any(t.get("enabled", True) and t.get("active", False) for t in trig_idx.get(name, []))
+ items.append((fn, http_t, cron_t, is_active))
+
+ # Сортировка: активные вверх, затем по имени
+ items.sort(key=lambda x: (not x[3], x[0]["name"]))
+
+ lines = []
+ for fn, http_t, cron_t, is_active in items:
+ name = fn["name"]
+ lines.append(SEP)
+ lines.append(f" {_comment(fn, http_t, cron_t)}")
+ lines.append(f" name: {name}")
+ lines.append(f" runtime: {fn.get('runtime', '?')}")
+ lines.append(f" phase: {fn.get('phase', '?')}")
+ lines.append(f" active: {'да' if is_active else 'нет'}")
+
+ if http_t:
+ url = f"{ext_url}/fn/{namespace}/{name}" if ext_url else http_t[0].get("url", "")
+ lines.append(f" url: {url}")
+ if cron_t:
+ lines.append(f" cron: {cron_t[0].get('schedule', '?')}")
+ if fn.get("created_at"):
+ lines.append(f" created: {fn['created_at']}")
+ if fn.get("last_built_at"):
+ lines.append(f" built: {fn['last_built_at']}")
+ if fn.get("message"):
+ lines.append(f" message: {fn['message']}")
+
+ lines.append(SEP)
+ lines.append(f" namespace: {namespace} | total: {len(items)}")
+ lines.append(SEP)
+
+ # Возвращаем str — python runtime отдаст text/plain напрямую
+ return "\n".join(lines) + "\n"
+
+
diff --git a/POSTGRES/code/funcs-list/requirements.txt b/POSTGRES/code/funcs-list/requirements.txt
new file mode 100644
index 0000000..2c24336
--- /dev/null
+++ b/POSTGRES/code/funcs-list/requirements.txt
@@ -0,0 +1 @@
+requests==2.31.0
diff --git a/POSTGRES/code/pg-info/package.json b/POSTGRES/code/pg-info/package.json
new file mode 100644
index 0000000..5e6394d
--- /dev/null
+++ b/POSTGRES/code/pg-info/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "pg-info",
+ "version": "1.0.0",
+ "description": "sless nodejs20 function: pg version + table info",
+ "dependencies": {
+ "pg": "8.11.0"
+ }
+}
\ No newline at end of file
diff --git a/POSTGRES/code/pg-info/pg_info.js b/POSTGRES/code/pg-info/pg_info.js
new file mode 100644
index 0000000..e08df17
--- /dev/null
+++ b/POSTGRES/code/pg-info/pg_info.js
@@ -0,0 +1,43 @@
+// 2026-03-18
+// pg_info.js — NodeJS-функция: проверка работы JS runtime + чтение мета-данных БД.
+// Подключается к PostgreSQL через пакет pg, возвращает версию сервера и счётчик строк.
+// Демонстрирует: nodejs20 runtime, npm-зависимость (package.json), PG из JS.
+//
+// ENV (те же что у python-функций):
+// PGHOST, PGPORT, PGDATABASE, PGUSER, PGPASSWORD, PGSSLMODE
+//
+// Entrypoint: pg_info.info
+
+'use strict';
+
+const { Client } = require('pg');
+
+exports.info = async (event) => {
+ const client = new Client({
+ host: process.env.PGHOST,
+ port: parseInt(process.env.PGPORT || '5432'),
+ database: process.env.PGDATABASE,
+ user: process.env.PGUSER,
+ password: process.env.PGPASSWORD,
+ // pg-пакет требует явного ssl-объекта; rejectUnauthorized: false — т.к.
+ // self-signed cert на nubes managed PG, но канал всё равно шифруется.
+ ssl: process.env.PGSSLMODE === 'require' ? { rejectUnauthorized: false } : false,
+ });
+
+ await client.connect();
+ try {
+ const [versionRes, countRes] = await Promise.all([
+ client.query('SELECT version() AS v'),
+ client.query('SELECT COUNT(*) AS cnt FROM terraform_demo_table'),
+ ]);
+
+ return {
+ runtime: 'nodejs20',
+ node_version: process.version,
+ pg_version: versionRes.rows[0].v,
+ table_rows: parseInt(countRes.rows[0].cnt, 10),
+ };
+ } finally {
+ await client.end();
+ }
+};
diff --git a/POSTGRES/code/table-rw/requirements.txt b/POSTGRES/code/table-rw/requirements.txt
new file mode 100644
index 0000000..58ab769
--- /dev/null
+++ b/POSTGRES/code/table-rw/requirements.txt
@@ -0,0 +1 @@
+psycopg2-binary==2.9.9
diff --git a/POSTGRES/code/table-rw/table_rw.py b/POSTGRES/code/table-rw/table_rw.py
new file mode 100644
index 0000000..9558739
--- /dev/null
+++ b/POSTGRES/code/table-rw/table_rw.py
@@ -0,0 +1,130 @@
+# 2026-03-19
+# table_rw.py — чтение и запись строк в terraform_demo_table.
+# Два entrypoint в одном файле: list_rows (JSON API) и add_row (HTML-страница + POST-обработчик).
+# ENV: PGHOST, PGPORT, PGDATABASE, PGUSER, PGPASSWORD, PGSSLMODE
+
+import os
+import json
+import psycopg2
+import psycopg2.extras
+
+
+def _connect():
+ return psycopg2.connect(
+ host=os.environ["PGHOST"],
+ port=os.environ.get("PGPORT", "5432"),
+ dbname=os.environ["PGDATABASE"],
+ user=os.environ["PGUSER"],
+ password=os.environ["PGPASSWORD"],
+ sslmode=os.environ.get("PGSSLMODE", "require"),
+ )
+
+
+def list_rows(event):
+ # Возвращает все строки terraform_demo_table, отсортированные по убыванию created_at.
+ conn = _connect()
+ try:
+ cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
+ cur.execute(
+ "SELECT id, title, created_at::text FROM terraform_demo_table ORDER BY created_at DESC"
+ )
+ rows = [dict(r) for r in cur.fetchall()]
+ return {"rows": rows, "count": len(rows)}
+ finally:
+ conn.close()
+
+
+def _render_page(rows, message=""):
+ # HTML-страница с формой ввода и таблицей строк.
+ # message — статус последней операции (успех / ошибка).
+ rows_html = "".join(
+ f"
| {r['id']} | {r['title']} | {r['created_at']} |
"
+ for r in rows
+ )
+ msg_html = f'{message}
' if message else ""
+ return f"""
+
+
+
+ pg-table-writer
+
+
+
+ pg-table-writer
+
+ {msg_html}
+
+ | # | title | created_at |
+ {rows_html}
+
+
+"""
+
+
+def add_row(event):
+ # GET → HTML-страница с формой и списком строк.
+ # POST → вставляет строку из form-поля title или JSON-поля title,
+ # затем возвращает обновлённую HTML-страницу.
+ # POST с Content-Type: application/json (curl/API) → возвращает JSON.
+ method = event.get("_method", "GET")
+
+ if method == "GET":
+ conn = _connect()
+ try:
+ cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
+ cur.execute("SELECT id, title, created_at::text FROM terraform_demo_table ORDER BY created_at DESC")
+ rows = [dict(r) for r in cur.fetchall()]
+ finally:
+ conn.close()
+ return _render_page(rows)
+
+ # POST — вставка строки
+ # Поле title приходит либо из JSON-тела, либо из application/x-www-form-urlencoded.
+ # Сервер уже распарсил JSON в event; form-данные приходят как event["body"] = "title=...".
+ title = event.get("title", "").strip()
+ if not title:
+ # Попытка распарсить form-encoded body (браузерная форма)
+ body = event.get("body", "")
+ if body.startswith("title="):
+ from urllib.parse import unquote_plus
+ title = unquote_plus(body[len("title="):].split("&")[0]).strip()
+
+ if not title:
+ return {"ok": False, "error": "title is required"}
+
+ conn = _connect()
+ try:
+ cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
+ cur.execute(
+ "INSERT INTO terraform_demo_table (title) VALUES (%s) RETURNING id, title, created_at::text",
+ (title,),
+ )
+ row = dict(cur.fetchone())
+ conn.commit()
+
+ # Если запрос из браузера (form POST) — возвращаем обновлённую страницу.
+ # Если из curl/API — возвращаем JSON.
+ accept = event.get("_accept", "")
+ if "application/json" in accept:
+ return {"ok": True, "row": row}
+
+ # Перечитываем все строки для обновлённой страницы
+ cur.execute("SELECT id, title, created_at::text FROM terraform_demo_table ORDER BY created_at DESC")
+ rows = [dict(r) for r in cur.fetchall()]
+ return _render_page(rows, message=f"Добавлено: «{row['title']}»")
+ finally:
+ conn.close()
diff --git a/POSTGRES/funcs_list.py b/POSTGRES/funcs_list.py
new file mode 100644
index 0000000..bc17fdc
--- /dev/null
+++ b/POSTGRES/funcs_list.py
@@ -0,0 +1,129 @@
+# 2026-03-18 (обновлено: фильтрация SLESS_EXCLUDE, читаемый вывод через "#"-ключ)
+# funcs_list.py — HTTP-функция: список всех пользовательских функций с их статусами.
+# Вызывает внутренний REST API оператора (ClusterIP, без TLS).
+# Объединяет данные функций и триггеров в один ответ; скрывает служебные функции.
+#
+# 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"
+#
+# Формат вывода: JSON-объект, где каждая функция содержит поле "#" — краткий комментарий.
+# При pretty-print (python3 -m json.tool) выглядит как читаемый список с аннотациями.
+
+import os
+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("/")
+ namespace = os.environ["SLESS_NAMESPACE"]
+ 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(
+ f"{api_url}/v1/namespaces/{namespace}/functions",
+ headers=headers,
+ timeout=10,
+ )
+ fns_resp.raise_for_status()
+
+ trs_resp = requests.get(
+ f"{api_url}/v1/namespaces/{namespace}/triggers",
+ headers=headers,
+ timeout=10,
+ )
+ trs_resp.raise_for_status()
+
+ # Индекс триггеров по имени функции
+ triggers_by_fn = {}
+ 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 fns_resp.json():
+ name = fn["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, [])
+ )
+
+ entry = {
+ # "#" — первый ключ: служит визуальным комментарием при pretty-print
+ "#": _short_comment(fn, http_triggers, cron_triggers),
+ "name": name,
+ "runtime": fn.get("runtime"),
+ "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 {
+ "namespace": namespace,
+ "count": len(result),
+ "functions": result,
+ }
+
diff --git a/POSTGRES/main.tf b/POSTGRES/main.tf
index 9582d07..ae97e59 100644
--- a/POSTGRES/main.tf
+++ b/POSTGRES/main.tf
@@ -29,16 +29,20 @@ variable "realm" {
description = "resource_realm parameter for nubes_postgres resource"
}
+// 2026-03-18 — pg_user/pg_password помечены optional (default="") для сверки.
+// Реальные credentials берутся из vault_secrets через locals в resources.tf.
variable "pg_user" {
type = string
sensitive = true
- description = "PostgreSQL username used by sless SQL runner"
+ default = ""
+ description = "Только для сверки. Реальный username из nubes_postgres_user.pg_user.username. Должен совпадать с vault."
}
variable "pg_password" {
type = string
sensitive = true
- description = "PostgreSQL password used by sless SQL runner"
+ default = ""
+ description = "Только для сверки. Реальный пароль из vault_secrets. Должен совпадать с tfvars."
}
provider "nubes" {
@@ -47,7 +51,7 @@ provider "nubes" {
}
provider "sless" {
- endpoint = "https://sless-api.kube5s.ru"
+ endpoint = "https://sless.kube5s.ru"
token = var.api_token
nubes_endpoint = "https://deck-api-test.ngcloud.ru/api/v1"
}
diff --git a/POSTGRES/resources.tf b/POSTGRES/resources.tf
index 4f12975..4041415 100644
--- a/POSTGRES/resources.tf
+++ b/POSTGRES/resources.tf
@@ -1,5 +1,17 @@
-// 2026-03-17 17:30
-// resources.tf — ресурсы Postgres и однократный запуск SQL-инициализации через sless_job.
+// 2026-03-18 — добавлены locals для извлечения credentials из vault_secrets (без хардкода).
+// Для сверки хардкод остаётся в terraform.tfvars на этапе разработки.
+// sless_function и sless_job закомментированы — сначала проверяется сетевое соединение.
+
+# Актуальные credentials из vault_secrets (authoritatively) — vault синхронизирован с кластером.
+# Структура vault_secrets["users"]: JSON-строка {"username": {"password": "...", "username": "..."}}
+locals {
+ pg_creds_map = jsondecode(nubes_postgres.npg.vault_secrets["users"])
+ pg_username = nubes_postgres_user.pg_user.username
+ pg_password = local.pg_creds_map[local.pg_username]["password"]
+ pg_host = nubes_postgres.npg.state_out_flat["internalConnect.master"]
+ pg_database = nubes_postgres_database.db.db_name
+}
+
resource "nubes_postgres" "npg" {
resource_name = "teststand-pg-2"
# s3_uid = "s01325"
@@ -44,6 +56,8 @@ resource "nubes_postgres_database" "db" {
}
# Служебная функция выполняет SQL-операторы из event_json.
+# Credentials берутся из locals (vault_secrets) — без хардкода.
+# Для сверки хардкод остаётся в terraform.tfvars.
resource "sless_function" "postgres_sql_runner_create_table" {
name = "pg-create-table-runner"
runtime = "python3.11"
@@ -52,21 +66,25 @@ resource "sless_function" "postgres_sql_runner_create_table" {
timeout_sec = 30
env_vars = {
- PGHOST = nubes_postgres.npg.state_out_flat["internalConnect.master"]
+ PGHOST = local.pg_host
PGPORT = "5432"
- PGDATABASE = nubes_postgres_database.db.db_name
- PGUSER = var.pg_user
- PGPASSWORD = var.pg_password
+ PGDATABASE = local.pg_database
+ PGUSER = local.pg_username
+ PGPASSWORD = local.pg_password
PGSSLMODE = "require"
+ # Для сверки (должно совпадать с vault):
+ # PGUSER = var.pg_user
+ # PGPASSWORD = var.pg_password
}
source_dir = "${path.module}/code/sql-runner"
}
+
resource "sless_job" "postgres_table_init_job" {
- name = "pg-create-table-job-main-v12"
+ name = "pg-create-table-job-main-v13"
function = sless_function.postgres_sql_runner_create_table.name
wait_timeout_sec = 180
- run_id = 12
+ run_id = 13
event_json = jsonencode({
statements = [
@@ -74,8 +92,105 @@ resource "sless_job" "postgres_table_init_job" {
]
})
- depends_on = [
- nubes_postgres_database.db
- ]
+ depends_on = [nubes_postgres_database.db]
+}
+
+# HTTP-функция на NodeJS: возвращает версию PG-сервера и счётчик строк в таблице.
+# Единственная функция примера на nodejs20 — проверка что JS runtime работает.
+# Доступна по URL: https://sless.kube5s.ru/fn//pg-info
+resource "sless_function" "pg_info" {
+ name = "pg-info"
+ runtime = "nodejs20"
+ entrypoint = "pg_info.info"
+ memory_mb = 128
+ timeout_sec = 15
+
+ env_vars = {
+ PGHOST = local.pg_host
+ PGPORT = "5432"
+ PGDATABASE = local.pg_database
+ PGUSER = local.pg_username
+ PGPASSWORD = local.pg_password
+ PGSSLMODE = "require"
+ }
+
+ source_dir = "${path.module}/code/pg-info"
+
+ depends_on = [sless_job.postgres_table_init_job]
+}
+
+resource "sless_trigger" "pg_info_http" {
+ name = "pg-info-http"
+ type = "http"
+ function = sless_function.pg_info.name
+ enabled = true
+}
+
+# HTTP-функции чтения и записи строк terraform_demo_table — в одном файле table_rw.py.
+# list_rows (GET) — читает все строки; add_row (POST {title}) — вставляет строку.
+# Доступны по URL: https://sless.kube5s.ru/fn//pg-table-reader
+# https://sless.kube5s.ru/fn//pg-table-writer
+resource "sless_function" "postgres_table_reader" {
+ name = "pg-table-reader"
+ runtime = "python3.11"
+ entrypoint = "table_rw.list_rows"
+ memory_mb = 128
+ timeout_sec = 30
+
+ env_vars = {
+ PGHOST = local.pg_host
+ PGPORT = "5432"
+ PGDATABASE = local.pg_database
+ PGUSER = local.pg_username
+ PGPASSWORD = local.pg_password
+ PGSSLMODE = "require"
+ }
+
+ source_dir = "${path.module}/code/table-rw"
+
+ depends_on = [sless_job.postgres_table_init_job]
+}
+
+resource "sless_trigger" "postgres_table_reader_http" {
+ name = "pg-table-reader-http"
+ type = "http"
+ function = sless_function.postgres_table_reader.name
+ enabled = true
+}
+
+output "table_reader_url" {
+ value = sless_trigger.postgres_table_reader_http.url
+}
+
+resource "sless_function" "postgres_table_writer" {
+ name = "pg-table-writer"
+ runtime = "python3.11"
+ entrypoint = "table_rw.add_row"
+ memory_mb = 128
+ timeout_sec = 30
+
+ env_vars = {
+ PGHOST = local.pg_host
+ PGPORT = "5432"
+ PGDATABASE = local.pg_database
+ PGUSER = local.pg_username
+ PGPASSWORD = local.pg_password
+ PGSSLMODE = "require"
+ }
+
+ source_dir = "${path.module}/code/table-rw"
+
+ depends_on = [sless_job.postgres_table_init_job]
+}
+
+resource "sless_trigger" "postgres_table_writer_http" {
+ name = "pg-table-writer-http"
+ type = "http"
+ function = sless_function.postgres_table_writer.name
+ enabled = true
+}
+
+output "table_writer_url" {
+ value = sless_trigger.postgres_table_writer_http.url
}
diff --git a/POSTGRES/scripts/pg-debug-pod.yaml b/POSTGRES/scripts/pg-debug-pod.yaml
new file mode 100644
index 0000000..652ed5b
--- /dev/null
+++ b/POSTGRES/scripts/pg-debug-pod.yaml
@@ -0,0 +1,51 @@
+# 2026-03-18 — debug pod для проверки psql-соединения из namespace функций.
+# Запускается разово. Подключается к тому же postgres, что и sless_function.
+# kubectl apply -f /tmp/pg-debug-pod.yaml
+# kubectl logs -n sless-fn-sless-ffd1f598c169b0ae pg-debug-pod
+
+apiVersion: v1
+kind: Pod
+metadata:
+ name: pg-debug-pod
+ namespace: sless-fn-sless-ffd1f598c169b0ae
+ labels:
+ purpose: debug-postgres-connectivity
+spec:
+ restartPolicy: Never
+ containers:
+ - name: psql
+ image: postgres:17-alpine
+ command:
+ - sh
+ - -c
+ - |
+ echo "=== Testing TCP connectivity to postgres ==="
+ nc -zv -w5 $PGHOST 5432 && echo "TCP OK" || echo "TCP FAILED"
+
+ echo ""
+ echo "=== Testing psql connection ==="
+ PGCONNECT_TIMEOUT=10 psql \
+ "host=$PGHOST port=$PGPORT dbname=$PGDATABASE user=$PGUSER sslmode=$PGSSLMODE" \
+ --command="SELECT current_user, current_database(), version();" \
+ 2>&1
+
+ echo ""
+ echo "=== Listing tables ==="
+ PGCONNECT_TIMEOUT=10 psql \
+ "host=$PGHOST port=$PGPORT dbname=$PGDATABASE user=$PGUSER sslmode=$PGSSLMODE" \
+ --command="\dt" \
+ 2>&1
+ env:
+ - name: PGHOST
+ value: "postgresqlk8s-master.36875359-dcea-48c4-a593-b4531f20fe96.svc.cluster.local"
+ - name: PGPORT
+ value: "5432"
+ - name: PGDATABASE
+ value: "db_terra"
+ - name: PGUSER
+ value: "u-user0"
+ - name: PGPASSWORD
+ # Актуальный пароль из vault_secrets (совпадает с tfvars.pg_password на 2026-03-18)
+ value: "M03O6fRsngWcVHB2YGivyLfbfxoii2R21nyh2A2r7WSZS5deLwBgLKkc9Wk24Zyl"
+ - name: PGSSLMODE
+ value: "require"
diff --git a/README.md b/README.md
index 0e94b03..1fd1bf3 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@
| `sless_trigger` | Публикует функцию: тип `http` создаёт публичный URL, тип `cron` — запуск по расписанию. |
| `sless_job` | Запускает функцию однократно и ожидает завершения. Используется для одноразовых операций: инициализация БД, миграции, пакетная обработка. |
-Стандартная связка для HTTP API: `sless_function` + `sless_trigger` с `type = "http"` — в результате функция доступна по URL вида `https://sless-api.kube5s.ru/fn//<имя-функции>`.
+Стандартная связка для HTTP API: `sless_function` + `sless_trigger` с `type = "http"` — в результате функция доступна по URL вида `https://sless.kube5s.ru/fn//<имя-функции>`.
---
@@ -20,7 +20,7 @@
- Terraform >= 1.0
- JWT-токен для аутентификации в sless API
-- Доступ к `https://sless-api.kube5s.ru`
+- Доступ к `https://sless.kube5s.ru`
## Конфигурация провайдера
@@ -28,9 +28,9 @@
```hcl
provider "sless" {
- endpoint = "https://sless-api.kube5s.ru"
+ endpoint = "https://sless.kube5s.ru"
token = var.token
- nubes_endpoint = "https://deck-api.ngcloud.ru/api/v1"
+ nubes_endpoint = "https://deck-api-test.ngcloud.ru/api/v1"
}
```
@@ -56,7 +56,7 @@ terraform init
terraform apply -auto-approve
# Вызов HTTP-функции с передачей имени:
-curl -s -X POST https://sless-api.kube5s.ru/fn//hello-http \
+curl -s -X POST https://sless.kube5s.ru/fn//hello-http \
-H 'Content-Type: application/json' -d '{"name":"World"}'
# Результат задания:
@@ -114,7 +114,7 @@ terraform init
terraform apply -auto-approve
terraform output job_result
-curl -s https://sless-api.kube5s.ru/fn//simple-py-time-display
+curl -s https://sless.kube5s.ru/fn//simple-py-time-display
```
---
@@ -127,7 +127,7 @@ terraform init
terraform apply -auto-approve
terraform output job_result
-curl -s https://sless-api.kube5s.ru/fn//simple-node-time-display
+curl -s https://sless.kube5s.ru/fn//simple-node-time-display
```
---
diff --git a/TNAR/code/funcs-list/funcs_list.py b/TNAR/code/funcs-list/funcs_list.py
new file mode 100644
index 0000000..68157d2
--- /dev/null
+++ b/TNAR/code/funcs-list/funcs_list.py
@@ -0,0 +1,94 @@
+# 2026-03-18 (обновлено: plain text вывод; фильтрация SLESS_EXCLUDE)
+# funcs_list.py — HTTP-функция: список пользовательских функций, человекочитаемый plain text.
+# Вызывает внутренний REST API оператора (ClusterIP, без TLS).
+# Возвращает str → python runtime отдаёт text/plain напрямую без json.dumps.
+#
+# 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 имена функций, которые не показывать
+
+import os
+import requests
+
+SEP = "─" * 52
+
+
+def _comment(fn, http_trigs, cron_trigs):
+ phase = fn.get("phase", "?")
+ runtime = fn.get("runtime", "?")
+ if http_trigs:
+ active = "активна" if http_trigs[0].get("active") else "неактивна"
+ return f"HTTP endpoint ({runtime}) — {phase}, {active}"
+ elif cron_trigs:
+ schedule = cron_trigs[0].get("schedule", "?")
+ active = "активна" if cron_trigs[0].get("active") else "неактивна"
+ return f"Cron '{schedule}' ({runtime}) — {phase}, {active}"
+ else:
+ return f"Job/runner без триггера ({runtime}) — {phase}"
+
+
+def list_all(event):
+ api_url = os.environ["SLESS_API_URL"].rstrip("/")
+ namespace = os.environ["SLESS_NAMESPACE"]
+ token = os.environ["SLESS_TOKEN"]
+ ext_url = os.environ.get("SLESS_EXTERNAL_URL", "").rstrip("/")
+ exclude = {n.strip() for n in os.environ.get("SLESS_EXCLUDE", "").split(",") if n.strip()}
+
+ headers = {"Authorization": f"Bearer {token}"}
+ fns = requests.get(f"{api_url}/v1/namespaces/{namespace}/functions", headers=headers, timeout=10)
+ trs = requests.get(f"{api_url}/v1/namespaces/{namespace}/triggers", headers=headers, timeout=10)
+ fns.raise_for_status()
+ trs.raise_for_status()
+
+ trig_idx = {}
+ for tr in trs.json():
+ fn_name = tr.get("function") or tr.get("functionRef")
+ if fn_name:
+ trig_idx.setdefault(fn_name, []).append(tr)
+
+ items = []
+ for fn in fns.json():
+ name = fn["name"]
+ if name in exclude:
+ continue
+ http_t = [t for t in trig_idx.get(name, []) if t.get("type") == "http"]
+ cron_t = [t for t in trig_idx.get(name, []) if t.get("type") == "cron"]
+ is_active = any(t.get("enabled", True) and t.get("active", False) for t in trig_idx.get(name, []))
+ items.append((fn, http_t, cron_t, is_active))
+
+ # Сортировка: активные вверх, затем по имени
+ items.sort(key=lambda x: (not x[3], x[0]["name"]))
+
+ lines = []
+ for fn, http_t, cron_t, is_active in items:
+ name = fn["name"]
+ lines.append(SEP)
+ lines.append(f" {_comment(fn, http_t, cron_t)}")
+ lines.append(f" name: {name}")
+ lines.append(f" runtime: {fn.get('runtime', '?')}")
+ lines.append(f" phase: {fn.get('phase', '?')}")
+ lines.append(f" active: {'да' if is_active else 'нет'}")
+
+ if http_t:
+ url = f"{ext_url}/fn/{namespace}/{name}" if ext_url else http_t[0].get("url", "")
+ lines.append(f" url: {url}")
+ if cron_t:
+ lines.append(f" cron: {cron_t[0].get('schedule', '?')}")
+ if fn.get("created_at"):
+ lines.append(f" created: {fn['created_at']}")
+ if fn.get("last_built_at"):
+ lines.append(f" built: {fn['last_built_at']}")
+ if fn.get("message"):
+ lines.append(f" message: {fn['message']}")
+
+ lines.append(SEP)
+ lines.append(f" namespace: {namespace} | total: {len(items)}")
+ lines.append(SEP)
+
+ # Возвращаем str — python runtime отдаст text/plain напрямую
+ return "\n".join(lines) + "\n"
+
+
diff --git a/TNAR/code/funcs-list/requirements.txt b/TNAR/code/funcs-list/requirements.txt
new file mode 100644
index 0000000..2c24336
--- /dev/null
+++ b/TNAR/code/funcs-list/requirements.txt
@@ -0,0 +1 @@
+requests==2.31.0
diff --git a/TNAR/code/pg-info/package.json b/TNAR/code/pg-info/package.json
new file mode 100644
index 0000000..5e6394d
--- /dev/null
+++ b/TNAR/code/pg-info/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "pg-info",
+ "version": "1.0.0",
+ "description": "sless nodejs20 function: pg version + table info",
+ "dependencies": {
+ "pg": "8.11.0"
+ }
+}
\ No newline at end of file
diff --git a/TNAR/code/pg-info/pg_info.js b/TNAR/code/pg-info/pg_info.js
new file mode 100644
index 0000000..e08df17
--- /dev/null
+++ b/TNAR/code/pg-info/pg_info.js
@@ -0,0 +1,43 @@
+// 2026-03-18
+// pg_info.js — NodeJS-функция: проверка работы JS runtime + чтение мета-данных БД.
+// Подключается к PostgreSQL через пакет pg, возвращает версию сервера и счётчик строк.
+// Демонстрирует: nodejs20 runtime, npm-зависимость (package.json), PG из JS.
+//
+// ENV (те же что у python-функций):
+// PGHOST, PGPORT, PGDATABASE, PGUSER, PGPASSWORD, PGSSLMODE
+//
+// Entrypoint: pg_info.info
+
+'use strict';
+
+const { Client } = require('pg');
+
+exports.info = async (event) => {
+ const client = new Client({
+ host: process.env.PGHOST,
+ port: parseInt(process.env.PGPORT || '5432'),
+ database: process.env.PGDATABASE,
+ user: process.env.PGUSER,
+ password: process.env.PGPASSWORD,
+ // pg-пакет требует явного ssl-объекта; rejectUnauthorized: false — т.к.
+ // self-signed cert на nubes managed PG, но канал всё равно шифруется.
+ ssl: process.env.PGSSLMODE === 'require' ? { rejectUnauthorized: false } : false,
+ });
+
+ await client.connect();
+ try {
+ const [versionRes, countRes] = await Promise.all([
+ client.query('SELECT version() AS v'),
+ client.query('SELECT COUNT(*) AS cnt FROM terraform_demo_table'),
+ ]);
+
+ return {
+ runtime: 'nodejs20',
+ node_version: process.version,
+ pg_version: versionRes.rows[0].v,
+ table_rows: parseInt(countRes.rows[0].cnt, 10),
+ };
+ } finally {
+ await client.end();
+ }
+};
diff --git a/TNAR/code/sql-runner/requirements.txt b/TNAR/code/sql-runner/requirements.txt
new file mode 100644
index 0000000..56ae88a
--- /dev/null
+++ b/TNAR/code/sql-runner/requirements.txt
@@ -0,0 +1,3 @@
+# 2026-03-17 00:00
+# requirements.txt — зависимости для функции запуска SQL.
+psycopg2-binary==2.9.9
diff --git a/TNAR/code/sql-runner/sql_runner.py b/TNAR/code/sql-runner/sql_runner.py
new file mode 100644
index 0000000..cca9e03
--- /dev/null
+++ b/TNAR/code/sql-runner/sql_runner.py
@@ -0,0 +1,39 @@
+# 2026-03-17 00:00
+# sql_runner.py — функция для выполнения SQL-операторов из входного события.
+import os
+import psycopg2
+
+
+def run_sql(event):
+ # Выполняет список SQL-операторов в одной транзакции для атомарной инициализации схемы.
+ # Параметры подключения передаются раздельно, чтобы избежать ошибок парсинга DSN при спецсимволах.
+ pg_host = os.environ["PGHOST"]
+ pg_port = os.environ.get("PGPORT", "5432")
+ pg_database = os.environ["PGDATABASE"]
+ pg_user = os.environ["PGUSER"]
+ pg_password = os.environ["PGPASSWORD"]
+ pg_sslmode = os.environ.get("PGSSLMODE", "require")
+ statements = event.get("statements", [])
+
+ if not statements:
+ return {"error": "no statements provided"}
+
+ connection = psycopg2.connect(
+ host=pg_host,
+ port=pg_port,
+ dbname=pg_database,
+ user=pg_user,
+ password=pg_password,
+ sslmode=pg_sslmode,
+ )
+ try:
+ cursor = connection.cursor()
+ for statement in statements:
+ cursor.execute(statement)
+ connection.commit()
+ return {"ok": True, "executed": len(statements)}
+ except Exception as error:
+ connection.rollback()
+ return {"error": str(error)}
+ finally:
+ connection.close()
diff --git a/TNAR/code/table-rw/requirements.txt b/TNAR/code/table-rw/requirements.txt
new file mode 100644
index 0000000..58ab769
--- /dev/null
+++ b/TNAR/code/table-rw/requirements.txt
@@ -0,0 +1 @@
+psycopg2-binary==2.9.9
diff --git a/TNAR/code/table-rw/table_rw.py b/TNAR/code/table-rw/table_rw.py
new file mode 100644
index 0000000..9558739
--- /dev/null
+++ b/TNAR/code/table-rw/table_rw.py
@@ -0,0 +1,130 @@
+# 2026-03-19
+# table_rw.py — чтение и запись строк в terraform_demo_table.
+# Два entrypoint в одном файле: list_rows (JSON API) и add_row (HTML-страница + POST-обработчик).
+# ENV: PGHOST, PGPORT, PGDATABASE, PGUSER, PGPASSWORD, PGSSLMODE
+
+import os
+import json
+import psycopg2
+import psycopg2.extras
+
+
+def _connect():
+ return psycopg2.connect(
+ host=os.environ["PGHOST"],
+ port=os.environ.get("PGPORT", "5432"),
+ dbname=os.environ["PGDATABASE"],
+ user=os.environ["PGUSER"],
+ password=os.environ["PGPASSWORD"],
+ sslmode=os.environ.get("PGSSLMODE", "require"),
+ )
+
+
+def list_rows(event):
+ # Возвращает все строки terraform_demo_table, отсортированные по убыванию created_at.
+ conn = _connect()
+ try:
+ cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
+ cur.execute(
+ "SELECT id, title, created_at::text FROM terraform_demo_table ORDER BY created_at DESC"
+ )
+ rows = [dict(r) for r in cur.fetchall()]
+ return {"rows": rows, "count": len(rows)}
+ finally:
+ conn.close()
+
+
+def _render_page(rows, message=""):
+ # HTML-страница с формой ввода и таблицей строк.
+ # message — статус последней операции (успех / ошибка).
+ rows_html = "".join(
+ f"| {r['id']} | {r['title']} | {r['created_at']} |
"
+ for r in rows
+ )
+ msg_html = f'{message}
' if message else ""
+ return f"""
+
+
+
+ pg-table-writer
+
+
+
+ pg-table-writer
+
+ {msg_html}
+
+ | # | title | created_at |
+ {rows_html}
+
+
+"""
+
+
+def add_row(event):
+ # GET → HTML-страница с формой и списком строк.
+ # POST → вставляет строку из form-поля title или JSON-поля title,
+ # затем возвращает обновлённую HTML-страницу.
+ # POST с Content-Type: application/json (curl/API) → возвращает JSON.
+ method = event.get("_method", "GET")
+
+ if method == "GET":
+ conn = _connect()
+ try:
+ cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
+ cur.execute("SELECT id, title, created_at::text FROM terraform_demo_table ORDER BY created_at DESC")
+ rows = [dict(r) for r in cur.fetchall()]
+ finally:
+ conn.close()
+ return _render_page(rows)
+
+ # POST — вставка строки
+ # Поле title приходит либо из JSON-тела, либо из application/x-www-form-urlencoded.
+ # Сервер уже распарсил JSON в event; form-данные приходят как event["body"] = "title=...".
+ title = event.get("title", "").strip()
+ if not title:
+ # Попытка распарсить form-encoded body (браузерная форма)
+ body = event.get("body", "")
+ if body.startswith("title="):
+ from urllib.parse import unquote_plus
+ title = unquote_plus(body[len("title="):].split("&")[0]).strip()
+
+ if not title:
+ return {"ok": False, "error": "title is required"}
+
+ conn = _connect()
+ try:
+ cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
+ cur.execute(
+ "INSERT INTO terraform_demo_table (title) VALUES (%s) RETURNING id, title, created_at::text",
+ (title,),
+ )
+ row = dict(cur.fetchone())
+ conn.commit()
+
+ # Если запрос из браузера (form POST) — возвращаем обновлённую страницу.
+ # Если из curl/API — возвращаем JSON.
+ accept = event.get("_accept", "")
+ if "application/json" in accept:
+ return {"ok": True, "row": row}
+
+ # Перечитываем все строки для обновлённой страницы
+ cur.execute("SELECT id, title, created_at::text FROM terraform_demo_table ORDER BY created_at DESC")
+ rows = [dict(r) for r in cur.fetchall()]
+ return _render_page(rows, message=f"Добавлено: «{row['title']}»")
+ finally:
+ conn.close()
diff --git a/TNAR/funcs_list.py b/TNAR/funcs_list.py
new file mode 100644
index 0000000..bc17fdc
--- /dev/null
+++ b/TNAR/funcs_list.py
@@ -0,0 +1,129 @@
+# 2026-03-18 (обновлено: фильтрация SLESS_EXCLUDE, читаемый вывод через "#"-ключ)
+# funcs_list.py — HTTP-функция: список всех пользовательских функций с их статусами.
+# Вызывает внутренний REST API оператора (ClusterIP, без TLS).
+# Объединяет данные функций и триггеров в один ответ; скрывает служебные функции.
+#
+# 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"
+#
+# Формат вывода: JSON-объект, где каждая функция содержит поле "#" — краткий комментарий.
+# При pretty-print (python3 -m json.tool) выглядит как читаемый список с аннотациями.
+
+import os
+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("/")
+ namespace = os.environ["SLESS_NAMESPACE"]
+ 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(
+ f"{api_url}/v1/namespaces/{namespace}/functions",
+ headers=headers,
+ timeout=10,
+ )
+ fns_resp.raise_for_status()
+
+ trs_resp = requests.get(
+ f"{api_url}/v1/namespaces/{namespace}/triggers",
+ headers=headers,
+ timeout=10,
+ )
+ trs_resp.raise_for_status()
+
+ # Индекс триггеров по имени функции
+ triggers_by_fn = {}
+ 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 fns_resp.json():
+ name = fn["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, [])
+ )
+
+ entry = {
+ # "#" — первый ключ: служит визуальным комментарием при pretty-print
+ "#": _short_comment(fn, http_triggers, cron_triggers),
+ "name": name,
+ "runtime": fn.get("runtime"),
+ "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 {
+ "namespace": namespace,
+ "count": len(result),
+ "functions": result,
+ }
+
diff --git a/TNAR/luceUNDnode.tf b/TNAR/luceUNDnode.tf
new file mode 100644
index 0000000..75c0658
--- /dev/null
+++ b/TNAR/luceUNDnode.tf
@@ -0,0 +1,73 @@
+
+# resource "nubes_lucee" "app1" {
+# # Lucee-приложение, зависит от Postgres
+# resource_name = "lucy_teststand_0"
+# # resource_realm = "k8s-3.ext.nubes.ru"
+# resource_realm = nubes_postgres.db2.resource_realm
+# # resource_realm = "k8s-4-sandbox-nubes-ru"
+# domain = "web-test-stand"
+
+# git_path = "https://gitea-naeel.giteak8s.services.ngcloud.ru/naeel/testlucee"
+
+# json_env = jsonencode({
+# # 🔗 Настройки Data Source 'testds' для Lucee (Application.cfc)
+# testds_class = "org.postgresql.Driver" # 📂 Драйвер БД
+# testds_bundleName = "org.postgresql.jdbc" # 📦 Имя бандла JDBC
+# testds_bundleVersion = "42.6.0" # 🔢 Версия драйвера
+# testds_connectionString = "jdbc:postgresql://${nubes_postgres.db2.state_out_flat["internalConnect.master"]}:5432/postgres?sslmode=require" # 🚀 Строка подключения
+# testds_username = nubes_postgres_user.db2_user.username # 👤 Логин
+# testds_password = jsondecode(nubes_postgres.db2.vault_secrets["users"])[nubes_postgres_user.db2_user.username]["password"] # 🔑 Пароль
+# testds_connectionLimit = "5" # 🚦 Лимит соединений
+# testds_liveTimeout = "15" # ⏳ Таймаут жизни
+# testds_validate = "false" # ✅ Валидация при запросе
+# })
+
+# resource_c_p_u = 300
+# resource_memory = 512
+# resource_instances = 1
+# app_version = "5.4"
+
+# depends_on = [nubes_postgres.db2]
+# }
+
+# resource "nubes_nodejs" "app3" {
+# # NodeJS демо, работающий с тем же Postgres.
+# resource_name = "node_01"
+# resource_realm = nubes_postgres.db2.resource_realm
+# domain = "node07"
+# git_path = "https://gitea-naeel.giteak8s.services.ngcloud.ru/naeel/testnode.git"
+# health_path = "/healthz"
+# app_version = "23"
+
+# json_env = jsonencode({
+# # Переменные подключения к Postgres.
+# PGHOST = nubes_postgres.db2.state_out_flat["internalConnect.master"]
+# PGPORT = "5432"
+# PGUSER = nubes_postgres_user.db2_user.username
+# PGPASSWORD = jsondecode(nubes_postgres.db2.vault_secrets["users"])[nubes_postgres_user.db2_user.username]["password"]
+# PGDATABASE = nubes_postgres_database.db2_app.db_name
+# PGSSLMODE = "require"
+# DATABASE_URL = format(
+# "postgresql://%s:%s@%s:5432/%s?sslmode=require",
+# nubes_postgres_user.db2_user.username,
+# jsondecode(nubes_postgres.db2.vault_secrets["users"])[nubes_postgres_user.db2_user.username]["password"],
+# nubes_postgres.db2.state_out_flat["internalConnect.master"],
+# nubes_postgres_database.db2_app.db_name
+# )
+# })
+
+# resource_c_p_u = 300
+# resource_memory = 256
+# resource_instances = 1
+
+# depends_on = [nubes_postgres.db2]
+# }
+
+# output "pg_vault_secrets" {
+# value = nubes_postgres.db2.vault_secrets
+# sensitive = true
+# }
+
+# terraform output -json pg_vault_secrets
+
+
diff --git a/TNAR/main.tf b/TNAR/main.tf
new file mode 100644
index 0000000..ae97e59
--- /dev/null
+++ b/TNAR/main.tf
@@ -0,0 +1,58 @@
+// 2026-03-17 17:05
+// main.tf — провайдеры и переменные для Nubes + sless.
+terraform {
+ required_providers {
+ nubes = {
+ source = "terra.k8c.ru/nubes/nubes"
+ version = "5.0.19"
+ }
+ sless = {
+ source = "terra.k8c.ru/naeel/sless"
+ version = "~> 0.1.18"
+ }
+ }
+}
+
+variable "api_token" {
+ type = string
+ sensitive = true
+ description = "Nubes API token"
+}
+variable "s3_uid" {
+ type = string
+ sensitive = true
+ description = "Nubes S3 UID"
+}
+variable "realm" {
+ type = string
+ sensitive = true
+ description = "resource_realm parameter for nubes_postgres resource"
+}
+
+// 2026-03-18 — pg_user/pg_password помечены optional (default="") для сверки.
+// Реальные credentials берутся из vault_secrets через locals в resources.tf.
+variable "pg_user" {
+ type = string
+ sensitive = true
+ default = ""
+ description = "Только для сверки. Реальный username из nubes_postgres_user.pg_user.username. Должен совпадать с vault."
+}
+
+variable "pg_password" {
+ type = string
+ sensitive = true
+ default = ""
+ description = "Только для сверки. Реальный пароль из vault_secrets. Должен совпадать с tfvars."
+}
+
+provider "nubes" {
+ api_token = var.api_token
+ api_endpoint = "https://deck-api-test.ngcloud.ru/api/v1/index.cfm"
+}
+
+provider "sless" {
+ endpoint = "https://sless.kube5s.ru"
+ token = var.api_token
+ nubes_endpoint = "https://deck-api-test.ngcloud.ru/api/v1"
+}
+
diff --git a/TNAR/resources.tf b/TNAR/resources.tf
new file mode 100644
index 0000000..cae3c49
--- /dev/null
+++ b/TNAR/resources.tf
@@ -0,0 +1,196 @@
+// 2026-03-18 — добавлены locals для извлечения credentials из vault_secrets (без хардкода).
+// Для сверки хардкод остаётся в terraform.tfvars на этапе разработки.
+// sless_function и sless_job закомментированы — сначала проверяется сетевое соединение.
+
+# Актуальные credentials из vault_secrets (authoritatively) — vault синхронизирован с кластером.
+# Структура vault_secrets["users"]: JSON-строка {"username": {"password": "...", "username": "..."}}
+locals {
+ pg_creds_map = jsondecode(nubes_postgres.npg.vault_secrets["users"])
+ pg_username = nubes_postgres_user.pg_user.username
+ pg_password = local.pg_creds_map[local.pg_username]["password"]
+ pg_host = nubes_postgres.npg.state_out_flat["internalConnect.master"]
+ pg_database = nubes_postgres_database.db.db_name
+}
+
+resource "nubes_postgres" "npg" {
+ resource_name = "testnarod-pg-0"
+ # s3_uid = "s01325"
+ s3_uid = var.s3_uid
+ resource_realm = var.realm
+ resource_instances = 1
+ resource_memory = 512
+ resource_c_p_u = 500
+ resource_disk = "1"
+ app_version = "17"
+ json_parameters = jsonencode({
+ log_connections = "off"
+ log_disconnections = "off"
+ })
+ enable_pg_pooler_master = false
+ enable_pg_pooler_slave = false
+ allow_no_s_s_l = false
+ auto_scale = false
+ auto_scale_percentage = 10
+ auto_scale_tech_window = 0
+ auto_scale_quota_gb = "1"
+ need_external_address_master = false
+
+ # suspend_on_destroy = false
+ operation_timeout = "11m"
+ adopt_existing_on_create = true
+}
+
+resource "nubes_postgres_user" "pg_user" {
+ postgres_id = nubes_postgres.npg.id
+ username = "u-user0"
+ role = "ddl_user"
+ adopt_existing_on_create = true
+}
+
+resource "nubes_postgres_database" "db" {
+ postgres_id = nubes_postgres.npg.id
+ db_name = "db_terra"
+ db_owner = nubes_postgres_user.pg_user.username
+ adopt_existing_on_create = true
+ # suspend_on_destroy = false
+}
+
+# Служебная функция выполняет SQL-операторы из event_json.
+# Credentials берутся из locals (vault_secrets) — без хардкода.
+# Для сверки хардкод остаётся в terraform.tfvars.
+resource "sless_function" "postgres_sql_runner_create_table" {
+ name = "pg-create-table-runner"
+ runtime = "python3.11"
+ entrypoint = "sql_runner.run_sql"
+ memory_mb = 128
+ timeout_sec = 30
+
+ env_vars = {
+ PGHOST = local.pg_host
+ PGPORT = "5432"
+ PGDATABASE = local.pg_database
+ PGUSER = local.pg_username
+ PGPASSWORD = local.pg_password
+ PGSSLMODE = "require"
+ # Для сверки (должно совпадать с vault):
+ # PGUSER = var.pg_user
+ # PGPASSWORD = var.pg_password
+ }
+
+ source_dir = "${path.module}/code/sql-runner"
+}
+
+resource "sless_job" "postgres_table_init_job" {
+ name = "pg-create-table-job-main-v13"
+ function = sless_function.postgres_sql_runner_create_table.name
+ wait_timeout_sec = 180
+ run_id = 13
+
+ event_json = jsonencode({
+ statements = [
+ "CREATE TABLE IF NOT EXISTS terraform_demo_table (id serial PRIMARY KEY, title text NOT NULL, created_at timestamp DEFAULT now())"
+ ]
+ })
+
+ depends_on = [nubes_postgres_database.db]
+}
+
+# HTTP-функция на NodeJS: возвращает версию PG-сервера и счётчик строк в таблице.
+# Единственная функция примера на nodejs20 — проверка что JS runtime работает.
+# Доступна по URL: https://sless.kube5s.ru/fn//pg-info
+resource "sless_function" "pg_info" {
+ name = "pg-info"
+ runtime = "nodejs20"
+ entrypoint = "pg_info.info"
+ memory_mb = 128
+ timeout_sec = 15
+
+ env_vars = {
+ PGHOST = local.pg_host
+ PGPORT = "5432"
+ PGDATABASE = local.pg_database
+ PGUSER = local.pg_username
+ PGPASSWORD = local.pg_password
+ PGSSLMODE = "require"
+ }
+
+ source_dir = "${path.module}/code/pg-info"
+
+ depends_on = [sless_job.postgres_table_init_job]
+}
+
+resource "sless_trigger" "pg_info_http" {
+ name = "pg-info-http"
+ type = "http"
+ function = sless_function.pg_info.name
+ enabled = true
+}
+
+# HTTP-функции чтения и записи строк terraform_demo_table — в одном файле table_rw.py.
+# list_rows (GET) — читает все строки; add_row (POST {title}) — вставляет строку.
+# Доступны по URL: https://sless.kube5s.ru/fn//pg-table-reader
+# https://sless.kube5s.ru/fn//pg-table-writer
+resource "sless_function" "postgres_table_reader" {
+ name = "pg-table-reader"
+ runtime = "python3.11"
+ entrypoint = "table_rw.list_rows"
+ memory_mb = 128
+ timeout_sec = 30
+
+ env_vars = {
+ PGHOST = local.pg_host
+ PGPORT = "5432"
+ PGDATABASE = local.pg_database
+ PGUSER = local.pg_username
+ PGPASSWORD = local.pg_password
+ PGSSLMODE = "require"
+ }
+
+ source_dir = "${path.module}/code/table-rw"
+
+ depends_on = [sless_job.postgres_table_init_job]
+}
+
+resource "sless_trigger" "postgres_table_reader_http" {
+ name = "pg-table-reader-http"
+ type = "http"
+ function = sless_function.postgres_table_reader.name
+ enabled = true
+}
+
+output "table_reader_url" {
+ value = sless_trigger.postgres_table_reader_http.url
+}
+
+resource "sless_function" "postgres_table_writer" {
+ name = "pg-table-writer"
+ runtime = "python3.11"
+ entrypoint = "table_rw.add_row"
+ memory_mb = 128
+ timeout_sec = 30
+
+ env_vars = {
+ PGHOST = local.pg_host
+ PGPORT = "5432"
+ PGDATABASE = local.pg_database
+ PGUSER = local.pg_username
+ PGPASSWORD = local.pg_password
+ PGSSLMODE = "require"
+ }
+
+ source_dir = "${path.module}/code/table-rw"
+
+ depends_on = [sless_job.postgres_table_init_job]
+}
+
+resource "sless_trigger" "postgres_table_writer_http" {
+ name = "pg-table-writer-http"
+ type = "http"
+ function = sless_function.postgres_table_writer.name
+ enabled = true
+}
+
+output "table_writer_url" {
+ value = sless_trigger.postgres_table_writer_http.url
+}
+
diff --git a/TNAR/scripts/pg-debug-pod.yaml b/TNAR/scripts/pg-debug-pod.yaml
new file mode 100644
index 0000000..652ed5b
--- /dev/null
+++ b/TNAR/scripts/pg-debug-pod.yaml
@@ -0,0 +1,51 @@
+# 2026-03-18 — debug pod для проверки psql-соединения из namespace функций.
+# Запускается разово. Подключается к тому же postgres, что и sless_function.
+# kubectl apply -f /tmp/pg-debug-pod.yaml
+# kubectl logs -n sless-fn-sless-ffd1f598c169b0ae pg-debug-pod
+
+apiVersion: v1
+kind: Pod
+metadata:
+ name: pg-debug-pod
+ namespace: sless-fn-sless-ffd1f598c169b0ae
+ labels:
+ purpose: debug-postgres-connectivity
+spec:
+ restartPolicy: Never
+ containers:
+ - name: psql
+ image: postgres:17-alpine
+ command:
+ - sh
+ - -c
+ - |
+ echo "=== Testing TCP connectivity to postgres ==="
+ nc -zv -w5 $PGHOST 5432 && echo "TCP OK" || echo "TCP FAILED"
+
+ echo ""
+ echo "=== Testing psql connection ==="
+ PGCONNECT_TIMEOUT=10 psql \
+ "host=$PGHOST port=$PGPORT dbname=$PGDATABASE user=$PGUSER sslmode=$PGSSLMODE" \
+ --command="SELECT current_user, current_database(), version();" \
+ 2>&1
+
+ echo ""
+ echo "=== Listing tables ==="
+ PGCONNECT_TIMEOUT=10 psql \
+ "host=$PGHOST port=$PGPORT dbname=$PGDATABASE user=$PGUSER sslmode=$PGSSLMODE" \
+ --command="\dt" \
+ 2>&1
+ env:
+ - name: PGHOST
+ value: "postgresqlk8s-master.36875359-dcea-48c4-a593-b4531f20fe96.svc.cluster.local"
+ - name: PGPORT
+ value: "5432"
+ - name: PGDATABASE
+ value: "db_terra"
+ - name: PGUSER
+ value: "u-user0"
+ - name: PGPASSWORD
+ # Актуальный пароль из vault_secrets (совпадает с tfvars.pg_password на 2026-03-18)
+ value: "M03O6fRsngWcVHB2YGivyLfbfxoii2R21nyh2A2r7WSZS5deLwBgLKkc9Wk24Zyl"
+ - name: PGSSLMODE
+ value: "require"
diff --git a/TNAR/scripts/read_pg_user_secret.py b/TNAR/scripts/read_pg_user_secret.py
new file mode 100644
index 0000000..b0735f3
--- /dev/null
+++ b/TNAR/scripts/read_pg_user_secret.py
@@ -0,0 +1,40 @@
+# 2026-03-17 13:05
+# read_pg_user_secret.py — читает пароль пользователя managed PostgreSQL из k8s Secret.
+# Используется из Terraform external data source, чтобы apply сам получал актуальный пароль
+# даже для уже существующего пользователя, созданного вне текущего state.
+
+import base64
+import json
+import subprocess
+import sys
+
+
+def main():
+ # Читаем query от Terraform external provider из stdin.
+ query = json.load(sys.stdin)
+ namespace = query["namespace"]
+ secret_name = query["secret"]
+
+ # kubectl уже настроен на удалённой машине; читаем ровно поле data.password.
+ result = subprocess.run(
+ [
+ "kubectl",
+ "get",
+ "secret",
+ "-n",
+ namespace,
+ secret_name,
+ "-o",
+ "jsonpath={.data.password}",
+ ],
+ check=True,
+ capture_output=True,
+ text=True,
+ )
+
+ password = base64.b64decode(result.stdout.strip()).decode()
+ json.dump({"password": password}, sys.stdout)
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/demo-managed-functions/README.md b/demo-managed-functions/README.md
new file mode 100644
index 0000000..1fd1bf3
--- /dev/null
+++ b/demo-managed-functions/README.md
@@ -0,0 +1,194 @@
+# Примеры использования sless
+
+## Обзор платформы
+
+**sless** — система управления serverless-функциями на базе Kubernetes. Разработчик загружает код функции, платформа собирает из него Docker-образ, разворачивает его в кластере и предоставляет HTTP-эндпоинт для вызова. Всё описывается декларативно через Terraform.
+
+### Основные ресурсы провайдера
+
+| Ресурс | Назначение |
+|---|---|
+| `sless_function` | Описывает функцию: язык, точку входа, лимиты, переменные окружения. При создании загружает код и запускает его сборку в образ. Сама по себе недоступна снаружи — нужен триггер или задание. |
+| `sless_trigger` | Публикует функцию: тип `http` создаёт публичный URL, тип `cron` — запуск по расписанию. |
+| `sless_job` | Запускает функцию однократно и ожидает завершения. Используется для одноразовых операций: инициализация БД, миграции, пакетная обработка. |
+
+Стандартная связка для HTTP API: `sless_function` + `sless_trigger` с `type = "http"` — в результате функция доступна по URL вида `https://sless.kube5s.ru/fn//<имя-функции>`.
+
+---
+
+## Требования
+
+- Terraform >= 1.0
+- JWT-токен для аутентификации в sless API
+- Доступ к `https://sless.kube5s.ru`
+
+## Конфигурация провайдера
+
+Во всех примерах файл `main.tf` содержит блок провайдера. Токен передаётся через переменную, значение которой задаётся в `terraform.tfvars`:
+
+```hcl
+provider "sless" {
+ endpoint = "https://sless.kube5s.ru"
+ token = var.token
+ nubes_endpoint = "https://deck-api-test.ngcloud.ru/api/v1"
+}
+```
+
+Namespace функций вычисляется автоматически из JWT-токена: `sless-{sha256[:8]}`.
+
+> **Перед запуском любого примера** откройте файл `terraform.tfvars` в директории примера и впишите свой токен Nubes API:
+> ```hcl
+> token = "ваш токен Nubes API"
+> ```
+> Токен выдаётся в личном кабинете Nubes. Файл `terraform.tfvars` добавлен в `.gitignore` — он не попадёт в репозиторий.
+
+---
+
+## Примеры
+
+### `hello-node` — минимальный пример на Node.js
+
+Две независимые функции: HTTP-функция, возвращающая приветствие, и одноразовое задание, суммирующее набор чисел. Хорошая отправная точка для знакомства с платформой.
+
+```bash
+cd hello-node
+terraform init
+terraform apply -auto-approve
+
+# Вызов HTTP-функции с передачей имени:
+curl -s -X POST https://sless.kube5s.ru/fn//hello-http \
+ -H 'Content-Type: application/json' -d '{"name":"World"}'
+
+# Результат задания:
+terraform output job_message
+```
+
+---
+
+### `hello-go` — минимальный пример на Go 1.23
+
+Аналог `hello-node`, но на Go. Демонстрирует поддержку Go-рантайма: HTTP-функция и одноразовое задание. Код пользователя оформляется как пакет `handler` с функцией `Handle(event)`.
+
+```bash
+cd hello-go
+terraform init
+terraform apply -auto-approve
+
+terraform output job_message
+terraform output trigger_url
+```
+
+---
+
+### `pg-list-python` — выборка данных из PostgreSQL (Python)
+
+Минимальный пример работы с базой данных: одна HTTP-функция читает список записей из таблицы PostgreSQL и возвращает их в JSON. Таблица с тестовыми данными создаётся автоматически при первом вызове. Нет заданий, нет инициализации — только функция и триггер.
+
+**Переменные:**
+
+| Переменная | Описание | Значение по умолчанию |
+|---|---|---|
+| `pg_dsn` | Строка подключения к PostgreSQL | `postgres://sless:sless-pg-password@postgres.sless.svc.cluster.local:5432/sless?sslmode=disable` |
+
+```bash
+cd pg-list-python
+terraform init
+terraform apply -auto-approve
+
+# URL функции выводится после применения:
+terraform output catalog_url
+
+# Запрос к функции:
+curl -s $(terraform output -raw catalog_url)
+```
+
+---
+
+### `simple-python` — одноразовое задание передаёт данные в HTTP-функцию (Python)
+
+При `apply` выполняется задание, которое фиксирует текущее время. Результат передаётся в HTTP-функцию через переменные окружения и отображается при каждом запросе.
+
+```bash
+cd simple-python
+terraform init
+terraform apply -auto-approve
+
+terraform output job_result
+curl -s https://sless.kube5s.ru/fn//simple-py-time-display
+```
+
+---
+
+### `simple-node` — то же самое на Node.js 20
+
+```bash
+cd simple-node
+terraform init
+terraform apply -auto-approve
+
+terraform output job_result
+curl -s https://sless.kube5s.ru/fn//simple-node-time-display
+```
+
+---
+
+### `notes-python` — CRUD API на Python с PostgreSQL
+
+Полноценное приложение: инициализация схемы базы данных через задания, CRUD-функция для работы с записями, отдельная функция для получения списка.
+
+**Переменные:**
+
+| Переменная | Описание | Значение по умолчанию |
+|---|---|---|
+| `pg_dsn` | Строка подключения к PostgreSQL | `postgres://sless:sless-pg-password@postgres.sless.svc.cluster.local:5432/sless?sslmode=disable` |
+
+```bash
+cd notes-python
+terraform init
+terraform apply -auto-approve
+
+# Статус инициализации базы данных:
+terraform output db_init_table_status
+terraform output db_init_index_status
+
+# Создать запись:
+curl -s -X POST "$(terraform output -raw notes_url)/add?title=Hello&body=World"
+
+# Получить список записей:
+curl -s $(terraform output -raw notes_list_url)
+
+# Обновить запись (id из предыдущего ответа):
+curl -s -X POST "$(terraform output -raw notes_url)/update?id=1&title=Updated&body=New+body"
+
+# Удалить запись:
+curl -s -X POST "$(terraform output -raw notes_url)/delete?id=1"
+```
+
+---
+
+## Полезные команды
+
+```bash
+# Посмотреть текущее состояние задеплоенных ресурсов:
+terraform show
+
+# Принудительно пересобрать функцию (например, после изменения кода):
+terraform apply -replace=sless_function.<имя> -auto-approve
+
+# Повторно запустить задание: увеличить значение run_id в .tf-файле, затем:
+terraform apply -auto-approve
+
+# Удалить все ресурсы примера:
+terraform destroy -auto-approve
+```
+
+## Структура примера
+
+```
+<пример>/
+├── main.tf — конфигурация провайдера
+├── *.tf — ресурсы: функции, триггеры, задания
+├── variables.tf — входные переменные
+├── terraform.tfvars — значения переменных (не коммитится в git)
+└── code/ — исходный код функций
+```
diff --git a/demo-managed-functions/terraform.tfvars.example b/demo-managed-functions/terraform.tfvars.example
index 983f6da..db09390 100644
--- a/demo-managed-functions/terraform.tfvars.example
+++ b/demo-managed-functions/terraform.tfvars.example
@@ -2,8 +2,8 @@
# Скопируй в terraform.tfvars и подставь актуальный токен.
token = "PUT_TOKEN_HERE"
-sless_endpoint = "http://sless-api.185.247.187.147.nip.io"
-nubes_endpoint = "https://deck-test.ngcloud.ru/api/v1"
+sless_endpoint = "https://sless-api.kube5s.ru"
+nubes_endpoint = "https://deck-api-test.ngcloud.ru/api/v1"
pg_dsn = "postgres://sless:sless-pg-password@postgres.sless.svc.cluster.local:5432/sless?sslmode=disable"
rabbitmq_url = "amqp://sless:sless123@rabbitmq.sless.svc.cluster.local:5672/"
diff --git a/demo-managed-functions/variables.tf b/demo-managed-functions/variables.tf
index a00a728..6012dff 100644
--- a/demo-managed-functions/variables.tf
+++ b/demo-managed-functions/variables.tf
@@ -10,7 +10,7 @@ variable "token" {
variable "sless_endpoint" {
description = "Endpoint sless API"
type = string
- default = "https://sless-api.kube5s.ru"
+ default = "https://sless.kube5s.ru"
}
variable "nubes_endpoint" {
diff --git a/hello-go/main.tf b/hello-go/main.tf
index ac319ca..ef1580e 100644
--- a/hello-go/main.tf
+++ b/hello-go/main.tf
@@ -11,7 +11,7 @@ terraform {
}
provider "sless" {
- endpoint = "https://sless-api.kube5s.ru"
+ endpoint = "https://sless.kube5s.ru"
token = var.token
nubes_endpoint = "https://deck-api-test.ngcloud.ru/api/v1"
}
diff --git a/hello-node/main.tf b/hello-node/main.tf
index ecc41af..09f8a39 100644
--- a/hello-node/main.tf
+++ b/hello-node/main.tf
@@ -17,7 +17,7 @@ terraform {
}
provider "sless" {
- endpoint = "https://sless-api.kube5s.ru"
+ endpoint = "https://sless.kube5s.ru"
token = var.token
nubes_endpoint = "https://deck-api-test.ngcloud.ru/api/v1"
}
diff --git a/notes-python/main.tf b/notes-python/main.tf
index 02a6e4a..21762bb 100644
--- a/notes-python/main.tf
+++ b/notes-python/main.tf
@@ -21,7 +21,7 @@ terraform {
# sless провайдер подключается к API кластера.
provider "sless" {
- endpoint = "https://sless-api.kube5s.ru"
+ endpoint = "https://sless.kube5s.ru"
token = var.token
nubes_endpoint = "https://deck-api-test.ngcloud.ru/api/v1"
}
diff --git a/pg-list-python/main.tf b/pg-list-python/main.tf
index 028d81c..871034b 100644
--- a/pg-list-python/main.tf
+++ b/pg-list-python/main.tf
@@ -11,7 +11,7 @@ terraform {
}
provider "sless" {
- endpoint = "https://sless-api.kube5s.ru"
+ endpoint = "https://sless.kube5s.ru"
token = var.token
nubes_endpoint = "https://deck-api-test.ngcloud.ru/api/v1"
}
diff --git a/simple-node/main.tf b/simple-node/main.tf
index 2376334..af95879 100644
--- a/simple-node/main.tf
+++ b/simple-node/main.tf
@@ -25,7 +25,7 @@ terraform {
}
provider "sless" {
- endpoint = "https://sless-api.kube5s.ru"
+ endpoint = "https://sless.kube5s.ru"
token = var.token
nubes_endpoint = "https://deck-api-test.ngcloud.ru/api/v1"
}
diff --git a/simple-python/main.tf b/simple-python/main.tf
index 7485ca6..e55c11a 100644
--- a/simple-python/main.tf
+++ b/simple-python/main.tf
@@ -24,7 +24,7 @@ terraform {
}
provider "sless" {
- endpoint = "https://sless-api.kube5s.ru"
+ endpoint = "https://sless.kube5s.ru"
token = var.token
nubes_endpoint = "https://deck-api-test.ngcloud.ru/api/v1"
}