01_generate_yamls.sh: - генерация в staging-каталог; рабочий каталог не удаляется заранее; атомарная замена (mv) только при полном успехе, старый каталог → бэкап resources_yaml.bak-<UTC> (ротация KEEP_BACKUPS=5); - убран легаси-фолбэк токена 'ls -t ROOT/*.token' (подхватывал чужой токен); - NUBES_API_ENDPOINT обязателен (убран молчаливый PROD-дефолт); - имя сервиса берётся из 2-го поля services_list.txt (убран лишний HTTP-запрос); - убран безусловный rm старого YAML перед генерацией (неатомарность); - удалён дубль SERVICES_FILE_DEFAULT; маркер .stand защищает от чужого стенда; - синхронизированы комментарии (REQUEST_DELAY 0.5, пути, формат списка). yaml-generator/internal/config: - NUBES_API_ENDPOINT обязателен (убран PROD-дефолт); - убран легаси-поиск последнего *.token в корне репо (loadToken/findLatestToken); - убран несуществующий путь provider/devops/config/services_list.txt (теперь требуется явный NUBES_SERVICES_FILE); - удалены мёртвые getenvDefault/findLatestToken. Проверено: bash -n OK, go vet/go build OK.
152 lines
4.4 KiB
Go
152 lines
4.4 KiB
Go
// Package config — загрузка конфигурации, токенов и списка сервисов.
|
|
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"yaml-generator/internal/normalize"
|
|
)
|
|
|
|
// Config — конфигурация генератора.
|
|
type Config struct {
|
|
APIEndpoint string
|
|
APIToken string
|
|
Services []ServiceRef
|
|
SingleServiceID int
|
|
SingleServiceName string
|
|
OutputDir string
|
|
}
|
|
|
|
// ServiceRef — ссылка на сервис (ID + имя).
|
|
type ServiceRef struct {
|
|
ID int
|
|
Name string
|
|
}
|
|
|
|
// Load читает конфигурацию из переменных окружения и файлов.
|
|
func Load() (Config, error) {
|
|
// Endpoint ОБЯЗАТЕЛЕН. Легаси-дефолт на PROD gateway удален (2026-09-30):
|
|
// провайдер не должен молча ходить в прод при незаданном endpoint.
|
|
apiEndpoint := strings.TrimSpace(os.Getenv("NUBES_API_ENDPOINT"))
|
|
if apiEndpoint == "" {
|
|
return Config{}, errors.New("NUBES_API_ENDPOINT is required (no default: refusing to guess a stand endpoint)")
|
|
}
|
|
apiEndpoint = normalize.APIEndpoint(apiEndpoint)
|
|
apiToken, err := loadToken()
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
|
|
singleID := 0
|
|
if raw := strings.TrimSpace(os.Getenv("NUBES_SERVICE_ID")); raw != "" {
|
|
val, err := strconv.Atoi(raw)
|
|
if err != nil {
|
|
return Config{}, fmt.Errorf("invalid NUBES_SERVICE_ID: %s", raw)
|
|
}
|
|
singleID = val
|
|
}
|
|
|
|
singleName := strings.TrimSpace(os.Getenv("NUBES_SERVICE_NAME"))
|
|
outputDir := strings.TrimSpace(os.Getenv("NUBES_OUTPUT_DIR"))
|
|
|
|
services := []ServiceRef{}
|
|
if singleID == 0 {
|
|
// Без per-service ID нужен явный список. Угадывание пути удалено (2026-09-30):
|
|
// старый дефолт указывал на несуществующий provider/devops/config/services_list.txt.
|
|
listPath := strings.TrimSpace(os.Getenv("NUBES_SERVICES_FILE"))
|
|
if listPath == "" {
|
|
return Config{}, errors.New("NUBES_SERVICES_FILE is required when NUBES_SERVICE_ID is not set")
|
|
}
|
|
list, err := ReadServicesList(listPath)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
services = list
|
|
}
|
|
|
|
return Config{
|
|
APIEndpoint: apiEndpoint,
|
|
APIToken: apiToken,
|
|
Services: services,
|
|
SingleServiceID: singleID,
|
|
SingleServiceName: singleName,
|
|
OutputDir: outputDir,
|
|
}, nil
|
|
}
|
|
|
|
func loadToken() (string, error) {
|
|
if tok := strings.TrimSpace(os.Getenv("NUBES_API_TOKEN")); tok != "" {
|
|
return tok, nil
|
|
}
|
|
if tf := strings.TrimSpace(os.Getenv("TOKEN_FILE")); tf != "" {
|
|
b, err := os.ReadFile(tf)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return strings.TrimSpace(string(b)), nil
|
|
}
|
|
// Легаси-фолбэк «найти последний *.token в корне репо» удален (2026-09-30):
|
|
// токенов там нет, а такой поиск мог молча подхватить чужой токен.
|
|
return "", errors.New("NUBES_API_TOKEN or TOKEN_FILE is required")
|
|
}
|
|
|
|
// ReadServicesList читает список сервисов из файла.
|
|
func ReadServicesList(path string) ([]ServiceRef, error) {
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
lines := strings.Split(string(b), "\n")
|
|
services := []ServiceRef{}
|
|
for _, raw := range lines {
|
|
line := strings.TrimSpace(strings.ReplaceAll(raw, "\r", ""))
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
if strings.Contains(line, "#") {
|
|
line = strings.TrimSpace(strings.SplitN(line, "#", 2)[0])
|
|
}
|
|
parts := strings.Fields(line)
|
|
if len(parts) == 0 {
|
|
continue
|
|
}
|
|
id, err := strconv.Atoi(parts[0])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid service id in %s: %s", path, parts[0])
|
|
}
|
|
name := ""
|
|
if len(parts) > 1 {
|
|
name = parts[1]
|
|
}
|
|
services = append(services, ServiceRef{ID: id, Name: name})
|
|
}
|
|
return services, nil
|
|
}
|
|
|
|
// FindRepoRoot ищет корень репозитория по директории provider.
|
|
func FindRepoRoot() (string, error) {
|
|
wd, err := os.Getwd()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
current := wd
|
|
for i := 0; i < 8; i++ {
|
|
candidate := filepath.Join(current, "provider")
|
|
info, err := os.Stat(candidate)
|
|
if err == nil && info.IsDir() {
|
|
return candidate, nil
|
|
}
|
|
parent := filepath.Dir(current)
|
|
if parent == current {
|
|
break
|
|
}
|
|
current = parent
|
|
}
|
|
return "", errors.New("failed to locate provider repo root")
|
|
}
|