66 lines
1.9 KiB
Go
66 lines
1.9 KiB
Go
|
|
package tenantengine
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"encoding/json"
|
||
|
|
"fmt"
|
||
|
|
"net/http"
|
||
|
|
"strings"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
// HTTPClient calls tenant-engine's live-lookup endpoint
|
||
|
|
// (GET /tenants/{id}/roles/live).
|
||
|
|
type HTTPClient struct {
|
||
|
|
BaseURL string
|
||
|
|
Client *http.Client
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewHTTPClient creates an HTTP-backed tenant-engine client.
|
||
|
|
func NewHTTPClient(baseURL string) (*HTTPClient, error) {
|
||
|
|
if baseURL == "" {
|
||
|
|
return nil, fmt.Errorf("tenant-engine base URL is required")
|
||
|
|
}
|
||
|
|
return &HTTPClient{
|
||
|
|
BaseURL: strings.TrimRight(baseURL, "/"),
|
||
|
|
Client: &http.Client{Timeout: 3 * time.Second},
|
||
|
|
}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// LiveRoles calls GET /tenants/{tenantID}/roles/live.
|
||
|
|
//
|
||
|
|
// Fail-closed by construction: any transport error, non-200 response, or
|
||
|
|
// malformed body returns LiveRolesResult{Available: false} alongside a
|
||
|
|
// non-nil error. Nothing is inferred as "zero roles" from a failure --
|
||
|
|
// callers must check Available, not just the length of Roles.
|
||
|
|
func (c *HTTPClient) LiveRoles(ctx context.Context, tenantID string) (LiveRolesResult, error) {
|
||
|
|
url := fmt.Sprintf("%s/tenants/%s/roles/live", c.BaseURL, tenantID)
|
||
|
|
|
||
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||
|
|
if err != nil {
|
||
|
|
return LiveRolesResult{Available: false}, NewBackendError(FailureUnavailable, "live_roles", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
resp, err := c.Client.Do(req)
|
||
|
|
if err != nil {
|
||
|
|
return LiveRolesResult{Available: false}, NewBackendError(FailureUnavailable, "live_roles", err)
|
||
|
|
}
|
||
|
|
defer resp.Body.Close()
|
||
|
|
|
||
|
|
if resp.StatusCode != http.StatusOK {
|
||
|
|
return LiveRolesResult{Available: false}, NewBackendError(
|
||
|
|
FailureUnavailable, "live_roles", fmt.Errorf("status %d", resp.StatusCode),
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
var body struct {
|
||
|
|
TenantID string `json:"tenant_id"`
|
||
|
|
Roles []string `json:"roles"`
|
||
|
|
}
|
||
|
|
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
|
||
|
|
return LiveRolesResult{Available: false}, NewBackendError(FailureInvalidResponse, "live_roles", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
return LiveRolesResult{Roles: body.Roles, Available: true}, nil
|
||
|
|
}
|