package client import ( "bytes" "context" "encoding/binary" "encoding/json" "fmt" "io" "net/http" "net/url" "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"` UpdateHint *NodeUpdateHint `json:"update_hint,omitempty"` } type NodeUpdateHint struct { SchemaVersion string `json:"schema_version"` Generation string `json:"generation,omitempty"` CheckNow bool `json:"check_now"` Products []string `json:"products,omitempty"` Reason string `json:"reason,omitempty"` DeliveryMode string `json:"delivery_mode,omitempty"` SubscriptionStatus string `json:"subscription_status,omitempty"` UpdateService *NodeUpdateServiceAssignment `json:"update_service,omitempty"` FallbackPollSeconds int `json:"fallback_poll_seconds,omitempty"` } type NodeUpdateServiceAssignment struct { SchemaVersion string `json:"schema_version"` NodeID string `json:"node_id,omitempty"` NodeName string `json:"node_name,omitempty"` Endpoint string `json:"endpoint,omitempty"` Region string `json:"region,omitempty"` Status string `json:"status"` Reason string `json:"reason,omitempty"` AssignedAt time.Time `json:"assigned_at"` ExpiresAt time.Time `json:"expires_at"` } type NodeUpdatePlanRequest struct { Product string CurrentVersion string OS string Arch string InstallType string Channel string } type NodeUpdatePlanResponse struct { Plan NodeUpdatePlan `json:"node_update_plan"` } type NodeUpdatePlan struct { SchemaVersion string `json:"schema_version"` ClusterID string `json:"cluster_id"` NodeID string `json:"node_id"` Product string `json:"product"` CurrentVersion string `json:"current_version,omitempty"` Action string `json:"action"` Reason string `json:"reason"` TargetVersion string `json:"target_version,omitempty"` Channel string `json:"channel,omitempty"` Strategy string `json:"strategy,omitempty"` RollbackAllowed bool `json:"rollback_allowed"` HealthWindowSec int `json:"health_window_seconds,omitempty"` Artifact *ReleaseArtifact `json:"artifact,omitempty"` AuthorityPayload json.RawMessage `json:"authority_payload,omitempty"` AuthoritySignature *ClusterSignature `json:"authority_signature,omitempty"` ProductionForwarding bool `json:"production_forwarding"` } type ReleaseArtifact struct { ID string `json:"id"` ReleaseID string `json:"release_id"` ClusterID string `json:"cluster_id"` Product string `json:"product"` Version string `json:"version"` OS string `json:"os"` Arch string `json:"arch"` InstallType string `json:"install_type"` Kind string `json:"kind"` URL string `json:"url"` URLs []string `json:"urls,omitempty"` SHA256 string `json:"sha256"` SizeBytes int64 `json:"size_bytes"` Signature *string `json:"signature,omitempty"` Metadata json.RawMessage `json:"metadata"` CreatedAt time.Time `json:"created_at"` } type NodeUpdateStatusRequest struct { Product string `json:"product"` CurrentVersion string `json:"current_version,omitempty"` TargetVersion string `json:"target_version,omitempty"` Phase string `json:"phase"` Status string `json:"status"` AttemptID string `json:"attempt_id,omitempty"` ErrorMessage *string `json:"error_message,omitempty"` RollbackVersion *string `json:"rollback_version,omitempty"` Payload map[string]any `json:"payload,omitempty"` ObservedAt time.Time `json:"observed_at,omitempty"` } 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 NodeVPNAssignmentLease struct { LeaseID string `json:"lease_id"` OwnerNodeID string `json:"owner_node_id"` LeaseGeneration int64 `json:"lease_generation"` Status string `json:"status"` RenewedAt time.Time `json:"renewed_at"` ExpiresAt time.Time `json:"expires_at"` } type NodeVPNAssignment struct { VPNConnectionID string `json:"vpn_connection_id"` ClusterID string `json:"cluster_id"` OrganizationID string `json:"organization_id"` Name string `json:"name"` TargetEndpoint json.RawMessage `json:"target_endpoint"` ProtocolFamily string `json:"protocol_family"` Mode string `json:"mode"` DesiredState string `json:"desired_state"` RoutingUsage json.RawMessage `json:"routing_usage"` RoutePolicy json.RawMessage `json:"route_policy"` QoSPolicy json.RawMessage `json:"qos_policy"` PlacementPolicy json.RawMessage `json:"placement_policy"` Status string `json:"status"` HasCredentialRef bool `json:"has_credential_ref"` AssignmentReason string `json:"assignment_reason"` ActiveLease *NodeVPNAssignmentLease `json:"active_lease,omitempty"` UpdatedAt time.Time `json:"updated_at"` } type NodeVPNAssignmentStatusRequest struct { ObservedStatus string `json:"observed_status"` StatusPayload map[string]any `json:"status_payload"` ObservedAt time.Time `json:"observed_at,omitempty"` } type NodeVPNAssignmentLeaseRenewRequest struct { TTLSeconds int `json:"ttl_seconds"` } type NodeVPNAssignmentLeaseAcquireRequest struct { TTLSeconds int `json:"ttl_seconds"` Metadata map[string]any `json:"metadata,omitempty"` } 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 { Raw json.RawMessage `json:"-"` 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"` ServiceChannelFeedback *FabricServiceChannelFeedbackReport `json:"service_channel_route_feedback,omitempty"` ServiceChannelAdaptivePolicy *FabricServiceChannelAdaptivePolicy `json:"service_channel_adaptive_policy,omitempty"` ServiceChannelRemediationCommands []FabricServiceChannelRemediationCommand `json:"service_channel_remediation_commands,omitempty"` MeshListener *MeshListenerConfig `json:"mesh_listener,omitempty"` Routes []SyntheticMeshRouteConfig `json:"routes"` ProductionForwarding bool `json:"production_forwarding"` } func (c *SyntheticMeshConfig) UnmarshalJSON(data []byte) error { type syntheticMeshConfigAlias SyntheticMeshConfig var decoded syntheticMeshConfigAlias if err := json.Unmarshal(data, &decoded); err != nil { return err } *c = SyntheticMeshConfig(decoded) c.Raw = append(c.Raw[:0], data...) return nil } type FabricServiceChannelRemediationCommand struct { SchemaVersion string `json:"schema_version"` CommandID string `json:"command_id"` Action string `json:"action"` ClusterID string `json:"cluster_id"` ChannelID string `json:"channel_id"` ResourceID string `json:"resource_id,omitempty"` ServiceClass string `json:"service_class"` EntryNodeID string `json:"entry_node_id,omitempty"` ExitNodeID string `json:"exit_node_id,omitempty"` PrimaryRouteID string `json:"primary_route_id,omitempty"` ReplacementRouteID string `json:"replacement_route_id,omitempty"` ReplacementRouteStatus string `json:"replacement_route_status,omitempty"` PoolPolicyFingerprint string `json:"pool_policy_fingerprint,omitempty"` GuardStatus string `json:"guard_status,omitempty"` GuardReason string `json:"guard_reason,omitempty"` ExecutionStatus string `json:"execution_status,omitempty"` ExecutionReason string `json:"execution_reason,omitempty"` ExecutionGeneration string `json:"execution_generation,omitempty"` ExecutionObservedAt string `json:"execution_observed_at,omitempty"` Reason string `json:"reason,omitempty"` OperatorAction string `json:"operator_action,omitempty"` IssuedAt time.Time `json:"issued_at"` ExpiresAt time.Time `json:"expires_at"` } type FabricServiceChannelFeedbackReport struct { SchemaVersion string `json:"schema_version"` GeneratedAt time.Time `json:"generated_at"` FeedbackMaxAgeSeconds int `json:"feedback_max_age_seconds"` RecoveryPolicy *FabricServiceChannelRecoveryPolicy `json:"recovery_policy,omitempty"` MissingProvenanceCount int `json:"missing_provenance_count,omitempty"` StalePolicyCount int `json:"stale_policy_count,omitempty"` StaleGenerationCount int `json:"stale_generation_count,omitempty"` ObservationCount int `json:"observation_count"` FencedRouteCount int `json:"fenced_route_count"` DegradedRouteCount int `json:"degraded_route_count"` HealthyRouteCount int `json:"healthy_route_count"` RecoveredRouteCount int `json:"recovered_route_count,omitempty"` RecoveryHysteresisCount int `json:"recovery_hysteresis_count,omitempty"` RecoveryPromotedCount int `json:"recovery_promoted_count,omitempty"` RecoveryDemotedCount int `json:"recovery_demoted_count,omitempty"` Observations []FabricServiceChannelFeedbackObservation `json:"observations,omitempty"` } type FabricServiceChannelAdaptivePolicy struct { SchemaVersion string `json:"schema_version"` Fingerprint string `json:"fingerprint,omitempty"` MaxParallelWindow int `json:"max_parallel_window"` BulkPressureChannelThreshold int `json:"bulk_pressure_channel_threshold"` QueuePressureHighWatermark int `json:"queue_pressure_high_watermark"` QueuePressureMaxInFlight int `json:"queue_pressure_max_in_flight"` ClassWindows map[string]int `json:"class_windows"` Source string `json:"source"` UpdatedByUserID *string `json:"updated_by_user_id,omitempty"` UpdatedAt string `json:"updated_at,omitempty"` ControlPlaneOnly bool `json:"control_plane_only"` ProductionForwarding bool `json:"production_forwarding"` } type FabricServiceChannelRecoveryPolicy struct { SchemaVersion string `json:"schema_version"` Fingerprint string `json:"fingerprint,omitempty"` HysteresisPenalty int `json:"hysteresis_penalty"` PromotionMinSamples int `json:"promotion_min_samples"` DemotionFailureThreshold int `json:"demotion_failure_threshold"` DemotionDropThreshold int `json:"demotion_drop_threshold"` DemotionSlowThreshold int `json:"demotion_slow_threshold"` DemotionRebuildEnabled bool `json:"demotion_rebuild_enabled"` DemotionFencedEnabled bool `json:"demotion_fenced_enabled"` Source string `json:"source"` UpdatedByUserID *string `json:"updated_by_user_id,omitempty"` UpdatedAt time.Time `json:"updated_at,omitempty"` ControlPlaneOnly bool `json:"control_plane_only"` ProductionForwarding bool `json:"production_forwarding"` } type FabricServiceChannelFeedbackObservation struct { ID string `json:"id,omitempty"` ClusterID string `json:"cluster_id"` ReporterNodeID string `json:"reporter_node_id"` RouteID string `json:"route_id"` ServiceClass string `json:"service_class"` FeedbackStatus string `json:"feedback_status"` ScoreAdjustment int `json:"score_adjustment"` EffectiveScoreAdjustment int `json:"effective_score_adjustment,omitempty"` Reasons []string `json:"reasons,omitempty"` LastError string `json:"last_error,omitempty"` ConsecutiveFailures int `json:"consecutive_failures,omitempty"` StallCount int `json:"stall_count,omitempty"` LastSendDurationMs int64 `json:"last_send_duration_ms,omitempty"` RecoveryState string `json:"recovery_state,omitempty"` ObservedPolicyFingerprint string `json:"observed_policy_fingerprint,omitempty"` EffectivePolicyFingerprint string `json:"effective_policy_fingerprint,omitempty"` ObservedRouteGeneration string `json:"observed_route_generation,omitempty"` EffectiveRouteGeneration string `json:"effective_route_generation,omitempty"` ProvenanceMissing bool `json:"provenance_missing,omitempty"` StalePolicy bool `json:"stale_policy,omitempty"` StaleGeneration bool `json:"stale_generation,omitempty"` StaleReason string `json:"stale_reason,omitempty"` Payload json.RawMessage `json:"payload"` ObservedAt time.Time `json:"observed_at"` ExpiresAt time.Time `json:"expires_at"` } type MeshListenerConfig struct { SchemaVersion string `json:"schema_version"` Source string `json:"source"` DesiredState string `json:"desired_state"` ListenAddr string `json:"listen_addr"` ListenPortMode string `json:"listen_port_mode"` AutoPortStart int `json:"auto_port_start,omitempty"` AutoPortEnd int `json:"auto_port_end,omitempty"` AdvertiseEndpoint string `json:"advertise_endpoint,omitempty"` AdvertiseTransport string `json:"advertise_transport,omitempty"` ConnectivityMode string `json:"connectivity_mode,omitempty"` NATType string `json:"nat_type,omitempty"` Region string `json:"region,omitempty"` ConfigVersion string `json:"config_version,omitempty"` UpdatedByUserID string `json:"updated_by_user_id,omitempty"` UpdatedAt string `json:"updated_at,omitempty"` ControlPlaneOnly bool `json:"control_plane_only"` 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"` ReplacementRouteID string `json:"replacement_route_id,omitempty"` RebuildRequestID string `json:"rebuild_request_id,omitempty"` RebuildStatus string `json:"rebuild_status,omitempty"` RebuildReason string `json:"rebuild_reason,omitempty"` RebuildAttempt int `json:"rebuild_attempt,omitempty"` 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"` RecoveryPolicy *FabricServiceChannelRecoveryPolicy `json:"recovery_policy,omitempty"` DecisionCount int `json:"decision_count"` ReplacementDecisionCount int `json:"replacement_decision_count"` DegradedDecisionCount int `json:"degraded_decision_count"` RebuildRequestCount int `json:"rebuild_request_count,omitempty"` RebuildAppliedCount int `json:"rebuild_applied_count,omitempty"` RecoveryHysteresisCount int `json:"recovery_hysteresis_count,omitempty"` RecoveryPromotedCount int `json:"recovery_promoted_count,omitempty"` RecoveryDemotedCount int `json:"recovery_demoted_count,omitempty"` 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) NodeUpdatePlan(ctx context.Context, clusterID, nodeID string, request NodeUpdatePlanRequest) (NodeUpdatePlan, error) { values := url.Values{} values.Set("product", request.Product) values.Set("current_version", request.CurrentVersion) values.Set("os", request.OS) values.Set("arch", request.Arch) values.Set("install_type", request.InstallType) if request.Channel != "" { values.Set("channel", request.Channel) } var response NodeUpdatePlanResponse path := fmt.Sprintf("/clusters/%s/nodes/%s/updates/plan?%s", clusterID, nodeID, values.Encode()) if err := c.getJSON(ctx, path, &response); err != nil { return NodeUpdatePlan{}, err } return response.Plan, nil } func (c *Client) ReportNodeUpdateStatus(ctx context.Context, clusterID, nodeID string, request NodeUpdateStatusRequest) error { path := fmt.Sprintf("/clusters/%s/nodes/%s/updates/status", clusterID, nodeID) return c.postJSON(ctx, path, request, 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) NodeVPNAssignments(ctx context.Context, clusterID, nodeID string) ([]NodeVPNAssignment, error) { var response struct { Assignments []NodeVPNAssignment `json:"vpn_assignments"` } path := fmt.Sprintf("/clusters/%s/nodes/%s/vpn/assignments", clusterID, nodeID) if err := c.getJSON(ctx, path, &response); err != nil { return nil, err } return response.Assignments, nil } func (c *Client) ReportNodeVPNAssignmentStatus(ctx context.Context, clusterID, nodeID, vpnConnectionID string, request NodeVPNAssignmentStatusRequest) error { path := fmt.Sprintf("/clusters/%s/nodes/%s/vpn/assignments/%s/status", clusterID, nodeID, vpnConnectionID) return c.postJSON(ctx, path, request, nil) } func (c *Client) AcquireNodeVPNAssignmentLease(ctx context.Context, clusterID, nodeID, vpnConnectionID string, request NodeVPNAssignmentLeaseAcquireRequest) (*NodeVPNAssignmentLease, error) { var response struct { Lease NodeVPNAssignmentLease `json:"lease"` } path := fmt.Sprintf("/clusters/%s/nodes/%s/vpn/assignments/%s/lease/acquire", clusterID, nodeID, vpnConnectionID) if err := c.postJSON(ctx, path, request, &response); err != nil { return nil, err } return &response.Lease, nil } func (c *Client) RenewNodeVPNAssignmentLease(ctx context.Context, clusterID, nodeID, vpnConnectionID, leaseID string, request NodeVPNAssignmentLeaseRenewRequest) error { path := fmt.Sprintf("/clusters/%s/nodes/%s/vpn/assignments/%s/lease/%s/renew", clusterID, nodeID, vpnConnectionID, leaseID) return c.postJSON(ctx, path, request, nil) } func (c *Client) SendVPNGatewayPacket(ctx context.Context, clusterID, vpnConnectionID string, packet []byte) error { if len(packet) == 0 { return nil } path := fmt.Sprintf("/clusters/%s/vpn-connections/%s/tunnel/gateway/packets", clusterID, vpnConnectionID) return c.postBytes(ctx, path, packet) } func (c *Client) SendVPNGatewayPacketBatch(ctx context.Context, clusterID, vpnConnectionID string, packets [][]byte) error { packets = cleanVPNPacketBatch(packets) if len(packets) == 0 { return nil } path := fmt.Sprintf("/clusters/%s/vpn-connections/%s/tunnel/gateway/packets?batch=true", clusterID, vpnConnectionID) return c.postBytes(ctx, path, encodeVPNPacketBatch(packets)) } func (c *Client) ReceiveVPNGatewayPacket(ctx context.Context, clusterID, vpnConnectionID string, timeout time.Duration) ([]byte, bool, error) { path := fmt.Sprintf("/clusters/%s/vpn-connections/%s/tunnel/gateway/packets?timeout_ms=%d", clusterID, vpnConnectionID, timeout.Milliseconds()) return c.getBytes(ctx, path) } func (c *Client) ReceiveVPNGatewayPacketBatch(ctx context.Context, clusterID, vpnConnectionID string, timeout time.Duration) ([][]byte, error) { path := fmt.Sprintf("/clusters/%s/vpn-connections/%s/tunnel/gateway/packets?batch=true&timeout_ms=%d", clusterID, vpnConnectionID, timeout.Milliseconds()) payload, ok, err := c.getBytes(ctx, path) if err != nil || !ok { return nil, err } return decodeVPNPacketBatch(payload) } 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) getBytes(ctx context.Context, path string) ([]byte, bool, error) { httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+path, nil) if err != nil { return nil, false, err } httpResp, err := c.httpClient.Do(httpReq) if err != nil { return nil, false, err } defer httpResp.Body.Close() if httpResp.StatusCode == http.StatusNoContent { return nil, false, nil } if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 { return nil, false, fmt.Errorf("backend returned status %d", httpResp.StatusCode) } payload, err := io.ReadAll(io.LimitReader(httpResp.Body, vpnPacketBatchMaxBytes)) if err != nil { return nil, false, err } if len(payload) == 0 { return nil, false, nil } return payload, true, nil } func (c *Client) postBytes(ctx context.Context, path string, payload []byte) error { httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+path, bytes.NewReader(payload)) if err != nil { return err } httpReq.Header.Set("Content-Type", "application/octet-stream") 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) } return nil } 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) } const ( vpnPacketMaxBytes = 65535 vpnPacketBatchMaxBytes = 4 * 1024 * 1024 ) func encodeVPNPacketBatch(packets [][]byte) []byte { packets = cleanVPNPacketBatch(packets) total := 0 for _, packet := range packets { total += 4 + len(packet) } out := make([]byte, total) offset := 0 for _, packet := range packets { binary.BigEndian.PutUint32(out[offset:offset+4], uint32(len(packet))) offset += 4 copy(out[offset:offset+len(packet)], packet) offset += len(packet) } return out } func decodeVPNPacketBatch(payload []byte) ([][]byte, error) { var packets [][]byte for offset := 0; offset < len(payload); { if offset+4 > len(payload) { return nil, fmt.Errorf("truncated vpn packet batch header") } size := int(binary.BigEndian.Uint32(payload[offset : offset+4])) offset += 4 if size <= 0 || size > vpnPacketMaxBytes { return nil, fmt.Errorf("invalid vpn packet batch item size") } if offset+size > len(payload) { return nil, fmt.Errorf("truncated vpn packet batch item") } packets = append(packets, append([]byte(nil), payload[offset:offset+size]...)) offset += size } return cleanVPNPacketBatch(packets), nil } func cleanVPNPacketBatch(packets [][]byte) [][]byte { if len(packets) == 0 { return nil } cleaned := make([][]byte, 0, len(packets)) for _, packet := range packets { if len(packet) == 0 { continue } cleaned = append(cleaned, append([]byte(nil), packet...)) } return cleaned }