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

13 KiB
Raw Blame History

План: AI-анализ terraform plan (уровни 05)

Дата: 2026-03-12
Статус: черновик / на согласование


1. Суть задачи

После выполнения terraform plan отправлять вывод на анализ в LLM. Глубина анализа управляется переменной ai_hint_level (05) в 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-переменная (рекомендуется):
export TF_VAR_ai_hint_level=3
  1. В variables.tf пользовательского проекта (опционально, только если нужно зафиксировать уровень в коде):
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"
  }
}
  1. CLI-флаг напрямую:
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-рекомендации ниже
# Пользователь вызывает:
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

Ключевой интерфейс:

// 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
// google_gemini.go — реализует LLMProvider
type GeminiProvider struct {
    client *genai.Client
    model  string
}

Шаг 5: Конфигурация

Через env-переменные (не хардкод):

# Какой 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 (опционально)
// Фабрика провайдеров — выбор по переменной окружения
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)

# В 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.