// 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) { apiEndpoint := getenvDefault("NUBES_API_ENDPOINT", "https://lk-api-gateway.ngcloud.ru/api/v1/svc") 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 { 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") } 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 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 } 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 } 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 } // 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") }