рабочий вариант, но скороть 10 МБит
This commit is contained in:
@@ -53,10 +53,8 @@ const (
|
||||
FabricServiceClassRemoteWorkspace = "remote_workspace"
|
||||
FabricServiceClassFileTransfer = "file_transfer"
|
||||
FabricServiceClassVideo = "video"
|
||||
FabricServiceClassPlatformAdmin = "platform_admin"
|
||||
FabricServiceClassClusterAdmin = "cluster_admin"
|
||||
FabricServiceClassOrganization = "organization_portal"
|
||||
FabricServiceClassUserPortal = "user_portal"
|
||||
FabricServiceClassAdminIngress = "admin-ingress"
|
||||
FabricServiceClassPublicIngress = "public-ingress"
|
||||
|
||||
FabricChannelControl = "control"
|
||||
FabricChannelInteractive = "interactive"
|
||||
@@ -66,27 +64,21 @@ const (
|
||||
)
|
||||
|
||||
var allowedNodeRoles = map[string]struct{}{
|
||||
"public-ingress": {},
|
||||
"admin-ingress": {},
|
||||
"global-admin-runtime": {},
|
||||
"cluster-admin-runtime": {},
|
||||
"organization-portal-runtime": {},
|
||||
"user-portal-runtime": {},
|
||||
"identity-runtime": {},
|
||||
"policy-authority": {},
|
||||
"audit-sink": {},
|
||||
"entry-node": {},
|
||||
"relay-node": {},
|
||||
"core-mesh": {},
|
||||
"rdp-worker": {},
|
||||
"vnc-worker": {},
|
||||
"vpn-exit": {},
|
||||
"vpn-connector": {},
|
||||
"vpn-client": {},
|
||||
"ipv4-egress": {},
|
||||
"file-storage-cache": {},
|
||||
"update-cache": {},
|
||||
"video-relay": {},
|
||||
"public-ingress": {},
|
||||
"admin-ingress": {},
|
||||
"entry-node": {},
|
||||
"relay-node": {},
|
||||
"core-mesh": {},
|
||||
"rdp-worker": {},
|
||||
"vnc-worker": {},
|
||||
"vpn-exit": {},
|
||||
"vpn-connector": {},
|
||||
"vpn-client": {},
|
||||
"ipv4-ingress": {},
|
||||
"ipv4-egress": {},
|
||||
"file-storage-cache": {},
|
||||
"update-cache": {},
|
||||
"video-relay": {},
|
||||
}
|
||||
|
||||
type Cluster struct {
|
||||
@@ -163,8 +155,7 @@ type DockerInstallProfileRequest struct {
|
||||
type DockerInstallProfile struct {
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
ClusterID string `json:"cluster_id"`
|
||||
BackendURL string `json:"backend_url"`
|
||||
ControlPlaneEndpoints []string `json:"control_plane_endpoints,omitempty"`
|
||||
ClusterAuthorityPublicKey string `json:"cluster_authority_public_key,omitempty"`
|
||||
ArtifactEndpoints []string `json:"artifact_endpoints,omitempty"`
|
||||
FabricRegistryRecords json.RawMessage `json:"fabric_registry_records,omitempty"`
|
||||
DockerImageArtifact *DockerArtifact `json:"docker_image_artifact,omitempty"`
|
||||
@@ -179,12 +170,12 @@ type DockerInstallProfile struct {
|
||||
Replace bool `json:"replace"`
|
||||
DockerVPNGatewayEnabled bool `json:"docker_vpn_gateway_enabled"`
|
||||
WorkloadSupervisionEnabled bool `json:"workload_supervision_enabled"`
|
||||
MeshSyntheticRuntimeEnabled bool `json:"mesh_synthetic_runtime_enabled"`
|
||||
FabricRuntimeEnabled bool `json:"fabric_runtime_enabled"`
|
||||
MeshProductionForwardingEnabled bool `json:"mesh_production_forwarding_enabled"`
|
||||
MeshListenAddr string `json:"mesh_listen_addr,omitempty"`
|
||||
MeshListenPortMode string `json:"mesh_listen_port_mode,omitempty"`
|
||||
MeshListenAutoPortStart int `json:"mesh_listen_auto_port_start,omitempty"`
|
||||
MeshListenAutoPortEnd int `json:"mesh_listen_auto_port_end,omitempty"`
|
||||
FabricListenAddr string `json:"fabric_listen_addr,omitempty"`
|
||||
FabricListenPortMode string `json:"fabric_listen_port_mode,omitempty"`
|
||||
FabricListenAutoPortStart int `json:"fabric_listen_auto_port_start,omitempty"`
|
||||
FabricListenAutoPortEnd int `json:"fabric_listen_auto_port_end,omitempty"`
|
||||
MeshAdvertiseEndpoint string `json:"mesh_advertise_endpoint,omitempty"`
|
||||
MeshAdvertiseEndpointsJSON json.RawMessage `json:"mesh_advertise_endpoints_json,omitempty"`
|
||||
MeshAdvertiseTransport string `json:"mesh_advertise_transport,omitempty"`
|
||||
@@ -201,8 +192,7 @@ type DockerInstallProfile struct {
|
||||
type WindowsInstallProfile struct {
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
ClusterID string `json:"cluster_id"`
|
||||
BackendURL string `json:"backend_url"`
|
||||
ControlPlaneEndpoints []string `json:"control_plane_endpoints,omitempty"`
|
||||
ClusterAuthorityPublicKey string `json:"cluster_authority_public_key,omitempty"`
|
||||
ArtifactEndpoints []string `json:"artifact_endpoints,omitempty"`
|
||||
FabricRegistryRecords json.RawMessage `json:"fabric_registry_records,omitempty"`
|
||||
NodeAgentArtifact *DockerArtifact `json:"node_agent_artifact,omitempty"`
|
||||
@@ -212,12 +202,12 @@ type WindowsInstallProfile struct {
|
||||
InstallDir string `json:"install_dir"`
|
||||
StartupMode string `json:"startup_mode"`
|
||||
WorkloadSupervisionEnabled bool `json:"workload_supervision_enabled"`
|
||||
MeshSyntheticRuntimeEnabled bool `json:"mesh_synthetic_runtime_enabled"`
|
||||
FabricRuntimeEnabled bool `json:"fabric_runtime_enabled"`
|
||||
MeshProductionForwardingEnabled bool `json:"mesh_production_forwarding_enabled"`
|
||||
MeshListenAddr string `json:"mesh_listen_addr,omitempty"`
|
||||
MeshListenPortMode string `json:"mesh_listen_port_mode,omitempty"`
|
||||
MeshListenAutoPortStart int `json:"mesh_listen_auto_port_start,omitempty"`
|
||||
MeshListenAutoPortEnd int `json:"mesh_listen_auto_port_end,omitempty"`
|
||||
FabricListenAddr string `json:"fabric_listen_addr,omitempty"`
|
||||
FabricListenPortMode string `json:"fabric_listen_port_mode,omitempty"`
|
||||
FabricListenAutoPortStart int `json:"fabric_listen_auto_port_start,omitempty"`
|
||||
FabricListenAutoPortEnd int `json:"fabric_listen_auto_port_end,omitempty"`
|
||||
MeshAdvertiseEndpoint string `json:"mesh_advertise_endpoint,omitempty"`
|
||||
MeshAdvertiseEndpointsJSON json.RawMessage `json:"mesh_advertise_endpoints_json,omitempty"`
|
||||
MeshAdvertiseTransport string `json:"mesh_advertise_transport,omitempty"`
|
||||
@@ -234,8 +224,7 @@ type WindowsInstallProfile struct {
|
||||
type LinuxInstallProfile struct {
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
ClusterID string `json:"cluster_id"`
|
||||
BackendURL string `json:"backend_url"`
|
||||
ControlPlaneEndpoints []string `json:"control_plane_endpoints,omitempty"`
|
||||
ClusterAuthorityPublicKey string `json:"cluster_authority_public_key,omitempty"`
|
||||
ArtifactEndpoints []string `json:"artifact_endpoints,omitempty"`
|
||||
FabricRegistryRecords json.RawMessage `json:"fabric_registry_records,omitempty"`
|
||||
NodeAgentArtifact *DockerArtifact `json:"node_agent_artifact,omitempty"`
|
||||
@@ -245,12 +234,12 @@ type LinuxInstallProfile struct {
|
||||
InstallDir string `json:"install_dir"`
|
||||
StartupMode string `json:"startup_mode"`
|
||||
WorkloadSupervisionEnabled bool `json:"workload_supervision_enabled"`
|
||||
MeshSyntheticRuntimeEnabled bool `json:"mesh_synthetic_runtime_enabled"`
|
||||
FabricRuntimeEnabled bool `json:"fabric_runtime_enabled"`
|
||||
MeshProductionForwardingEnabled bool `json:"mesh_production_forwarding_enabled"`
|
||||
MeshListenAddr string `json:"mesh_listen_addr,omitempty"`
|
||||
MeshListenPortMode string `json:"mesh_listen_port_mode,omitempty"`
|
||||
MeshListenAutoPortStart int `json:"mesh_listen_auto_port_start,omitempty"`
|
||||
MeshListenAutoPortEnd int `json:"mesh_listen_auto_port_end,omitempty"`
|
||||
FabricListenAddr string `json:"fabric_listen_addr,omitempty"`
|
||||
FabricListenPortMode string `json:"fabric_listen_port_mode,omitempty"`
|
||||
FabricListenAutoPortStart int `json:"fabric_listen_auto_port_start,omitempty"`
|
||||
FabricListenAutoPortEnd int `json:"fabric_listen_auto_port_end,omitempty"`
|
||||
MeshAdvertiseEndpoint string `json:"mesh_advertise_endpoint,omitempty"`
|
||||
MeshAdvertiseEndpointsJSON json.RawMessage `json:"mesh_advertise_endpoints_json,omitempty"`
|
||||
MeshAdvertiseTransport string `json:"mesh_advertise_transport,omitempty"`
|
||||
@@ -264,6 +253,19 @@ type LinuxInstallProfile struct {
|
||||
Roles []string `json:"roles,omitempty"`
|
||||
}
|
||||
|
||||
type InstallJoinBundle struct {
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
BundleKind string `json:"bundle_kind"`
|
||||
ClusterID string `json:"cluster_id"`
|
||||
ClusterAuthority *ClusterAuthorityDescriptor `json:"cluster_authority,omitempty"`
|
||||
AuthorityPayload json.RawMessage `json:"authority_payload,omitempty"`
|
||||
AuthoritySignature *ClusterSignature `json:"authority_signature,omitempty"`
|
||||
IssuedAt time.Time `json:"issued_at"`
|
||||
DockerInstallProfile *DockerInstallProfile `json:"docker_install_profile,omitempty"`
|
||||
WindowsInstallProfile *WindowsInstallProfile `json:"windows_install_profile,omitempty"`
|
||||
LinuxInstallProfile *LinuxInstallProfile `json:"linux_install_profile,omitempty"`
|
||||
}
|
||||
|
||||
type DockerArtifact struct {
|
||||
Kind string `json:"kind"`
|
||||
Image string `json:"image,omitempty"`
|
||||
@@ -324,15 +326,19 @@ type NodeUpdatePolicy struct {
|
||||
}
|
||||
|
||||
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"`
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
Generation string `json:"generation,omitempty"`
|
||||
CheckNow bool `json:"check_now"`
|
||||
Products []string `json:"products,omitempty"`
|
||||
TargetVersions map[string]string `json:"target_versions,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
DeliveryMode string `json:"delivery_mode,omitempty"`
|
||||
SubscriptionStatus string `json:"subscription_status,omitempty"`
|
||||
UpdateService *NodeUpdateServiceAssignment `json:"update_service,omitempty"`
|
||||
UpdateServiceCandidates []NodeUpdateServiceAssignment `json:"update_service_candidates,omitempty"`
|
||||
RescuePollSeconds int `json:"rescue_poll_seconds,omitempty"`
|
||||
AuthorityPayload json.RawMessage `json:"authority_payload,omitempty"`
|
||||
AuthoritySignature *ClusterSignature `json:"authority_signature,omitempty"`
|
||||
}
|
||||
|
||||
type NodeUpdateServiceAssignment struct {
|
||||
@@ -356,23 +362,75 @@ type NodeUpdateServiceCandidate struct {
|
||||
}
|
||||
|
||||
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"`
|
||||
AuthorityQuorum *QuorumEnvelope `json:"authority_quorum,omitempty"`
|
||||
ProductionForwarding bool `json:"production_forwarding"`
|
||||
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"`
|
||||
FabricRegistryRecords json.RawMessage `json:"fabric_registry_records,omitempty"`
|
||||
UpdateIntent *NodeUpdateIntent `json:"update_intent,omitempty"`
|
||||
RolloutLease *NodeUpdateLease `json:"rollout_lease,omitempty"`
|
||||
Artifact *ReleaseArtifact `json:"artifact,omitempty"`
|
||||
AuthorityPayload json.RawMessage `json:"authority_payload,omitempty"`
|
||||
AuthoritySignature *ClusterSignature `json:"authority_signature,omitempty"`
|
||||
AuthorityQuorum *QuorumEnvelope `json:"authority_quorum,omitempty"`
|
||||
ProductionForwarding bool `json:"production_forwarding"`
|
||||
}
|
||||
|
||||
type NodeUpdateArtifactContent struct {
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
ArtifactID string `json:"artifact_id"`
|
||||
Product string `json:"product"`
|
||||
Version string `json:"version"`
|
||||
DataBase64 string `json:"data_base64"`
|
||||
SHA256 string `json:"sha256,omitempty"`
|
||||
ChunkSHA256 string `json:"chunk_sha256,omitempty"`
|
||||
SizeBytes int64 `json:"size_bytes,omitempty"`
|
||||
Offset int64 `json:"offset,omitempty"`
|
||||
ChunkSize int64 `json:"chunk_size,omitempty"`
|
||||
Complete bool `json:"complete,omitempty"`
|
||||
DistributorID string `json:"distributor_id,omitempty"`
|
||||
}
|
||||
|
||||
type NodeUpdateIntent struct {
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
IntentID string `json:"intent_id"`
|
||||
ClusterID string `json:"cluster_id"`
|
||||
NodeID string `json:"node_id,omitempty"`
|
||||
Product string `json:"product"`
|
||||
TargetVersion string `json:"target_version"`
|
||||
Strategy string `json:"strategy"`
|
||||
Generation string `json:"generation"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
RollbackAllowed bool `json:"rollback_allowed"`
|
||||
HealthWindowSec int `json:"health_window_seconds,omitempty"`
|
||||
RequiredLease bool `json:"required_lease"`
|
||||
AllowedMirrors []string `json:"allowed_mirrors,omitempty"`
|
||||
}
|
||||
|
||||
type NodeUpdateLease struct {
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
LeaseID string `json:"lease_id"`
|
||||
IntentID string `json:"intent_id"`
|
||||
ClusterID string `json:"cluster_id"`
|
||||
NodeID string `json:"node_id"`
|
||||
Product string `json:"product"`
|
||||
TargetVersion string `json:"target_version"`
|
||||
Strategy string `json:"strategy"`
|
||||
Status string `json:"status"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
MaxParallel int `json:"max_parallel"`
|
||||
ActiveUpdateCnt int `json:"active_update_count"`
|
||||
AcquiredAt time.Time `json:"acquired_at"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
}
|
||||
|
||||
type NodeBridgeReplayProductPlan struct {
|
||||
@@ -414,52 +472,87 @@ type NodeUpdateStatus struct {
|
||||
}
|
||||
|
||||
type StaleNodeRiskReport struct {
|
||||
ClusterID string `json:"cluster_id"`
|
||||
GeneratedAt time.Time `json:"generated_at"`
|
||||
HeartbeatStaleAfterSeconds int `json:"heartbeat_stale_after_seconds"`
|
||||
LegacyRemovalAllowed bool `json:"legacy_removal_allowed"`
|
||||
BridgeHoldRequired bool `json:"bridge_hold_required"`
|
||||
BridgeHoldNodeIDs []string `json:"bridge_hold_node_ids,omitempty"`
|
||||
BridgeHoldReasons []string `json:"bridge_hold_reasons,omitempty"`
|
||||
BlockedOperations []string `json:"blocked_operations,omitempty"`
|
||||
Nodes []StaleNodeRiskNode `json:"nodes"`
|
||||
Summary StaleNodeRiskSummary `json:"summary"`
|
||||
ClusterID string `json:"cluster_id"`
|
||||
GeneratedAt time.Time `json:"generated_at"`
|
||||
HeartbeatStaleAfterSeconds int `json:"heartbeat_stale_after_seconds"`
|
||||
FabricStandardCleanupAllowed bool `json:"fabric_standard_cleanup_allowed"`
|
||||
BridgeHoldRequired bool `json:"bridge_hold_required"`
|
||||
BridgeHoldNodeIDs []string `json:"bridge_hold_node_ids,omitempty"`
|
||||
BridgeHoldReasons []string `json:"bridge_hold_reasons,omitempty"`
|
||||
BlockedOperations []string `json:"blocked_operations,omitempty"`
|
||||
Nodes []StaleNodeRiskNode `json:"nodes"`
|
||||
Summary StaleNodeRiskSummary `json:"summary"`
|
||||
}
|
||||
|
||||
type StaleNodeRiskSummary struct {
|
||||
TotalNodes int `json:"total_nodes"`
|
||||
StaleNodes int `json:"stale_nodes"`
|
||||
BlockedNodes int `json:"blocked_nodes"`
|
||||
DirectPeerAlertNodes int `json:"direct_peer_alert_nodes"`
|
||||
ArtifactGapNodes int `json:"artifact_gap_nodes"`
|
||||
UnknownProfileNodes int `json:"unknown_profile_nodes"`
|
||||
WaitingUpdateStatusNodes int `json:"waiting_update_status_nodes"`
|
||||
UnknownVersionNodes int `json:"unknown_version_nodes"`
|
||||
LegacyRecoveryContractNodes int `json:"legacy_recovery_contract_nodes"`
|
||||
RecoveryBridgeRequiredNodes int `json:"recovery_bridge_required_nodes"`
|
||||
RecoveryBridgeReplayReadyNodes int `json:"recovery_bridge_replay_ready_nodes"`
|
||||
WaitingRecoveryHeartbeatNodes int `json:"waiting_recovery_heartbeat_nodes"`
|
||||
TotalNodes int `json:"total_nodes"`
|
||||
StaleNodes int `json:"stale_nodes"`
|
||||
BlockedNodes int `json:"blocked_nodes"`
|
||||
DirectPeerAlertNodes int `json:"direct_peer_alert_nodes"`
|
||||
AreaDiversityAlertNodes int `json:"area_diversity_alert_nodes"`
|
||||
IndependentIngressAlertNodes int `json:"independent_ingress_alert_nodes"`
|
||||
DirectoryDisseminationAlertNodes int `json:"directory_dissemination_alert_nodes"`
|
||||
UpdaterSubscriptionAlertNodes int `json:"updater_subscription_alert_nodes"`
|
||||
UpdaterWakeUnsupportedNodes int `json:"updater_wake_unsupported_nodes"`
|
||||
UpdaterRuntimeMissingNodes int `json:"updater_runtime_missing_nodes"`
|
||||
StandardUpdaterLineNodes int `json:"standard_updater_line_nodes"`
|
||||
StagedSelfUpdatePendingNodes int `json:"staged_self_update_pending_nodes"`
|
||||
PostUpdateHeartbeatGapNodes int `json:"post_update_heartbeat_gap_nodes"`
|
||||
ArtifactGapNodes int `json:"artifact_gap_nodes"`
|
||||
StandardControlDependencyNodes int `json:"standard_control_dependency_nodes"`
|
||||
RegistryCandidateOnlyNodes int `json:"registry_candidate_only_nodes"`
|
||||
RegistryJoinContractMissingNodes int `json:"registry_join_missing_nodes"`
|
||||
UnknownProfileNodes int `json:"unknown_profile_nodes"`
|
||||
WaitingUpdateStatusNodes int `json:"waiting_update_status_nodes"`
|
||||
UnknownVersionNodes int `json:"unknown_version_nodes"`
|
||||
StandardRecoveryContractNodes int `json:"standard_recovery_contract_nodes"`
|
||||
RecoveryBridgeRequiredNodes int `json:"recovery_bridge_required_nodes"`
|
||||
RecoveryBridgeReplayReadyNodes int `json:"recovery_bridge_replay_ready_nodes"`
|
||||
WaitingRecoveryHeartbeatNodes int `json:"waiting_recovery_heartbeat_nodes"`
|
||||
}
|
||||
|
||||
type StaleNodeRiskNode struct {
|
||||
NodeID string `json:"node_id"`
|
||||
Name string `json:"name"`
|
||||
RegistrationStatus string `json:"registration_status"`
|
||||
HealthStatus string `json:"health_status"`
|
||||
ReportedVersion *string `json:"reported_version,omitempty"`
|
||||
LastSeenAt *time.Time `json:"last_seen_at,omitempty"`
|
||||
HeartbeatStale bool `json:"heartbeat_stale"`
|
||||
Blocked bool `json:"blocked"`
|
||||
DirectPeerAlert bool `json:"direct_peer_alert"`
|
||||
DirectPeerReadyCount int `json:"direct_peer_ready_count,omitempty"`
|
||||
DirectPeerTargetCount int `json:"direct_peer_target_count,omitempty"`
|
||||
DirectPeerDeficit int `json:"direct_peer_deficit,omitempty"`
|
||||
Alerts []string `json:"alerts,omitempty"`
|
||||
RecoveryBridgeRequired bool `json:"recovery_bridge_required"`
|
||||
RecoveryBridgeReplayReady bool `json:"recovery_bridge_replay_ready"`
|
||||
RecoveryBridgeActions []string `json:"recovery_bridge_actions,omitempty"`
|
||||
Risks []string `json:"risks,omitempty"`
|
||||
Products []StaleNodeRiskProduct `json:"products,omitempty"`
|
||||
NodeID string `json:"node_id"`
|
||||
Name string `json:"name"`
|
||||
Area string `json:"area,omitempty"`
|
||||
RegistrationStatus string `json:"registration_status"`
|
||||
HealthStatus string `json:"health_status"`
|
||||
ReportedVersion *string `json:"reported_version,omitempty"`
|
||||
LastSeenAt *time.Time `json:"last_seen_at,omitempty"`
|
||||
HeartbeatStale bool `json:"heartbeat_stale"`
|
||||
Blocked bool `json:"blocked"`
|
||||
DirectPeerAlert bool `json:"direct_peer_alert"`
|
||||
DirectPeerReadyCount int `json:"direct_peer_ready_count,omitempty"`
|
||||
DirectPeerTargetCount int `json:"direct_peer_target_count,omitempty"`
|
||||
DirectPeerDeficit int `json:"direct_peer_deficit,omitempty"`
|
||||
DirectReadyAreas []string `json:"direct_ready_areas,omitempty"`
|
||||
ExternalAreaReadyCount int `json:"external_area_ready_count,omitempty"`
|
||||
RequiredExternalAreaCount int `json:"required_external_area_count,omitempty"`
|
||||
AreaDiversityAlert bool `json:"area_diversity_alert"`
|
||||
RequiredIndependentIngressCount int `json:"required_independent_ingress_count,omitempty"`
|
||||
IndependentIngressAlert bool `json:"independent_ingress_alert"`
|
||||
FullDirectoryExpected bool `json:"full_directory_expected"`
|
||||
KnownPeerDirectoryCount int `json:"known_peer_directory_count,omitempty"`
|
||||
ExpectedDirectoryCount int `json:"expected_directory_count,omitempty"`
|
||||
DirectoryDisseminationAlert bool `json:"directory_dissemination_alert"`
|
||||
UpdaterSubscriptionAlert bool `json:"updater_subscription_alert"`
|
||||
UpdaterWakeUnsupported bool `json:"updater_wake_unsupported"`
|
||||
UpdaterRuntimeMissing bool `json:"updater_runtime_missing"`
|
||||
StandardUpdaterLine bool `json:"standard_updater_line"`
|
||||
StagedSelfUpdatePending bool `json:"staged_self_update_pending"`
|
||||
PostUpdateHeartbeatGap bool `json:"post_update_heartbeat_gap"`
|
||||
StandardControlDependency bool `json:"standard_control_dependency"`
|
||||
StandardControlURL string `json:"standard_control_url,omitempty"`
|
||||
RegistryRuntimeStatus string `json:"registry_runtime_status,omitempty"`
|
||||
RegistryJoinContractMissing bool `json:"registry_join_missing"`
|
||||
ResolvedServiceCount int `json:"resolved_service_count,omitempty"`
|
||||
IndependentIngressCount int `json:"independent_ingress_count,omitempty"`
|
||||
Alerts []string `json:"alerts,omitempty"`
|
||||
RecoveryBridgeRequired bool `json:"recovery_bridge_required"`
|
||||
RecoveryBridgeReplayReady bool `json:"recovery_bridge_replay_ready"`
|
||||
RecoveryBridgeActions []string `json:"recovery_bridge_actions,omitempty"`
|
||||
Risks []string `json:"risks,omitempty"`
|
||||
Products []StaleNodeRiskProduct `json:"products,omitempty"`
|
||||
}
|
||||
|
||||
type StaleNodeRiskProduct struct {
|
||||
@@ -478,13 +571,15 @@ type StaleNodeRiskProduct struct {
|
||||
LastStatusPhase string `json:"last_status_phase,omitempty"`
|
||||
LastStatusValue string `json:"last_status_value,omitempty"`
|
||||
LastStatusReason string `json:"last_status_reason,omitempty"`
|
||||
StagedSelfUpdatePending bool `json:"staged_self_update_pending"`
|
||||
PostUpdateHeartbeatGap bool `json:"post_update_heartbeat_gap"`
|
||||
RecoveryBridgeRequired bool `json:"recovery_bridge_required"`
|
||||
RecoveryBridgeReplayReady bool `json:"recovery_bridge_replay_ready"`
|
||||
RecoveryBridgeMode string `json:"recovery_bridge_mode,omitempty"`
|
||||
Risks []string `json:"risks,omitempty"`
|
||||
}
|
||||
|
||||
type NodeBootstrap struct {
|
||||
type NodeJoinContract struct {
|
||||
NodeID string `json:"node_id"`
|
||||
ClusterID string `json:"cluster_id"`
|
||||
IdentityStatus string `json:"identity_status"`
|
||||
@@ -664,23 +759,23 @@ type FabricServiceChannelAdaptivePolicy struct {
|
||||
}
|
||||
|
||||
type FabricServiceChannelPoolPolicy struct {
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
Fingerprint string `json:"fingerprint,omitempty"`
|
||||
EntryPoolNodeIDs []string `json:"entry_pool_node_ids,omitempty"`
|
||||
ExitPoolNodeIDs []string `json:"exit_pool_node_ids,omitempty"`
|
||||
PreferredEntryNodeID string `json:"preferred_entry_node_id,omitempty"`
|
||||
PreferredExitNodeID string `json:"preferred_exit_node_id,omitempty"`
|
||||
SelectionStrategy string `json:"selection_strategy"`
|
||||
RouteRebuild string `json:"route_rebuild"`
|
||||
EntryFailover string `json:"entry_failover"`
|
||||
ExitFailover string `json:"exit_failover"`
|
||||
BackendFallbackAllowed bool `json:"backend_fallback_allowed"`
|
||||
StickySession bool `json:"sticky_session"`
|
||||
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"`
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
Fingerprint string `json:"fingerprint,omitempty"`
|
||||
EntryPoolNodeIDs []string `json:"entry_pool_node_ids,omitempty"`
|
||||
ExitPoolNodeIDs []string `json:"exit_pool_node_ids,omitempty"`
|
||||
PreferredEntryNodeID string `json:"preferred_entry_node_id,omitempty"`
|
||||
PreferredExitNodeID string `json:"preferred_exit_node_id,omitempty"`
|
||||
SelectionStrategy string `json:"selection_strategy"`
|
||||
RouteRebuild string `json:"route_rebuild"`
|
||||
EntryFailover string `json:"entry_failover"`
|
||||
ExitFailover string `json:"exit_failover"`
|
||||
CompatFallbackAllowed bool `json:"degraded_route_allowed"`
|
||||
StickySession bool `json:"sticky_session"`
|
||||
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 FabricServiceChannelBreadcrumbWindowPolicy struct {
|
||||
@@ -851,12 +946,12 @@ type NodeSyntheticMeshConfig struct {
|
||||
ServiceChannelFeedback *FabricServiceChannelRouteFeedbackReport `json:"service_channel_route_feedback,omitempty"`
|
||||
ServiceChannelAdaptivePolicy *FabricServiceChannelAdaptivePolicy `json:"service_channel_adaptive_policy,omitempty"`
|
||||
ServiceChannelRemediationCommands []FabricServiceChannelAccessRemediationCommand `json:"service_channel_remediation_commands,omitempty"`
|
||||
MeshListener *NodeMeshListenerConfig `json:"mesh_listener,omitempty"`
|
||||
FabricListener *NodeFabricListenerConfig `json:"fabric_listener,omitempty"`
|
||||
Routes []SyntheticMeshRouteConfig `json:"routes"`
|
||||
ProductionForwarding bool `json:"production_forwarding"`
|
||||
}
|
||||
|
||||
type NodeMeshListenerConfig struct {
|
||||
type NodeFabricListenerConfig struct {
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
Source string `json:"source"`
|
||||
DesiredState string `json:"desired_state"`
|
||||
@@ -1094,23 +1189,23 @@ type FabricServiceChannelLeaseRecord struct {
|
||||
}
|
||||
|
||||
type FabricServiceChannelLeaseSummary struct {
|
||||
ClusterID string `json:"cluster_id"`
|
||||
ChannelID string `json:"channel_id"`
|
||||
ResourceID string `json:"resource_id,omitempty"`
|
||||
ServiceClass string `json:"service_class"`
|
||||
Status string `json:"status"`
|
||||
SelectedEntryNodeID string `json:"selected_entry_node_id,omitempty"`
|
||||
SelectedExitNodeID string `json:"selected_exit_node_id,omitempty"`
|
||||
AllowedChannels []string `json:"allowed_channels,omitempty"`
|
||||
PrimaryRouteID string `json:"primary_route_id,omitempty"`
|
||||
PrimaryRouteStatus string `json:"primary_route_status,omitempty"`
|
||||
DataPlane FabricServiceChannelDataPlaneContract `json:"data_plane,omitempty"`
|
||||
ForceBackendFallback bool `json:"force_backend_fallback"`
|
||||
Expired bool `json:"expired"`
|
||||
IssuedAt time.Time `json:"issued_at"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ClusterID string `json:"cluster_id"`
|
||||
ChannelID string `json:"channel_id"`
|
||||
ResourceID string `json:"resource_id,omitempty"`
|
||||
ServiceClass string `json:"service_class"`
|
||||
Status string `json:"status"`
|
||||
SelectedEntryNodeID string `json:"selected_entry_node_id,omitempty"`
|
||||
SelectedExitNodeID string `json:"selected_exit_node_id,omitempty"`
|
||||
AllowedChannels []string `json:"allowed_channels,omitempty"`
|
||||
PrimaryRouteID string `json:"primary_route_id,omitempty"`
|
||||
PrimaryRouteStatus string `json:"primary_route_status,omitempty"`
|
||||
DataPlane FabricServiceChannelDataPlaneContract `json:"data_plane,omitempty"`
|
||||
ForceCompatFallback bool `json:"force_degraded_route"`
|
||||
Expired bool `json:"expired"`
|
||||
IssuedAt time.Time `json:"issued_at"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type FabricServiceChannelLeaseMaintenance struct {
|
||||
@@ -1139,15 +1234,15 @@ type FabricServiceChannelAccessTelemetry struct {
|
||||
TotalAccepted int `json:"total_accepted"`
|
||||
SignedAccepted int `json:"signed_accepted"`
|
||||
IntrospectionAccepted int `json:"introspection_accepted"`
|
||||
LegacyUnsignedAccepted int `json:"legacy_unsigned_accepted"`
|
||||
BackendFallbackCount int `json:"backend_fallback_count"`
|
||||
BackendFallbackBlockedCount int `json:"backend_fallback_blocked_count,omitempty"`
|
||||
CompatUnsignedAccepted int `json:"unsigned_accepted"`
|
||||
CompatFallbackCount int `json:"degraded_route_use_count"`
|
||||
CompatFallbackBlockedCount int `json:"degraded_route_blocked_count,omitempty"`
|
||||
FabricRouteSendFailureCount int `json:"fabric_route_send_failure_count,omitempty"`
|
||||
DataPlaneContractCount int `json:"data_plane_contract_count,omitempty"`
|
||||
LastDataPlaneMode string `json:"last_data_plane_mode,omitempty"`
|
||||
LastWorkingDataTransport string `json:"last_working_data_transport,omitempty"`
|
||||
LastSteadyStateTransport string `json:"last_steady_state_transport,omitempty"`
|
||||
LastBackendRelayPolicy string `json:"last_backend_relay_policy,omitempty"`
|
||||
LastCompatRelayPolicy string `json:"last_degraded_route_policy,omitempty"`
|
||||
LastLogicalFlowMode string `json:"last_logical_flow_mode,omitempty"`
|
||||
LastDataPlaneViolationStatus string `json:"last_data_plane_violation_status,omitempty"`
|
||||
LastDataPlaneViolationReason string `json:"last_data_plane_violation_reason,omitempty"`
|
||||
@@ -1184,15 +1279,15 @@ type FabricServiceChannelAccessTelemetryNode struct {
|
||||
TotalAccepted int `json:"total_accepted"`
|
||||
SignedAccepted int `json:"signed_accepted"`
|
||||
IntrospectionAccepted int `json:"introspection_accepted"`
|
||||
LegacyUnsignedAccepted int `json:"legacy_unsigned_accepted"`
|
||||
BackendFallbackCount int `json:"backend_fallback_count"`
|
||||
BackendFallbackBlockedCount int `json:"backend_fallback_blocked_count,omitempty"`
|
||||
CompatUnsignedAccepted int `json:"unsigned_accepted"`
|
||||
CompatFallbackCount int `json:"degraded_route_use_count"`
|
||||
CompatFallbackBlockedCount int `json:"degraded_route_blocked_count,omitempty"`
|
||||
FabricRouteSendFailureCount int `json:"fabric_route_send_failure_count,omitempty"`
|
||||
DataPlaneContractCount int `json:"data_plane_contract_count,omitempty"`
|
||||
LastDataPlaneMode string `json:"last_data_plane_mode,omitempty"`
|
||||
LastWorkingDataTransport string `json:"last_working_data_transport,omitempty"`
|
||||
LastSteadyStateTransport string `json:"last_steady_state_transport,omitempty"`
|
||||
LastBackendRelayPolicy string `json:"last_backend_relay_policy,omitempty"`
|
||||
LastCompatRelayPolicy string `json:"last_degraded_route_policy,omitempty"`
|
||||
LastLogicalFlowMode string `json:"last_logical_flow_mode,omitempty"`
|
||||
LastDataPlaneViolationStatus string `json:"last_data_plane_violation_status,omitempty"`
|
||||
LastDataPlaneViolationReason string `json:"last_data_plane_violation_reason,omitempty"`
|
||||
@@ -1219,17 +1314,17 @@ type FabricServiceChannelAccessTelemetryChannel struct {
|
||||
SelectedExitNodeID string `json:"selected_exit_node_id,omitempty"`
|
||||
PrimaryRouteID string `json:"primary_route_id,omitempty"`
|
||||
PrimaryRouteStatus string `json:"primary_route_status,omitempty"`
|
||||
ForceBackendFallback bool `json:"force_backend_fallback"`
|
||||
ForceCompatFallback bool `json:"force_degraded_route"`
|
||||
EntryNodeTotalAccepted int `json:"entry_node_total_accepted"`
|
||||
EntryNodeIntrospectionAccepted int `json:"entry_node_introspection_accepted"`
|
||||
EntryNodeBackendFallbackCount int `json:"entry_node_backend_fallback_count"`
|
||||
EntryNodeBackendFallbackBlockedCount int `json:"entry_node_backend_fallback_blocked_count,omitempty"`
|
||||
EntryNodeCompatFallbackCount int `json:"entry_node_degraded_route_count"`
|
||||
EntryNodeCompatFallbackBlockedCount int `json:"entry_node_degraded_route_blocked_count,omitempty"`
|
||||
EntryNodeFabricRouteSendFailureCount int `json:"entry_node_fabric_route_send_failure_count,omitempty"`
|
||||
EntryNodeDataPlaneContractCount int `json:"entry_node_data_plane_contract_count,omitempty"`
|
||||
EntryNodeLastDataPlaneMode string `json:"entry_node_last_data_plane_mode,omitempty"`
|
||||
EntryNodeLastWorkingDataTransport string `json:"entry_node_last_working_data_transport,omitempty"`
|
||||
EntryNodeLastSteadyStateTransport string `json:"entry_node_last_steady_state_transport,omitempty"`
|
||||
EntryNodeLastBackendRelayPolicy string `json:"entry_node_last_backend_relay_policy,omitempty"`
|
||||
EntryNodeLastCompatRelayPolicy string `json:"entry_node_last_degraded_route_policy,omitempty"`
|
||||
EntryNodeLastLogicalFlowMode string `json:"entry_node_last_logical_flow_mode,omitempty"`
|
||||
EntryNodeLastDataPlaneViolationStatus string `json:"entry_node_last_data_plane_violation_status,omitempty"`
|
||||
EntryNodeLastDataPlaneViolationReason string `json:"entry_node_last_data_plane_violation_reason,omitempty"`
|
||||
@@ -1305,26 +1400,26 @@ type FabricServiceChannelAccessRemediationCommand struct {
|
||||
}
|
||||
|
||||
type FabricServiceChannelLeaseIntrospection struct {
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
ClusterID string `json:"cluster_id"`
|
||||
ChannelID string `json:"channel_id"`
|
||||
ResourceID string `json:"resource_id,omitempty"`
|
||||
ServiceClass string `json:"service_class"`
|
||||
Allowed bool `json:"allowed"`
|
||||
Status string `json:"status"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
AcceptedBy string `json:"accepted_by"`
|
||||
SelectedEntryNodeID string `json:"selected_entry_node_id,omitempty"`
|
||||
SelectedExitNodeID string `json:"selected_exit_node_id,omitempty"`
|
||||
AllowedChannels []string `json:"allowed_channels,omitempty"`
|
||||
PreferredRouteID string `json:"preferred_route_id,omitempty"`
|
||||
ForceBackendFallback bool `json:"force_backend_fallback"`
|
||||
LeaseStatus string `json:"lease_status,omitempty"`
|
||||
PrimaryRoute FabricServiceChannelRoute `json:"primary_route,omitempty"`
|
||||
DataPlane FabricServiceChannelDataPlaneContract `json:"data_plane,omitempty"`
|
||||
RouteGeneration string `json:"route_generation,omitempty"`
|
||||
FencingEpoch int64 `json:"fencing_epoch,omitempty"`
|
||||
ExpiresAt time.Time `json:"expires_at,omitempty"`
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
ClusterID string `json:"cluster_id"`
|
||||
ChannelID string `json:"channel_id"`
|
||||
ResourceID string `json:"resource_id,omitempty"`
|
||||
ServiceClass string `json:"service_class"`
|
||||
Allowed bool `json:"allowed"`
|
||||
Status string `json:"status"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
AcceptedBy string `json:"accepted_by"`
|
||||
SelectedEntryNodeID string `json:"selected_entry_node_id,omitempty"`
|
||||
SelectedExitNodeID string `json:"selected_exit_node_id,omitempty"`
|
||||
AllowedChannels []string `json:"allowed_channels,omitempty"`
|
||||
PreferredRouteID string `json:"preferred_route_id,omitempty"`
|
||||
ForceCompatFallback bool `json:"force_degraded_route"`
|
||||
LeaseStatus string `json:"lease_status,omitempty"`
|
||||
PrimaryRoute FabricServiceChannelRoute `json:"primary_route,omitempty"`
|
||||
DataPlane FabricServiceChannelDataPlaneContract `json:"data_plane,omitempty"`
|
||||
RouteGeneration string `json:"route_generation,omitempty"`
|
||||
FencingEpoch int64 `json:"fencing_epoch,omitempty"`
|
||||
ExpiresAt time.Time `json:"expires_at,omitempty"`
|
||||
}
|
||||
|
||||
type FabricServiceChannelRouteFeedbackObservation struct {
|
||||
@@ -1970,7 +2065,17 @@ type CreateJoinRequestInput struct {
|
||||
RequestedRoles json.RawMessage
|
||||
}
|
||||
|
||||
type GetJoinRequestBootstrapInput struct {
|
||||
type RegisterFabricNodeInput struct {
|
||||
ClusterID string
|
||||
NodeKey string
|
||||
Name string
|
||||
OwnershipType string
|
||||
OwnerOrganizationID *string
|
||||
ReportedVersion *string
|
||||
Metadata json.RawMessage
|
||||
}
|
||||
|
||||
type GetJoinRequestJoinInput struct {
|
||||
ClusterID string
|
||||
JoinRequestID string
|
||||
NodeFingerprint string
|
||||
@@ -1988,14 +2093,14 @@ type ApproveJoinRequestInput struct {
|
||||
}
|
||||
|
||||
type ApprovedJoinRequest struct {
|
||||
JoinRequest NodeJoinRequest `json:"join_request"`
|
||||
Bootstrap NodeBootstrap `json:"node_bootstrap"`
|
||||
JoinRequest NodeJoinRequest `json:"join_request"`
|
||||
JoinContract NodeJoinContract `json:"node_join"`
|
||||
}
|
||||
|
||||
type JoinRequestBootstrapResult struct {
|
||||
Status string `json:"status"`
|
||||
JoinRequest NodeJoinRequest `json:"join_request"`
|
||||
Bootstrap *NodeBootstrap `json:"node_bootstrap,omitempty"`
|
||||
type JoinRequestJoinResult struct {
|
||||
Status string `json:"status"`
|
||||
JoinRequest NodeJoinRequest `json:"join_request"`
|
||||
JoinContract *NodeJoinContract `json:"node_join,omitempty"`
|
||||
}
|
||||
|
||||
type RejectJoinRequestInput struct {
|
||||
@@ -2114,15 +2219,24 @@ type UpsertNodeUpdatePolicyInput struct {
|
||||
}
|
||||
|
||||
type GetNodeUpdatePlanInput struct {
|
||||
ClusterID string
|
||||
NodeID string
|
||||
Product string
|
||||
CurrentVersion string
|
||||
OS string
|
||||
Arch string
|
||||
InstallType string
|
||||
Channel string
|
||||
ArtifactOrigin string
|
||||
ClusterID string
|
||||
NodeID string
|
||||
Product string
|
||||
CurrentVersion string
|
||||
OS string
|
||||
Arch string
|
||||
InstallType string
|
||||
Channel string
|
||||
ArtifactOrigin string
|
||||
ExecutorCapabilities []string
|
||||
}
|
||||
|
||||
type GetNodeUpdateArtifactContentInput struct {
|
||||
ClusterID string
|
||||
NodeID string
|
||||
ArtifactID string
|
||||
Offset int64
|
||||
Length int64
|
||||
}
|
||||
|
||||
type GetStaleNodeRiskReportInput struct {
|
||||
@@ -2276,38 +2390,38 @@ type SetFabricEgressPoolNodeInput struct {
|
||||
}
|
||||
|
||||
type IssueFabricServiceChannelLeaseInput struct {
|
||||
ActorUserID string
|
||||
ClusterID string
|
||||
OrganizationID string
|
||||
UserID string
|
||||
ResourceID string
|
||||
ServiceClass string
|
||||
EntryNodeIDs []string
|
||||
ExitNodeIDs []string
|
||||
PreferredEntryNodeID string
|
||||
PreferredExitNodeID string
|
||||
RequiredRoles []string
|
||||
AllowedChannels []string
|
||||
QoS json.RawMessage
|
||||
Failover json.RawMessage
|
||||
Metadata json.RawMessage
|
||||
TTL time.Duration
|
||||
BackendFallbackAllowed *bool
|
||||
ActorUserID string
|
||||
ClusterID string
|
||||
OrganizationID string
|
||||
UserID string
|
||||
ResourceID string
|
||||
ServiceClass string
|
||||
EntryNodeIDs []string
|
||||
ExitNodeIDs []string
|
||||
PreferredEntryNodeID string
|
||||
PreferredExitNodeID string
|
||||
RequiredRoles []string
|
||||
AllowedChannels []string
|
||||
QoS json.RawMessage
|
||||
Failover json.RawMessage
|
||||
Metadata json.RawMessage
|
||||
TTL time.Duration
|
||||
CompatFallbackAllowed *bool
|
||||
}
|
||||
|
||||
type UpdateFabricServiceChannelPoolPolicyInput struct {
|
||||
ActorUserID string
|
||||
ClusterID string
|
||||
EntryPoolNodeIDs []string
|
||||
ExitPoolNodeIDs []string
|
||||
PreferredEntryNodeID string
|
||||
PreferredExitNodeID string
|
||||
SelectionStrategy string
|
||||
RouteRebuild string
|
||||
EntryFailover string
|
||||
ExitFailover string
|
||||
BackendFallbackAllowed *bool
|
||||
StickySession *bool
|
||||
ActorUserID string
|
||||
ClusterID string
|
||||
EntryPoolNodeIDs []string
|
||||
ExitPoolNodeIDs []string
|
||||
PreferredEntryNodeID string
|
||||
PreferredExitNodeID string
|
||||
SelectionStrategy string
|
||||
RouteRebuild string
|
||||
EntryFailover string
|
||||
ExitFailover string
|
||||
CompatFallbackAllowed *bool
|
||||
StickySession *bool
|
||||
}
|
||||
|
||||
type UpdateFabricServiceChannelBreadcrumbWindowPolicyInput struct {
|
||||
|
||||
@@ -60,13 +60,30 @@ func (m *Module) Name() string {
|
||||
}
|
||||
|
||||
func (m *Module) RegisterRoutes(router chi.Router) {
|
||||
router.Get("/ui/admin", m.renderAdminHTML)
|
||||
router.Get("/ui/htmx-lite.js", m.renderHTMXLiteJS)
|
||||
router.Get("/downloads/{fileName}", m.downloadReleaseFile)
|
||||
router.Get("/downloads/releases/{version}/{fileName}", m.downloadVersionedReleaseFile)
|
||||
router.Route("/clusters", func(r chi.Router) {
|
||||
r.Get("/", m.listClusters)
|
||||
r.Post("/", m.createCluster)
|
||||
r.Get("/{clusterID}", m.getCluster)
|
||||
r.Put("/{clusterID}", m.updateCluster)
|
||||
r.Get("/{clusterID}/nodes", m.listClusterNodes)
|
||||
r.Get("/{clusterID}/ui/overview", m.renderFarmOverviewHTML)
|
||||
r.Get("/{clusterID}/ui/nodes", m.renderNodesHTML)
|
||||
r.Get("/{clusterID}/ui/nodes/fragment", m.renderNodesHTMLFragment)
|
||||
r.Get("/{clusterID}/ui/nodes/{nodeID}/details", m.renderNodeDetailsHTML)
|
||||
r.Get("/{clusterID}/ui/updates", m.renderUpdatesHTML)
|
||||
r.Get("/{clusterID}/ui/updates/fragment", m.renderUpdatesHTMLFragment)
|
||||
r.Post("/{clusterID}/ui/updates/check-now", m.renderUpdateCheckNowAllHTML)
|
||||
r.Post("/{clusterID}/ui/updates/{nodeID}/check-now", m.renderUpdateCheckNowHTML)
|
||||
r.Get("/{clusterID}/ui/topology", m.renderTopologyHTML)
|
||||
r.Get("/{clusterID}/ui/fabric", m.renderFabricConsoleHTML)
|
||||
r.Post("/{clusterID}/ui/fabric/policy", m.renderFabricPolicyHTML)
|
||||
r.Get("/{clusterID}/ui/web-control", m.renderWebControlHTML)
|
||||
r.Post("/{clusterID}/ui/web-control/desired", m.renderWebControlDesiredHTML)
|
||||
r.Get("/{clusterID}/ui/audit", m.renderAuditConsoleHTML)
|
||||
r.Get("/{clusterID}/node-groups", m.listNodeGroups)
|
||||
r.Post("/{clusterID}/node-groups", m.createNodeGroup)
|
||||
r.Get("/{clusterID}/join-requests", m.listJoinRequests)
|
||||
@@ -84,6 +101,7 @@ func (m *Module) RegisterRoutes(router chi.Router) {
|
||||
r.Post("/{clusterID}/updates/releases", m.createReleaseVersion)
|
||||
r.Put("/{clusterID}/nodes/{nodeID}/updates/policy", m.upsertNodeUpdatePolicy)
|
||||
r.Get("/{clusterID}/nodes/{nodeID}/updates/plan", m.getNodeUpdatePlan)
|
||||
r.Get("/{clusterID}/nodes/{nodeID}/updates/artifacts/{artifactID}/content", m.getNodeUpdateArtifactContent)
|
||||
r.Get("/{clusterID}/nodes/{nodeID}/updates/bridge-replay-plan", m.getNodeBridgeReplayPlan)
|
||||
r.Post("/{clusterID}/nodes/{nodeID}/updates/status", m.reportNodeUpdateStatus)
|
||||
r.Get("/{clusterID}/nodes/{nodeID}/updates/statuses", m.listNodeUpdateStatuses)
|
||||
@@ -191,9 +209,45 @@ func (m *Module) downloadReleaseFile(w http.ResponseWriter, r *http.Request) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
setReleaseDownloadHeaders(w, fileName)
|
||||
http.ServeFile(w, r, path)
|
||||
}
|
||||
|
||||
func (m *Module) downloadVersionedReleaseFile(w http.ResponseWriter, r *http.Request) {
|
||||
version := filepath.Base(strings.TrimSpace(chi.URLParam(r, "version")))
|
||||
fileName := filepath.Base(strings.TrimSpace(chi.URLParam(r, "fileName")))
|
||||
if version == "" || version == "." || version != strings.TrimSpace(chi.URLParam(r, "version")) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
if fileName == "" || fileName == "." || fileName != strings.TrimSpace(chi.URLParam(r, "fileName")) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
releaseDir := strings.TrimSpace(os.Getenv("RAP_RELEASE_DIR"))
|
||||
if releaseDir == "" {
|
||||
releaseDir = "/tmp/rap-release"
|
||||
}
|
||||
path := filepath.Join(releaseDir, "releases", version, fileName)
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
setReleaseDownloadHeaders(w, fileName)
|
||||
http.ServeFile(w, r, path)
|
||||
}
|
||||
|
||||
func setReleaseDownloadHeaders(w http.ResponseWriter, fileName string) {
|
||||
switch strings.ToLower(filepath.Ext(fileName)) {
|
||||
case ".apk":
|
||||
w.Header().Set("Content-Type", "application/vnd.android.package-archive")
|
||||
w.Header().Set("Content-Disposition", `attachment; filename="`+fileName+`"`)
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
case ".json":
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Module) listClusters(w http.ResponseWriter, r *http.Request) {
|
||||
items, err := m.service.ListClusters(r.Context(), r.URL.Query().Get("actor_user_id"))
|
||||
if writeServiceError(w, err) {
|
||||
@@ -340,14 +394,10 @@ func adminRuntimeProjectionResponse(statusCode int, status string, reason string
|
||||
|
||||
func isAllowedAdminRuntimeProjectionScope(scope string, serviceClass string) bool {
|
||||
switch serviceClass {
|
||||
case FabricServiceClassPlatformAdmin:
|
||||
return scope == "platform"
|
||||
case FabricServiceClassClusterAdmin:
|
||||
return scope == "cluster"
|
||||
case FabricServiceClassOrganization:
|
||||
return scope == "organization"
|
||||
case FabricServiceClassUserPortal:
|
||||
return scope == "user" || scope == "organization"
|
||||
case FabricServiceClassAdminIngress:
|
||||
return scope == "platform" || scope == "cluster"
|
||||
case FabricServiceClassPublicIngress:
|
||||
return scope == "organization" || scope == "user"
|
||||
default:
|
||||
return false
|
||||
}
|
||||
@@ -357,18 +407,22 @@ func adminRuntimeManifest(scope string, serviceClass string) map[string]any {
|
||||
sections := []string{"status"}
|
||||
actions := []string{"read_status"}
|
||||
switch strings.TrimSpace(serviceClass) {
|
||||
case FabricServiceClassPlatformAdmin:
|
||||
case FabricServiceClassAdminIngress:
|
||||
sections = []string{"clusters", "nodes", "roles", "fabric", "workloads", "audit"}
|
||||
actions = []string{"read_platform_summary", "read_cluster_summaries", "read_node_status"}
|
||||
case FabricServiceClassClusterAdmin:
|
||||
sections = []string{"cluster", "nodes", "fabric", "workloads", "audit"}
|
||||
actions = []string{"read_cluster_summary", "read_node_status"}
|
||||
case FabricServiceClassOrganization:
|
||||
if scope == "cluster" {
|
||||
sections = []string{"cluster", "nodes", "fabric", "workloads", "audit"}
|
||||
actions = []string{"read_cluster_summary", "read_node_status"}
|
||||
} else {
|
||||
actions = []string{"read_platform_summary", "read_cluster_summaries", "read_node_status"}
|
||||
}
|
||||
case FabricServiceClassPublicIngress:
|
||||
sections = []string{"organization", "sessions", "resources", "audit"}
|
||||
actions = []string{"read_organization_summary", "read_sessions"}
|
||||
case FabricServiceClassUserPortal:
|
||||
sections = []string{"profile", "sessions", "resources"}
|
||||
actions = []string{"read_profile", "read_sessions"}
|
||||
if scope == "user" {
|
||||
sections = []string{"profile", "sessions", "resources"}
|
||||
actions = []string{"read_profile", "read_sessions"}
|
||||
} else {
|
||||
actions = []string{"read_organization_summary", "read_sessions"}
|
||||
}
|
||||
}
|
||||
return map[string]any{
|
||||
"schema_version": adminRuntimeManifestSchema,
|
||||
@@ -729,6 +783,10 @@ func (m *Module) getNodeUpdatePlan(w http.ResponseWriter, r *http.Request) {
|
||||
InstallType: r.URL.Query().Get("install_type"),
|
||||
Channel: r.URL.Query().Get("channel"),
|
||||
ArtifactOrigin: requestOrigin(r),
|
||||
ExecutorCapabilities: append(
|
||||
r.URL.Query()["executor_capability"],
|
||||
r.URL.Query()["executor_capabilities"]...,
|
||||
),
|
||||
})
|
||||
if writeServiceError(w, err) {
|
||||
return
|
||||
@@ -736,6 +794,35 @@ func (m *Module) getNodeUpdatePlan(w http.ResponseWriter, r *http.Request) {
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"node_update_plan": item})
|
||||
}
|
||||
|
||||
func (m *Module) getNodeUpdateArtifactContent(w http.ResponseWriter, r *http.Request) {
|
||||
item, err := m.service.GetNodeUpdateArtifactContent(r.Context(), GetNodeUpdateArtifactContentInput{
|
||||
ClusterID: chi.URLParam(r, "clusterID"),
|
||||
NodeID: chi.URLParam(r, "nodeID"),
|
||||
ArtifactID: chi.URLParam(r, "artifactID"),
|
||||
Offset: parseInt64Query(r, "offset"),
|
||||
Length: parseInt64Query(r, "length"),
|
||||
})
|
||||
if writeServiceError(w, err) {
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(w, http.StatusOK, item)
|
||||
}
|
||||
|
||||
func parseInt64Query(r *http.Request, key string) int64 {
|
||||
if r == nil {
|
||||
return 0
|
||||
}
|
||||
value := strings.TrimSpace(r.URL.Query().Get(key))
|
||||
if value == "" {
|
||||
return 0
|
||||
}
|
||||
parsed, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil || parsed < 0 {
|
||||
return 0
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
func requestOrigin(r *http.Request) string {
|
||||
proto := strings.TrimSpace(r.Header.Get("X-Forwarded-Proto"))
|
||||
if proto == "" {
|
||||
@@ -1732,35 +1819,35 @@ func (m *Module) getFabricServiceChannelPoolPolicy(w http.ResponseWriter, r *htt
|
||||
|
||||
func (m *Module) updateFabricServiceChannelPoolPolicy(w http.ResponseWriter, r *http.Request) {
|
||||
var payload struct {
|
||||
ActorUserID string `json:"actor_user_id"`
|
||||
EntryPoolNodeIDs []string `json:"entry_pool_node_ids"`
|
||||
ExitPoolNodeIDs []string `json:"exit_pool_node_ids"`
|
||||
PreferredEntryNodeID string `json:"preferred_entry_node_id"`
|
||||
PreferredExitNodeID string `json:"preferred_exit_node_id"`
|
||||
SelectionStrategy string `json:"selection_strategy"`
|
||||
RouteRebuild string `json:"route_rebuild"`
|
||||
EntryFailover string `json:"entry_failover"`
|
||||
ExitFailover string `json:"exit_failover"`
|
||||
BackendFallbackAllowed *bool `json:"backend_fallback_allowed"`
|
||||
StickySession *bool `json:"sticky_session"`
|
||||
ActorUserID string `json:"actor_user_id"`
|
||||
EntryPoolNodeIDs []string `json:"entry_pool_node_ids"`
|
||||
ExitPoolNodeIDs []string `json:"exit_pool_node_ids"`
|
||||
PreferredEntryNodeID string `json:"preferred_entry_node_id"`
|
||||
PreferredExitNodeID string `json:"preferred_exit_node_id"`
|
||||
SelectionStrategy string `json:"selection_strategy"`
|
||||
RouteRebuild string `json:"route_rebuild"`
|
||||
EntryFailover string `json:"entry_failover"`
|
||||
ExitFailover string `json:"exit_failover"`
|
||||
CompatFallbackAllowed *bool `json:"degraded_route_allowed"`
|
||||
StickySession *bool `json:"sticky_session"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||
httpx.WriteError(w, http.StatusBadRequest, "invalid pool policy payload")
|
||||
return
|
||||
}
|
||||
item, err := m.service.UpdateFabricServiceChannelPoolPolicy(r.Context(), UpdateFabricServiceChannelPoolPolicyInput{
|
||||
ActorUserID: payload.ActorUserID,
|
||||
ClusterID: chi.URLParam(r, "clusterID"),
|
||||
EntryPoolNodeIDs: payload.EntryPoolNodeIDs,
|
||||
ExitPoolNodeIDs: payload.ExitPoolNodeIDs,
|
||||
PreferredEntryNodeID: payload.PreferredEntryNodeID,
|
||||
PreferredExitNodeID: payload.PreferredExitNodeID,
|
||||
SelectionStrategy: payload.SelectionStrategy,
|
||||
RouteRebuild: payload.RouteRebuild,
|
||||
EntryFailover: payload.EntryFailover,
|
||||
ExitFailover: payload.ExitFailover,
|
||||
BackendFallbackAllowed: payload.BackendFallbackAllowed,
|
||||
StickySession: payload.StickySession,
|
||||
ActorUserID: payload.ActorUserID,
|
||||
ClusterID: chi.URLParam(r, "clusterID"),
|
||||
EntryPoolNodeIDs: payload.EntryPoolNodeIDs,
|
||||
ExitPoolNodeIDs: payload.ExitPoolNodeIDs,
|
||||
PreferredEntryNodeID: payload.PreferredEntryNodeID,
|
||||
PreferredExitNodeID: payload.PreferredExitNodeID,
|
||||
SelectionStrategy: payload.SelectionStrategy,
|
||||
RouteRebuild: payload.RouteRebuild,
|
||||
EntryFailover: payload.EntryFailover,
|
||||
ExitFailover: payload.ExitFailover,
|
||||
CompatFallbackAllowed: payload.CompatFallbackAllowed,
|
||||
StickySession: payload.StickySession,
|
||||
})
|
||||
if writeServiceError(w, err) {
|
||||
return
|
||||
@@ -3411,7 +3498,7 @@ func writeServiceError(w http.ResponseWriter, err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
var legacyRemovalBlocked *LegacyRemovalBlockedError
|
||||
var standardCleanupBlocked *FabricStandardCleanupBlockedError
|
||||
switch {
|
||||
case errors.Is(err, ErrAccessDenied):
|
||||
httpx.WriteError(w, http.StatusForbidden, err.Error())
|
||||
@@ -3419,11 +3506,11 @@ func writeServiceError(w http.ResponseWriter, err error) bool {
|
||||
httpx.WriteError(w, http.StatusForbidden, err.Error())
|
||||
case errors.Is(err, ErrClusterReadOnly):
|
||||
httpx.WriteError(w, http.StatusConflict, err.Error())
|
||||
case errors.As(err, &legacyRemovalBlocked):
|
||||
case errors.As(err, &standardCleanupBlocked):
|
||||
httpx.WriteErrorMessage(w, http.StatusConflict, httpx.ErrorResponse{
|
||||
Error: httpx.NewErrorMessage(http.StatusConflict, err.Error(), legacyRemovalBlockedErrorDetails(*legacyRemovalBlocked), ""),
|
||||
Error: httpx.NewErrorMessage(http.StatusConflict, err.Error(), FabricStandardCleanupBlockedErrorDetails(*standardCleanupBlocked), ""),
|
||||
})
|
||||
case errors.Is(err, ErrLegacyRemovalBlocked):
|
||||
case errors.Is(err, ErrFabricStandardCleanupBlocked):
|
||||
httpx.WriteError(w, http.StatusConflict, err.Error())
|
||||
case errors.Is(err, ErrVPNLeaseAlreadyActive):
|
||||
httpx.WriteError(w, http.StatusConflict, err.Error())
|
||||
@@ -3437,24 +3524,31 @@ func writeServiceError(w http.ResponseWriter, err error) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func legacyRemovalBlockedErrorDetails(err LegacyRemovalBlockedError) map[string]any {
|
||||
func FabricStandardCleanupBlockedErrorDetails(err FabricStandardCleanupBlockedError) map[string]any {
|
||||
details := map[string]any{
|
||||
"blocked_operation": err.BlockedOperation,
|
||||
"legacy_removal_allowed": err.Report.LegacyRemovalAllowed,
|
||||
"bridge_hold_required": err.Report.BridgeHoldRequired,
|
||||
"bridge_hold_reasons": err.Report.BridgeHoldReasons,
|
||||
"blocked_operations": err.Report.BlockedOperations,
|
||||
"heartbeat_stale_after_seconds": err.Report.HeartbeatStaleAfterSeconds,
|
||||
"stale_nodes": err.Report.Summary.StaleNodes,
|
||||
"blocked_nodes": err.Report.Summary.BlockedNodes,
|
||||
"artifact_gap_nodes": err.Report.Summary.ArtifactGapNodes,
|
||||
"unknown_profile_nodes": err.Report.Summary.UnknownProfileNodes,
|
||||
"waiting_update_status_nodes": err.Report.Summary.WaitingUpdateStatusNodes,
|
||||
"unknown_version_nodes": err.Report.Summary.UnknownVersionNodes,
|
||||
"legacy_recovery_contract_nodes": err.Report.Summary.LegacyRecoveryContractNodes,
|
||||
"recovery_bridge_required_nodes": err.Report.Summary.RecoveryBridgeRequiredNodes,
|
||||
"blocked_operation": err.BlockedOperation,
|
||||
"fabric_standard_cleanup_allowed": err.Report.FabricStandardCleanupAllowed,
|
||||
"bridge_hold_required": err.Report.BridgeHoldRequired,
|
||||
"bridge_hold_reasons": err.Report.BridgeHoldReasons,
|
||||
"blocked_operations": err.Report.BlockedOperations,
|
||||
"heartbeat_stale_after_seconds": err.Report.HeartbeatStaleAfterSeconds,
|
||||
"stale_nodes": err.Report.Summary.StaleNodes,
|
||||
"blocked_nodes": err.Report.Summary.BlockedNodes,
|
||||
"artifact_gap_nodes": err.Report.Summary.ArtifactGapNodes,
|
||||
"area_diversity_alert_nodes": err.Report.Summary.AreaDiversityAlertNodes,
|
||||
"independent_ingress_alert_nodes": err.Report.Summary.IndependentIngressAlertNodes,
|
||||
"updater_wake_unsupported_nodes": err.Report.Summary.UpdaterWakeUnsupportedNodes,
|
||||
"updater_runtime_missing_nodes": err.Report.Summary.UpdaterRuntimeMissingNodes,
|
||||
"staged_self_update_pending_nodes": err.Report.Summary.StagedSelfUpdatePendingNodes,
|
||||
"standard_control_dependency_nodes": err.Report.Summary.StandardControlDependencyNodes,
|
||||
"registry_candidate_only_nodes": err.Report.Summary.RegistryCandidateOnlyNodes,
|
||||
"unknown_profile_nodes": err.Report.Summary.UnknownProfileNodes,
|
||||
"waiting_update_status_nodes": err.Report.Summary.WaitingUpdateStatusNodes,
|
||||
"unknown_version_nodes": err.Report.Summary.UnknownVersionNodes,
|
||||
"standard_recovery_contract_nodes": err.Report.Summary.StandardRecoveryContractNodes,
|
||||
"recovery_bridge_required_nodes": err.Report.Summary.RecoveryBridgeRequiredNodes,
|
||||
"recovery_bridge_replay_ready_nodes": err.Report.Summary.RecoveryBridgeReplayReadyNodes,
|
||||
"waiting_recovery_heartbeat_nodes": err.Report.Summary.WaitingRecoveryHeartbeatNodes,
|
||||
"waiting_recovery_heartbeat_nodes": err.Report.Summary.WaitingRecoveryHeartbeatNodes,
|
||||
}
|
||||
blockedNodeIDs := make([]string, 0, len(err.Report.Nodes))
|
||||
for _, node := range err.Report.Nodes {
|
||||
|
||||
@@ -18,9 +18,9 @@ func TestProjectAdminRuntimeReturnsReadOnlyManifest(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPost, "/clusters/cluster-1/nodes/node-1/admin-runtime/projection", bytes.NewReader([]byte(`{
|
||||
"schema_version":"rap.web_ingress.control_api_projection_request.v1",
|
||||
"method":"GET",
|
||||
"path":"/platform-admin/ui-manifest",
|
||||
"path":"/admin/ui-manifest",
|
||||
"scope":"platform",
|
||||
"service_class":"platform_admin"
|
||||
"service_class":"admin-ingress"
|
||||
}`)))
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -72,9 +72,9 @@ func TestProjectAdminRuntimeRejectsMutations(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPost, "/clusters/cluster-1/nodes/node-1/admin-runtime/projection", bytes.NewReader([]byte(`{
|
||||
"schema_version":"rap.web_ingress.control_api_projection_request.v1",
|
||||
"method":"POST",
|
||||
"path":"/platform-admin/nodes",
|
||||
"path":"/admin/nodes",
|
||||
"scope":"platform",
|
||||
"service_class":"platform_admin"
|
||||
"service_class":"admin-ingress"
|
||||
}`)))
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -106,7 +106,7 @@ func TestProjectAdminRuntimeReturnsHealthProjection(t *testing.T) {
|
||||
"method":"GET",
|
||||
"path":"/readyz",
|
||||
"scope":"platform",
|
||||
"service_class":"platform_admin"
|
||||
"service_class":"admin-ingress"
|
||||
}`)))
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -147,9 +147,9 @@ func TestProjectAdminRuntimeBlocksUnknownReadProjection(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPost, "/clusters/cluster-1/nodes/node-1/admin-runtime/projection", bytes.NewReader([]byte(`{
|
||||
"schema_version":"rap.web_ingress.control_api_projection_request.v1",
|
||||
"method":"GET",
|
||||
"path":"/platform-admin/nodes",
|
||||
"path":"/admin/nodes",
|
||||
"scope":"platform",
|
||||
"service_class":"platform_admin"
|
||||
"service_class":"admin-ingress"
|
||||
}`)))
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -181,9 +181,9 @@ func TestProjectAdminRuntimeRejectsScopeClassMismatch(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPost, "/clusters/cluster-1/nodes/node-1/admin-runtime/projection", bytes.NewReader([]byte(`{
|
||||
"schema_version":"rap.web_ingress.control_api_projection_request.v1",
|
||||
"method":"GET",
|
||||
"path":"/platform-admin/ui-manifest",
|
||||
"path":"/admin/ui-manifest",
|
||||
"scope":"organization",
|
||||
"service_class":"platform_admin"
|
||||
"service_class":"admin-ingress"
|
||||
}`)))
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -217,7 +217,7 @@ func TestProjectAdminRuntimeRejectsInvalidSchema(t *testing.T) {
|
||||
"method":"GET",
|
||||
"path":"/readyz",
|
||||
"scope":"platform",
|
||||
"service_class":"platform_admin"
|
||||
"service_class":"admin-ingress"
|
||||
}`)))
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -7,17 +7,17 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWriteServiceErrorLegacyRemovalBlockedIncludesBreakdownDetails(t *testing.T) {
|
||||
func TestWriteServiceErrorDisallowedRemovalBlockedIncludesBreakdownDetails(t *testing.T) {
|
||||
recorder := httptest.NewRecorder()
|
||||
handled := writeServiceError(recorder, &LegacyRemovalBlockedError{
|
||||
handled := writeServiceError(recorder, &FabricStandardCleanupBlockedError{
|
||||
BlockedOperation: "create_breaking_release",
|
||||
Report: StaleNodeRiskReport{
|
||||
HeartbeatStaleAfterSeconds: 900,
|
||||
LegacyRemovalAllowed: false,
|
||||
BridgeHoldRequired: true,
|
||||
BridgeHoldNodeIDs: []string{"node-1"},
|
||||
BridgeHoldReasons: []string{"legacy_contract_overlap"},
|
||||
BlockedOperations: []string{"create_breaking_release", "target_breaking_update_policy", "remove_recovery_bridge_overlap"},
|
||||
HeartbeatStaleAfterSeconds: 900,
|
||||
FabricStandardCleanupAllowed: false,
|
||||
BridgeHoldRequired: true,
|
||||
BridgeHoldNodeIDs: []string{"node-1"},
|
||||
BridgeHoldReasons: []string{"standard_contract_overlap"},
|
||||
BlockedOperations: []string{"create_breaking_release", "target_breaking_update_policy", "remove_recovery_bridge_overlap"},
|
||||
Nodes: []StaleNodeRiskNode{
|
||||
{NodeID: "node-1", Blocked: true, RecoveryBridgeRequired: true},
|
||||
{NodeID: "node-2", Blocked: false},
|
||||
@@ -25,11 +25,13 @@ func TestWriteServiceErrorLegacyRemovalBlockedIncludesBreakdownDetails(t *testin
|
||||
Summary: StaleNodeRiskSummary{
|
||||
StaleNodes: 1,
|
||||
BlockedNodes: 1,
|
||||
UpdaterRuntimeMissingNodes: 1,
|
||||
StagedSelfUpdatePendingNodes: 1,
|
||||
ArtifactGapNodes: 0,
|
||||
UnknownProfileNodes: 0,
|
||||
WaitingUpdateStatusNodes: 0,
|
||||
UnknownVersionNodes: 0,
|
||||
LegacyRecoveryContractNodes: 0,
|
||||
StandardRecoveryContractNodes: 0,
|
||||
WaitingRecoveryHeartbeatNodes: 1,
|
||||
},
|
||||
},
|
||||
@@ -54,6 +56,12 @@ func TestWriteServiceErrorLegacyRemovalBlockedIncludesBreakdownDetails(t *testin
|
||||
if payload.Error.Details["waiting_recovery_heartbeat_nodes"] != float64(1) {
|
||||
t.Fatalf("waiting_recovery_heartbeat_nodes = %v", payload.Error.Details["waiting_recovery_heartbeat_nodes"])
|
||||
}
|
||||
if payload.Error.Details["staged_self_update_pending_nodes"] != float64(1) {
|
||||
t.Fatalf("staged_self_update_pending_nodes = %v", payload.Error.Details["staged_self_update_pending_nodes"])
|
||||
}
|
||||
if payload.Error.Details["updater_runtime_missing_nodes"] != float64(1) {
|
||||
t.Fatalf("updater_runtime_missing_nodes = %v", payload.Error.Details["updater_runtime_missing_nodes"])
|
||||
}
|
||||
if payload.Error.Details["bridge_hold_required"] != true {
|
||||
t.Fatalf("bridge_hold_required = %v", payload.Error.Details["bridge_hold_required"])
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -311,6 +311,69 @@ func (s *PostgresStore) ListClusterNodes(ctx context.Context, clusterID string)
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (s *PostgresStore) RegisterFabricNode(ctx context.Context, input RegisterFabricNodeInput) (ClusterNode, error) {
|
||||
tx, err := s.db.Begin(ctx)
|
||||
if err != nil {
|
||||
return ClusterNode{}, err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
|
||||
now := time.Now().UTC()
|
||||
nodeID := uuid.NewString()
|
||||
row := tx.QueryRow(ctx, `
|
||||
INSERT INTO nodes (
|
||||
id, owner_organization_id, node_key, name, ownership_type, registration_status, health_status,
|
||||
version_state, partition_state, reported_version, metadata, created_at, updated_at
|
||||
) VALUES ($1::uuid, $2::uuid, $3, $4, $5, 'active', 'healthy', 'current', 'healthy', $6, $7::jsonb, $8, $8)
|
||||
ON CONFLICT (node_key) DO UPDATE SET
|
||||
owner_organization_id = COALESCE(EXCLUDED.owner_organization_id, nodes.owner_organization_id),
|
||||
name = EXCLUDED.name,
|
||||
ownership_type = EXCLUDED.ownership_type,
|
||||
registration_status = 'active',
|
||||
health_status = 'healthy',
|
||||
reported_version = COALESCE(EXCLUDED.reported_version, nodes.reported_version),
|
||||
metadata = nodes.metadata || EXCLUDED.metadata,
|
||||
updated_at = EXCLUDED.updated_at
|
||||
RETURNING id::text
|
||||
`, nodeID, input.OwnerOrganizationID, input.NodeKey, input.Name, input.OwnershipType, input.ReportedVersion, []byte(input.Metadata), now)
|
||||
if err := row.Scan(&nodeID); err != nil {
|
||||
return ClusterNode{}, err
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(ctx, `
|
||||
INSERT INTO cluster_memberships (cluster_id, node_id, membership_status, joined_at, last_seen_at, metadata)
|
||||
VALUES ($1::uuid, $2::uuid, 'active', $3, $3, $4::jsonb)
|
||||
ON CONFLICT (cluster_id, node_id) DO UPDATE SET
|
||||
membership_status = 'active',
|
||||
last_seen_at = EXCLUDED.last_seen_at,
|
||||
metadata = cluster_memberships.metadata || EXCLUDED.metadata
|
||||
`, input.ClusterID, nodeID, now, []byte(`{"source":"fabric_control_register"}`)); err != nil {
|
||||
return ClusterNode{}, err
|
||||
}
|
||||
|
||||
itemRow := tx.QueryRow(ctx, `
|
||||
SELECT n.id::text, n.owner_organization_id::text, n.node_key, n.name, n.ownership_type,
|
||||
n.registration_status, n.health_status, n.version_state, n.partition_state,
|
||||
n.reported_version, n.last_seen_at, cm.membership_status, cm.metadata,
|
||||
ng.id::text, ng.name,
|
||||
n.created_at, n.updated_at
|
||||
FROM cluster_memberships cm
|
||||
JOIN nodes n ON n.id = cm.node_id
|
||||
LEFT JOIN cluster_node_group_memberships ngm ON ngm.cluster_id = cm.cluster_id AND ngm.node_id = cm.node_id
|
||||
LEFT JOIN cluster_node_groups ng ON ng.cluster_id = ngm.cluster_id AND ng.id = ngm.group_id
|
||||
WHERE cm.cluster_id = $1::uuid
|
||||
AND cm.node_id = $2::uuid
|
||||
`, input.ClusterID, nodeID)
|
||||
item, err := scanClusterNode(itemRow)
|
||||
if err != nil {
|
||||
return ClusterNode{}, err
|
||||
}
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return ClusterNode{}, err
|
||||
}
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (s *PostgresStore) ListNodeGroups(ctx context.Context, clusterID string) ([]ClusterNodeGroup, error) {
|
||||
rows, err := s.db.Query(ctx, `
|
||||
SELECT id::text, cluster_id::text, parent_group_id::text, name, description,
|
||||
@@ -511,7 +574,7 @@ func (s *PostgresStore) CreateJoinRequest(ctx context.Context, input CreateJoinR
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (s *PostgresStore) GetJoinRequestForBootstrap(ctx context.Context, input GetJoinRequestBootstrapInput) (NodeJoinRequest, error) {
|
||||
func (s *PostgresStore) GetJoinRequestForJoin(ctx context.Context, input GetJoinRequestJoinInput) (NodeJoinRequest, error) {
|
||||
row := s.db.QueryRow(ctx, `
|
||||
SELECT id::text, cluster_id::text, join_token_id::text, node_name, node_fingerprint, public_key,
|
||||
reported_capabilities, reported_facts, requested_roles, status, reviewed_by_user_id::text,
|
||||
@@ -666,7 +729,7 @@ func (s *PostgresStore) ApproveJoinRequest(ctx context.Context, input ApproveJoi
|
||||
}
|
||||
return ApprovedJoinRequest{
|
||||
JoinRequest: updated,
|
||||
Bootstrap: NodeBootstrap{
|
||||
JoinContract: NodeJoinContract{
|
||||
NodeID: nodeID,
|
||||
ClusterID: input.ClusterID,
|
||||
IdentityStatus: "active",
|
||||
@@ -1310,6 +1373,17 @@ func (s *PostgresStore) listReleaseArtifacts(ctx context.Context, releaseID stri
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (s *PostgresStore) GetReleaseArtifact(ctx context.Context, clusterID, artifactID string) (ReleaseArtifact, error) {
|
||||
row := s.db.QueryRow(ctx, `
|
||||
SELECT id::text, release_id::text, cluster_id::text, product, version, os, arch,
|
||||
install_type, kind, url, sha256, size_bytes, signature, metadata, created_at
|
||||
FROM release_artifacts
|
||||
WHERE cluster_id = $1::uuid
|
||||
AND id = $2::uuid
|
||||
`, clusterID, artifactID)
|
||||
return scanReleaseArtifact(row)
|
||||
}
|
||||
|
||||
func (s *PostgresStore) ListNodeUpdateServiceCandidates(ctx context.Context, clusterID string) ([]NodeUpdateServiceCandidate, error) {
|
||||
rows, err := s.db.Query(ctx, `
|
||||
SELECT n.id::text,
|
||||
@@ -4922,9 +4996,6 @@ func vpnEntryEndpointCandidatesFromHeartbeat(nodeID string, capabilities json.Ra
|
||||
item["tls_cert_sha256"] = certSHA256
|
||||
item["peer_cert_sha256"] = certSHA256
|
||||
}
|
||||
if apiBaseURL := vpnEntryAPIBaseURL(address); apiBaseURL != "" {
|
||||
item["api_base_url"] = apiBaseURL
|
||||
}
|
||||
out = append(out, item)
|
||||
}
|
||||
if len(out) == 0 {
|
||||
@@ -4943,9 +5014,6 @@ func vpnEntryEndpointCandidatesFromHeartbeat(nodeID string, capabilities json.Ra
|
||||
"status": "reported",
|
||||
"source": "node_latest_heartbeat.mesh_endpoint_report.peer_endpoint",
|
||||
}
|
||||
if apiBaseURL := vpnEntryAPIBaseURL(address); apiBaseURL != "" {
|
||||
item["api_base_url"] = apiBaseURL
|
||||
}
|
||||
out = append(out, item)
|
||||
}
|
||||
}
|
||||
@@ -5072,17 +5140,6 @@ func heartbeatCapabilityEnabled(capabilities json.RawMessage, name string) bool
|
||||
}
|
||||
}
|
||||
|
||||
func vpnEntryAPIBaseURL(address string) string {
|
||||
address = strings.TrimRight(strings.TrimSpace(address), "/")
|
||||
if address == "" {
|
||||
return ""
|
||||
}
|
||||
if !strings.HasPrefix(address, "http://") && !strings.HasPrefix(address, "https://") {
|
||||
return ""
|
||||
}
|
||||
return address + "/api/v1"
|
||||
}
|
||||
|
||||
func enrichVPNClientEntryEndpointCandidates(connection VPNClientConnection, endpoints map[string][]map[string]any) json.RawMessage {
|
||||
var cfg map[string]any
|
||||
if err := json.Unmarshal(connection.ClientConfig, &cfg); err != nil || cfg == nil {
|
||||
@@ -5346,7 +5403,7 @@ func enrichVPNClientFabricRoute(item VPNClientConnection, preferredEntryNodeID,
|
||||
entryPool := dedupeStrings(append([]string{}, item.EntryNodeIDs...))
|
||||
placementPolicy := jsonObjectFromRaw(item.PlacementPolicy)
|
||||
entrySelector, _ := placementPolicy["entry_selector"].(string)
|
||||
clientNodeEntry := strings.EqualFold(strings.TrimSpace(entrySelector), "client_node") || placementPolicy["android_node_agent_target"] == true
|
||||
clientNodeEntry := strings.EqualFold(strings.TrimSpace(entrySelector), "client_node") || placementPolicy["ipv4_ingress_node_target"] == true || placementPolicy["android_node_agent_target"] == true
|
||||
if len(entryPool) == 0 && !clientNodeEntry {
|
||||
entryPool = dedupeStrings(append([]string{}, item.AllowedNodeIDs...))
|
||||
}
|
||||
@@ -5450,7 +5507,7 @@ func enrichVPNClientFabricRoute(item VPNClientConnection, preferredEntryNodeID,
|
||||
"queue_policy": "bounded_queue_then_route_failover",
|
||||
"drop_policy": "drop_only_when_all_routes_unavailable_or_queue_full",
|
||||
"bulk_and_realtime": "same_packet_path",
|
||||
"flow_isolation": "hash_by_ip_protocol_and_ports",
|
||||
"flow_isolation": "opaque_packet_hash_shards",
|
||||
"target_dataplane": "fabric_farm_entry_to_exit_service_channel",
|
||||
"temporary_fallback": "none",
|
||||
},
|
||||
@@ -5519,8 +5576,8 @@ func vpnFabricRouteCandidates(entryPool, exitPool []string, selectedEntry, selec
|
||||
"role": role,
|
||||
"priority": priority,
|
||||
"status": "candidate",
|
||||
"source_role": "vpn-client",
|
||||
"route_scope": "client_node_to_exit_pool",
|
||||
"source_role": "ipv4-ingress",
|
||||
"route_scope": "ipv4_ingress_to_egress_pool",
|
||||
}
|
||||
if pair.entry != "" {
|
||||
candidate["entry_node_id"] = pair.entry
|
||||
|
||||
@@ -18,10 +18,10 @@ func TestMeshLatestObservationKeySeparatesRouteHealthByRoute(t *testing.T) {
|
||||
func TestMeshLatestObservationKeySeparatesConnectionManagerMode(t *testing.T) {
|
||||
key := meshLatestObservationKey(json.RawMessage(`{
|
||||
"observation_type":"peer_connection_manager",
|
||||
"transport_mode":"relay_control",
|
||||
"transport_mode":"relay_quic",
|
||||
"relay_node_id":"node-r"
|
||||
}`))
|
||||
if key != "peer_connection_manager:relay_control:node-r" {
|
||||
if key != "peer_connection_manager:relay_quic:node-r" {
|
||||
t.Fatalf("key = %q", key)
|
||||
}
|
||||
}
|
||||
@@ -192,7 +192,7 @@ func TestEnrichVPNClientEntryEndpointCandidatesAddsReportedQUICEndpoint(t *testi
|
||||
}
|
||||
}
|
||||
|
||||
func TestVPNEntryEndpointCandidatesKeepsQUICEndpointsAndRejectsLegacyHTTP(t *testing.T) {
|
||||
func TestVPNEntryEndpointCandidatesKeepsQUICEndpointsAndRejectsDisallowedHTTP(t *testing.T) {
|
||||
heartbeatMetadata := json.RawMessage(`{
|
||||
"mesh_endpoint_report": {
|
||||
"transport": "direct_quic",
|
||||
|
||||
@@ -29,7 +29,8 @@ type Repository interface {
|
||||
ExpireJoinTokens(ctx context.Context, clusterID string) error
|
||||
|
||||
CreateJoinRequest(ctx context.Context, input CreateJoinRequestInput, joinTokenID string) (NodeJoinRequest, error)
|
||||
GetJoinRequestForBootstrap(ctx context.Context, input GetJoinRequestBootstrapInput) (NodeJoinRequest, error)
|
||||
RegisterFabricNode(ctx context.Context, input RegisterFabricNodeInput) (ClusterNode, error)
|
||||
GetJoinRequestForJoin(ctx context.Context, input GetJoinRequestJoinInput) (NodeJoinRequest, error)
|
||||
ListJoinRequests(ctx context.Context, clusterID string) ([]NodeJoinRequest, error)
|
||||
ApproveJoinRequest(ctx context.Context, input ApproveJoinRequestInput) (ApprovedJoinRequest, error)
|
||||
SetJoinRequestApprovalAuthority(ctx context.Context, clusterID, joinRequestID string, payload json.RawMessage, signature ClusterSignature) (NodeJoinRequest, error)
|
||||
@@ -43,6 +44,7 @@ type Repository interface {
|
||||
ListNodeHeartbeats(ctx context.Context, clusterID, nodeID string, limit int) ([]NodeHeartbeat, error)
|
||||
CreateReleaseVersion(ctx context.Context, input CreateReleaseVersionInput) (ReleaseVersion, error)
|
||||
ListReleaseVersions(ctx context.Context, clusterID, product, channel string) ([]ReleaseVersion, error)
|
||||
GetReleaseArtifact(ctx context.Context, clusterID, artifactID string) (ReleaseArtifact, error)
|
||||
ListNodeUpdateServiceCandidates(ctx context.Context, clusterID string) ([]NodeUpdateServiceCandidate, error)
|
||||
UpsertNodeUpdatePolicy(ctx context.Context, input UpsertNodeUpdatePolicyInput) (NodeUpdatePolicy, error)
|
||||
GetNodeUpdatePolicy(ctx context.Context, clusterID, nodeID, product string) (NodeUpdatePolicy, error)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user