diff --git a/examples/POSTGRES/test_cache_matrix.sh b/examples/POSTGRES/test_cache_matrix.sh new file mode 100755 index 0000000..bfd42ef --- /dev/null +++ b/examples/POSTGRES/test_cache_matrix.sh @@ -0,0 +1,119 @@ +#!/bin/bash +# test_cache_matrix.sh — 2026-03-23 (v3) +# Комплексный тест кэша registry: +# Phase 1 — полный деплой всех 24 ресурсов (kaniko builds, т.к. нет образов) +# Phase 2 — destroy sless_* + re-apply (все образы из кэша) +# Phase 3 — одновременно: удаление 2, смена кода 2, смена параметров 2 +# ВАЖНО: nubes_postgres.npg и nubes_postgres_database НЕ уничтожаются. +# Причина: пересоздание БД ротирует пароль user0 в k8s secret; +# vault_secrets на это не реагирует сразу → FunctionJob получает старый пароль. + +set -euo pipefail +DIR="$(cd "$(dirname "$0")" && pwd)" +LOG="$DIR/test_cache_matrix_$(date +%Y%m%d_%H%M%S).log" +TIMINGS="$LOG.timings" +PASS=0 +FAIL=0 + +log() { echo "[$(date +%H:%M:%S)] $*" | tee -a "$LOG"; } +sep() { log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"; } + +timed_op() { + local label="$1"; shift + log "▶ START: $label" + local t0; t0=$(date +%s%3N) + "$@" 2>&1 | tee -a "$LOG" + local rc=${PIPESTATUS[0]} + local t1; t1=$(date +%s%3N) + local elapsed=$(( (t1 - t0) / 1000 )) + if [[ $rc -eq 0 ]]; then + log "✓ DONE: $label — ${elapsed}s" + PASS=$((PASS+1)) + else + log "✗ FAIL: $label — ${elapsed}s (exit $rc)" + FAIL=$((FAIL+1)) + fi + echo "$label: ${elapsed}s" >> "$TIMINGS" + return $rc +} + +destroy_sless_only() { + local label="$1" + local targets + targets=$(terraform state list 2>/dev/null | grep -E '^(sless_service|sless_job)' | sed 's/^/-target=/' | tr ' +' ' ' || true) + if [[ -z "$targets" ]]; then + log " (nothing to destroy for $label — state empty)" + return 0 + fi + local n; n=$(terraform state list 2>/dev/null | grep -cE '^(sless_service|sless_job)' || true) + log " Targets: $n resources" + timed_op "$label" terraform destroy $targets -auto-approve +} + +cd "$DIR" + +sep +log "PHASE 1: Полный начальный деплой" +sep +destroy_sless_only "phase1-pre-clean" +timed_op "phase1-apply-all" terraform apply -auto-approve + +log "--- Образы в registry после Phase 1 ---" +kubectl exec -n sless deployment/sless-registry -- sh -c 'find /var/lib/registry -name "*.json" -path "*/tags/*" 2>/dev/null | sed "s|.*repository/||;s|/_manifests.*||" | sort | uniq -c | sort -rn' 2>/dev/null | head -30 | tee -a "$LOG" || log "(registry inspect failed)" + +sep +log "PHASE 2: Destroy sless_* → Re-apply (ожидаем cache hits)" +sep +destroy_sless_only "phase2-destroy" +timed_op "phase2-apply-cached" terraform apply -auto-approve + +sep +log "PHASE 3: Mixed ops (delete+code+params)" +sep + +log "--- 3a: destroy targets: stress_divzero, chaos_echo ---" +timed_op "phase3a-destroy-targets" terraform destroy -target=sless_service.stress_divzero -target=sless_service.chaos_echo -auto-approve + +log "--- 3b: code changes (new sha256 → kaniko) ---" +echo "" >> "$DIR/code/pg-counter/pg_counter.py" +echo "# cache-test-$(date +%s)" >> "$DIR/code/pg-counter/pg_counter.py" +echo "" >> "$DIR/code/stress-js-async/stress_js_async.js" +echo "// cache-test-$(date +%s)" >> "$DIR/code/stress-js-async/stress_js_async.js" +log " changed: pg_counter.py, stress_js_async.js" + +log "--- 3c: param changes (same sha256 → no kaniko) ---" +python3 - <<'PYEOF' +import re, sys +with open("stress.tf") as f: + content = f.read() +orig = content +content = re.sub( + r'(resource "sless_service" "stress_slow" \{[^}]*?)memory_mb\s*=\s*\d+', + lambda m: m.group(1) + 'memory_mb = 192', + content, flags=re.DOTALL +) +content = re.sub( + r'(resource "sless_service" "pg_stats" \{[^}]*?)timeout_sec\s*=\s*\d+', + lambda m: m.group(1) + 'timeout_sec = 20', + content, flags=re.DOTALL +) +if content == orig: + print(" stress.tf: no changes (already patched?)", file=sys.stderr) +else: + with open("stress.tf", "w") as f: + f.write(content) + print(" stress.tf: stress_slow→memory_mb=192, pg_stats→timeout_sec=20") +PYEOF + +log "--- 3d: apply всех mixed изменений ---" +log " Expected: stress_divzero+chaos_echo=cache_hit, pg_counter+stress_js_async=kaniko, stress_slow+pg_stats=k8s_only" +timed_op "phase3d-mixed-apply" terraform apply -auto-approve + +sep +log "ИТОГ" +sep +log "Timings:" +cat "$TIMINGS" 2>/dev/null | tee -a "$LOG" +log "Pass: $PASS | Fail: $FAIL" +log "Лог: $LOG" diff --git a/internal/builder/context.go b/internal/builder/context.go index 1b0e6ee..31d9fa5 100644 --- a/internal/builder/context.go +++ b/internal/builder/context.go @@ -93,23 +93,30 @@ func generateDockerfile(runtime string, hasRequirements bool, hasPackageJSON boo // Go: multi-stage build с go.work (v0.1.3+). // base-образ содержит: /app/server/ (module sless/fn/server, package main). // kaniko копирует пользовательский код в /app/handler/. - // Генерируем go.mod для handler если его нет (module sless/fn/handler). - // Генерируем go.work: use ./server + use ./handler + replace sless/fn/handler => ./handler. - // replace позволяет server.go импортировать sless/fn/handler независимо от имени модуля пользователя. - // GOFLAGS=-mod=mod: позволяет go build подтягивать зависимости из handler/go.mod через GOPROXY. + // Модуль handler всегда переименовывается в sless/fn/handler — это позволяет server.go + // импортировать его по фиксированному пути без replace-директивы. + // Почему нет replace: Go 1.23 запрещает replace для модуля, который одновременно в workspace + // (use ./handler). Ошибка: "workspace module sless/fn/handler is replaced at all versions". + // Решение: rename module → use workspace → require без replace. goModStep := "" if !hasGoMod { - // Пользователь не предоставил go.mod — генерируем минимальный + // Пользователь не предоставил go.mod — генерируем минимальный с правильным именем. goModStep = "RUN printf 'module sless/fn/handler\\n\\ngo 1.23\\n' > /app/handler/go.mod\n" + } else { + // Пользователь предоставил go.mod с произвольным именем модуля — переименовываем. + goModStep = "RUN sed -i 's/^module .*/module sless\\/fn\\/handler/' /app/handler/go.mod\n" } content := fmt.Sprintf( "FROM %s AS builder\n"+ "WORKDIR /app\n"+ "COPY . /app/handler/\n"+ goModStep+ - "RUN printf 'go 1.23\\n\\nuse ./server\\nuse ./handler\\n\\nreplace sless/fn/handler => ./handler\\n' > /app/go.work\n"+ // server/go.mod не содержит require sless/fn/handler — Go требует явного require даже при replace в go.work. - // Патчим server/go.mod в момент kaniko-сборки (не меняет базовый образ). - "RUN printf '\\nrequire sless/fn/handler v0.0.0\\n' >> /app/server/go.mod\n"+"RUN CGO_ENABLED=0 go build -o /server ./server\n"+ + // go.work: workspace из двух модулей без replace-директив. + "RUN printf 'go 1.23\\n\\nuse ./server\\nuse ./handler\\n' > /app/go.work\n"+ + // Патчим server/go.mod в момент kaniko-сборки — добавляем require на handler. + // Не меняет базовый образ (выполняется на этапе сборки пользовательского образа). + "RUN printf '\\nrequire sless/fn/handler v0.0.0\\n' >> /app/server/go.mod\n"+ + "RUN CGO_ENABLED=0 go build -o /server ./server\n"+ "FROM alpine:3.20\n"+ "COPY --from=builder /server /server\n"+ "EXPOSE 8080\n"+ diff --git a/runtimes/go1.23/Dockerfile b/runtimes/go1.23/Dockerfile index 0ecac60..a76604a 100644 --- a/runtimes/go1.23/Dockerfile +++ b/runtimes/go1.23/Dockerfile @@ -1,4 +1,4 @@ -# Изменено: 2026-03-22 — v0.1.3: server/ субдиректория + go.work для поддержки пользовательских go.mod. +# Изменено: 2026-03-23 — v0.1.4: fix go.work (убран replace, добавлен sed rename модуля). # Base builder image для Go 1.23 serverless функций. # Это BUILDER-образ: golang:1.23-alpine + server/ + pre-cached зависимости. # go mod download кеширует pgx/v5 в /root/go/pkg/mod — kaniko не скачивает их при каждой сборке. @@ -7,14 +7,16 @@ # FROM naeel/sless-runtime-go1.23:v0.1.3 AS builder # WORKDIR /app # COPY . /app/handler/ -# RUN [ -f /app/handler/go.mod ] || printf 'module sless/fn/handler\n\ngo 1.23\n' > /app/handler/go.mod -# RUN printf 'go 1.23\n\nuse ./server\nuse ./handler\n\nreplace sless/fn/handler => ./handler\n' > /app/go.work +# RUN [ -f /app/handler/go.mod ] && sed -i 's/^module .*/module sless\/fn\/handler/' || printf '...' > /app/handler/go.mod +# RUN printf 'go 1.23\n\nuse ./server\nuse ./handler\n' > /app/go.work # БЕЗ replace! +# RUN printf '\nrequire sless/fn/handler v0.0.0\n' >> /app/server/go.mod # RUN CGO_ENABLED=0 go build -o /server ./server # FROM alpine:3.20 # COPY --from=builder /server /server # -# Почему server/ субдиректория: go.work + replace позволяет пользователю иметь любой go.mod -# с любыми зависимостями. Вложенные модули (nested) поддерживаются через workspace. +# Почему replace убран: Go 1.23 запрещает replace для модуля, который одновременно в workspace. +# Ошибка: "workspace module sless/fn/handler is replaced at all versions in the go.work file". +# Решение: handler всегда переименовывается в sless/fn/handler через sed — replace не нужен. FROM golang:1.23-alpine