Files
sless/doc/decisions/ai-terraform-plan-analysis.md
T

270 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# План: 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.