mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-07 00:37:36 +00:00
643 lines
21 KiB
Go
643 lines
21 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
|
|
)
|
|
|
|
// MonitoredSystemLedgerEntry represents a single counted top-level monitored
|
|
// system.
|
|
type MonitoredSystemLedgerEntry struct {
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
Status string `json:"status"` // "online", "warning", "offline", "unknown"
|
|
StatusExplanation MonitoredSystemLedgerStatusExplanation `json:"status_explanation"`
|
|
LatestIncludedSignal MonitoredSystemLedgerLatestSignal `json:"latest_included_signal"`
|
|
Source string `json:"source"`
|
|
Explanation MonitoredSystemLedgerExplanation `json:"explanation"`
|
|
}
|
|
|
|
type MonitoredSystemLedgerLatestSignal struct {
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
Source string `json:"source"`
|
|
At string `json:"at"`
|
|
}
|
|
|
|
type MonitoredSystemLedgerStatusExplanation struct {
|
|
Summary string `json:"summary"`
|
|
Reasons []MonitoredSystemLedgerStatusReason `json:"reasons"`
|
|
}
|
|
|
|
type MonitoredSystemLedgerStatusReason struct {
|
|
Kind string `json:"kind"`
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
Source string `json:"source"`
|
|
Status string `json:"status"`
|
|
ReportedAt string `json:"reported_at"`
|
|
Summary string `json:"summary"`
|
|
}
|
|
|
|
type MonitoredSystemLedgerExplanation struct {
|
|
Summary string `json:"summary"`
|
|
Reasons []MonitoredSystemLedgerExplanationReason `json:"reasons"`
|
|
Surfaces []MonitoredSystemLedgerExplanationSurface `json:"surfaces"`
|
|
}
|
|
|
|
type MonitoredSystemLedgerExplanationReason struct {
|
|
Kind string `json:"kind"`
|
|
Signal string `json:"signal"`
|
|
Summary string `json:"summary"`
|
|
}
|
|
|
|
type MonitoredSystemLedgerExplanationSurface struct {
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
Source string `json:"source"`
|
|
}
|
|
|
|
// MonitoredSystemLedgerResponse is the response for GET /api/license/monitored-system-ledger.
|
|
type MonitoredSystemLedgerResponse struct {
|
|
Systems []MonitoredSystemLedgerEntry `json:"systems"`
|
|
Total int `json:"total"`
|
|
Limit int `json:"limit"` // 0 = unlimited
|
|
}
|
|
|
|
// MonitoredSystemLedgerExplainRequest describes an optional monitored-system
|
|
// preview to explain alongside the current counted ledger.
|
|
type MonitoredSystemLedgerExplainRequest struct {
|
|
Candidate *MonitoredSystemLedgerPreviewCandidate `json:"candidate"`
|
|
Replacement *MonitoredSystemLedgerPreviewReplacement `json:"replacement"`
|
|
}
|
|
|
|
// MonitoredSystemLedgerExplainResponse is the response for
|
|
// POST /api/license/monitored-system-ledger/explain.
|
|
type MonitoredSystemLedgerExplainResponse struct {
|
|
Ledger MonitoredSystemLedgerResponse `json:"ledger"`
|
|
Preview *MonitoredSystemLedgerPreviewResponse `json:"preview"`
|
|
}
|
|
|
|
// MonitoredSystemLedgerPreviewRequest describes one prospective monitored-system
|
|
// add or replacement to project through the canonical grouped ledger.
|
|
type MonitoredSystemLedgerPreviewRequest struct {
|
|
Candidate MonitoredSystemLedgerPreviewCandidate `json:"candidate"`
|
|
Replacement *MonitoredSystemLedgerPreviewReplacement `json:"replacement"`
|
|
}
|
|
|
|
type MonitoredSystemLedgerPreviewCandidate struct {
|
|
Source string `json:"source"`
|
|
Type string `json:"type"`
|
|
Name string `json:"name"`
|
|
Hostname string `json:"hostname"`
|
|
HostURL string `json:"host_url"`
|
|
AgentID string `json:"agent_id"`
|
|
MachineID string `json:"machine_id"`
|
|
ResourceID string `json:"resource_id"`
|
|
Active *bool `json:"active,omitempty"`
|
|
}
|
|
|
|
type MonitoredSystemLedgerPreviewReplacement struct {
|
|
Source string `json:"source"`
|
|
Name string `json:"name"`
|
|
Hostname string `json:"hostname"`
|
|
HostURL string `json:"host_url"`
|
|
AgentID string `json:"agent_id"`
|
|
MachineID string `json:"machine_id"`
|
|
ResourceID string `json:"resource_id"`
|
|
}
|
|
|
|
// MonitoredSystemLedgerPreviewResponse is the response for
|
|
// POST /api/license/monitored-system-ledger/preview.
|
|
type MonitoredSystemLedgerPreviewResponse struct {
|
|
CurrentCount int `json:"current_count"`
|
|
ProjectedCount int `json:"projected_count"`
|
|
AdditionalCount int `json:"additional_count"`
|
|
Limit int `json:"limit"`
|
|
WouldExceedLimit bool `json:"would_exceed_limit"`
|
|
Effect string `json:"effect"`
|
|
CurrentSystems []MonitoredSystemLedgerEntry `json:"current_systems"`
|
|
ProjectedSystems []MonitoredSystemLedgerEntry `json:"projected_systems"`
|
|
CurrentSystem *MonitoredSystemLedgerEntry `json:"current_system"`
|
|
ProjectedSystem *MonitoredSystemLedgerEntry `json:"projected_system"`
|
|
}
|
|
|
|
func EmptyMonitoredSystemLedgerResponse() MonitoredSystemLedgerResponse {
|
|
return MonitoredSystemLedgerResponse{}.NormalizeCollections()
|
|
}
|
|
|
|
func (r MonitoredSystemLedgerResponse) NormalizeCollections() MonitoredSystemLedgerResponse {
|
|
if r.Systems == nil {
|
|
r.Systems = []MonitoredSystemLedgerEntry{}
|
|
}
|
|
for i := range r.Systems {
|
|
r.Systems[i] = r.Systems[i].NormalizeCollections()
|
|
}
|
|
return r
|
|
}
|
|
|
|
func (r MonitoredSystemLedgerExplainResponse) NormalizeCollections() MonitoredSystemLedgerExplainResponse {
|
|
r.Ledger = r.Ledger.NormalizeCollections()
|
|
if r.Preview != nil {
|
|
preview := r.Preview.NormalizeCollections()
|
|
r.Preview = &preview
|
|
}
|
|
return r
|
|
}
|
|
|
|
func (e MonitoredSystemLedgerEntry) NormalizeCollections() MonitoredSystemLedgerEntry {
|
|
if e.LatestIncludedSignal.Name == "" {
|
|
e.LatestIncludedSignal.Name = "Unnamed source"
|
|
}
|
|
if e.LatestIncludedSignal.Type == "" {
|
|
e.LatestIncludedSignal.Type = "system"
|
|
}
|
|
if e.StatusExplanation.Reasons == nil {
|
|
e.StatusExplanation.Reasons = []MonitoredSystemLedgerStatusReason{}
|
|
}
|
|
if e.Explanation.Reasons == nil {
|
|
e.Explanation.Reasons = []MonitoredSystemLedgerExplanationReason{}
|
|
}
|
|
if e.Explanation.Surfaces == nil {
|
|
e.Explanation.Surfaces = []MonitoredSystemLedgerExplanationSurface{}
|
|
}
|
|
return e
|
|
}
|
|
|
|
func (r *Router) handleMonitoredSystemLedger(w http.ResponseWriter, req *http.Request) {
|
|
usage := monitoredSystemUsage(r.getTenantMonitor(req.Context()))
|
|
if !usage.available {
|
|
writeMonitoredSystemUsageUnavailable(w, usage.unavailableReason)
|
|
return
|
|
}
|
|
|
|
resp := monitoredSystemLedgerResponseFromReadState(req.Context(), usage.readState)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(resp.NormalizeCollections())
|
|
}
|
|
|
|
func (r *Router) handleMonitoredSystemLedgerPreview(w http.ResponseWriter, req *http.Request) {
|
|
var previewReq MonitoredSystemLedgerPreviewRequest
|
|
decoder := json.NewDecoder(req.Body)
|
|
if err := decoder.Decode(&previewReq); err != nil {
|
|
writeErrorResponse(w, http.StatusBadRequest, "invalid_request", "Invalid request body", map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
candidate, err := previewReq.Candidate.toUnifiedCandidate()
|
|
if err != nil {
|
|
writeErrorResponse(w, http.StatusBadRequest, "validation_error", err.Error(), nil)
|
|
return
|
|
}
|
|
|
|
monitor := r.getTenantMonitor(req.Context())
|
|
usage := monitoredSystemUsage(monitor)
|
|
if !usage.available {
|
|
writeMonitoredSystemUsageUnavailable(w, usage.unavailableReason)
|
|
return
|
|
}
|
|
|
|
var preview unifiedresources.MonitoredSystemProjectionPreview
|
|
if previewReq.Replacement != nil {
|
|
replacement, err := previewReq.Replacement.toUnifiedReplacement(candidate.Source)
|
|
if err != nil {
|
|
writeErrorResponse(w, http.StatusBadRequest, "validation_error", err.Error(), nil)
|
|
return
|
|
}
|
|
preview = unifiedresources.PreviewMonitoredSystemCandidateReplacement(usage.readState, replacement, candidate)
|
|
} else {
|
|
preview = unifiedresources.PreviewMonitoredSystemCandidate(usage.readState, candidate)
|
|
}
|
|
|
|
if candidate.CountsTowardMonitoredSystems() && len(preview.ProjectedSystems) == 0 {
|
|
writeErrorResponse(w, http.StatusBadRequest, "validation_error", "Candidate did not resolve to a canonical monitored system preview", nil)
|
|
return
|
|
}
|
|
|
|
resp := monitoredSystemLedgerPreviewResponse(req.Context(), previewReq.Replacement != nil, preview)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(resp.NormalizeCollections())
|
|
}
|
|
|
|
func (r *Router) handleMonitoredSystemLedgerExplain(w http.ResponseWriter, req *http.Request) {
|
|
var explainReq MonitoredSystemLedgerExplainRequest
|
|
decoder := json.NewDecoder(req.Body)
|
|
if err := decoder.Decode(&explainReq); err != nil && err != io.EOF {
|
|
writeErrorResponse(w, http.StatusBadRequest, "invalid_request", "Invalid request body", map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
if explainReq.Candidate == nil && explainReq.Replacement != nil {
|
|
writeErrorResponse(w, http.StatusBadRequest, "validation_error", "candidate is required when replacement is provided", nil)
|
|
return
|
|
}
|
|
|
|
usage := monitoredSystemUsage(r.getTenantMonitor(req.Context()))
|
|
if !usage.available {
|
|
writeMonitoredSystemUsageUnavailable(w, usage.unavailableReason)
|
|
return
|
|
}
|
|
|
|
resp := MonitoredSystemLedgerExplainResponse{
|
|
Ledger: monitoredSystemLedgerResponseFromReadState(req.Context(), usage.readState),
|
|
}
|
|
|
|
if explainReq.Candidate != nil {
|
|
candidate, err := explainReq.Candidate.toUnifiedCandidate()
|
|
if err != nil {
|
|
writeErrorResponse(w, http.StatusBadRequest, "validation_error", err.Error(), nil)
|
|
return
|
|
}
|
|
|
|
var preview unifiedresources.MonitoredSystemProjectionPreview
|
|
if explainReq.Replacement != nil {
|
|
replacement, err := explainReq.Replacement.toUnifiedReplacement(candidate.Source)
|
|
if err != nil {
|
|
writeErrorResponse(w, http.StatusBadRequest, "validation_error", err.Error(), nil)
|
|
return
|
|
}
|
|
preview = unifiedresources.PreviewMonitoredSystemCandidateReplacement(
|
|
usage.readState,
|
|
replacement,
|
|
candidate,
|
|
)
|
|
} else {
|
|
preview = unifiedresources.PreviewMonitoredSystemCandidate(usage.readState, candidate)
|
|
}
|
|
|
|
if candidate.CountsTowardMonitoredSystems() && len(preview.ProjectedSystems) == 0 {
|
|
writeErrorResponse(w, http.StatusBadRequest, "validation_error", "Candidate did not resolve to a canonical monitored system preview", nil)
|
|
return
|
|
}
|
|
|
|
previewResp := monitoredSystemLedgerPreviewResponse(
|
|
req.Context(),
|
|
explainReq.Replacement != nil,
|
|
preview,
|
|
).NormalizeCollections()
|
|
resp.Preview = &previewResp
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(resp.NormalizeCollections())
|
|
}
|
|
|
|
func (r MonitoredSystemLedgerPreviewResponse) NormalizeCollections() MonitoredSystemLedgerPreviewResponse {
|
|
if r.CurrentSystems == nil {
|
|
r.CurrentSystems = []MonitoredSystemLedgerEntry{}
|
|
}
|
|
if r.ProjectedSystems == nil {
|
|
r.ProjectedSystems = []MonitoredSystemLedgerEntry{}
|
|
}
|
|
for i := range r.CurrentSystems {
|
|
r.CurrentSystems[i] = r.CurrentSystems[i].NormalizeCollections()
|
|
}
|
|
for i := range r.ProjectedSystems {
|
|
r.ProjectedSystems[i] = r.ProjectedSystems[i].NormalizeCollections()
|
|
}
|
|
if r.CurrentSystem != nil {
|
|
entry := r.CurrentSystem.NormalizeCollections()
|
|
r.CurrentSystem = &entry
|
|
}
|
|
if r.ProjectedSystem != nil {
|
|
entry := r.ProjectedSystem.NormalizeCollections()
|
|
r.ProjectedSystem = &entry
|
|
}
|
|
return r
|
|
}
|
|
|
|
func monitoredSystemLedgerEntry(system unifiedresources.MonitoredSystemRecord) MonitoredSystemLedgerEntry {
|
|
status := normalizeStatus(string(system.Status))
|
|
latestIncludedSignal := monitoredSystemLedgerLatestSignal(system.LatestIncludedSignal)
|
|
return MonitoredSystemLedgerEntry{
|
|
Name: system.Name,
|
|
Type: system.Type,
|
|
Status: status,
|
|
StatusExplanation: monitoredSystemLedgerStatusExplanation(system.StatusExplanation, status),
|
|
LatestIncludedSignal: latestIncludedSignal,
|
|
Source: system.Source,
|
|
Explanation: monitoredSystemLedgerExplanation(system.Explanation),
|
|
}
|
|
}
|
|
|
|
func monitoredSystemLedgerEntryPointer(system *unifiedresources.MonitoredSystemRecord) *MonitoredSystemLedgerEntry {
|
|
if system == nil {
|
|
return nil
|
|
}
|
|
entry := monitoredSystemLedgerEntry(*system)
|
|
return &entry
|
|
}
|
|
|
|
func monitoredSystemLedgerEntries(
|
|
systems []unifiedresources.MonitoredSystemRecord,
|
|
) []MonitoredSystemLedgerEntry {
|
|
if len(systems) == 0 {
|
|
return []MonitoredSystemLedgerEntry{}
|
|
}
|
|
entries := make([]MonitoredSystemLedgerEntry, 0, len(systems))
|
|
for _, system := range systems {
|
|
entries = append(entries, monitoredSystemLedgerEntry(system))
|
|
}
|
|
return entries
|
|
}
|
|
|
|
func monitoredSystemLedgerResponseFromReadState(
|
|
ctx context.Context,
|
|
rs unifiedresources.ReadState,
|
|
) MonitoredSystemLedgerResponse {
|
|
systems := unifiedresources.MonitoredSystems(rs)
|
|
entries := monitoredSystemLedgerEntries(systems)
|
|
return MonitoredSystemLedgerResponse{
|
|
Systems: entries,
|
|
Total: len(entries),
|
|
Limit: maxMonitoredSystemsLimitForContext(ctx),
|
|
}
|
|
}
|
|
|
|
func monitoredSystemLedgerPreviewResponse(
|
|
ctx context.Context,
|
|
hasReplacement bool,
|
|
preview unifiedresources.MonitoredSystemProjectionPreview,
|
|
) MonitoredSystemLedgerPreviewResponse {
|
|
limit := maxMonitoredSystemsLimitForContext(ctx)
|
|
decision := monitoredSystemLimitDecisionFromAdditional(ctx, limit, preview.CurrentCount, preview.AdditionalCount)
|
|
currentSystems := monitoredSystemLedgerEntries(preview.CurrentSystems)
|
|
projectedSystems := monitoredSystemLedgerEntries(preview.ProjectedSystems)
|
|
|
|
resp := MonitoredSystemLedgerPreviewResponse{
|
|
CurrentCount: preview.CurrentCount,
|
|
ProjectedCount: preview.ProjectedCount,
|
|
AdditionalCount: preview.AdditionalCount,
|
|
Limit: limit,
|
|
WouldExceedLimit: decision.exceeded,
|
|
Effect: monitoredSystemLedgerPreviewEffect(hasReplacement, preview),
|
|
CurrentSystems: currentSystems,
|
|
ProjectedSystems: projectedSystems,
|
|
}
|
|
if len(currentSystems) == 1 {
|
|
entry := currentSystems[0]
|
|
resp.CurrentSystem = &entry
|
|
}
|
|
if len(projectedSystems) == 1 {
|
|
entry := projectedSystems[0]
|
|
resp.ProjectedSystem = &entry
|
|
}
|
|
return resp
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Status helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func normalizeStatus(s string) string {
|
|
switch s {
|
|
case "online", "warning", "offline", "unknown":
|
|
return s
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
func monitoredSystemLedgerStatusExplanation(
|
|
explanation unifiedresources.MonitoredSystemStatusExplanation,
|
|
status string,
|
|
) MonitoredSystemLedgerStatusExplanation {
|
|
reasons := make([]MonitoredSystemLedgerStatusReason, 0, len(explanation.Reasons))
|
|
for _, reason := range explanation.Reasons {
|
|
reasons = append(reasons, MonitoredSystemLedgerStatusReason{
|
|
Kind: reason.Kind,
|
|
Name: reason.Name,
|
|
Type: reason.Type,
|
|
Source: reason.Source,
|
|
Status: normalizeMonitoredSystemLedgerReasonStatus(reason.Status),
|
|
ReportedAt: formatMonitoredSystemTime(reason.ReportedAt),
|
|
Summary: reason.Summary,
|
|
})
|
|
}
|
|
|
|
summary := explanation.Summary
|
|
if summary == "" {
|
|
summary = defaultMonitoredSystemLedgerStatusSummary(status)
|
|
}
|
|
|
|
return MonitoredSystemLedgerStatusExplanation{
|
|
Summary: summary,
|
|
Reasons: reasons,
|
|
}
|
|
}
|
|
|
|
func defaultMonitoredSystemLedgerStatusSummary(status string) string {
|
|
switch status {
|
|
case "online":
|
|
return "All included top-level collection paths currently report online status."
|
|
case "warning":
|
|
return "At least one included top-level collection path is degraded, so Pulse marks this monitored system as warning."
|
|
case "offline":
|
|
return "At least one included source is offline or disconnected, so Pulse marks this monitored system as offline."
|
|
default:
|
|
return "Pulse cannot determine a canonical runtime status for this monitored system yet."
|
|
}
|
|
}
|
|
|
|
func normalizeMonitoredSystemLedgerReasonStatus(status string) string {
|
|
switch status {
|
|
case "online", "stale", "offline", "unknown":
|
|
return status
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
func normalizeMonitoredSystemLedgerSource(source string) string {
|
|
switch source {
|
|
case "agent", "docker", "kubernetes", "pbs", "pmg", "proxmox", "truenas", "vmware":
|
|
return source
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func monitoredSystemLedgerLatestSignal(
|
|
signal unifiedresources.MonitoredSystemLatestSignal,
|
|
) MonitoredSystemLedgerLatestSignal {
|
|
return MonitoredSystemLedgerLatestSignal{
|
|
Name: signal.Name,
|
|
Type: signal.Type,
|
|
Source: normalizeMonitoredSystemLedgerSource(signal.Source),
|
|
At: formatMonitoredSystemTime(signal.At),
|
|
}
|
|
}
|
|
|
|
func formatMonitoredSystemTime(t time.Time) string {
|
|
if t.IsZero() {
|
|
return ""
|
|
}
|
|
return t.UTC().Format(time.RFC3339)
|
|
}
|
|
|
|
func monitoredSystemLedgerExplanation(
|
|
explanation unifiedresources.MonitoredSystemGroupingExplanation,
|
|
) MonitoredSystemLedgerExplanation {
|
|
reasons := make([]MonitoredSystemLedgerExplanationReason, 0, len(explanation.Reasons))
|
|
for _, reason := range explanation.Reasons {
|
|
reasons = append(reasons, MonitoredSystemLedgerExplanationReason{
|
|
Kind: reason.Kind,
|
|
Signal: reason.Signal,
|
|
Summary: reason.Summary,
|
|
})
|
|
}
|
|
|
|
surfaces := make([]MonitoredSystemLedgerExplanationSurface, 0, len(explanation.Surfaces))
|
|
for _, surface := range explanation.Surfaces {
|
|
surfaces = append(surfaces, MonitoredSystemLedgerExplanationSurface{
|
|
Name: surface.Name,
|
|
Type: surface.Type,
|
|
Source: surface.Source,
|
|
})
|
|
}
|
|
|
|
return MonitoredSystemLedgerExplanation{
|
|
Summary: explanation.Summary,
|
|
Reasons: reasons,
|
|
Surfaces: surfaces,
|
|
}
|
|
}
|
|
|
|
func (c MonitoredSystemLedgerPreviewCandidate) toUnifiedCandidate() (unifiedresources.MonitoredSystemCandidate, error) {
|
|
source := normalizedMonitoredSystemLedgerDataSource(c.Source)
|
|
if source == "" {
|
|
return unifiedresources.MonitoredSystemCandidate{}, errMonitoredSystemLedgerValidation("candidate.source is required")
|
|
}
|
|
if strings.TrimSpace(c.Name) == "" &&
|
|
strings.TrimSpace(c.Hostname) == "" &&
|
|
strings.TrimSpace(c.HostURL) == "" &&
|
|
strings.TrimSpace(c.ResourceID) == "" &&
|
|
strings.TrimSpace(c.AgentID) == "" &&
|
|
strings.TrimSpace(c.MachineID) == "" {
|
|
return unifiedresources.MonitoredSystemCandidate{}, errMonitoredSystemLedgerValidation("candidate must include at least one canonical identifier or display field")
|
|
}
|
|
|
|
return unifiedresources.MonitoredSystemCandidate{
|
|
Source: source,
|
|
Type: unifiedresources.ResourceType(strings.TrimSpace(c.Type)),
|
|
Name: strings.TrimSpace(c.Name),
|
|
Hostname: strings.TrimSpace(c.Hostname),
|
|
HostURL: strings.TrimSpace(c.HostURL),
|
|
AgentID: strings.TrimSpace(c.AgentID),
|
|
MachineID: strings.TrimSpace(c.MachineID),
|
|
ResourceID: strings.TrimSpace(c.ResourceID),
|
|
State: monitoredSystemLedgerCandidateState(c.Active),
|
|
}, nil
|
|
}
|
|
|
|
func monitoredSystemLedgerCandidateState(
|
|
active *bool,
|
|
) unifiedresources.MonitoredSystemCandidateState {
|
|
if active != nil && !*active {
|
|
return unifiedresources.MonitoredSystemCandidateStateInactive
|
|
}
|
|
return unifiedresources.MonitoredSystemCandidateStateActive
|
|
}
|
|
|
|
func (r MonitoredSystemLedgerPreviewReplacement) toUnifiedReplacement(
|
|
fallbackSource unifiedresources.DataSource,
|
|
) (unifiedresources.MonitoredSystemReplacement, error) {
|
|
source := normalizedMonitoredSystemLedgerDataSource(r.Source)
|
|
if source == "" {
|
|
source = fallbackSource
|
|
}
|
|
if source == "" {
|
|
return unifiedresources.MonitoredSystemReplacement{}, errMonitoredSystemLedgerValidation("replacement.source is required")
|
|
}
|
|
if strings.TrimSpace(r.Name) == "" &&
|
|
strings.TrimSpace(r.Hostname) == "" &&
|
|
strings.TrimSpace(r.HostURL) == "" &&
|
|
strings.TrimSpace(r.ResourceID) == "" &&
|
|
strings.TrimSpace(r.AgentID) == "" &&
|
|
strings.TrimSpace(r.MachineID) == "" {
|
|
return unifiedresources.MonitoredSystemReplacement{}, errMonitoredSystemLedgerValidation("replacement must include at least one canonical selector field")
|
|
}
|
|
|
|
return unifiedresources.MonitoredSystemReplacement{
|
|
Source: source,
|
|
Selector: unifiedresources.MonitoredSystemReplacementSelector{
|
|
Name: strings.TrimSpace(r.Name),
|
|
Hostname: strings.TrimSpace(r.Hostname),
|
|
HostURL: strings.TrimSpace(r.HostURL),
|
|
AgentID: strings.TrimSpace(r.AgentID),
|
|
MachineID: strings.TrimSpace(r.MachineID),
|
|
ResourceID: strings.TrimSpace(r.ResourceID),
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func normalizedMonitoredSystemLedgerDataSource(source string) unifiedresources.DataSource {
|
|
normalized := normalizeMonitoredSystemLedgerSource(strings.ToLower(strings.TrimSpace(source)))
|
|
if normalized == "" {
|
|
return ""
|
|
}
|
|
return unifiedresources.DataSource(normalized)
|
|
}
|
|
|
|
func monitoredSystemLedgerPreviewEffect(
|
|
hasReplacement bool,
|
|
preview unifiedresources.MonitoredSystemProjectionPreview,
|
|
) string {
|
|
currentCount := len(preview.CurrentSystems)
|
|
projectedCount := len(preview.ProjectedSystems)
|
|
|
|
if !hasReplacement {
|
|
if currentCount == 0 && projectedCount == 0 {
|
|
return "no_change"
|
|
}
|
|
if preview.AdditionalCount == 0 && currentCount > 0 {
|
|
return "attaches_existing"
|
|
}
|
|
if projectedCount > 1 && currentCount > 0 {
|
|
return "mixed_existing_and_new"
|
|
}
|
|
if projectedCount > 1 {
|
|
return "creates_multiple"
|
|
}
|
|
return "creates_new"
|
|
}
|
|
if currentCount > 0 && projectedCount == 0 {
|
|
if currentCount > 1 {
|
|
return "removes_multiple"
|
|
}
|
|
return "removes_existing"
|
|
}
|
|
if currentCount == 0 {
|
|
if projectedCount == 0 {
|
|
return "no_change"
|
|
}
|
|
if projectedCount > 1 {
|
|
return "creates_multiple"
|
|
}
|
|
return "creates_new"
|
|
}
|
|
if preview.AdditionalCount > 0 {
|
|
return "splits_existing"
|
|
}
|
|
if currentCount > 1 || projectedCount > 1 {
|
|
return "replaces_multiple"
|
|
}
|
|
return "replaces_existing"
|
|
}
|
|
|
|
func errMonitoredSystemLedgerValidation(message string) error {
|
|
return monitoredSystemLedgerValidationError(message)
|
|
}
|
|
|
|
type monitoredSystemLedgerValidationError string
|
|
|
|
func (e monitoredSystemLedgerValidationError) Error() string {
|
|
return string(e)
|
|
}
|