Files
rdp-proxy/backend/internal/modules/cluster/module.go
T
2026-04-28 22:29:50 +03:00

1269 lines
49 KiB
Go

package cluster
import (
"encoding/json"
"errors"
"net/http"
"strconv"
"time"
"github.com/go-chi/chi/v5"
"github.com/jackc/pgx/v5"
"github.com/example/remote-access-platform/backend/internal/platform/authority"
"github.com/example/remote-access-platform/backend/internal/platform/httpx"
"github.com/example/remote-access-platform/backend/internal/platform/module"
"github.com/example/remote-access-platform/backend/internal/platform/secrets"
)
type Module struct {
service *Service
}
func NewModule(deps module.Dependencies, verifiers ...*authority.Verifier) *Module {
store := NewPostgresStore(deps.Infra.DB, verifiers...)
if deps.Config.Secret.EncryptionKeyBase64 != "" {
if encryptor, err := secrets.NewEncryptor(deps.Config.Secret.EncryptionKeyBase64, deps.Config.Secret.EncryptionKeyID); err == nil {
store.WithClusterKeyEncryptor(encryptor)
}
}
return &Module{service: NewService(store)}
}
func (m *Module) Name() string {
return "cluster"
}
func (m *Module) RegisterRoutes(router chi.Router) {
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}/node-groups", m.listNodeGroups)
r.Post("/{clusterID}/node-groups", m.createNodeGroup)
r.Get("/{clusterID}/join-requests", m.listJoinRequests)
r.Post("/{clusterID}/join-requests", m.createJoinRequest)
r.Post("/{clusterID}/join-requests/{requestID}/approve", m.approveJoinRequest)
r.Post("/{clusterID}/join-requests/{requestID}/reject", m.rejectJoinRequest)
r.Post("/{clusterID}/join-tokens", m.createJoinToken)
r.Post("/{clusterID}/join-tokens/{tokenID}/revoke", m.revokeJoinToken)
r.Get("/{clusterID}/nodes/{nodeID}/roles", m.listNodeRoles)
r.Post("/{clusterID}/nodes/{nodeID}/roles", m.assignNodeRole)
r.Post("/{clusterID}/nodes/{nodeID}/heartbeats", m.recordHeartbeat)
r.Get("/{clusterID}/nodes/{nodeID}/heartbeats", m.listNodeHeartbeats)
r.Get("/{clusterID}/nodes/{nodeID}/testing-flags", m.getEffectiveNodeTestingFlags)
r.Get("/{clusterID}/nodes/{nodeID}/mesh/synthetic-config", m.getNodeSyntheticMeshConfig)
r.Post("/{clusterID}/nodes/{nodeID}/telemetry", m.recordNodeTelemetry)
r.Get("/{clusterID}/nodes/{nodeID}/telemetry", m.listNodeTelemetry)
r.Post("/{clusterID}/nodes/{nodeID}/membership/attach", m.attachExistingNodeToCluster)
r.Put("/{clusterID}/nodes/{nodeID}/group", m.assignNodeGroup)
r.Post("/{clusterID}/nodes/{nodeID}/identity/revoke", m.revokeNodeIdentity)
r.Post("/{clusterID}/nodes/{nodeID}/membership/disable", m.disableMembership)
r.Get("/{clusterID}/nodes/{nodeID}/workloads/desired", m.listDesiredWorkloads)
r.Put("/{clusterID}/nodes/{nodeID}/workloads/{serviceType}/desired", m.setDesiredWorkload)
r.Post("/{clusterID}/nodes/{nodeID}/workloads/{serviceType}/status", m.reportWorkloadStatus)
r.Get("/{clusterID}/nodes/{nodeID}/workloads/status", m.listWorkloadStatuses)
r.Get("/{clusterID}/mesh/links", m.listMeshLinks)
r.Post("/{clusterID}/mesh/links", m.reportMeshLink)
r.Get("/{clusterID}/mesh/route-intents", m.listRouteIntents)
r.Post("/{clusterID}/mesh/route-intents", m.createRouteIntent)
r.Get("/{clusterID}/mesh/qos-policies", m.listQoSPolicies)
r.Get("/{clusterID}/fabric/entry-points", m.listFabricEntryPoints)
r.Post("/{clusterID}/fabric/entry-points", m.createFabricEntryPoint)
r.Get("/{clusterID}/fabric/entry-points/{entryPointID}/nodes", m.listFabricEntryPointNodes)
r.Put("/{clusterID}/fabric/entry-points/{entryPointID}/nodes/{nodeID}", m.setFabricEntryPointNode)
r.Get("/{clusterID}/fabric/egress-pools", m.listFabricEgressPools)
r.Post("/{clusterID}/fabric/egress-pools", m.createFabricEgressPool)
r.Get("/{clusterID}/fabric/egress-pools/{egressPoolID}/nodes", m.listFabricEgressPoolNodes)
r.Put("/{clusterID}/fabric/egress-pools/{egressPoolID}/nodes/{nodeID}", m.setFabricEgressPoolNode)
r.Post("/{clusterID}/vpn-connection-leases/expire-stale", m.expireStaleVPNConnectionLeases)
r.Get("/{clusterID}/vpn-connections", m.listVPNConnections)
r.Post("/{clusterID}/vpn-connections", m.createVPNConnection)
r.Get("/{clusterID}/vpn-connections/{vpnConnectionID}", m.getVPNConnection)
r.Put("/{clusterID}/vpn-connections/{vpnConnectionID}/desired-state", m.updateVPNConnectionDesiredState)
r.Get("/{clusterID}/vpn-connections/{vpnConnectionID}/allowed-nodes", m.listVPNConnectionAllowedNodes)
r.Put("/{clusterID}/vpn-connections/{vpnConnectionID}/allowed-nodes", m.setVPNConnectionAllowedNodes)
r.Get("/{clusterID}/vpn-connections/{vpnConnectionID}/route-policies", m.listVPNConnectionRoutePolicies)
r.Post("/{clusterID}/vpn-connections/{vpnConnectionID}/route-policies", m.upsertVPNConnectionRoutePolicy)
r.Get("/{clusterID}/vpn-connections/{vpnConnectionID}/leases/active", m.getActiveVPNConnectionLease)
r.Post("/{clusterID}/vpn-connections/{vpnConnectionID}/leases/acquire", m.acquireVPNConnectionLease)
r.Post("/{clusterID}/vpn-connections/{vpnConnectionID}/leases/{leaseID}/renew", m.renewVPNConnectionLease)
r.Post("/{clusterID}/vpn-connections/{vpnConnectionID}/leases/{leaseID}/release", m.releaseVPNConnectionLease)
r.Post("/{clusterID}/vpn-connections/{vpnConnectionID}/leases/{leaseID}/fence", m.fenceVPNConnectionLease)
r.Get("/{clusterID}/authority", m.getClusterAuthority)
r.Put("/{clusterID}/authority", m.updateClusterAuthority)
r.Get("/{clusterID}/audit", m.listAuditEvents)
})
router.Get("/cluster-admin-summaries", m.listClusterAdminSummaries)
router.Get("/fabric/testing-flags", m.listFabricTestingFlags)
router.Put("/fabric/testing-flags", m.upsertFabricTestingFlag)
}
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) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"clusters": items})
}
func (m *Module) getCluster(w http.ResponseWriter, r *http.Request) {
item, err := m.service.GetCluster(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"cluster": item})
}
func (m *Module) createCluster(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Slug string `json:"slug"`
Name string `json:"name"`
Region *string `json:"region"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid cluster payload")
return
}
item, err := m.service.CreateCluster(r.Context(), CreateClusterInput{
ActorUserID: payload.ActorUserID,
Slug: payload.Slug,
Name: payload.Name,
Region: payload.Region,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"cluster": item})
}
func (m *Module) updateCluster(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Name string `json:"name"`
Status string `json:"status"`
Region *string `json:"region"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid cluster payload")
return
}
item, err := m.service.UpdateCluster(r.Context(), UpdateClusterInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
Name: payload.Name,
Status: payload.Status,
Region: payload.Region,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"cluster": item})
}
func (m *Module) listClusterNodes(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListClusterNodes(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"nodes": items})
}
func (m *Module) listNodeGroups(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListNodeGroups(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"node_groups": items})
}
func (m *Module) createNodeGroup(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
ParentGroupID *string `json:"parent_group_id"`
Name string `json:"name"`
Description *string `json:"description"`
SortOrder int `json:"sort_order"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid node group payload")
return
}
item, err := m.service.CreateNodeGroup(r.Context(), CreateNodeGroupInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
ParentGroupID: payload.ParentGroupID,
Name: payload.Name,
Description: payload.Description,
SortOrder: payload.SortOrder,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"node_group": item})
}
func (m *Module) createJoinToken(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Scope json.RawMessage `json:"scope"`
ExpiresAt *time.Time `json:"expires_at"`
MaxUses int `json:"max_uses"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid join token payload")
return
}
expiresAt := time.Time{}
if payload.ExpiresAt != nil {
expiresAt = *payload.ExpiresAt
}
item, err := m.service.CreateJoinToken(r.Context(), CreateJoinTokenInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
Scope: payload.Scope,
ExpiresAt: expiresAt,
MaxUses: payload.MaxUses,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"join_token": item})
}
func (m *Module) createJoinRequest(w http.ResponseWriter, r *http.Request) {
var payload struct {
JoinToken string `json:"join_token"`
NodeName string `json:"node_name"`
NodeFingerprint string `json:"node_fingerprint"`
PublicKey string `json:"public_key"`
ReportedCapabilities json.RawMessage `json:"reported_capabilities"`
ReportedFacts json.RawMessage `json:"reported_facts"`
RequestedRoles json.RawMessage `json:"requested_roles"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid join request payload")
return
}
item, err := m.service.CreateJoinRequest(r.Context(), CreateJoinRequestInput{
ClusterID: chi.URLParam(r, "clusterID"),
JoinToken: payload.JoinToken,
NodeName: payload.NodeName,
NodeFingerprint: payload.NodeFingerprint,
PublicKey: payload.PublicKey,
ReportedCapabilities: payload.ReportedCapabilities,
ReportedFacts: payload.ReportedFacts,
RequestedRoles: payload.RequestedRoles,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"join_request": item})
}
func (m *Module) listJoinRequests(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListJoinRequests(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"join_requests": items})
}
func (m *Module) approveJoinRequest(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
NodeKey string `json:"node_key"`
OwnershipType string `json:"ownership_type"`
OwnerOrganizationID *string `json:"owner_organization_id"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid join request approval payload")
return
}
item, err := m.service.ApproveJoinRequest(r.Context(), ApproveJoinRequestInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
JoinRequestID: chi.URLParam(r, "requestID"),
NodeKey: payload.NodeKey,
OwnershipType: payload.OwnershipType,
OwnerOrganizationID: payload.OwnerOrganizationID,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, item)
}
func (m *Module) rejectJoinRequest(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Reason string `json:"reason"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid join request rejection payload")
return
}
item, err := m.service.RejectJoinRequest(r.Context(), RejectJoinRequestInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
JoinRequestID: chi.URLParam(r, "requestID"),
Reason: payload.Reason,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"join_request": item})
}
func (m *Module) revokeJoinToken(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid join token revoke payload")
return
}
item, err := m.service.RevokeJoinToken(r.Context(), RevokeJoinTokenInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
TokenID: chi.URLParam(r, "tokenID"),
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"join_token": item})
}
func (m *Module) assignNodeRole(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
OrganizationID *string `json:"organization_id"`
Role string `json:"role"`
Status string `json:"status"`
Policy json.RawMessage `json:"policy"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid node role payload")
return
}
item, err := m.service.AssignNodeRole(r.Context(), AssignNodeRoleInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
NodeID: chi.URLParam(r, "nodeID"),
OrganizationID: payload.OrganizationID,
Role: payload.Role,
Status: payload.Status,
Policy: payload.Policy,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"role_assignment": item})
}
func (m *Module) listNodeRoles(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListNodeRoleAssignments(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"), chi.URLParam(r, "nodeID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"role_assignments": items})
}
func (m *Module) recordHeartbeat(w http.ResponseWriter, r *http.Request) {
var payload struct {
HealthStatus string `json:"health_status"`
ReportedVersion *string `json:"reported_version"`
Capabilities json.RawMessage `json:"capabilities"`
ServiceStates json.RawMessage `json:"service_states"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid node heartbeat payload")
return
}
item, err := m.service.RecordHeartbeat(r.Context(), RecordHeartbeatInput{
ClusterID: chi.URLParam(r, "clusterID"),
NodeID: chi.URLParam(r, "nodeID"),
HealthStatus: payload.HealthStatus,
ReportedVersion: payload.ReportedVersion,
Capabilities: payload.Capabilities,
ServiceStates: payload.ServiceStates,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
flags, _ := m.service.GetEffectiveNodeTestingFlags(r.Context(), chi.URLParam(r, "clusterID"), chi.URLParam(r, "nodeID"))
httpx.WriteJSON(w, http.StatusAccepted, map[string]any{"heartbeat": item, "testing_flags": flags})
}
func (m *Module) listNodeHeartbeats(w http.ResponseWriter, r *http.Request) {
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
items, err := m.service.ListNodeHeartbeats(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"), chi.URLParam(r, "nodeID"), limit)
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"heartbeats": items})
}
func (m *Module) getEffectiveNodeTestingFlags(w http.ResponseWriter, r *http.Request) {
item, err := m.service.GetEffectiveNodeTestingFlags(r.Context(), chi.URLParam(r, "clusterID"), chi.URLParam(r, "nodeID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"testing_flags": item})
}
func (m *Module) getNodeSyntheticMeshConfig(w http.ResponseWriter, r *http.Request) {
item, err := m.service.GetNodeSyntheticMeshConfig(r.Context(), GetNodeSyntheticMeshConfigInput{
ClusterID: chi.URLParam(r, "clusterID"),
NodeID: chi.URLParam(r, "nodeID"),
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"synthetic_mesh_config": item})
}
func (m *Module) recordNodeTelemetry(w http.ResponseWriter, r *http.Request) {
var payload struct {
CPUPercent *float64 `json:"cpu_percent"`
MemoryUsedBytes *int64 `json:"memory_used_bytes"`
MemoryTotalBytes *int64 `json:"memory_total_bytes"`
DiskUsedBytes *int64 `json:"disk_used_bytes"`
DiskTotalBytes *int64 `json:"disk_total_bytes"`
NetworkRxBytes *int64 `json:"network_rx_bytes"`
NetworkTxBytes *int64 `json:"network_tx_bytes"`
ProcessCount *int `json:"process_count"`
Payload json.RawMessage `json:"payload"`
ObservedAt *time.Time `json:"observed_at"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid node telemetry payload")
return
}
observedAt := time.Time{}
if payload.ObservedAt != nil {
observedAt = *payload.ObservedAt
}
item, err := m.service.RecordNodeTelemetry(r.Context(), RecordNodeTelemetryInput{
ClusterID: chi.URLParam(r, "clusterID"),
NodeID: chi.URLParam(r, "nodeID"),
CPUPercent: payload.CPUPercent,
MemoryUsedBytes: payload.MemoryUsedBytes,
MemoryTotalBytes: payload.MemoryTotalBytes,
DiskUsedBytes: payload.DiskUsedBytes,
DiskTotalBytes: payload.DiskTotalBytes,
NetworkRxBytes: payload.NetworkRxBytes,
NetworkTxBytes: payload.NetworkTxBytes,
ProcessCount: payload.ProcessCount,
Payload: payload.Payload,
ObservedAt: observedAt,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusAccepted, map[string]any{"telemetry": item})
}
func (m *Module) listNodeTelemetry(w http.ResponseWriter, r *http.Request) {
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
items, err := m.service.ListNodeTelemetry(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"), chi.URLParam(r, "nodeID"), limit)
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"telemetry": items})
}
func (m *Module) attachExistingNodeToCluster(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Roles []string `json:"roles"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid membership attach payload")
return
}
item, err := m.service.AttachExistingNodeToCluster(r.Context(), AttachExistingNodeInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
NodeID: chi.URLParam(r, "nodeID"),
Roles: payload.Roles,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"node": item})
}
func (m *Module) assignNodeGroup(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
GroupID *string `json:"group_id"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid node group assignment payload")
return
}
item, err := m.service.AssignNodeToGroup(r.Context(), AssignNodeGroupInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
NodeID: chi.URLParam(r, "nodeID"),
GroupID: payload.GroupID,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"node": item})
}
func (m *Module) revokeNodeIdentity(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Reason string `json:"reason"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid node identity revoke payload")
return
}
err := m.service.RevokeNodeIdentity(r.Context(), RevokeNodeIdentityInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
NodeID: chi.URLParam(r, "nodeID"),
Reason: payload.Reason,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusAccepted, map[string]any{"status": "accepted"})
}
func (m *Module) disableMembership(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Reason string `json:"reason"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid membership disable payload")
return
}
err := m.service.DisableClusterMembership(r.Context(), DisableMembershipInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
NodeID: chi.URLParam(r, "nodeID"),
Reason: payload.Reason,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusAccepted, map[string]any{"status": "accepted"})
}
func (m *Module) setDesiredWorkload(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
DesiredState string `json:"desired_state"`
Version *string `json:"version"`
RuntimeMode string `json:"runtime_mode"`
ArtifactRef *string `json:"artifact_ref"`
Config json.RawMessage `json:"config"`
Environment json.RawMessage `json:"environment"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid desired workload payload")
return
}
item, err := m.service.SetDesiredWorkload(r.Context(), SetDesiredWorkloadInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
NodeID: chi.URLParam(r, "nodeID"),
ServiceType: chi.URLParam(r, "serviceType"),
DesiredState: payload.DesiredState,
Version: payload.Version,
RuntimeMode: payload.RuntimeMode,
ArtifactRef: payload.ArtifactRef,
Config: payload.Config,
Environment: payload.Environment,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"desired_workload": item})
}
func (m *Module) listDesiredWorkloads(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListDesiredWorkloads(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"), chi.URLParam(r, "nodeID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"desired_workloads": items})
}
func (m *Module) reportWorkloadStatus(w http.ResponseWriter, r *http.Request) {
var payload struct {
ReportedState string `json:"reported_state"`
RuntimeMode string `json:"runtime_mode"`
Version *string `json:"version"`
StatusPayload json.RawMessage `json:"status_payload"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid workload status payload")
return
}
item, err := m.service.ReportWorkloadStatus(r.Context(), ReportWorkloadStatusInput{
ClusterID: chi.URLParam(r, "clusterID"),
NodeID: chi.URLParam(r, "nodeID"),
ServiceType: chi.URLParam(r, "serviceType"),
ReportedState: payload.ReportedState,
RuntimeMode: payload.RuntimeMode,
Version: payload.Version,
StatusPayload: payload.StatusPayload,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusAccepted, map[string]any{"workload_status": item})
}
func (m *Module) listWorkloadStatuses(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListLatestWorkloadStatuses(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"), chi.URLParam(r, "nodeID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"workload_statuses": items})
}
func (m *Module) reportMeshLink(w http.ResponseWriter, r *http.Request) {
var payload struct {
SourceNodeID string `json:"source_node_id"`
TargetNodeID string `json:"target_node_id"`
LinkStatus string `json:"link_status"`
LatencyMs *int `json:"latency_ms"`
QualityScore *int `json:"quality_score"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid mesh link payload")
return
}
item, err := m.service.ReportMeshLink(r.Context(), ReportMeshLinkInput{
ClusterID: chi.URLParam(r, "clusterID"),
SourceNodeID: payload.SourceNodeID,
TargetNodeID: payload.TargetNodeID,
LinkStatus: payload.LinkStatus,
LatencyMs: payload.LatencyMs,
QualityScore: payload.QualityScore,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusAccepted, map[string]any{"mesh_link": item})
}
func (m *Module) listMeshLinks(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListMeshLinks(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"mesh_links": items})
}
func (m *Module) createRouteIntent(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
SourceSelector json.RawMessage `json:"source_selector"`
DestinationSelector json.RawMessage `json:"destination_selector"`
ServiceClass string `json:"service_class"`
Priority int `json:"priority"`
Policy json.RawMessage `json:"policy"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid route intent payload")
return
}
item, err := m.service.CreateRouteIntent(r.Context(), CreateRouteIntentInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
SourceSelector: payload.SourceSelector,
DestinationSelector: payload.DestinationSelector,
ServiceClass: payload.ServiceClass,
Priority: payload.Priority,
Policy: payload.Policy,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"route_intent": item})
}
func (m *Module) listRouteIntents(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListRouteIntents(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"route_intents": items})
}
func (m *Module) listQoSPolicies(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListQoSPolicies(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"qos_policies": items})
}
func (m *Module) listFabricEntryPoints(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListFabricEntryPoints(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"entry_points": items})
}
func (m *Module) createFabricEntryPoint(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Name string `json:"name"`
Status string `json:"status"`
EndpointType string `json:"endpoint_type"`
PublicEndpoint *string `json:"public_endpoint"`
Policy json.RawMessage `json:"policy"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid fabric entry point payload")
return
}
item, err := m.service.CreateFabricEntryPoint(r.Context(), CreateFabricEntryPointInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
Name: payload.Name,
Status: payload.Status,
EndpointType: payload.EndpointType,
PublicEndpoint: payload.PublicEndpoint,
Policy: payload.Policy,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"entry_point": item})
}
func (m *Module) setFabricEntryPointNode(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Status string `json:"status"`
Priority int `json:"priority"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid fabric entry point node payload")
return
}
item, err := m.service.SetFabricEntryPointNode(r.Context(), SetFabricEntryPointNodeInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
EntryPointID: chi.URLParam(r, "entryPointID"),
NodeID: chi.URLParam(r, "nodeID"),
Status: payload.Status,
Priority: payload.Priority,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"entry_point_node": item})
}
func (m *Module) listFabricEntryPointNodes(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListFabricEntryPointNodes(
r.Context(),
r.URL.Query().Get("actor_user_id"),
chi.URLParam(r, "clusterID"),
chi.URLParam(r, "entryPointID"),
)
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"entry_point_nodes": items})
}
func (m *Module) listFabricEgressPools(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListFabricEgressPools(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"egress_pools": items})
}
func (m *Module) createFabricEgressPool(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Name string `json:"name"`
Status string `json:"status"`
Description *string `json:"description"`
RouteScope json.RawMessage `json:"route_scope"`
Policy json.RawMessage `json:"policy"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid fabric egress pool payload")
return
}
item, err := m.service.CreateFabricEgressPool(r.Context(), CreateFabricEgressPoolInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
Name: payload.Name,
Status: payload.Status,
Description: payload.Description,
RouteScope: payload.RouteScope,
Policy: payload.Policy,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"egress_pool": item})
}
func (m *Module) setFabricEgressPoolNode(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Status string `json:"status"`
Priority int `json:"priority"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid fabric egress pool node payload")
return
}
item, err := m.service.SetFabricEgressPoolNode(r.Context(), SetFabricEgressPoolNodeInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
EgressPoolID: chi.URLParam(r, "egressPoolID"),
NodeID: chi.URLParam(r, "nodeID"),
Status: payload.Status,
Priority: payload.Priority,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"egress_pool_node": item})
}
func (m *Module) listFabricEgressPoolNodes(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListFabricEgressPoolNodes(
r.Context(),
r.URL.Query().Get("actor_user_id"),
chi.URLParam(r, "clusterID"),
chi.URLParam(r, "egressPoolID"),
)
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"egress_pool_nodes": items})
}
func (m *Module) createVPNConnection(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
OrganizationID string `json:"organization_id"`
Name string `json:"name"`
TargetEndpoint json.RawMessage `json:"target_endpoint"`
ProtocolFamily string `json:"protocol_family"`
CredentialRef *string `json:"credential_ref"`
Mode string `json:"mode"`
DesiredState string `json:"desired_state"`
AllowedNodePolicy json.RawMessage `json:"allowed_node_policy"`
RoutingUsage json.RawMessage `json:"routing_usage"`
RoutePolicy json.RawMessage `json:"route_policy"`
QoSPolicy json.RawMessage `json:"qos_policy"`
PlacementPolicy json.RawMessage `json:"placement_policy"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid vpn connection payload")
return
}
item, err := m.service.CreateVPNConnection(r.Context(), CreateVPNConnectionInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
OrganizationID: payload.OrganizationID,
Name: payload.Name,
TargetEndpoint: payload.TargetEndpoint,
ProtocolFamily: payload.ProtocolFamily,
CredentialRef: payload.CredentialRef,
Mode: payload.Mode,
DesiredState: payload.DesiredState,
AllowedNodePolicy: payload.AllowedNodePolicy,
RoutingUsage: payload.RoutingUsage,
RoutePolicy: payload.RoutePolicy,
QoSPolicy: payload.QoSPolicy,
PlacementPolicy: payload.PlacementPolicy,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"vpn_connection": item})
}
func (m *Module) listVPNConnections(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListVPNConnections(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"vpn_connections": items})
}
func (m *Module) getVPNConnection(w http.ResponseWriter, r *http.Request) {
item, err := m.service.GetVPNConnection(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"), chi.URLParam(r, "vpnConnectionID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"vpn_connection": item})
}
func (m *Module) updateVPNConnectionDesiredState(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
DesiredState string `json:"desired_state"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid vpn connection desired state payload")
return
}
item, err := m.service.UpdateVPNConnectionDesiredState(r.Context(), UpdateVPNConnectionDesiredStateInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
VPNConnectionID: chi.URLParam(r, "vpnConnectionID"),
DesiredState: payload.DesiredState,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"vpn_connection": item})
}
func (m *Module) upsertVPNConnectionRoutePolicy(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
RouteType string `json:"route_type"`
Destination string `json:"destination"`
Action string `json:"action"`
ServiceType *string `json:"service_type"`
Priority int `json:"priority"`
Policy json.RawMessage `json:"policy"`
Status string `json:"status"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid vpn route policy payload")
return
}
item, err := m.service.UpsertVPNConnectionRoutePolicy(r.Context(), UpsertVPNConnectionRoutePolicyInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
VPNConnectionID: chi.URLParam(r, "vpnConnectionID"),
RouteType: payload.RouteType,
Destination: payload.Destination,
Action: payload.Action,
ServiceType: payload.ServiceType,
Priority: payload.Priority,
Policy: payload.Policy,
Status: payload.Status,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"route_policy": item})
}
func (m *Module) listVPNConnectionRoutePolicies(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListVPNConnectionRoutePolicies(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"), chi.URLParam(r, "vpnConnectionID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"route_policies": items})
}
func (m *Module) setVPNConnectionAllowedNodes(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
NodeIDs []string `json:"node_ids"`
RolePreference string `json:"role_preference"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid vpn allowed nodes payload")
return
}
items, err := m.service.SetVPNConnectionAllowedNodes(r.Context(), SetVPNConnectionAllowedNodesInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
VPNConnectionID: chi.URLParam(r, "vpnConnectionID"),
NodeIDs: payload.NodeIDs,
RolePreference: payload.RolePreference,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"allowed_nodes": items})
}
func (m *Module) listVPNConnectionAllowedNodes(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListVPNConnectionAllowedNodes(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"), chi.URLParam(r, "vpnConnectionID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"allowed_nodes": items})
}
func (m *Module) acquireVPNConnectionLease(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
OwnerNodeID string `json:"owner_node_id"`
TTLSeconds int `json:"ttl_seconds"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid vpn lease acquire payload")
return
}
item, err := m.service.AcquireVPNConnectionLease(r.Context(), AcquireVPNConnectionLeaseInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
VPNConnectionID: chi.URLParam(r, "vpnConnectionID"),
OwnerNodeID: payload.OwnerNodeID,
TTL: time.Duration(payload.TTLSeconds) * time.Second,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"lease": item})
}
func (m *Module) renewVPNConnectionLease(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
OwnerNodeID string `json:"owner_node_id"`
FencingToken string `json:"fencing_token"`
TTLSeconds int `json:"ttl_seconds"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid vpn lease renew payload")
return
}
item, err := m.service.RenewVPNConnectionLease(r.Context(), RenewVPNConnectionLeaseInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
VPNConnectionID: chi.URLParam(r, "vpnConnectionID"),
LeaseID: chi.URLParam(r, "leaseID"),
OwnerNodeID: payload.OwnerNodeID,
FencingToken: payload.FencingToken,
TTL: time.Duration(payload.TTLSeconds) * time.Second,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"lease": item})
}
func (m *Module) releaseVPNConnectionLease(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
OwnerNodeID string `json:"owner_node_id"`
FencingToken string `json:"fencing_token"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid vpn lease release payload")
return
}
item, err := m.service.ReleaseVPNConnectionLease(r.Context(), ReleaseVPNConnectionLeaseInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
VPNConnectionID: chi.URLParam(r, "vpnConnectionID"),
LeaseID: chi.URLParam(r, "leaseID"),
OwnerNodeID: payload.OwnerNodeID,
FencingToken: payload.FencingToken,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"lease": item})
}
func (m *Module) fenceVPNConnectionLease(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
Reason string `json:"reason"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid vpn lease fence payload")
return
}
item, err := m.service.FenceVPNConnectionLease(r.Context(), FenceVPNConnectionLeaseInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
VPNConnectionID: chi.URLParam(r, "vpnConnectionID"),
LeaseID: chi.URLParam(r, "leaseID"),
Reason: payload.Reason,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"lease": item})
}
func (m *Module) getActiveVPNConnectionLease(w http.ResponseWriter, r *http.Request) {
item, err := m.service.GetActiveVPNConnectionLease(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"), chi.URLParam(r, "vpnConnectionID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"lease": item})
}
func (m *Module) expireStaleVPNConnectionLeases(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid vpn stale lease payload")
return
}
items, err := m.service.ExpireStaleVPNConnectionLeases(r.Context(), ExpireStaleVPNConnectionLeasesInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"expired_leases": items})
}
func (m *Module) getClusterAuthority(w http.ResponseWriter, r *http.Request) {
item, err := m.service.GetClusterAuthorityState(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"authority_state": item})
}
func (m *Module) updateClusterAuthority(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
AuthorityState string `json:"authority_state"`
MutationMode string `json:"mutation_mode"`
Notes *string `json:"notes"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid cluster authority payload")
return
}
item, err := m.service.UpdateClusterAuthorityState(r.Context(), UpdateClusterAuthorityInput{
ActorUserID: payload.ActorUserID,
ClusterID: chi.URLParam(r, "clusterID"),
AuthorityState: payload.AuthorityState,
MutationMode: payload.MutationMode,
Notes: payload.Notes,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"authority_state": item})
}
func (m *Module) listClusterAdminSummaries(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListClusterAdminSummaries(r.Context(), r.URL.Query().Get("actor_user_id"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"cluster_summaries": items})
}
func (m *Module) listFabricTestingFlags(w http.ResponseWriter, r *http.Request) {
items, err := m.service.ListFabricTestingFlags(r.Context(), r.URL.Query().Get("actor_user_id"))
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"testing_flags": items})
}
func (m *Module) upsertFabricTestingFlag(w http.ResponseWriter, r *http.Request) {
var payload struct {
ActorUserID string `json:"actor_user_id"`
ScopeType string `json:"scope_type"`
ScopeID *string `json:"scope_id"`
ClusterID *string `json:"cluster_id"`
Enabled bool `json:"enabled"`
TelemetryEnabled bool `json:"telemetry_enabled"`
SyntheticLinksEnabled bool `json:"synthetic_links_enabled"`
HistoryRetentionHours int `json:"history_retention_hours"`
Metadata json.RawMessage `json:"metadata"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid testing flag payload")
return
}
item, err := m.service.UpsertFabricTestingFlag(r.Context(), UpsertFabricTestingFlagInput{
ActorUserID: payload.ActorUserID,
ScopeType: payload.ScopeType,
ScopeID: payload.ScopeID,
ClusterID: payload.ClusterID,
Enabled: payload.Enabled,
TelemetryEnabled: payload.TelemetryEnabled,
SyntheticLinksEnabled: payload.SyntheticLinksEnabled,
HistoryRetentionHours: payload.HistoryRetentionHours,
Metadata: payload.Metadata,
})
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"testing_flag": item})
}
func (m *Module) listAuditEvents(w http.ResponseWriter, r *http.Request) {
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
items, err := m.service.ListAuditEvents(r.Context(), r.URL.Query().Get("actor_user_id"), chi.URLParam(r, "clusterID"), limit)
if writeServiceError(w, err) {
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"audit_events": items})
}
func writeServiceError(w http.ResponseWriter, err error) bool {
if err == nil {
return false
}
switch {
case errors.Is(err, ErrAccessDenied):
httpx.WriteError(w, http.StatusForbidden, err.Error())
case errors.Is(err, ErrVPNLeaseOwnerNotAllowed), errors.Is(err, ErrVPNLeaseOwnerRoleRequired):
httpx.WriteError(w, http.StatusForbidden, err.Error())
case errors.Is(err, ErrClusterReadOnly):
httpx.WriteError(w, http.StatusConflict, err.Error())
case errors.Is(err, ErrVPNLeaseAlreadyActive):
httpx.WriteError(w, http.StatusConflict, err.Error())
case errors.Is(err, ErrInvalidPayload), errors.Is(err, ErrInvalidJoinToken), errors.Is(err, ErrInvalidNodeRole):
httpx.WriteError(w, http.StatusBadRequest, err.Error())
case errors.Is(err, ErrInvalidCluster), errors.Is(err, ErrInvalidJoinRequest), errors.Is(err, ErrInvalidVPNConnection), errors.Is(err, ErrInvalidVPNLease), errors.Is(err, pgx.ErrNoRows):
httpx.WriteError(w, http.StatusNotFound, err.Error())
default:
httpx.WriteError(w, http.StatusInternalServerError, err.Error())
}
return true
}