mirror of
https://github.com/Noooste/garage-ui.git
synced 2026-07-26 07:48:13 +00:00
8f81c7ffea
Signed-off-by: Noste <83548733+Noooste@users.noreply.github.com>
450 lines
14 KiB
Go
450 lines
14 KiB
Go
package services
|
|
|
|
import (
|
|
"Noooste/garage-ui/internal/config"
|
|
"Noooste/garage-ui/internal/models"
|
|
"Noooste/garage-ui/pkg/utils"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
|
|
"github.com/Noooste/azuretls-client"
|
|
)
|
|
|
|
// GarageAdminService handles interactions with the Garage Admin API
|
|
type GarageAdminService struct {
|
|
baseURL string
|
|
token string
|
|
httpClient *azuretls.Session
|
|
}
|
|
|
|
// NewGarageAdminService creates a new Garage Admin API service
|
|
func NewGarageAdminService(cfg *config.GarageConfig, logLevel string) *GarageAdminService {
|
|
session := azuretls.NewSession()
|
|
|
|
if logLevel == "debug" {
|
|
session.Log()
|
|
}
|
|
|
|
return &GarageAdminService{
|
|
baseURL: cfg.AdminEndpoint,
|
|
token: cfg.AdminToken,
|
|
httpClient: session,
|
|
}
|
|
}
|
|
|
|
// doRequest performs an HTTP request to the Admin API with retry logic for connection refused errors
|
|
func (s *GarageAdminService) doRequest(ctx context.Context, method, path string, body interface{}) (*azuretls.Response, error) {
|
|
var resp *azuretls.Response
|
|
|
|
retryConfig := utils.DefaultRetryConfig()
|
|
err := utils.RetryWithBackoff(ctx, retryConfig, func() error {
|
|
var reqErr error
|
|
resp, reqErr = s.httpClient.Do(&azuretls.Request{
|
|
Method: method,
|
|
Url: s.baseURL + path,
|
|
Body: body,
|
|
IgnoreBody: true, // decodeResponse will handle body reading
|
|
OrderedHeaders: azuretls.OrderedHeaders{
|
|
{"Authorization", fmt.Sprintf("Bearer %s", s.token)},
|
|
},
|
|
}, ctx)
|
|
return reqErr
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
// decodeResponse decodes a JSON response into the target structure
|
|
func decodeResponse(resp *azuretls.Response, target interface{}) error {
|
|
defer resp.RawBody.Close()
|
|
|
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
bodyBytes, _ := io.ReadAll(resp.RawBody)
|
|
return fmt.Errorf("API returned status %d: %s", resp.StatusCode, string(bodyBytes))
|
|
}
|
|
|
|
if target != nil {
|
|
if err := json.NewDecoder(resp.RawBody).Decode(target); err != nil {
|
|
return fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ListKeys returns all access keys in the cluster
|
|
func (s *GarageAdminService) ListKeys(ctx context.Context) ([]models.ListKeysResponseItem, error) {
|
|
resp, err := s.doRequest(ctx, http.MethodGet, "/v2/ListKeys", nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("request failed: %w", err)
|
|
}
|
|
|
|
var result []models.ListKeysResponseItem
|
|
if err := decodeResponse(resp, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// CreateKey creates a new API access key
|
|
func (s *GarageAdminService) CreateKey(ctx context.Context, req models.CreateKeyRequest) (*models.GarageKeyInfo, error) {
|
|
resp, err := s.doRequest(ctx, http.MethodPost, "/v2/CreateKey", req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("request failed: %w", err)
|
|
}
|
|
|
|
var result models.GarageKeyInfo
|
|
if err := decodeResponse(resp, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// GetKeyInfo returns information about a specific access key
|
|
func (s *GarageAdminService) GetKeyInfo(ctx context.Context, keyID string, showSecret bool) (*models.GarageKeyInfo, error) {
|
|
path := fmt.Sprintf("/v2/GetKeyInfo?id=%s", keyID)
|
|
if showSecret {
|
|
path += "&showSecretKey=true"
|
|
}
|
|
|
|
resp, err := s.doRequest(ctx, http.MethodGet, path, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("request failed: %w", err)
|
|
}
|
|
|
|
var result models.GarageKeyInfo
|
|
if err := decodeResponse(resp, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// UpdateKey updates information about an access key
|
|
func (s *GarageAdminService) UpdateKey(ctx context.Context, keyID string, req models.UpdateKeyRequest) (*models.GarageKeyInfo, error) {
|
|
path := fmt.Sprintf("/v2/UpdateKey?id=%s", keyID)
|
|
|
|
resp, err := s.doRequest(ctx, http.MethodPost, path, req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("request failed: %w", err)
|
|
}
|
|
|
|
var result models.GarageKeyInfo
|
|
if err := decodeResponse(resp, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// DeleteKey deletes an access key from the cluster
|
|
func (s *GarageAdminService) DeleteKey(ctx context.Context, keyID string) error {
|
|
path := fmt.Sprintf("/v2/DeleteKey?id=%s", keyID)
|
|
|
|
resp, err := s.doRequest(ctx, http.MethodPost, path, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("request failed: %w", err)
|
|
}
|
|
|
|
if err := decodeResponse(resp, nil); err != nil {
|
|
return fmt.Errorf("failed to process response: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ImportKey imports an existing API access key
|
|
func (s *GarageAdminService) ImportKey(ctx context.Context, req models.ImportKeyRequest) (*models.GarageKeyInfo, error) {
|
|
resp, err := s.doRequest(ctx, http.MethodPost, "/v2/ImportKey", req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("request failed: %w", err)
|
|
}
|
|
|
|
var result models.GarageKeyInfo
|
|
if err := decodeResponse(resp, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// ListBuckets returns all buckets in the cluster
|
|
func (s *GarageAdminService) ListBuckets(ctx context.Context) ([]models.ListBucketsResponseItem, error) {
|
|
resp, err := s.doRequest(ctx, http.MethodGet, "/v2/ListBuckets", nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("request failed: %w", err)
|
|
}
|
|
|
|
var result []models.ListBucketsResponseItem
|
|
if err := decodeResponse(resp, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// GetBucketInfo returns detailed information about a bucket by ID
|
|
func (s *GarageAdminService) GetBucketInfo(ctx context.Context, bucketID string) (*models.GarageBucketInfo, error) {
|
|
path := fmt.Sprintf("/v2/GetBucketInfo?id=%s", bucketID)
|
|
|
|
resp, err := s.doRequest(ctx, http.MethodGet, path, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("request failed: %w", err)
|
|
}
|
|
|
|
var result models.GarageBucketInfo
|
|
if err := decodeResponse(resp, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// GetBucketInfoByAlias returns detailed information about a bucket by its global alias
|
|
func (s *GarageAdminService) GetBucketInfoByAlias(ctx context.Context, globalAlias string) (*models.GarageBucketInfo, error) {
|
|
path := fmt.Sprintf("/v2/GetBucketInfo?globalAlias=%s", globalAlias)
|
|
|
|
resp, err := s.doRequest(ctx, http.MethodGet, path, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("request failed: %w", err)
|
|
}
|
|
|
|
var result models.GarageBucketInfo
|
|
if err = decodeResponse(resp, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// CreateBucket creates a new bucket via the Admin API
|
|
func (s *GarageAdminService) CreateBucket(ctx context.Context, req models.CreateBucketAdminRequest) (*models.GarageBucketInfo, error) {
|
|
resp, err := s.doRequest(ctx, http.MethodPost, "/v2/CreateBucket", req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("request failed: %w", err)
|
|
}
|
|
|
|
var result models.GarageBucketInfo
|
|
if err := decodeResponse(resp, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// UpdateBucket updates bucket settings
|
|
func (s *GarageAdminService) UpdateBucket(ctx context.Context, bucketID string, req models.UpdateBucketRequest) (*models.GarageBucketInfo, error) {
|
|
path := fmt.Sprintf("/v2/UpdateBucket?id=%s", bucketID)
|
|
|
|
resp, err := s.doRequest(ctx, http.MethodPost, path, req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("request failed: %w", err)
|
|
}
|
|
|
|
var result models.GarageBucketInfo
|
|
if err := decodeResponse(resp, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// DeleteBucket deletes a bucket
|
|
func (s *GarageAdminService) DeleteBucket(ctx context.Context, bucketID string) error {
|
|
path := fmt.Sprintf("/v2/DeleteBucket?id=%s", bucketID)
|
|
|
|
resp, err := s.doRequest(ctx, http.MethodPost, path, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("request failed: %w", err)
|
|
}
|
|
|
|
if err := decodeResponse(resp, nil); err != nil {
|
|
return fmt.Errorf("failed to process response: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// AddBucketAlias adds an alias to a bucket
|
|
func (s *GarageAdminService) AddBucketAlias(ctx context.Context, req models.AddBucketAliasRequest) (*models.GarageBucketInfo, error) {
|
|
resp, err := s.doRequest(ctx, http.MethodPost, "/v2/AddBucketAlias", req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("request failed: %w", err)
|
|
}
|
|
|
|
var result models.GarageBucketInfo
|
|
if err := decodeResponse(resp, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// RemoveBucketAlias removes an alias from a bucket
|
|
func (s *GarageAdminService) RemoveBucketAlias(ctx context.Context, req models.RemoveBucketAliasRequest) (*models.GarageBucketInfo, error) {
|
|
resp, err := s.doRequest(ctx, http.MethodPost, "/v2/RemoveBucketAlias", req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("request failed: %w", err)
|
|
}
|
|
|
|
var result models.GarageBucketInfo
|
|
if err := decodeResponse(resp, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// AllowBucketKey grants permissions for a key on a bucket
|
|
func (s *GarageAdminService) AllowBucketKey(ctx context.Context, req models.BucketKeyPermRequest) (*models.GarageBucketInfo, error) {
|
|
resp, err := s.doRequest(ctx, http.MethodPost, "/v2/AllowBucketKey", req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("request failed: %w", err)
|
|
}
|
|
|
|
var result models.GarageBucketInfo
|
|
if err := decodeResponse(resp, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// DenyBucketKey revokes permissions for a key on a bucket
|
|
func (s *GarageAdminService) DenyBucketKey(ctx context.Context, req models.BucketKeyPermRequest) (*models.GarageBucketInfo, error) {
|
|
resp, err := s.doRequest(ctx, http.MethodPost, "/v2/DenyBucketKey", req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("request failed: %w", err)
|
|
}
|
|
|
|
var result models.GarageBucketInfo
|
|
if err := decodeResponse(resp, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// GetClusterHealth returns the health status of the cluster
|
|
func (s *GarageAdminService) GetClusterHealth(ctx context.Context) (*models.ClusterHealth, error) {
|
|
resp, err := s.doRequest(ctx, http.MethodGet, "/v2/GetClusterHealth", nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("request failed: %w", err)
|
|
}
|
|
|
|
var result models.ClusterHealth
|
|
if err := decodeResponse(resp, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// GetClusterStatus returns the current status of the cluster
|
|
func (s *GarageAdminService) GetClusterStatus(ctx context.Context) (*models.ClusterStatus, error) {
|
|
resp, err := s.doRequest(ctx, http.MethodGet, "/v2/GetClusterStatus", nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("request failed: %w", err)
|
|
}
|
|
|
|
var result models.ClusterStatus
|
|
if err := decodeResponse(resp, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// GetClusterStatistics returns global cluster statistics
|
|
func (s *GarageAdminService) GetClusterStatistics(ctx context.Context) (*models.ClusterStatistics, error) {
|
|
resp, err := s.doRequest(ctx, http.MethodGet, "/v2/GetClusterStatistics", nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("request failed: %w", err)
|
|
}
|
|
|
|
var result models.ClusterStatistics
|
|
if err := decodeResponse(resp, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// GetNodeInfo returns information about a specific node
|
|
func (s *GarageAdminService) GetNodeInfo(ctx context.Context, nodeID string) (*models.MultiNodeResponse, error) {
|
|
path := fmt.Sprintf("/v2/GetNodeInfo?node=%s", nodeID)
|
|
|
|
resp, err := s.doRequest(ctx, http.MethodGet, path, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("request failed: %w", err)
|
|
}
|
|
|
|
var result models.MultiNodeResponse
|
|
if err := decodeResponse(resp, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// GetNodeStatistics returns statistics for a specific node
|
|
func (s *GarageAdminService) GetNodeStatistics(ctx context.Context, nodeID string) (*models.MultiNodeResponse, error) {
|
|
path := fmt.Sprintf("/v2/GetNodeStatistics?node=%s", nodeID)
|
|
|
|
resp, err := s.doRequest(ctx, http.MethodGet, path, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("request failed: %w", err)
|
|
}
|
|
|
|
var result models.MultiNodeResponse
|
|
if err := decodeResponse(resp, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// HealthCheck checks if the Admin API is reachable
|
|
func (s *GarageAdminService) HealthCheck(ctx context.Context) error {
|
|
resp, err := s.doRequest(ctx, http.MethodGet, "/health", nil)
|
|
if err != nil {
|
|
return fmt.Errorf("health check failed: %w", err)
|
|
}
|
|
|
|
if err := decodeResponse(resp, nil); err != nil {
|
|
return fmt.Errorf("health check returned error: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetMetrics returns Prometheus metrics from the Admin API
|
|
func (s *GarageAdminService) GetMetrics(ctx context.Context) (string, error) {
|
|
resp, err := s.doRequest(ctx, http.MethodGet, "/metrics", nil)
|
|
if err != nil {
|
|
return "", fmt.Errorf("request failed: %w", err)
|
|
}
|
|
defer resp.RawBody.Close()
|
|
|
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
bodyBytes, _ := io.ReadAll(resp.RawBody)
|
|
return "", fmt.Errorf("API returned status %d: %s", resp.StatusCode, string(bodyBytes))
|
|
}
|
|
|
|
bodyBytes, err := io.ReadAll(resp.RawBody)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to read response: %w", err)
|
|
}
|
|
|
|
return string(bodyBytes), nil
|
|
}
|