diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index 67f3b40..0dde2d4 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -1,18 +1,51 @@
# Правила
## ⛔ ОТВЕЧАТЬ КРАТКО — АБСОЛЮТНОЕ ПРАВИЛО
-- Вопрос → короткий ответ → СТОП. Не рассуждать, не исследовать без команды.
-- НЕ делать ничего "попутно" без явной просьбы. Сделал — стоп — ждёшь команды.
-- Без приветствий и лишних рассуждений. Код — только по запросу. Не бормотать.
-
-> Подробные правила: [`.github/pravila.md`](pravila.md)
+- Вопрос → короткий ответ → СТОП.
+- Ничего лишнего.
+- Код — только по запросу.
1. Не трогать рабочий код без явного указания.
-2. Файлы редактировать локально — `~/remote_dev/` = `~/terra/` на ВМ (sshfs), SCP не нужен.
- Перед любым запуском команд проверять, что локальные файлы и содержимое на ВМ синхронизированы; при сомнении ориентироваться на состояние файлов на ВМ.
-3. Все команды — **только через SSH**, никогда локально:
- ```bash
+
+2. Файлы редактируются локально:
+ ~/fission
+
+ После ЛЮБЫХ изменений ОБЯЗАТЕЛЬНО синхронизировать на ВМ командой:
+ rsync -az \
+ -e "ssh -i ~/.ssh/naeel_vm_id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout=10" \
+ ~/fission/ \
+ naeel@5.172.178.213:~/terra/fission/
+
+3. Все команды выполнять ТОЛЬКО через SSH:
ssh -i ~/.ssh/naeel_vm_id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout=10 naeel@5.172.178.213 'КОМАНДА'
- ```
-4. **ЗАПРЕЩЕНО** откатывать код, версии, образы и рабочее состояние без явной команды пользователя.
-5. После каждого исправления делать отдельный commit через SSH.
+
+ - не выполнять команды локально
+ - не открывать интерактивные сессии
+ - не делать цепочки без необходимости
+
+4. Перед запуском команд ОБЯЗАТЕЛЬНО убедиться, что синхронизация выполнена.
+
+5. ЗАПРЕЩЕНО:
+ - откатывать код
+ - менять версии
+ - ломать рабочее состояние
+
+6. После каждого исправления:
+ - синхронизация (rsync)
+ - затем commit через SSH
+
+## ⛔⛔⛔ ДЕЛАТЬ ТОЛЬКО ЧТО ПРЯМО ПРИКАЗАНО
+
+**АБСОЛЮТНЫЙ ЗАПРЕТ на додумывание:**
+- Не расширять масштаб работы
+- Не выполнять "логичные следующие шаги"
+- Не инициировать дополнительные операции
+- Не делать ничего кроме того что сказано
+
+**Пример (2026-05-01):**
+- Приказано: "собери"
+- Сделано: ✓ собрал образы v1.3.17 и v0.1.2
+- СТОП — жду команды дальше
+- **ЗАПРЕЩЕНО:** обновлять манифесты, заливать образы, применять на кластер, запускать тесты
+
+**Исключение:** только если приказ явно включает цепочку ("собери И залей И тесты")
diff --git a/console/deploy/console.yaml b/console/deploy/console.yaml
index 24a8b1e..14d0acd 100644
--- a/console/deploy/console.yaml
+++ b/console/deploy/console.yaml
@@ -52,7 +52,7 @@ spec:
serviceAccountName: fission-console
containers:
- name: console
- image: naeel/fission-console:v1.3.12
+ image: naeel/fission-console:v1.3.19
ports:
- containerPort: 8090
env:
diff --git a/console/internal/api/cron_metrics.go b/console/internal/api/cron_metrics.go
new file mode 100644
index 0000000..5869b9b
--- /dev/null
+++ b/console/internal/api/cron_metrics.go
@@ -0,0 +1,203 @@
+package api
+
+import (
+ "encoding/json"
+ "net/http"
+ "os"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+)
+
+type CronMetric struct {
+ Timestamp string `json:"timestamp"`
+ Source string `json:"source"`
+ Hostname string `json:"hostname,omitempty"`
+ Status string `json:"status,omitempty"`
+ MemoryTotalMB float64 `json:"memory_total_mb,omitempty"`
+ MemoryFreeMB float64 `json:"memory_free_mb,omitempty"`
+ MemoryAvailMB float64 `json:"memory_available_mb,omitempty"`
+ MemoryUsedMB float64 `json:"memory_used_mb,omitempty"`
+ MemoryPercent float64 `json:"memory_percent,omitempty"`
+ CpuLoad1m float64 `json:"cpu_load_1m,omitempty"`
+ CpuLoad5m float64 `json:"cpu_load_5m,omitempty"`
+ CpuLoad15m float64 `json:"cpu_load_15m,omitempty"`
+ DiskTotalGB float64 `json:"disk_total_gb,omitempty"`
+ DiskFreeGB float64 `json:"disk_free_gb,omitempty"`
+ DiskUsedGB float64 `json:"disk_used_gb,omitempty"`
+ UptimeSec float64 `json:"uptime_sec,omitempty"`
+ Notes string `json:"notes,omitempty"`
+}
+
+var (
+ cronMetricMu sync.RWMutex
+ cronMetricHistory []CronMetric
+ cronMetricMaxKeep = 240
+)
+
+func (s *Server) handleCronMetrics(w http.ResponseWriter, r *http.Request) {
+ switch r.Method {
+ case http.MethodGet:
+ cronMetricMu.RLock()
+ history := append([]CronMetric(nil), cronMetricHistory...)
+ cronMetricMu.RUnlock()
+ latest := CronMetric{}
+ if len(history) > 0 {
+ latest = history[len(history)-1]
+ }
+ writeAnyJSON(w, http.StatusOK, map[string]any{
+ "count": len(history),
+ "latest": latest,
+ "history": history,
+ })
+ case http.MethodPost:
+ if !allowCronIngest(r) {
+ writeJSONError(w, http.StatusUnauthorized, "invalid cron token")
+ return
+ }
+ var payload map[string]any
+ if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
+ writeJSONError(w, http.StatusBadRequest, "invalid json payload")
+ return
+ }
+ sample := normalizeCronMetric(payload)
+ cronMetricMu.Lock()
+ cronMetricHistory = append(cronMetricHistory, sample)
+ if len(cronMetricHistory) > cronMetricMaxKeep {
+ cronMetricHistory = append([]CronMetric(nil), cronMetricHistory[len(cronMetricHistory)-cronMetricMaxKeep:]...)
+ }
+ count := len(cronMetricHistory)
+ cronMetricMu.Unlock()
+ writeAnyJSON(w, http.StatusOK, map[string]any{"ok": true, "stored": count, "latest": sample})
+ default:
+ http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
+ }
+}
+
+func allowCronIngest(r *http.Request) bool {
+ expected := strings.TrimSpace(os.Getenv("CRON_TOKEN"))
+ if expected == "" {
+ return true
+ }
+ return strings.TrimSpace(r.Header.Get("X-Cron-Token")) == expected
+}
+
+func normalizeCronMetric(payload map[string]any) CronMetric {
+ now := time.Now().UTC().Format(time.RFC3339)
+ metric := CronMetric{
+ Timestamp: now,
+ Source: textValue(payload, "source", "function", "name", "host", "hostname", "instance"),
+ Hostname: textValue(payload, "hostname", "host", "node", "instance"),
+ Status: textValue(payload, "status"),
+ Notes: textValue(payload, "notes", "note", "message"),
+ }
+ if metric.Source == "" {
+ metric.Source = "cron"
+ }
+
+ metric.MemoryTotalMB = numberValue(payload, "memory_total_mb", "total_memory_mb", "mem_total_mb", "mem_total")
+ metric.MemoryFreeMB = numberValue(payload, "memory_free_mb", "free_memory_mb", "mem_free_mb", "free_mb")
+ metric.MemoryAvailMB = numberValue(payload, "memory_available_mb", "available_memory_mb", "mem_available_mb", "avail_memory_mb")
+ metric.MemoryUsedMB = numberValue(payload, "memory_used_mb", "used_memory_mb", "mem_used_mb", "used_mb")
+ metric.MemoryPercent = numberValue(payload, "memory_percent", "mem_percent", "memory_usage_percent")
+ if metric.MemoryUsedMB == 0 && metric.MemoryTotalMB > 0 && metric.MemoryFreeMB > 0 {
+ metric.MemoryUsedMB = metric.MemoryTotalMB - metric.MemoryFreeMB
+ }
+ if metric.MemoryAvailMB == 0 && metric.MemoryFreeMB > 0 {
+ metric.MemoryAvailMB = metric.MemoryFreeMB
+ }
+ if metric.MemoryPercent == 0 && metric.MemoryTotalMB > 0 && metric.MemoryUsedMB > 0 {
+ metric.MemoryPercent = (metric.MemoryUsedMB / metric.MemoryTotalMB) * 100
+ }
+
+ metric.CpuLoad1m = numberValue(payload, "cpu_load_1m", "loadavg_1m", "load_1m", "load1")
+ metric.CpuLoad5m = numberValue(payload, "cpu_load_5m", "loadavg_5m", "load_5m", "load5")
+ metric.CpuLoad15m = numberValue(payload, "cpu_load_15m", "loadavg_15m", "load_15m", "load15")
+
+ metric.DiskTotalGB = numberValue(payload, "disk_total_gb", "total_disk_gb", "disk_total_mb", "disk_total")
+ metric.DiskFreeGB = numberValue(payload, "disk_free_gb", "free_disk_gb", "disk_free_mb", "disk_free")
+ metric.DiskUsedGB = numberValue(payload, "disk_used_gb", "used_disk_gb", "disk_used_mb", "disk_used")
+ if metric.DiskUsedGB == 0 && metric.DiskTotalGB > 0 && metric.DiskFreeGB > 0 {
+ metric.DiskUsedGB = metric.DiskTotalGB - metric.DiskFreeGB
+ }
+
+ if uptime := numberValue(payload, "uptime_sec", "uptime", "uptime_seconds"); uptime > 0 {
+ metric.UptimeSec = uptime
+ }
+ if ts := textValue(payload, "timestamp", "ts", "collected_at"); ts != "" {
+ metric.Timestamp = ts
+ }
+ return metric
+}
+
+func textValue(payload map[string]any, keys ...string) string {
+ for _, key := range keys {
+ if value, ok := payload[key]; ok {
+ text := strings.TrimSpace(toString(value))
+ if text != "" {
+ return text
+ }
+ }
+ }
+ return ""
+}
+
+func numberValue(payload map[string]any, keys ...string) float64 {
+ for _, key := range keys {
+ if value, ok := payload[key]; ok {
+ if number, ok := toFloat64(value); ok {
+ return number
+ }
+ }
+ }
+ return 0
+}
+
+func toString(value any) string {
+ switch v := value.(type) {
+ case string:
+ return v
+ case []byte:
+ return string(v)
+ case float64:
+ return strconv.FormatFloat(v, 'f', -1, 64)
+ case float32:
+ return strconv.FormatFloat(float64(v), 'f', -1, 32)
+ case int:
+ return strconv.FormatInt(int64(v), 10)
+ case int64:
+ return strconv.FormatInt(v, 10)
+ case json.Number:
+ return v.String()
+ default:
+ return ""
+ }
+}
+
+func toFloat64(value any) (float64, bool) {
+ switch v := value.(type) {
+ case float64:
+ return v, true
+ case float32:
+ return float64(v), true
+ case int:
+ return float64(v), true
+ case int64:
+ return float64(v), true
+ case json.Number:
+ f, err := v.Float64()
+ return f, err == nil
+ case string:
+ if strings.TrimSpace(v) == "" {
+ return 0, false
+ }
+ n, err := strconv.ParseFloat(strings.TrimSpace(v), 64)
+ if err == nil {
+ return n, true
+ }
+ return 0, false
+ default:
+ return 0, false
+ }
+}
diff --git a/console/internal/api/server.go b/console/internal/api/server.go
index 8da9770..16c2a46 100644
--- a/console/internal/api/server.go
+++ b/console/internal/api/server.go
@@ -127,6 +127,15 @@ func (s *Server) RegisterRoutes(mux *http.ServeMux) {
// Auth: не требует токена — сам проверяет и возвращает namespace
mux.HandleFunc("/console/api/auth", s.handleAuth)
+ // Cron: публичные маршруты для метрик
+ cronHandler := ui.CronHandler()
+ mux.Handle("/cron", cronHandler)
+ mux.Handle("/cron/", cronHandler)
+ mux.Handle("/console/cron", cronHandler)
+ mux.Handle("/console/cron/", cronHandler)
+ mux.HandleFunc("/cron/api/metrics", s.handleCronMetrics)
+ mux.HandleFunc("/console/cron/api/metrics", s.handleCronMetrics)
+
// Приватные: требуют X-Auth-Token / X-Test-Sub
auth := s.authMiddleware
diff --git a/console/ui/cron.go b/console/ui/cron.go
new file mode 100644
index 0000000..3764e24
--- /dev/null
+++ b/console/ui/cron.go
@@ -0,0 +1,16 @@
+package ui
+
+import (
+ _ "embed"
+ "net/http"
+)
+
+//go:embed cron.html
+var cronPage string
+
+func CronHandler() http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ _, _ = w.Write([]byte(cronPage))
+ })
+}
diff --git a/console/ui/cron.html b/console/ui/cron.html
new file mode 100644
index 0000000..d7ac743
--- /dev/null
+++ b/console/ui/cron.html
@@ -0,0 +1,430 @@
+
+
+
+
+
+ NUBES Cron
+
+
+
+
+
+
+
+
+
+
Снимки состояния по cron
+
+ Python-функция по расписанию отправляет сюда snapshot со свободной памятью, CPU load, диском и uptime.
+ Страница показывает последний снимок и историю замеров в том же стиле, что и основной dashboard: карточки + таблица.
+
+ Примечание: метрики читаются из /proc внутри pod-а и отражают состояние
+ ноды кластера , на которой запущена функция — не ресурсы самого pod-а и не агрегированные показатели кластера.
+
+
+
+ Current time
+ --:--:--
+
+
+
+
+ 0 points
+
+
+
+
+
+
+
+
+
+
История
+
Ожидание первых данных…
+
+
+
+
+
+
+
+ Время
+ Источник
+ Хост
+ Своб. МБ
+ Дост. МБ
+ Исп. МБ
+ Память %
+ ЦП 1м
+ ЦП 5м
+ ЦП 15м
+ Диск своб.
+ Аптайм
+ Статус
+
+
+
+ Данных пока нет
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/console/ui/embed.go b/console/ui/embed.go
index 20ef7f6..69baf15 100644
--- a/console/ui/embed.go
+++ b/console/ui/embed.go
@@ -5,7 +5,7 @@ import (
"net/http"
)
-//go:embed index.html
+//go:embed index.html cron.html
var content embed.FS
func Handler() http.Handler {
diff --git a/console/ui/index.html b/console/ui/index.html
index 6333ecf..35a314f 100644
--- a/console/ui/index.html
+++ b/console/ui/index.html
@@ -406,7 +406,7 @@
-
N
+
N
NUBES
FISSION CONSOLE
@@ -416,7 +416,6 @@
Refresh
+ Создать функцию
Help
-
Cron
Выход
@@ -1246,6 +1245,7 @@
}
localStorage.setItem('auth_token', token);
localStorage.setItem('auth_env', env);
+ setUserAvatar(token);
hideLoginOverlay();
// Проверяем статус NS — если не ready, показываем init overlay
try {
@@ -1265,10 +1265,37 @@
}
}
+ function jwtEmail(token) {
+ try {
+ var payload = JSON.parse(atob(token.split('.')[1].replace(/-/g,'+').replace(/_/g,'/')));
+ return payload.email || payload.sub || null;
+ } catch (_) { return null; }
+ }
+
+ function setUserAvatar(token) {
+ var el = document.getElementById('user-avatar');
+ if (!el) return;
+ var email = token ? jwtEmail(token) : null;
+ if (email) {
+ el.textContent = email;
+ el.title = email;
+ el.style.fontSize = '0.7rem';
+ el.style.minWidth = '60px';
+ el.style.padding = '0 8px';
+ } else {
+ el.textContent = 'N';
+ el.title = '';
+ el.style.fontSize = '';
+ el.style.minWidth = '';
+ el.style.padding = '';
+ }
+ }
+
function doLogout() {
localStorage.removeItem('auth_token');
localStorage.removeItem('auth_env');
try { document.getElementById('l-token').value = ''; } catch (_) { }
+ setUserAvatar(null);
showLoginOverlay();
}
@@ -1480,10 +1507,12 @@
}
function checkAuth() {
- if (!localStorage.getItem('auth_token')) {
+ var storedToken = localStorage.getItem('auth_token');
+ if (!storedToken) {
showLoginOverlay();
return;
}
+ setUserAvatar(storedToken);
hideLoginOverlay();
reloadAll();
}
diff --git a/doc/progress-2026-04-30-archive-upload.md b/doc/progress-2026-04-30-archive-upload.md
new file mode 100644
index 0000000..5731655
--- /dev/null
+++ b/doc/progress-2026-04-30-archive-upload.md
@@ -0,0 +1,36 @@
+# План интеграции загрузки функций из архивов в UI Fission Console
+
+## Цель
+Добавить в web-интерфейс Fission Console возможность загружать функцию в виде архива (zip/tar), аналогично `fission function create --deploy ...`.
+
+## Основные шаги
+1. UI: добавить компонент для выбора и загрузки архива (drag&drop или file input).
+2. Backend: реализовать обработку архива, валидацию структуры, запуск линтеров и (опционально) AI-проверку.
+3. Интеграция с Fission API: отправка архива через REST (multipart/form-data) для создания/обновления функции.
+4. UI: отображение статуса загрузки, ошибок, логов билда.
+
+## Валидация архива
+- Проверка наличия entrypoint-файла (main.py/handler.js и т.д.)
+- Проверка структуры (отсутствие мусора, секретов)
+- Линтеры (flake8/pylint/go vet и т.д.)
+- (Опционально) AI-проверка через облачный LLM
+
+## Безопасность
+- Ограничить размер архива
+- Фильтрация запрещённых файлов
+- Логирование действий
+
+## Синхронизация с Terraform/CLI
+- Архивы, деплоенные через UI, CLI, Terraform — одинакового формата
+- UI всегда отображает актуальный список функций из Fission API
+
+## Возможные сложности
+- Ошибки структуры архива (нет entrypoint)
+- Долгая сборка/деплой больших архивов
+- Drift при одновременном управлении через Terraform и UI
+
+## Следующие шаги
+- Реализовать UI-компонент для загрузки архива
+- Прототип backend-валидации
+- Интеграция с Fission API
+- Документировать пайплайн и типовые ошибки
diff --git a/examples/big-suite/code/python-metrics/croned.py b/examples/big-suite/code/python-metrics/croned.py
new file mode 100644
index 0000000..2091d58
--- /dev/null
+++ b/examples/big-suite/code/python-metrics/croned.py
@@ -0,0 +1,73 @@
+import os
+import urllib.request
+import urllib.error
+import json
+
+COLLECTOR_URL = "http://metrics-collector.fission.svc.cluster.local:8091/metrics"
+
+def main():
+ payload = {
+ "source": "fission-cron-metrics",
+ "hostname": os.uname().nodename,
+ "status": "ok",
+ }
+
+ # CPU load
+ try:
+ load = os.getloadavg()
+ payload["cpu_load_1m"] = load[0]
+ payload["cpu_load_5m"] = load[1]
+ payload["cpu_load_15m"] = load[2]
+ except Exception:
+ pass
+
+ # Memory
+ try:
+ with open("/proc/meminfo") as f:
+ meminfo = {}
+ for line in f:
+ k, v = line.split(":")
+ meminfo[k.strip()] = int(v.split()[0])
+ total = meminfo.get("MemTotal", 0) / 1024
+ free = meminfo.get("MemFree", 0) / 1024
+ avail = meminfo.get("MemAvailable", 0) / 1024
+ used = total - free
+ payload["memory_total_mb"] = round(total, 1)
+ payload["memory_free_mb"] = round(free, 1)
+ payload["memory_available_mb"] = round(avail, 1)
+ payload["memory_used_mb"] = round(used, 1)
+ payload["memory_percent"] = round(used / total * 100, 1) if total else 0
+ except Exception:
+ pass
+
+ # Disk
+ try:
+ st = os.statvfs("/")
+ total_gb = st.f_blocks * st.f_frsize / 1024**3
+ free_gb = st.f_bfree * st.f_frsize / 1024**3
+ used_gb = total_gb - free_gb
+ payload["disk_total_gb"] = round(total_gb, 1)
+ payload["disk_free_gb"] = round(free_gb, 1)
+ payload["disk_used_gb"] = round(used_gb, 1)
+ except Exception:
+ pass
+
+ # Uptime
+ try:
+ with open("/proc/uptime") as f:
+ payload["uptime_sec"] = float(f.read().split()[0])
+ except Exception:
+ pass
+
+ data = json.dumps(payload).encode()
+ req = urllib.request.Request(
+ COLLECTOR_URL,
+ data=data,
+ headers={"Content-Type": "application/json"},
+ method="POST",
+ )
+ try:
+ with urllib.request.urlopen(req, timeout=5) as resp:
+ return {"ok": True, "status": resp.status}
+ except urllib.error.URLError as e:
+ return {"ok": False, "error": str(e)}
diff --git a/test_simple_function.sh b/test_simple_function.sh
index d8cef5c..8e51ec4 100755
--- a/test_simple_function.sh
+++ b/test_simple_function.sh
@@ -158,21 +158,6 @@ sub main {
EOF
}
-write_go() {
- local dir=$1
- mkdir -p "${dir}"
- cat > "${dir}/handler.go" <<'EOF'
-package main
-
-import (
- "encoding/json"
- "net/http"
-)
-
-func Handler(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(map[string]string{"msg": "hello-go"})
-}
EOF
cat > "${dir}/go.mod" <<'EOF'
module fn
@@ -260,41 +245,6 @@ kube_exists "environments.fission.io" "simple-python-env" \
tf_destroy "${D}" | grep -q "Destroy complete" && pass "T02: destroy OK" || fail "T02: destroy FAILED"
kube_gone "functions.fission.io" "${T02}" && pass "T02: Function удалена" || fail "T02: Function осталась"
-# ════════════════════════════════════════════════════════════════════════
-echo ""
-echo ">>> T03: go — source pipeline, create с URL"
-# ════════════════════════════════════════════════════════════════════════
-T03="${RUN_ID}-go-basic"
-D=$(setup_workdir "t03")
-write_provider "${D}"
-write_go "${D}/code"
-cat >> "${D}/main.tf" </dev/null || echo "")
-[ "${PKG_SOURCE}" = "literal" ] \
- && pass "T03: Package.spec.source.type=literal (source deploy)" \
- || fail "T03: Package.spec.source.type='${PKG_SOURCE}' (ожидалось literal)"
-
-tf_destroy "${D}" | grep -q "Destroy complete" && pass "T03: destroy OK" || fail "T03: destroy FAILED"
-kube_gone "functions.fission.io" "${T03}" && pass "T03: Function удалена" || fail "T03: Function осталась"
-
# ════════════════════════════════════════════════════════════════════════
echo ""
echo ">>> T04: ruby — create с URL"
diff --git a/tests_v2.sh b/tests_v2.sh
index 0b3f597..c3db912 100644
--- a/tests_v2.sh
+++ b/tests_v2.sh
@@ -10,7 +10,6 @@ RUN_ID=$(echo "$RUN_ID" | sha256sum 2>/dev/null | head -c 8 || echo "${RUN_ID}"
cleanup_namespaces() {
local subs=()
- subs+=("${GO_NS}")
subs+=("alice@test.local" "routetest-alice@test.local" "routetest-bob@test.local")
subs+=("val-user@test.local" "dup-user@test.local" "upd-user@test.local" "get-user@test.local")
subs+=("ns-u1@test.local" "ns-u2@test.local" "alice-del@test.local" "carol-del@test.local")
@@ -38,12 +37,6 @@ cleanup_namespaces() {
}
trap cleanup_namespaces EXIT
-# GO_NS фиксированный — namespace создаётся один раз, Fission не рестартует при повторных прогонах.
-# Первый прогон занимает ~5 мин (rolling restart Fission), последующие — мгновенно.
-GO_NS="go-livetest@test.local"
-# Триггерим ensureUserNamespace заранее — Fission начнёт регистрировать NS пока идут остальные тесты.
-curl -s -o /dev/null -X GET ${BASE}/functions -H "X-Test-Sub: ${GO_NS}" || true
-
pass() { PASS=$((PASS+1)); echo " PASS: $1"; }
fail() { FAIL=$((FAIL+1)); echo " FAIL: $1"; }
check_http() { local got=$1 want=$2 label=$3; [ "$got" = "$want" ] && pass "$label (HTTP $got)" || fail "$label --- got HTTP $got, want $want"; }
@@ -783,33 +776,6 @@ check_http "$CODE" "400" "Невалидный язык cobol → 400"
# ──────────────────────────────────────────────
echo ""
-echo ">>> БЛОК AE: Go — создание и вызов"
-
-# Go: namespace GO_NS уже прогрет в начале теста (curl в самом верху).
-# При первом прогоне Fission делает rolling restart (~5 мин) — retry до 600s покрывает это.
-# При последующих прогонах NS уже зарегистрирован → invoke занимает ~10-15s.
-GO_CODE=$(python3 -c "import json; print(json.dumps('package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n)\n\nfunc Handler(w http.ResponseWriter, r *http.Request) {\n\tfmt.Fprintf(w, \"go-ok\")\n}'))")
-# Удаляем старую функцию если осталась от прошлого прогона (игнорируем ошибку)
-curl -s -o /dev/null -X DELETE ${BASE}/functions/go-fn -H "X-Test-Sub: ${GO_NS}" || true
-R=$(curl -s -w "\n%{http_code}" -X POST ${BASE}/functions \
- -H "X-Test-Sub: ${GO_NS}" -H "Content-Type: application/json" \
- -d "{\"name\":\"go-fn\",\"language\":\"go\",\"code\":${GO_CODE},\"ttl\":\"30m\"}")
-CODE=$(echo "$R" | tail -1)
-check_http "$CODE" "201" "Go: create → 201"
-
-# Retry до 600s (60 итераций × 10s): первый прогон — ждём Fission rolling restart + build + pool pod.
-STATUS=""; RAW=""
-for _try in $(seq 1 60); do
- sleep 10
- INV=$(curl -s -X POST ${BASE}/functions/go-fn/invoke \
- -H "X-Test-Sub: ${GO_NS}")
- STATUS=$(echo "$INV" | python3 -c "import sys,json; print(json.load(sys.stdin).get('status','?'))" 2>/dev/null)
- RAW=$(echo "$INV" | python3 -c "import sys,json; print(json.load(sys.stdin).get('response_raw','?'))" 2>/dev/null)
- [ "$STATUS" = "200" ] && break
-done
-[ "$STATUS" = "200" ] && pass "Go: invoke → 200" || fail "Go: invoke → got $STATUS (body: $RAW)"
-echo "$RAW" | grep -q "go-ok" && pass "Go: тело содержит go-ok" || fail "Go: тело не содержит go-ok (got: $RAW)"
-
# ──────────────────────────────────────────────
echo ""
echo "========================================"
diff --git a/token.txt b/token.txt
index 659d224..f2ff862 100644
--- a/token.txt
+++ b/token.txt
@@ -1,10 +1,3 @@
-api_token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoLWFwaSIsInN1YiI6IjAxOWNjMjY4LTZjNmEtNzgxZS04NjEzLTRiZWQ0ZWM3Y2QyMCIsImV4cCI6MTc4OTAxNTg2NywiaWF0IjoxNzczNDYzODY3LCJqdGkiOiJmYjRkMGFiNy1lYT>
-# s3_uid = "332cdb0d-34bf-43bf-864d-4adcc3b556fc"
-s3_uid = "332cdb0d-34bf-43bf-864d-4adcc3b556fb"
-# realm = "k8s-5-sandbox-nubes-ru"
-realm = "k8s-3-sandbox-nubes-ru"
-#realm = "naeel-test-3"
+ntazetdinov@nubes.ru
-
-
-eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoLWFwaSIsInN1YiI6IjAxOWNjMjY4LTZjNmEtNzgxZS04NjEzLTRiZWQ0ZWM3Y2QyMCIsImV4cCI6MTc4OTAxNTg2Nywia
\ No newline at end of file
+eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoLWFwaSIsInN1YiI6IjAxOWQ3NmM1LTQyNGMtN2MyYy04OWI3LWUzZjA5OTQ5YTk4ZiIsImV4cCI6MTc5MzA3ODQzOSwiaWF0IjoxNzc3NTI2NDM5LCJqdGkiOiI4MTU1ODNkNy0xZTRkLTRiMmMtOGM5ZS0wZDZmMGU2NmJjNTciLCJhdXRoX3RpbWUiOjAsInR5cCI6IiIsImF6cCI6IiIsInNlc3Npb25fc3RhdGUiOiIiLCJhY3IiOiIiLCJhbGxvd2VkLW9yaWdpbnMiOm51bGwsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6bnVsbH0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpudWxsfX0sInNjb3BlIjoiIiwic2lkIjoiIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoiIiwiQ2xpZW50SUQiOiJXWjAxMTEyIiwiY29tcGFueV9pZCI6IjhlYzcwYWMwLTU0NmQtNDJhNy04Y2ZmLTMzOWM4ZmI1MWEyMyIsImNvbXBhbnlfbmFtZSI6ItCe0J7QniDCq9Cd0KPQkdCV0KHCuyIsImlkcF91c3JfdWlkIjoiMDE5ZDc2YzUtNDI0Yy03YzJjLTg5YjctZTNmMDk5NDlhOThmIiwibG9naW4iOiJudGF6ZXRkaW5vdkBudWJlcy5ydSIsImZpcnN0bmFtZSI6ItCd0LDQuNC70YwiLCJtaWRkbGVuYW1lIjoi0KTQsNGA0LjRgtC-0LLQuNGHIiwibGFzdG5hbWUiOiLQotCw0LfQtdGC0LTQuNC90L7QsiIsImdyb3VwcyI6bnVsbCwicHJlZmVycmVkX3VzZXJuYW1lIjoiIiwiZ2l2ZW5fbmFtZSI6IiIsImZhbWlseV9uYW1lIjoiIiwiZW1haWwiOiJudGF6ZXRkaW5vdkBudWJlcy5ydSJ9.SaZsKAha45-fvKuJkLHUe_09AFsbH5QpzBVdPnjiEDQGIhl1A3ThgnM-oEh_H6CBXXfnOs2QDczzgCD8K8_tYzZKU1Wgk5lj04YW_fbTI89kHTO5wLtrAht9tFEjzBQZ-kmnG8mUK5tgqyNEjsngdQcfqVWRvneF366TiiRXk_76poUPpXWQmqdgCCKb3wq1rRgWcKDZcUC3JvDpOaqYL40zITZlM855drlJhMut4Gkg-EEk7ZGykl6YCsTRWVjFKGQ-7C-BDU2dkmIWhxzBAkvzzM2kBwvtlczCEDiUin7bXE-F_io89oJUYDzY4yPeHzwREwxCp6x9eD1NAIKKuA
\ No newline at end of file