package licensing import ( "crypto/sha256" "encoding/hex" "strings" "github.com/rcourtman/pulse-go-rewrite/internal/models" agentsdocker "github.com/rcourtman/pulse-go-rewrite/pkg/agents/docker" agentshost "github.com/rcourtman/pulse-go-rewrite/pkg/agents/host" agentsk8s "github.com/rcourtman/pulse-go-rewrite/pkg/agents/kubernetes" ) // MaxMonitoredSystemsLicenseGateKey is retained only to identify and scrub // retired v5/v6 monitored-system volume-limit metadata from old licenses, // billing records, purchase-return state, and entitlement leases. const MaxMonitoredSystemsLicenseGateKey = "max_monitored_systems" // InstalledUnifiedAgentCount returns the number of installed Pulse Unified // Agents. This is inventory-only metadata and is not a commercial counted unit. func InstalledUnifiedAgentCount(state models.StateSnapshot) int { return len(state.Hosts) } func CanonicalizeMonitoredSystemLimitKey(key string) string { normalized := strings.TrimSpace(key) switch normalized { case "", MaxMonitoredSystemsLicenseGateKey: return MaxMonitoredSystemsLicenseGateKey } if canonical, ok := canonicalizeLegacyV5MonitoredSystemLimitKey(normalized); ok { return canonical } return normalized } func NormalizeMonitoredSystemLimits(limits map[string]int64) map[string]int64 { if limits == nil { return nil } normalized := cloneInt64Map(limits) delete(normalized, MaxMonitoredSystemsLicenseGateKey) for _, key := range legacyV5MonitoredSystemLimitAliasKeys { delete(normalized, key) } return normalized } func HostReportTargetsExistingHost( snapshot models.StateSnapshot, report agentshost.Report, tokenID string, ) bool { return HostReportTargetsExistingHosts(snapshot.Hosts, report, tokenID) } func HostReportTargetsExistingHosts( hosts []models.Host, report agentshost.Report, tokenID string, ) bool { hostname := strings.TrimSpace(report.Host.Hostname) tokenID = strings.TrimSpace(tokenID) candidates := CollectNonEmptyStrings( report.Host.ID, report.Host.MachineID, report.Agent.ID, ) for _, existing := range hosts { for _, candidate := range candidates { if existing.ID == candidate || existing.MachineID == candidate { return true } } if hostname != "" && strings.EqualFold(existing.Hostname, hostname) { // Token-bound identity takes precedence when token is present. if tokenID == "" || (existing.TokenID != "" && existing.TokenID == tokenID) { return true } } } return false } func DockerReportTargetsExistingHost(snapshot models.StateSnapshot, report agentsdocker.Report, tokenID string) bool { hostname := strings.TrimSpace(report.Host.Hostname) agentKey := strings.TrimSpace(report.AgentKey()) tokenID = strings.TrimSpace(tokenID) for _, existing := range snapshot.DockerHosts { if agentKey != "" && (existing.ID == agentKey || existing.AgentID == agentKey || existing.MachineID == agentKey) { return true } if hostname != "" && strings.EqualFold(existing.Hostname, hostname) { if tokenID == "" || (existing.TokenID != "" && existing.TokenID == tokenID) { return true } } } return false } func KubernetesReportTargetsExistingCluster(snapshot models.StateSnapshot, report agentsk8s.Report, tokenID string) bool { identifier := KubernetesReportIdentifier(report) agentID := strings.TrimSpace(report.Agent.ID) clusterName := strings.TrimSpace(report.Cluster.Name) tokenID = strings.TrimSpace(tokenID) for _, existing := range snapshot.KubernetesClusters { if identifier != "" && existing.ID == identifier { return true } if agentID != "" && existing.AgentID == agentID { return true } if tokenID != "" && existing.TokenID == tokenID { if identifier == "" || existing.ID == identifier { return true } if clusterName != "" && strings.EqualFold(existing.Name, clusterName) { return true } } if tokenID == "" && identifier == "" && clusterName != "" && strings.EqualFold(existing.Name, clusterName) { return true } } return false } func KubernetesReportIdentifier(report agentsk8s.Report) string { if v := strings.TrimSpace(report.Cluster.ID); v != "" { return v } if v := strings.TrimSpace(report.Agent.ID); v != "" { return v } stableKey := strings.TrimSpace(report.Cluster.Server) + "|" + strings.TrimSpace(report.Cluster.Context) + "|" + strings.TrimSpace(report.Cluster.Name) stableKey = strings.TrimSpace(stableKey) if stableKey == "||" || stableKey == "" { return "" } sum := sha256.Sum256([]byte(stableKey)) return hex.EncodeToString(sum[:]) } func CollectNonEmptyStrings(values ...string) []string { out := make([]string, 0, len(values)) seen := make(map[string]struct{}, len(values)) for _, value := range values { trimmed := strings.TrimSpace(value) if trimmed == "" { continue } if _, exists := seen[trimmed]; exists { continue } seen[trimmed] = struct{}{} out = append(out, trimmed) } return out }