91 lines
2.6 KiB
Go
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: слишком много страниц роутеров")
|
|
}
|