Files
traefik-selectel/internal/traefik/client.go
T
vasyanskandClaude Sonnet 5 273cd54415
Build / Tests (push) Successful in 13s
Build / Build image (push) Failing after 15s
Build / Notify on failure (push) Failing after 0s
feat: traefik-selectel-dns — A-записи Selectel по роутам Traefik + CI сборка образа
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 21:50:07 +07:00

91 lines
2.6 KiB
Go

// Package traefik — клиент Traefik API (GET /api/http/routers).
package traefik
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/realmanual/traefik-selectel/internal/httpx"
)
// Router — подмножество полей роутера Traefik.
type Router struct {
Name string `json:"name"`
Rule string `json:"rule"`
Status string `json:"status"`
Provider string `json:"provider"`
}
// RouterSource — источник роутов (интерфейс для тестов).
type RouterSource interface {
Routers(ctx context.Context) ([]Router, error)
}
// Client читает роутеры из Traefik API.
type Client struct {
baseURL string
http *http.Client
retry httpx.Retrier
}
// NewClient создаёт клиент. timeout — таймаут одного HTTP-запроса.
func NewClient(apiURL string, timeout time.Duration) (*Client, error) {
u, err := url.Parse(apiURL)
if err != nil || (u.Scheme != "http" && u.Scheme != "https") || u.Host == "" {
return nil, fmt.Errorf("traefik: некорректный apiURL %q", apiURL)
}
return &Client{
baseURL: strings.TrimRight(u.String(), "/"),
http: &http.Client{Timeout: timeout},
retry: httpx.DefaultRetrier(),
}, nil
}
const perPage = 500
const maxPages = 100
// Routers возвращает все HTTP-роутеры (с учётом пагинации X-Next-Page).
func (c *Client) Routers(ctx context.Context) ([]Router, error) {
var all []Router
page := 1
for i := 0; i < maxPages; i++ {
u := fmt.Sprintf("%s/api/http/routers?page=%d&per_page=%d", c.baseURL, page, perPage)
resp, err := c.retry.Do(ctx, c.http, func() (*http.Request, error) {
req, err := http.NewRequest(http.MethodGet, u, nil)
if err != nil {
return nil, err
}
req.Header.Set("Accept", "application/json")
return req, nil
})
if err != nil {
return nil, fmt.Errorf("traefik: запрос роутеров: %w", err)
}
body, err := httpx.ReadBody(resp)
resp.Body.Close()
if err != nil {
return nil, fmt.Errorf("traefik: чтение ответа: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("traefik: статус %d: %s", resp.StatusCode, httpx.Snippet(body, 200))
}
var chunk []Router
if err := json.Unmarshal(body, &chunk); err != nil {
return nil, fmt.Errorf("traefik: разбор JSON: %w", err)
}
all = append(all, chunk...)
next, _ := strconv.Atoi(resp.Header.Get("X-Next-Page"))
if next <= page {
return all, nil
}
page = next
}
return nil, fmt.Errorf("traefik: слишком много страниц роутеров")
}