Initial project snapshot
This commit is contained in:
@@ -0,0 +1,400 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
baseURL string
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
type EnrollRequest struct {
|
||||
ClusterID string `json:"cluster_id"`
|
||||
JoinToken string `json:"join_token"`
|
||||
NodeName string `json:"node_name"`
|
||||
NodeFingerprint string `json:"node_fingerprint"`
|
||||
PublicKey string `json:"public_key"`
|
||||
ReportedCapabilities map[string]any `json:"reported_capabilities"`
|
||||
ReportedFacts map[string]any `json:"reported_facts"`
|
||||
RequestedRoles []string `json:"requested_roles"`
|
||||
}
|
||||
|
||||
type EnrollResponse struct {
|
||||
Status string `json:"status"`
|
||||
JoinRequest json.RawMessage `json:"join_request"`
|
||||
}
|
||||
|
||||
type EnrollmentBootstrapRequest struct {
|
||||
ClusterID string `json:"cluster_id"`
|
||||
NodeFingerprint string `json:"node_fingerprint"`
|
||||
PublicKey string `json:"public_key"`
|
||||
}
|
||||
|
||||
type EnrollmentBootstrapResponse struct {
|
||||
Status string `json:"status"`
|
||||
JoinRequest json.RawMessage `json:"join_request"`
|
||||
Bootstrap *NodeBootstrap `json:"node_bootstrap,omitempty"`
|
||||
}
|
||||
|
||||
type NodeBootstrap struct {
|
||||
NodeID string `json:"node_id"`
|
||||
ClusterID string `json:"cluster_id"`
|
||||
IdentityStatus string `json:"identity_status"`
|
||||
Certificate map[string]any `json:"certificate"`
|
||||
HeartbeatEndpoint string `json:"heartbeat_endpoint"`
|
||||
ClusterAuthority *ClusterAuthorityDescriptor `json:"cluster_authority,omitempty"`
|
||||
AuthorityPayload json.RawMessage `json:"authority_payload,omitempty"`
|
||||
AuthoritySignature *ClusterSignature `json:"authority_signature,omitempty"`
|
||||
}
|
||||
|
||||
type HeartbeatRequest struct {
|
||||
HealthStatus string `json:"health_status"`
|
||||
ReportedVersion string `json:"reported_version,omitempty"`
|
||||
Capabilities map[string]any `json:"capabilities"`
|
||||
ServiceStates map[string]any `json:"service_states"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
}
|
||||
|
||||
type HeartbeatResponse struct {
|
||||
Heartbeat json.RawMessage `json:"heartbeat"`
|
||||
TestingFlags EffectiveTestingFlags `json:"testing_flags"`
|
||||
}
|
||||
|
||||
type EffectiveTestingFlags struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
TelemetryEnabled bool `json:"telemetry_enabled"`
|
||||
SyntheticLinksEnabled bool `json:"synthetic_links_enabled"`
|
||||
HistoryRetentionHours int `json:"history_retention_hours"`
|
||||
AppliedScopes []string `json:"applied_scopes"`
|
||||
}
|
||||
|
||||
type DesiredWorkload struct {
|
||||
ServiceType string `json:"service_type"`
|
||||
DesiredState string `json:"desired_state"`
|
||||
Version string `json:"version,omitempty"`
|
||||
RuntimeMode string `json:"runtime_mode"`
|
||||
ArtifactRef string `json:"artifact_ref,omitempty"`
|
||||
Config map[string]any `json:"config"`
|
||||
Environment map[string]any `json:"environment"`
|
||||
}
|
||||
|
||||
type WorkloadStatusRequest struct {
|
||||
ReportedState string `json:"reported_state"`
|
||||
RuntimeMode string `json:"runtime_mode"`
|
||||
Version string `json:"version,omitempty"`
|
||||
StatusPayload map[string]any `json:"status_payload"`
|
||||
}
|
||||
|
||||
type MeshLinkObservationRequest struct {
|
||||
SourceNodeID string `json:"source_node_id"`
|
||||
TargetNodeID string `json:"target_node_id"`
|
||||
LinkStatus string `json:"link_status"`
|
||||
LatencyMs *int `json:"latency_ms,omitempty"`
|
||||
QualityScore *int `json:"quality_score,omitempty"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
}
|
||||
|
||||
type TelemetryRequest struct {
|
||||
CPUPercent *float64 `json:"cpu_percent,omitempty"`
|
||||
MemoryUsedBytes *int64 `json:"memory_used_bytes,omitempty"`
|
||||
MemoryTotalBytes *int64 `json:"memory_total_bytes,omitempty"`
|
||||
DiskUsedBytes *int64 `json:"disk_used_bytes,omitempty"`
|
||||
DiskTotalBytes *int64 `json:"disk_total_bytes,omitempty"`
|
||||
NetworkRxBytes *int64 `json:"network_rx_bytes,omitempty"`
|
||||
NetworkTxBytes *int64 `json:"network_tx_bytes,omitempty"`
|
||||
ProcessCount *int `json:"process_count,omitempty"`
|
||||
Payload map[string]any `json:"payload"`
|
||||
ObservedAt time.Time `json:"observed_at"`
|
||||
}
|
||||
|
||||
type SyntheticMeshRouteConfig struct {
|
||||
RouteID string `json:"route_id"`
|
||||
ClusterID string `json:"cluster_id"`
|
||||
SourceNodeID string `json:"source_node_id"`
|
||||
DestinationNodeID string `json:"destination_node_id"`
|
||||
Hops []string `json:"hops"`
|
||||
AllowedChannels []string `json:"allowed_channels"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
MaxTTL int `json:"max_ttl"`
|
||||
MaxHops int `json:"max_hops"`
|
||||
RouteVersion string `json:"route_version,omitempty"`
|
||||
PolicyVersion string `json:"policy_version,omitempty"`
|
||||
PeerDirectoryVersion string `json:"peer_directory_version,omitempty"`
|
||||
}
|
||||
|
||||
type SyntheticMeshConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
ClusterID string `json:"cluster_id"`
|
||||
LocalNodeID string `json:"local_node_id"`
|
||||
AuthorityRequired bool `json:"authority_required"`
|
||||
ClusterAuthority *ClusterAuthorityDescriptor `json:"cluster_authority,omitempty"`
|
||||
AuthorityPayload json.RawMessage `json:"authority_payload,omitempty"`
|
||||
AuthoritySignature *ClusterSignature `json:"authority_signature,omitempty"`
|
||||
ConfigVersion string `json:"config_version,omitempty"`
|
||||
PeerDirectoryVersion string `json:"peer_directory_version,omitempty"`
|
||||
PolicyVersion string `json:"policy_version,omitempty"`
|
||||
PeerEndpoints map[string]string `json:"peer_endpoints"`
|
||||
PeerEndpointCandidates map[string][]PeerEndpointCandidate `json:"peer_endpoint_candidates,omitempty"`
|
||||
PeerDirectory []PeerDirectoryEntry `json:"peer_directory,omitempty"`
|
||||
RecoverySeeds []PeerRecoverySeed `json:"recovery_seeds,omitempty"`
|
||||
RendezvousLeases []PeerRendezvousLease `json:"rendezvous_leases,omitempty"`
|
||||
RendezvousRelayPolicy *RendezvousRelayPolicyReport `json:"rendezvous_relay_policy,omitempty"`
|
||||
RoutePathDecisions *RoutePathDecisionReport `json:"route_path_decisions,omitempty"`
|
||||
Routes []SyntheticMeshRouteConfig `json:"routes"`
|
||||
ProductionForwarding bool `json:"production_forwarding"`
|
||||
}
|
||||
|
||||
type ClusterAuthorityDescriptor struct {
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
ClusterID string `json:"cluster_id"`
|
||||
AuthorityState string `json:"authority_state"`
|
||||
KeyAlgorithm string `json:"key_algorithm"`
|
||||
PublicKey string `json:"public_key"`
|
||||
PublicKeyFingerprint string `json:"public_key_fingerprint"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type ClusterSignature struct {
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
Algorithm string `json:"algorithm"`
|
||||
KeyFingerprint string `json:"key_fingerprint"`
|
||||
Signature string `json:"signature"`
|
||||
SignedAt time.Time `json:"signed_at"`
|
||||
}
|
||||
|
||||
type PeerDirectoryEntry struct {
|
||||
NodeID string `json:"node_id"`
|
||||
RouteIDs []string `json:"route_ids,omitempty"`
|
||||
EndpointCount int `json:"endpoint_count"`
|
||||
CandidateCount int `json:"candidate_count"`
|
||||
ConnectivityModes []string `json:"connectivity_modes,omitempty"`
|
||||
RecoverySeed bool `json:"recovery_seed"`
|
||||
}
|
||||
|
||||
type PeerRecoverySeed struct {
|
||||
NodeID string `json:"node_id"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
Transport string `json:"transport"`
|
||||
ConnectivityMode string `json:"connectivity_mode,omitempty"`
|
||||
Region string `json:"region,omitempty"`
|
||||
Priority int `json:"priority"`
|
||||
LastVerifiedAt *time.Time `json:"last_verified_at,omitempty"`
|
||||
Metadata json.RawMessage `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type PeerRendezvousLease struct {
|
||||
LeaseID string `json:"lease_id"`
|
||||
PeerNodeID string `json:"peer_node_id"`
|
||||
RelayNodeID string `json:"relay_node_id"`
|
||||
RelayEndpoint string `json:"relay_endpoint"`
|
||||
Transport string `json:"transport"`
|
||||
ConnectivityMode string `json:"connectivity_mode,omitempty"`
|
||||
RouteIDs []string `json:"route_ids,omitempty"`
|
||||
AllowedChannels []string `json:"allowed_channels,omitempty"`
|
||||
Priority int `json:"priority"`
|
||||
ControlPlaneOnly bool `json:"control_plane_only"`
|
||||
IssuedAt time.Time `json:"issued_at"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Metadata json.RawMessage `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type RendezvousRelayPolicyDecision struct {
|
||||
RouteID string `json:"route_id,omitempty"`
|
||||
PeerNodeID string `json:"peer_node_id"`
|
||||
WithdrawnLeaseID string `json:"withdrawn_lease_id,omitempty"`
|
||||
StaleRelayNodeID string `json:"stale_relay_node_id,omitempty"`
|
||||
SelectedRelayID string `json:"selected_relay_id,omitempty"`
|
||||
SelectedEndpoint string `json:"selected_endpoint,omitempty"`
|
||||
Score int `json:"score,omitempty"`
|
||||
Reason string `json:"reason"`
|
||||
ScoreReasons []string `json:"score_reasons,omitempty"`
|
||||
ReporterNodeID string `json:"reporter_node_id,omitempty"`
|
||||
}
|
||||
|
||||
type RendezvousRelayPolicyReport struct {
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
ScoringMode string `json:"scoring_mode"`
|
||||
FeedbackMaxAgeSeconds int `json:"feedback_max_age_seconds"`
|
||||
StaleRelayCount int `json:"stale_relay_count"`
|
||||
WithdrawnLeaseCount int `json:"withdrawn_lease_count"`
|
||||
ReplacementLeaseCount int `json:"replacement_lease_count"`
|
||||
Decisions []RendezvousRelayPolicyDecision `json:"decisions,omitempty"`
|
||||
}
|
||||
|
||||
type RoutePathDecision struct {
|
||||
DecisionID string `json:"decision_id"`
|
||||
RouteID string `json:"route_id"`
|
||||
ClusterID string `json:"cluster_id"`
|
||||
LocalNodeID string `json:"local_node_id"`
|
||||
SourceNodeID string `json:"source_node_id"`
|
||||
DestinationNodeID string `json:"destination_node_id"`
|
||||
OriginalHops []string `json:"original_hops"`
|
||||
EffectiveHops []string `json:"effective_hops"`
|
||||
PreviousHopID string `json:"previous_hop_id,omitempty"`
|
||||
NextHopID string `json:"next_hop_id,omitempty"`
|
||||
LocalRole string `json:"local_role"`
|
||||
SelectedRelayID string `json:"selected_relay_id,omitempty"`
|
||||
SelectedRelayEndpoint string `json:"selected_relay_endpoint,omitempty"`
|
||||
StaleRelayNodeID string `json:"stale_relay_node_id,omitempty"`
|
||||
RendezvousPeerNodeID string `json:"rendezvous_peer_node_id,omitempty"`
|
||||
RendezvousLeaseID string `json:"rendezvous_lease_id,omitempty"`
|
||||
RendezvousLeaseReason string `json:"rendezvous_lease_reason,omitempty"`
|
||||
DecisionSource string `json:"decision_source"`
|
||||
Generation string `json:"generation"`
|
||||
PathScore int `json:"path_score,omitempty"`
|
||||
ScoreReasons []string `json:"score_reasons,omitempty"`
|
||||
ControlPlaneOnly bool `json:"control_plane_only"`
|
||||
ProductionForwarding bool `json:"production_forwarding"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
}
|
||||
|
||||
type RoutePathDecisionReport struct {
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
DecisionMode string `json:"decision_mode"`
|
||||
Generation string `json:"generation"`
|
||||
DecisionCount int `json:"decision_count"`
|
||||
ReplacementDecisionCount int `json:"replacement_decision_count"`
|
||||
ControlPlaneOnly bool `json:"control_plane_only"`
|
||||
ProductionForwarding bool `json:"production_forwarding"`
|
||||
Decisions []RoutePathDecision `json:"decisions,omitempty"`
|
||||
}
|
||||
|
||||
type PeerEndpointCandidate struct {
|
||||
EndpointID string `json:"endpoint_id"`
|
||||
NodeID string `json:"node_id"`
|
||||
Transport string `json:"transport"`
|
||||
Address string `json:"address"`
|
||||
AddressFamily string `json:"address_family,omitempty"`
|
||||
Reachability string `json:"reachability"`
|
||||
NATType string `json:"nat_type,omitempty"`
|
||||
ConnectivityMode string `json:"connectivity_mode"`
|
||||
Region string `json:"region,omitempty"`
|
||||
Priority int `json:"priority"`
|
||||
PolicyTags []string `json:"policy_tags,omitempty"`
|
||||
LastVerifiedAt *time.Time `json:"last_verified_at,omitempty"`
|
||||
Metadata json.RawMessage `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
func New(baseURL string) *Client {
|
||||
return &Client{
|
||||
baseURL: baseURL,
|
||||
httpClient: &http.Client{
|
||||
Timeout: 15 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Enroll(ctx context.Context, request EnrollRequest) (EnrollResponse, error) {
|
||||
var response EnrollResponse
|
||||
if err := c.postJSON(ctx, "/node-agents/enroll", request, &response); err != nil {
|
||||
return EnrollResponse{}, err
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (c *Client) BootstrapEnrollment(ctx context.Context, joinRequestID string, request EnrollmentBootstrapRequest) (EnrollmentBootstrapResponse, error) {
|
||||
var response EnrollmentBootstrapResponse
|
||||
path := fmt.Sprintf("/node-agents/enrollments/%s/bootstrap", joinRequestID)
|
||||
if err := c.postJSON(ctx, path, request, &response); err != nil {
|
||||
return EnrollmentBootstrapResponse{}, err
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (c *Client) Heartbeat(ctx context.Context, clusterID, nodeID string, request HeartbeatRequest) (HeartbeatResponse, error) {
|
||||
var response HeartbeatResponse
|
||||
path := fmt.Sprintf("/clusters/%s/nodes/%s/heartbeats", clusterID, nodeID)
|
||||
if err := c.postJSON(ctx, path, request, &response); err != nil {
|
||||
return HeartbeatResponse{}, err
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (c *Client) DesiredWorkloads(ctx context.Context, clusterID, nodeID string) ([]DesiredWorkload, error) {
|
||||
var response struct {
|
||||
DesiredWorkloads []DesiredWorkload `json:"desired_workloads"`
|
||||
}
|
||||
path := fmt.Sprintf("/clusters/%s/nodes/%s/workloads/desired", clusterID, nodeID)
|
||||
if err := c.getJSON(ctx, path, &response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response.DesiredWorkloads, nil
|
||||
}
|
||||
|
||||
func (c *Client) ReportWorkloadStatus(ctx context.Context, clusterID, nodeID, serviceType string, request WorkloadStatusRequest) error {
|
||||
path := fmt.Sprintf("/clusters/%s/nodes/%s/workloads/%s/status", clusterID, nodeID, serviceType)
|
||||
return c.postJSON(ctx, path, request, nil)
|
||||
}
|
||||
|
||||
func (c *Client) ReportMeshLink(ctx context.Context, clusterID string, request MeshLinkObservationRequest) error {
|
||||
path := fmt.Sprintf("/clusters/%s/mesh/links", clusterID)
|
||||
return c.postJSON(ctx, path, request, nil)
|
||||
}
|
||||
|
||||
func (c *Client) ReportTelemetry(ctx context.Context, clusterID, nodeID string, request TelemetryRequest) error {
|
||||
path := fmt.Sprintf("/clusters/%s/nodes/%s/telemetry", clusterID, nodeID)
|
||||
return c.postJSON(ctx, path, request, nil)
|
||||
}
|
||||
|
||||
func (c *Client) SyntheticMeshConfig(ctx context.Context, clusterID, nodeID string) (SyntheticMeshConfig, error) {
|
||||
var response struct {
|
||||
Config SyntheticMeshConfig `json:"synthetic_mesh_config"`
|
||||
}
|
||||
path := fmt.Sprintf("/clusters/%s/nodes/%s/mesh/synthetic-config", clusterID, nodeID)
|
||||
if err := c.getJSON(ctx, path, &response); err != nil {
|
||||
return SyntheticMeshConfig{}, err
|
||||
}
|
||||
return response.Config, nil
|
||||
}
|
||||
|
||||
func (c *Client) getJSON(ctx context.Context, path string, response any) error {
|
||||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+path, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
httpResp, err := c.httpClient.Do(httpReq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer httpResp.Body.Close()
|
||||
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
|
||||
return fmt.Errorf("backend returned status %d", httpResp.StatusCode)
|
||||
}
|
||||
if response == nil {
|
||||
return nil
|
||||
}
|
||||
return json.NewDecoder(httpResp.Body).Decode(response)
|
||||
}
|
||||
|
||||
func (c *Client) postJSON(ctx context.Context, path string, request any, response any) error {
|
||||
payload, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+path, bytes.NewReader(payload))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
httpResp, err := c.httpClient.Do(httpReq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer httpResp.Body.Close()
|
||||
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
|
||||
return fmt.Errorf("backend returned status %d", httpResp.StatusCode)
|
||||
}
|
||||
if response == nil {
|
||||
return nil
|
||||
}
|
||||
return json.NewDecoder(httpResp.Body).Decode(response)
|
||||
}
|
||||
Reference in New Issue
Block a user