fix(tools): безопасная генерация YAML — staging + атомарная замена, без легаси-фолбэков

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.
This commit is contained in:
Repinoid
2026-09-30 09:28:41 +03:00
parent 190fc68f93
commit 12b3932817
2 changed files with 96 additions and 138 deletions
+12 -54
View File
@@ -30,7 +30,12 @@ type ServiceRef struct {
// Load читает конфигурацию из переменных окружения и файлов.
func Load() (Config, error) {
apiEndpoint := getenvDefault("NUBES_API_ENDPOINT", "https://lk-api-gateway.ngcloud.ru/api/v1/svc")
// 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 {
@@ -51,13 +56,11 @@ func Load() (Config, error) {
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 == "" {
repoRoot, err := FindRepoRoot()
if err != nil {
return Config{}, err
}
listPath = filepath.Join(repoRoot, "devops", "config", "services_list.txt")
return Config{}, errors.New("NUBES_SERVICES_FILE is required when NUBES_SERVICE_ID is not set")
}
list, err := ReadServicesList(listPath)
if err != nil {
@@ -76,14 +79,6 @@ func Load() (Config, error) {
}, nil
}
func getenvDefault(key string, def string) string {
val := strings.TrimSpace(os.Getenv(key))
if val == "" {
return def
}
return val
}
func loadToken() (string, error) {
if tok := strings.TrimSpace(os.Getenv("NUBES_API_TOKEN")); tok != "" {
return tok, nil
@@ -95,46 +90,9 @@ func loadToken() (string, error) {
}
return strings.TrimSpace(string(b)), nil
}
repoRoot, err := FindRepoRoot()
if err != nil {
return "", err
}
latest, err := findLatestToken(repoRoot)
if err != nil {
return "", err
}
if latest == "" {
return "", errors.New("NUBES_API_TOKEN or TOKEN_FILE is required")
}
b, err := os.ReadFile(latest)
if err != nil {
return "", err
}
return strings.TrimSpace(string(b)), nil
}
func findLatestToken(dir string) (string, error) {
entries, err := os.ReadDir(dir)
if err != nil {
return "", err
}
var latest string
var latestTime int64
for _, e := range entries {
if e.IsDir() || !strings.HasSuffix(e.Name(), ".token") {
continue
}
info, err := e.Info()
if err != nil {
continue
}
mt := info.ModTime().Unix()
if mt > latestTime {
latestTime = mt
latest = filepath.Join(dir, e.Name())
}
}
return latest, nil
// Легаси-фолбэк «найти последний *.token в корне репо» удален (2026-09-30):
// токенов там нет, а такой поиск мог молча подхватить чужой токен.
return "", errors.New("NUBES_API_TOKEN or TOKEN_FILE is required")
}
// ReadServicesList читает список сервисов из файла.