mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-05-01 10:44:12 +00:00
- Replace CLI-based rootwallet calls with agent-based communication - Update production provisioner to support sudo-based service management - Add API key-to-wallet resolution for gateway operator handlers
162 lines
4.7 KiB
Go
162 lines
4.7 KiB
Go
// Package noderesolver provides unified node discovery for the orama CLI.
|
|
//
|
|
// It resolves operator-owned nodes by querying the network's gateway API
|
|
// (primary) or falling back to the legacy nodes.conf file.
|
|
package noderesolver
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"time"
|
|
|
|
"github.com/DeBrosOfficial/network/pkg/auth"
|
|
"github.com/DeBrosOfficial/network/pkg/cli"
|
|
"github.com/DeBrosOfficial/network/pkg/cli/remotessh"
|
|
"github.com/DeBrosOfficial/network/pkg/inspector"
|
|
)
|
|
|
|
// httpClient is the shared HTTP client for API calls.
|
|
var httpClient = &http.Client{Timeout: 10 * time.Second}
|
|
|
|
// ResolveNodes returns the operator's nodes for a given environment.
|
|
// It first tries the network API (GET /v1/operator/nodes), then falls
|
|
// back to nodes.conf if the API is unreachable or returns no results.
|
|
func ResolveNodes(env string) ([]inspector.Node, error) {
|
|
nodes, err := resolveFromNetwork(env)
|
|
if err == nil && len(nodes) > 0 {
|
|
return nodes, nil
|
|
}
|
|
|
|
// Fallback to nodes.conf
|
|
confNodes, confErr := remotessh.LoadEnvNodes(env)
|
|
if confErr != nil {
|
|
if err != nil {
|
|
return nil, fmt.Errorf("network API: %w; nodes.conf: %v", err, confErr)
|
|
}
|
|
return nil, confErr
|
|
}
|
|
return confNodes, nil
|
|
}
|
|
|
|
// ResolveNodesNetworkOnly queries only the network API without nodes.conf fallback.
|
|
func ResolveNodesNetworkOnly(env string) ([]inspector.Node, error) {
|
|
return resolveFromNetwork(env)
|
|
}
|
|
|
|
// resolveFromNetwork queries the gateway API for operator-owned nodes.
|
|
func resolveFromNetwork(env string) ([]inspector.Node, error) {
|
|
// 1. Get gateway URL for the environment
|
|
gatewayURL, err := gatewayURLForEnv(env)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to resolve gateway URL: %w", err)
|
|
}
|
|
|
|
// 2. Load stored credentials for this gateway
|
|
apiKey, err := loadAPIKey(gatewayURL)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("no credentials for %s: %w (run 'orama auth login' first)", gatewayURL, err)
|
|
}
|
|
|
|
return resolveFromNetworkWithURL(gatewayURL, apiKey, env)
|
|
}
|
|
|
|
// resolveFromNetworkWithURL queries a specific gateway URL with an API key.
|
|
// Exported for testing.
|
|
func resolveFromNetworkWithURL(gatewayURL, apiKey, env string) ([]inspector.Node, error) {
|
|
endpoint := fmt.Sprintf("%s/v1/operator/nodes?env=%s", gatewayURL, url.QueryEscape(env))
|
|
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, endpoint, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
req.Header.Set("X-API-Key", apiKey)
|
|
|
|
resp, err := httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to reach gateway: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read response: %w", err)
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("gateway returned HTTP %d: %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
var result struct {
|
|
Nodes []struct {
|
|
ID string `json:"id"`
|
|
IPAddress string `json:"ip_address"`
|
|
InternalIP string `json:"internal_ip"`
|
|
Environment string `json:"environment"`
|
|
Role string `json:"role"`
|
|
SSHUser string `json:"ssh_user"`
|
|
Status string `json:"status"`
|
|
} `json:"nodes"`
|
|
}
|
|
if err := json.Unmarshal(body, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to parse response: %w", err)
|
|
}
|
|
|
|
nodes := make([]inspector.Node, 0, len(result.Nodes))
|
|
for _, n := range result.Nodes {
|
|
user := n.SSHUser
|
|
if user == "" {
|
|
user = "root"
|
|
}
|
|
// Sandbox nodes share a single SSH key; production nodes use per-host keys.
|
|
vaultTarget := fmt.Sprintf("%s/%s", n.IPAddress, user)
|
|
if n.Environment == "sandbox" {
|
|
vaultTarget = "sandbox/root"
|
|
}
|
|
nodes = append(nodes, inspector.Node{
|
|
Environment: n.Environment,
|
|
User: user,
|
|
Host: n.IPAddress,
|
|
Role: n.Role,
|
|
VaultTarget: vaultTarget,
|
|
})
|
|
}
|
|
|
|
return nodes, nil
|
|
}
|
|
|
|
// gatewayURLForEnv returns the gateway URL for a given environment name.
|
|
// If env is empty, uses the active environment.
|
|
func gatewayURLForEnv(env string) (string, error) {
|
|
if env == "" {
|
|
e, err := cli.GetActiveEnvironment()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return e.GatewayURL, nil
|
|
}
|
|
|
|
e, err := cli.GetEnvironmentByName(env)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return e.GatewayURL, nil
|
|
}
|
|
|
|
// loadAPIKey loads the stored API key for a gateway URL.
|
|
func loadAPIKey(gatewayURL string) (string, error) {
|
|
store, err := auth.LoadEnhancedCredentials()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to load credentials: %w", err)
|
|
}
|
|
|
|
creds := store.GetDefaultCredential(gatewayURL)
|
|
if creds == nil || creds.APIKey == "" {
|
|
return "", fmt.Errorf("no credentials found for %s", gatewayURL)
|
|
}
|
|
|
|
return creds.APIKey, nil
|
|
}
|