From 824dad7969a34cb2d03afeb8780d3445504d192e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Thu, 12 Mar 2026 09:02:48 +0400 Subject: [PATCH] =?UTF-8?q?docs:=20=D0=BF=D0=BB=D0=B0=D0=BD=20AI-=D0=B0?= =?UTF-8?q?=D0=BD=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=20terraform=20plan=20(?= =?UTF-8?q?=D1=83=D1=80=D0=BE=D0=B2=D0=BD=D0=B8=200-5,=20Google=20Gemini?= =?UTF-8?q?=20=E2=86=92=20Cloud=20LLM)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/decisions/ai-terraform-plan-analysis.md | 269 ++++++++++++++++++++ doc/sshfs-mount-setup.md | 96 +++++++ 2 files changed, 365 insertions(+) create mode 100644 doc/decisions/ai-terraform-plan-analysis.md create mode 100644 doc/sshfs-mount-setup.md diff --git a/doc/decisions/ai-terraform-plan-analysis.md b/doc/decisions/ai-terraform-plan-analysis.md new file mode 100644 index 0000000..ee07df6 --- /dev/null +++ b/doc/decisions/ai-terraform-plan-analysis.md @@ -0,0 +1,269 @@ +# План: AI-анализ terraform plan (уровни 0–5) + +Дата: 2026-03-12 +Статус: черновик / на согласование + +--- + +## 1. Суть задачи + +После выполнения `terraform plan` отправлять вывод на анализ в LLM. +Глубина анализа управляется переменной `ai_hint_level` (0–5) в Terraform-конфигурации. +Сейчас — Google AI (Gemini API). В будущем — собственный облачный LLM (drop-in замена). + +--- + +## 2. Уровни подсказок (`ai_hint_level`) + +| Уровень | Название | Что делает | +|---------|----------------|-------------------------------------------------------------------| +| 0 | OFF | Ничего. AI не вызывается. `terraform plan` работает как обычно. | +| 1 | SYNTAX | Проверка синтаксиса: ошибки в HCL, опечатки, невалидные блоки. | +| 2 | BASIC | + базовый анализ: неиспользуемые ресурсы, пустые значения. | +| 3 | MODERATE | + анализ зависимостей, потенциальные конфликты, порядок apply. | +| 4 | DETAILED | + best practices, безопасность (открытые порты, широкие IAM). | +| 5 | FULL | Полные рекомендации: оптимизация, рефакторинг, альтернативы. | + +--- + +## 3. Как задаётся уровень + +**По умолчанию AI не активен и ничего не показывает.** Если `ai_hint_level` нигде не задан — уровень 0, `sless plan` работает как обычный `terraform plan`, без единого упоминания об AI. + +Уровень задаётся **только явно**, одним из способов (приоритет сверху вниз): + +1. Env-переменная (рекомендуется): +```bash +export TF_VAR_ai_hint_level=3 +``` + +2. В `variables.tf` пользовательского проекта (опционально, только если нужно зафиксировать уровень в коде): +```hcl +variable "ai_hint_level" { + description = "Уровень AI-анализа terraform plan (0=off, 5=full)" + type = number + default = 0 + + validation { + condition = var.ai_hint_level >= 0 && var.ai_hint_level <= 5 + error_message = "ai_hint_level должен быть от 0 до 5" + } +} +``` + +3. CLI-флаг напрямую: +```bash +sless plan --ai-level=3 +``` + +**Важно:** CLI сам читает уровень из env `TF_VAR_ai_hint_level` или флага `--ai-level`. Если ни того ни другого нет — молча применяет уровень 0. Никакой переменной в манифестах не требуется. Пользователь не видит AI, пока не включит его явно. + +--- + +## 4. Архитектура решения + +``` +┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐ +│ terraform │────>│ wrapper-скрипт │────>│ AI Analyzer │ +│ plan │ │ (bash/Go CLI) │ │ (Go сервис) │ +│ (вывод JSON)│ │ │ │ │ +└─────────────┘ └──────────────────┘ └────────┬────────┘ + │ + ┌────────▼────────┐ + │ LLM Backend │ + │ (Google Gemini) │ + │ → Cloud LLM │ + └─────────────────┘ +``` + +### Компоненты: + +1. **Wrapper-скрипт / CLI** — вызывает `terraform plan -json`, читает `ai_hint_level`, передаёт вывод в анализатор. +2. **AI Analyzer (Go)** — формирует промпт по уровню, отправляет в LLM, форматирует ответ. +3. **LLM Backend** — подключаемый через интерфейс (сначала Google, потом облачный). + +--- + +## 5. Реализация по шагам + +### Шаг 1: CLI-обёртка (`sless-plan`) + +Новый бинарник или shell-скрипт, который: +- Запускает `terraform plan -json -out=plan.bin` +- Читает `ai_hint_level` из state/variables (через `terraform output` или парсинг .tf) +- Если level > 0, передаёт JSON-вывод плана в AI Analyzer +- Выводит стандартный plan + AI-рекомендации ниже + +```bash +# Пользователь вызывает: +sless plan # вместо terraform plan +# или: +terraform plan && sless analyze --level=3 +``` + +### Шаг 2: AI Analyzer сервис (Go) + +Расположение: `internal/ai/` или отдельный `cmd/ai-analyzer/` + +``` +internal/ai/ +├── analyzer.go # основная логика: формирование промпта, парсинг ответа +├── provider.go # интерфейс LLM-провайдера +├── google_gemini.go # реализация для Google Gemini API +├── cloud_llm.go # (заглушка) реализация для будущего облачного LLM +└── prompts.go # шаблоны промптов по уровням 1–5 +``` + +Ключевой интерфейс: + +```go +// LLMProvider — абстракция над любым LLM-бэкендом. +// Сейчас: Google Gemini. В будущем: облачный LLM (drop-in замена). +type LLMProvider interface { + Analyze(ctx context.Context, prompt string) (string, error) + Name() string +} +``` + +### Шаг 3: Промпты по уровням + +Каждый уровень = свой system prompt + ограничения: + +| Уровень | System prompt (суть) | +|---------|-----------------------------------------------------------| +| 1 | "Проверь только синтаксис HCL. Ничего лишнего." | +| 2 | "Синтаксис + базовые проблемы (unused, empty values)." | +| 3 | "Анализ зависимостей, порядок, потенциальные конфликты." | +| 4 | "Best practices, безопасность, IAM, открытые порты." | +| 5 | "Полный аудит: оптимизация, рефакторинг, альтернативы." | + +### Шаг 4: Google Gemini интеграция + +- API: `generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent` +- Авторизация: API key (хранить в env `GOOGLE_AI_API_KEY`, не в коде) +- SDK: `github.com/google/generative-ai-go` (официальный Go SDK) +- Модель: `gemini-pro` или `gemini-1.5-pro` + +```go +// google_gemini.go — реализует LLMProvider +type GeminiProvider struct { + client *genai.Client + model string +} +``` + +### Шаг 5: Конфигурация + +Через env-переменные (не хардкод): + +```bash +# Какой LLM-провайдер использовать +export AI_PROVIDER=google # google | cloud (в будущем) + +# Google Gemini +export GOOGLE_AI_API_KEY=AIza... # API ключ + +# Будущий облачный LLM +export CLOUD_LLM_ENDPOINT=https://llm.cloud.example.com/v1/analyze +export CLOUD_LLM_TOKEN=... +``` + +--- + +## 6. Миграция на облачный LLM + +Когда облако предоставит свой LLM: + +1. Создать `cloud_llm.go` — реализация `LLMProvider` для нового API +2. Переключить `AI_PROVIDER=cloud` в env +3. Код анализатора НЕ меняется — интерфейс `LLMProvider` тот же +4. Google Gemini остаётся как fallback (опционально) + +```go +// Фабрика провайдеров — выбор по переменной окружения +func NewProvider(cfg Config) (LLMProvider, error) { + switch cfg.Provider { + case "google": + return NewGeminiProvider(cfg.GoogleAPIKey) + case "cloud": + return NewCloudLLMProvider(cfg.CloudEndpoint, cfg.CloudToken) + default: + return nil, fmt.Errorf("unknown AI provider: %s", cfg.Provider) + } +} +``` + +--- + +## 7. Безопасность + +- API-ключи — только в env/secrets, никогда в коде или .tf файлах +- `terraform plan -json` может содержать sensitive data → фильтровать перед отправкой в LLM +- Ограничить размер payload (большие планы обрезать/суммаризировать) +- Логировать факт вызова AI, но НЕ содержимое запроса/ответа в production + +--- + +## 8. Структура файлов (итого) + +``` +sless/ +├── cmd/ +│ └── sless-plan/ # CLI-обёртка для terraform plan + AI +│ └── main.go +├── internal/ +│ └── ai/ +│ ├── analyzer.go # логика анализа +│ ├── provider.go # интерфейс LLMProvider +│ ├── google_gemini.go # Google Gemini реализация +│ ├── cloud_llm.go # заглушка для облачного LLM +│ └── prompts.go # промпты по уровням 1–5 +├── terraform/ +│ └── provider/ # (существующий провайдер — без изменений) +└── examples/ + └── */ + └── variables.tf # добавить ai_hint_level variable +``` + +--- + +## 9. Порядок реализации + +| # | Задача | Приоритет | +|----|------------------------------------------------|-----------| +| 1 | Создать `internal/ai/provider.go` (интерфейс) | HIGH | +| 2 | Реализовать `google_gemini.go` | HIGH | +| 3 | Написать промпты по уровням (`prompts.go`) | HIGH | +| 4 | Создать `analyzer.go` (оркестрация) | HIGH | +| 5 | CLI-обёртка `cmd/sless-plan/main.go` | HIGH | +| 6 | Добавить `ai_hint_level` в examples | MEDIUM | +| 7 | Тесты (unit + integration с mock LLM) | MEDIUM | +| 8 | Заглушка `cloud_llm.go` для будущей миграции | LOW | +| 9 | Документация для пользователей | MEDIUM | + +--- + +## 10. Пример использования (целевой UX) + +```bash +# В terraform проекте пользователя: +$ export TF_VAR_ai_hint_level=3 +$ export GOOGLE_AI_API_KEY=AIza... + +$ sless plan +# Terraform Plan output (стандартный) +# ... +# ──────────────── AI Analysis (level 3: MODERATE) ──────────────── +# ⚠ Resource "kubernetes_deployment.func" зависит от "kubernetes_namespace.ns" +# но namespace создаётся в другом модуле — возможен race condition. +# ⚠ Порядок destroy может вызвать проблемы: trigger удалится раньше function. +# ✓ Синтаксис корректен, конфликтов не обнаружено. +# ───────────────────────────────────────────────────────────────── +``` + +--- + +## Решение зафиксировано + +Подход: **интерфейс LLMProvider** → сначала Google Gemini → потом drop-in замена на облачный LLM. +Уровень задаётся через обычную Terraform variable. Никаких изменений в самом провайдере sless. diff --git a/doc/sshfs-mount-setup.md b/doc/sshfs-mount-setup.md new file mode 100644 index 0000000..eff0821 --- /dev/null +++ b/doc/sshfs-mount-setup.md @@ -0,0 +1,96 @@ +# Инструкции: монтирование удалённой папки через SSHFS + +Дата: 2026-03-12 + +Кратко: эти команды повторяют текущее у вас подключение, где +источник: `naeel@5.172.178.213:/home/naeel/terra`, и точка монтирования — `/home//remote_dev`. + +1) Установка `sshfs` (Debian/Ubuntu): + +```bash +sudo apt update +sudo apt install -y sshfs +``` + +2) Подготовка приватного ключа: + +- Скопируйте приватный ключ на целевую машину или загрузите его безопасным способом. +- Пример копирования с локальной машины на целевой хост: + +```bash +scp /home/naeel/remote_dev/sless/secrets/naeel_vm_id_ed25519 user@target:/home/user/.ssh/id_ed25519_sless +ssh user@target 'chmod 600 /home/user/.ssh/id_ed25519_sless' +``` + +(Оригинал ключа в репозитории: `secrets/naeel_vm_id_ed25519`) + +3) Создать точку монтирования на целевом хосте: + +```bash +mkdir -p /home/user/remote_dev +chown user:user /home/user/remote_dev +``` + +4) Команда монтирования (пример, повторяет ваше текущее подключение): + +```bash +sshfs -o IdentityFile=/home/user/.ssh/id_ed25519_sless \ + -o StrictHostKeyChecking=no \ + -o UserKnownHostsFile=/dev/null \ + -o reconnect \ + -o ServerAliveInterval=15 \ + -o ServerAliveCountMax=3 \ + naeel@5.172.178.213:/home/naeel/terra /home/user/remote_dev +``` + +5) Проверка статуса монтирования и содержимого: + +```bash +findmnt /home/user/remote_dev +ls -la /home/user/remote_dev +``` + +6) Отключение: + +```bash +fusermount -u /home/user/remote_dev +``` + +7) (Опционально) systemd unit для автоподключения при старте: + +Создайте файл `/etc/systemd/system/remote_dev.mount` со следующим содержимым (отредактируйте путь к ключу и `Where`): + +```ini +[Unit] +Description=SSHFS mount for /home/user/remote_dev +After=network-online.target +Wants=network-online.target + +[Mount] +What=naeel@5.172.178.213:/home/naeel/terra +Where=/home/user/remote_dev +Type=fuse.sshfs +Options=IdentityFile=/home/user/.ssh/id_ed25519_sless,allow_other,reconnect,ServerAliveInterval=15,ServerAliveCountMax=3,StrictHostKeyChecking=no,UserKnownHostsFile=/dev/null + +[Install] +WantedBy=multi-user.target +``` + +Затем выполнить: + +```bash +sudo systemctl daemon-reload +sudo systemctl enable --now remote_dev.mount +``` + +8) Заметки по безопасности и надёжности: + +- Храните приватный ключ с правами `600` и ограниченным доступом. +- Рассмотрите использование `ssh-agent` вместо копирования ключа. +- Опция `StrictHostKeyChecking=no` ослабляет проверку host key — используйте осознанно. +- Для массовой настройки можете создать скрипт установки и unit-файл через Ansible/Cloud-init. + +--- +Файл создан автоматически по запросу пользователя. Если хотите, могу: +- добавить инструкции для `macOS` или `CentOS`/`RHEL`; +- создать такой же unit-файл и загрузить его на целевой хост (при доступе по SSH).