Files
tf_provider/HISTORY/2026-08-31_opus_code_review.md
T

14 KiB
Raw Blame History

Code Review провайдера — Opus — 2026-08-31

Источник: анализ и код-ревью через VS Code Copilot Chat Статус: анализ завершён; часть исправлений внесена 2026-08-31

Область анализа

Проверены:

  • рукописное ядро провайдера в provider/internal/core и provider/internal/resources_core;
  • CRUD, state management и валидация;
  • HTTP-слой и client.go;
  • регистрация провайдера и TLS-настройки;
  • генераторы Go-ресурсов, YAML и build-пайплайн;
  • Python- и shell-скрипты;
  • gateway.

Критичные находки

1. Отладочный лог с данными инстансов пишется в /tmp безусловно

В provider/internal/core/client.go:629-637 замыкание debug() в FindInstanceByDisplayName всегда пишет в /tmp/nubes_find_debug.log с правами 0644. В лог попадают instanceUid, displayName и serviceId.

Файл не защищён условием NUBES_DEBUG_HTTP, не ротируется и не очищается. Это создаёт риск раскрытия данных и неконтролируемого роста файла.

Рекомендация: убрать постоянную запись либо включать её только через явный debug-флаг; использовать безопасный путь и контролируемую ротацию.

2. Bearer-токен попадает в stderr при HTTP-отладке

В provider/internal/core/client.go:1100-1101 вызов httputil.DumpRequestOut(req, ...) выводит полный исходящий запрос вместе с заголовком Authorization: Bearer <token> при NUBES_DEBUG_HTTP=1.

Токен может попасть в логи CI/CD или окружения выполнения.

Рекомендация: перед дампом удалять или маскировать Authorization; не выводить секреты ни в одном режиме.

3. В Python-скрипте сетевые вызовы выполняются без таймаутов

В scripts/check_cloud_instances.py:87-88 вызовы self.session.get(...) не передают timeout=. При зависании API процесс может ожидать ответ бесконечно.

Рекомендация: добавить явные таймауты ко всем HTTP-вызовам и определить единое значение или конфигурационный параметр.

Существенные находки

4. Retry сетевых ошибок применяется к POST-запросам

В provider/internal/core/client.go:1113-1120 при сетевой ошибке повторяется любой HTTP-метод, включая POST к /instances и /instanceOperations.

Если сервер принял запрос, но ответ потерян, повтор может создать дубликат инстанса или операции. Идемпотентность POST не гарантирована.

Рекомендация: ограничить retry идемпотентными методами либо использовать идемпотency key и явную серверную поддержку повторов.

5. Ответ 401 Unauthorized включён в retryable

В provider/internal/core/client.go:1150-1156 статус 401 считается повторяемым. Протухший или неверный токен приводит к трём попыткам с задержкой, маскируя исходную ошибку авторизации и увеличивая время отказа.

Рекомендация: исключить 401 из retryable; возвращать ошибку авторизации сразу.

6. Gateway раскрывает внутренние upstream-адреса

В gateway/server.js:60-71 корневой endpoint / и обработчик 404 возвращают наружу адреса upstream для маршрутов.

Публичный ответ раскрывает внутреннюю топологию сервисов.

Рекомендация: убрать upstream из публичных ответов; внутренние адреса оставлять только в серверных логах с необходимой санацией.

7. Некорректное определение неуспешной операции в Python

В scripts/check_cloud_instances.py:187-189 используется сравнение last_op.get("isSuccessful") == False. При отсутствии поля возвращается None, поэтому состояние OPERATION_FAILED не определяется.

Рекомендация: использовать проверку is False либо явно обрабатывать отсутствие ключа согласно контракту API.

Умеренные находки

8. Retry-логика дублируется в трёх местах

В provider/internal/core/client.go:777-905 похожие циклы retry присутствуют в doRequest, GetInstanceState и GetInstanceStateRaw.

Дублирование увеличивает риск расхождения поведения и повторного появления ошибок безопасности.

Рекомендация: вынести общую retry-логику в единый внутренний helper с параметрами метода, таймаутов и политики повторов.

9. Пагинация имеет тихий предел 10 000 инстансов

В fallback-ветке FindInstanceByDisplayName (provider/internal/core/client.go:747-749) поиск прекращается после page > 100 при размере страницы 100.

При большем количестве инстансов совпадение может не быть найдено без предупреждения.

Рекомендация: убрать произвольный предел либо возвращать диагностируемую ошибку/предупреждение при достижении лимита.

10. Ошибка gofmt не останавливает генерацию

FormatSourceOrWarn в TOOLS/resource-generator/writers.go:61 при ошибке форматирования только выводит предупреждение и записывает исходник.

В результате pipeline может сохранить неформатированный или потенциально некомпилируемый Go-код.

Рекомендация: считать ошибку форматирования фатальной для генерации либо выполнять последующую обязательную компиляционную проверку.

11. Секрет передаётся в командной строке shell-скрипта

В TOOLS/s3_notification_example.sh:74 значение SECRET_KEY передаётся аргументом в mc alias set.

Секрет может быть виден через ps или аналогичный список процессов.

Рекомендация: использовать механизм передачи секрета через stdin, переменную окружения, конфигурационный файл с безопасными правами или другой поддерживаемый секретный канал.

Дополнительные замечания

  • В provider/internal/core/client.go ссылка на tools/gen_v2/generate_resources_v2.go обновлена на актуальный путь TOOLS/resource-generator/internal/templates/instance.go.
  • В исходниках генератора (TOOLS/resource-generator/internal/templates/*, TOOLS/resource-generator/internal/writers/writers.go) метка Code generated by tools/gen_v2 обновлена на Code generated by TOOLS/resource-generator.
  • Текущий provider/internal/resources_gen/registry.go обновлён на новую метку генератора.
  • TOOLS/resource-generator/main.go переведён на run() с корректным exit code=1 и агрегированным отчётом по ошибкам записи ресурсов (instance/subresource/action).
  • Пути debug-логов в provider/internal/core/client.go переведены на os.TempDir() с override через NUBES_DEBUG_DIR (без хардкода /tmp).

Что выглядит хорошо

  • Сериализация операций на инстансе через instanceMutexes в client.go защищает от параллельных операций API.
  • TLS настроен с MinVersion: TLS 1.2; InsecureSkipVerify по умолчанию равен false.
  • api_token отмечен как Sensitive: true в схеме провайдера.
  • Канонизация JSON для сравнения state устраняет ложные различия из-за порядка ключей.

Итоговый статус

Находка Статус
Безусловная запись данных инстансов в /tmp Исправлено: debug gated + права 0600
Bearer-токен в HTTP debug dump Исправлено: Authorization маскируется
Python HTTP-вызовы без таймаутов Исправлено: добавлен REQUEST_TIMEOUT
Retry POST-запросов Исправлено: retry сетевых ошибок только для GET
401 в retryable Исправлено: исключён из retryable
Раскрытие upstream в gateway Исправлено: upstream удалён из root-ответа
Ошибка определения OPERATION_FAILED Исправлено: сравнение через is False
Дублирование retry-логики Исправлено: общий helper для чтения состояния
Тихий предел пагинации Частично исправлено: добавлена явная ошибка при достижении лимита
Некритичная ошибка gofmt в генераторе Исправлено: fail-fast при ошибке форматирования
Секрет в аргументах shell-команды Исправлено: исключена передача в argv

Выполненные изменения (2026-08-31)

  • provider/internal/core/client.go:
    • debug-лог FindInstanceByDisplayName теперь пишется только при NUBES_DEBUG_HTTP=1;
    • права debug-логов снижены до 0600;
    • в stderr-дампе HTTP-запроса маскируется заголовок Authorization;
    • retry сетевых ошибок ограничен методом GET;
    • 401 Unauthorized удалён из isRetryable;
    • при достижении лимита fallback-пагинации возвращается явная ошибка.
    • GetInstanceState и GetInstanceStateRaw переведены на общий helper getInstanceStateWithRetry с единым retry/HTTP-поведением.
  • scripts/check_cloud_instances.py:
    • добавлен REQUEST_TIMEOUT = 30 и применён ко всем session.get(...);
    • проверка failed-операции изменена на is False.
  • gateway/server.js:
    • удалено поле upstream из публичного ответа GET /.
  • TOOLS/resource-generator/internal/helpers/helpers.go:
    • FormatSourceOrWarn переведён на fail-fast: возвращает ошибку при сбое gofmt.
  • TOOLS/resource-generator/internal/writers/writers.go:
    • все вызовы форматирования обрабатывают ошибку и прерывают генерацию.
  • TOOLS/resource-generator/main.go:
    • убраны panic на первом сбое записи ресурса;
    • добавлена агрегация ошибок генерации с отчётом по каждому ресурсу;
    • завершение с exit code=1 и человекочитаемым сообщением в stderr.
  • TOOLS/resource-generator/internal/templates/instance.go:
    • обновлён marker генерации на Code generated by TOOLS/resource-generator.
  • TOOLS/resource-generator/internal/templates/subresource.go:
    • обновлён marker генерации на Code generated by TOOLS/resource-generator.
  • TOOLS/resource-generator/internal/templates/action.go:
    • обновлён marker генерации на Code generated by TOOLS/resource-generator.
  • provider/internal/resources_gen/registry.go:
    • обновлён marker генерации на Code generated by TOOLS/resource-generator.
  • scripts/s3_notification_example.sh:
    • убрана передача секрета в аргументах процесса;
    • для mc используется временный --config-dir и переменная MC_HOST_<alias>.
  • provider/internal/core/client.go:
    • debug log path переведён на os.TempDir();
    • добавлен override директории через NUBES_DEBUG_DIR.