Files
pulse/internal/models/deepcopy.go
T
courtmanr@gmail.com 13371a6b6c Surface host-agent identity collapse from cloned machine-ids
Host agents key their identity on the machine-derived agent ID, so MSP
template deployments that clone /etc/machine-id fold two physical
machines at different sites into one host row whose reports overwrite
each other (hostname, report IP, and interfaces flapping between
sites), silently poisoning node-agent linking.

Mirror the Docker host identity-collapse doctrine (#1584) for host
report ingest: track hostname and report-IP revisits per resolved
agent identity inside the monitoring-owned flap window, publish an
active conflict as models.Host.IdentityConflict through unified
resources, and warn on the Machines page. The report IP is tracked
alongside the hostname because template fleets often reuse hostnames
across sites (pve01 at two customers), leaving the address as the only
field that betrays the clone. A one-time hostname rename never
revisits and is not flagged; the conflict clears on its own once only
one machine keeps reporting for the window.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 12:26:18 +01:00

1419 lines
38 KiB
Go

package models
import (
"reflect"
"time"
"github.com/rcourtman/pulse-go-rewrite/pkg/diskinventory"
)
func cloneBoolPtr(src *bool) *bool {
if src == nil {
return nil
}
v := *src
return &v
}
func cloneFloat64Ptr(src *float64) *float64 {
if src == nil {
return nil
}
v := *src
return &v
}
func cloneIntPtr(src *int) *int {
if src == nil {
return nil
}
v := *src
return &v
}
func cloneInt64Ptr(src *int64) *int64 {
if src == nil {
return nil
}
v := *src
return &v
}
func cloneTimePtr(src *time.Time) *time.Time {
if src == nil {
return nil
}
v := *src
return &v
}
func cloneStringMap(src map[string]string) map[string]string {
if len(src) == 0 {
return nil
}
dest := make(map[string]string, len(src))
for k, v := range src {
dest[k] = v
}
return dest
}
func clonePVETagStyles(src map[string]PVETagStyle) map[string]PVETagStyle {
if len(src) == 0 {
return nil
}
dest := make(map[string]PVETagStyle, len(src))
for instance, style := range src {
colors := cloneStringMap(style.Colors)
if colors == nil {
colors = map[string]string{}
}
dest[instance] = PVETagStyle{
Colors: colors,
CaseSensitive: style.CaseSensitive,
}
}
return dest
}
func cloneStringFloat64Map(src map[string]float64) map[string]float64 {
if len(src) == 0 {
return nil
}
dest := make(map[string]float64, len(src))
for k, v := range src {
dest[k] = v
}
return dest
}
func cloneStringIntMap(src map[string]int) map[string]int {
if len(src) == 0 {
return nil
}
dest := make(map[string]int, len(src))
for k, v := range src {
dest[k] = v
}
return dest
}
func cloneCoreTemps(src []CoreTemp) []CoreTemp {
return append([]CoreTemp(nil), src...)
}
func cloneGPUTemps(src []GPUTemp) []GPUTemp {
return append([]GPUTemp(nil), src...)
}
func cloneNVMeTemps(src []NVMeTemp) []NVMeTemp {
return append([]NVMeTemp(nil), src...)
}
func cloneDiskTemps(src []DiskTemp) []DiskTemp {
return append([]DiskTemp(nil), src...)
}
func cloneTemperature(src *Temperature) *Temperature {
if src == nil {
return nil
}
dest := *src
dest.Cores = cloneCoreTemps(src.Cores)
dest.GPU = cloneGPUTemps(src.GPU)
dest.NVMe = cloneNVMeTemps(src.NVMe)
dest.SMART = cloneDiskTemps(src.SMART)
normalized := dest.NormalizeCollections()
return &normalized
}
func cloneGuestNetworkInterfaces(src []GuestNetworkInterface) []GuestNetworkInterface {
if len(src) == 0 {
return nil
}
dest := make([]GuestNetworkInterface, len(src))
for i, nic := range src {
nicCopy := nic
nicCopy.Addresses = append([]string(nil), nic.Addresses...)
dest[i] = nicCopy.NormalizeCollections()
}
return dest
}
func cloneNode(src Node) Node {
dest := src
dest.LoadAverage = append([]float64(nil), src.LoadAverage...)
dest.NativeNameAliases = append([]string(nil), src.NativeNameAliases...)
dest.Temperature = cloneTemperature(src.Temperature)
dest.TemperatureMonitoringEnabled = cloneBoolPtr(src.TemperatureMonitoringEnabled)
return dest.NormalizeCollections()
}
func cloneNodes(src []Node) []Node {
if len(src) == 0 {
return nil
}
dest := make([]Node, len(src))
for i, node := range src {
dest[i] = cloneNode(node)
}
return dest
}
func cloneVM(src VM) VM {
dest := src
dest.Disks = append([]Disk(nil), src.Disks...)
dest.IPAddresses = append([]string(nil), src.IPAddresses...)
dest.Tags = append([]string(nil), src.Tags...)
dest.NetworkInterfaces = cloneGuestNetworkInterfaces(src.NetworkInterfaces)
return dest.NormalizeCollections()
}
func cloneVMs(src []VM) []VM {
if len(src) == 0 {
return nil
}
dest := make([]VM, len(src))
for i, vm := range src {
dest[i] = cloneVM(vm)
}
return dest
}
func cloneContainer(src Container) Container {
dest := src
dest.Disks = append([]Disk(nil), src.Disks...)
dest.IPAddresses = append([]string(nil), src.IPAddresses...)
dest.Tags = append([]string(nil), src.Tags...)
dest.NetworkInterfaces = cloneGuestNetworkInterfaces(src.NetworkInterfaces)
return dest.NormalizeCollections()
}
func cloneContainers(src []Container) []Container {
if len(src) == 0 {
return nil
}
dest := make([]Container, len(src))
for i, container := range src {
dest[i] = cloneContainer(container)
}
return dest
}
func cloneHostNetworkInterfaces(src []HostNetworkInterface) []HostNetworkInterface {
if len(src) == 0 {
return nil
}
dest := make([]HostNetworkInterface, len(src))
for i, nic := range src {
nicCopy := nic
nicCopy.Addresses = append([]string(nil), nic.Addresses...)
nicCopy.SpeedMbps = cloneInt64Ptr(nic.SpeedMbps)
dest[i] = nicCopy.NormalizeCollections()
}
return dest
}
func cloneSMARTAttributes(src *SMARTAttributes) *SMARTAttributes {
if src == nil {
return nil
}
dest := *src
dest.PowerOnHours = cloneInt64Ptr(src.PowerOnHours)
dest.PowerCycles = cloneInt64Ptr(src.PowerCycles)
dest.ReallocatedSectors = cloneInt64Ptr(src.ReallocatedSectors)
dest.PendingSectors = cloneInt64Ptr(src.PendingSectors)
dest.OfflineUncorrectable = cloneInt64Ptr(src.OfflineUncorrectable)
dest.UDMACRCErrors = cloneInt64Ptr(src.UDMACRCErrors)
dest.PercentageUsed = cloneIntPtr(src.PercentageUsed)
dest.AvailableSpare = cloneIntPtr(src.AvailableSpare)
dest.MediaErrors = cloneInt64Ptr(src.MediaErrors)
dest.UnsafeShutdowns = cloneInt64Ptr(src.UnsafeShutdowns)
return &dest
}
func cloneHostDiskSMART(src []HostDiskSMART) []HostDiskSMART {
if len(src) == 0 {
return nil
}
dest := make([]HostDiskSMART, len(src))
for i, disk := range src {
diskCopy := disk
diskCopy.Attributes = cloneSMARTAttributes(disk.Attributes)
diskCopy.Collection = diskinventory.CloneStatus(disk.Collection)
if disk.IO != nil {
ioCopy := *disk.IO
diskCopy.IO = &ioCopy
}
dest[i] = diskCopy
}
return dest
}
func cloneHostSensorSummary(src HostSensorSummary) HostSensorSummary {
return HostSensorSummary{
TemperatureCelsius: cloneStringFloat64Map(src.TemperatureCelsius),
FanRPM: cloneStringFloat64Map(src.FanRPM),
PowerWatts: cloneStringFloat64Map(src.PowerWatts),
Additional: cloneStringFloat64Map(src.Additional),
GPU: cloneHostGPUSensors(src.GPU),
ThermalState: cloneHostThermalState(src.ThermalState),
SMART: cloneHostDiskSMART(src.SMART),
}.NormalizeCollections()
}
func cloneHostGPUSensors(src []HostGPUSensor) []HostGPUSensor {
if len(src) == 0 {
return nil
}
dest := make([]HostGPUSensor, len(src))
for i, gpu := range src {
dest[i] = HostGPUSensor{
ID: gpu.ID,
Name: gpu.Name,
TemperatureCelsius: cloneFloat64Ptr(gpu.TemperatureCelsius),
UtilizationPercent: cloneFloat64Ptr(gpu.UtilizationPercent),
MemoryUsedBytes: cloneInt64Ptr(gpu.MemoryUsedBytes),
MemoryTotalBytes: cloneInt64Ptr(gpu.MemoryTotalBytes),
}
}
return dest
}
func cloneHostThermalState(src *HostThermalState) *HostThermalState {
if src == nil {
return nil
}
dest := *src
dest.ThermalWarningLevel = cloneIntPtr(src.ThermalWarningLevel)
dest.PerformanceWarningLevel = cloneIntPtr(src.PerformanceWarningLevel)
dest.CPUPowerStatus = cloneIntPtr(src.CPUPowerStatus)
dest.LimitsPercent = cloneStringIntMap(src.LimitsPercent)
return &dest
}
func cloneHostRAIDArrays(src []HostRAIDArray) []HostRAIDArray {
if len(src) == 0 {
return nil
}
dest := make([]HostRAIDArray, len(src))
for i, arr := range src {
arrCopy := arr
arrCopy.Devices = append([]HostRAIDDevice(nil), arr.Devices...)
dest[i] = arrCopy.NormalizeCollections()
}
return dest
}
func cloneHostUnraidStorage(src *HostUnraidStorage) *HostUnraidStorage {
if src == nil {
return nil
}
dest := *src
if len(src.Disks) > 0 {
dest.Disks = make([]HostUnraidDisk, len(src.Disks))
copy(dest.Disks, src.Disks)
}
normalized := dest.NormalizeCollections()
return &normalized
}
func cloneHostCephHealth(src HostCephHealth) HostCephHealth {
dest := HostCephHealth{
Status: src.Status,
Checks: make(map[string]HostCephCheck, len(src.Checks)),
Summary: append([]HostCephHealthSummary(nil), src.Summary...),
}
for key, check := range src.Checks {
checkCopy := check
checkCopy.Detail = append([]string(nil), check.Detail...)
dest.Checks[key] = checkCopy
}
if len(src.Checks) == 0 {
dest.Checks = nil
}
return dest.NormalizeCollections()
}
func cloneHostCephCluster(src *HostCephCluster) *HostCephCluster {
if src == nil {
return nil
}
dest := *src
dest.Health = cloneHostCephHealth(src.Health)
dest.MonMap.Monitors = append([]HostCephMonitor(nil), src.MonMap.Monitors...)
dest.Pools = append([]HostCephPool(nil), src.Pools...)
dest.Services = make([]HostCephService, len(src.Services))
for i, service := range src.Services {
serviceCopy := service
serviceCopy.Daemons = append([]string(nil), service.Daemons...)
dest.Services[i] = serviceCopy.NormalizeCollections()
}
if len(src.Services) == 0 {
dest.Services = nil
}
normalized := dest.NormalizeCollections()
return &normalized
}
func cloneHostPackageUpdateStatus(src *HostPackageUpdateStatus) *HostPackageUpdateStatus {
if src == nil {
return nil
}
dest := *src
dest.Packages = append([]HostPackageUpdate(nil), src.Packages...)
return &dest
}
func cloneHostStorageCleanupStatus(src *HostStorageCleanupStatus) *HostStorageCleanupStatus {
if src == nil {
return nil
}
dest := *src
return &dest
}
func cloneHost(src Host) Host {
dest := src
dest.LoadAverage = append([]float64(nil), src.LoadAverage...)
dest.Disks = append([]Disk(nil), src.Disks...)
dest.DiskIO = append([]DiskIO(nil), src.DiskIO...)
dest.NetworkInterfaces = cloneHostNetworkInterfaces(src.NetworkInterfaces)
dest.Sensors = cloneHostSensorSummary(src.Sensors)
dest.RAID = cloneHostRAIDArrays(src.RAID)
dest.Unraid = cloneHostUnraidStorage(src.Unraid)
dest.Ceph = cloneHostCephCluster(src.Ceph)
dest.PackageUpdates = cloneHostPackageUpdateStatus(src.PackageUpdates)
dest.StorageCleanup = cloneHostStorageCleanupStatus(src.StorageCleanup)
dest.TokenLastUsedAt = cloneTimePtr(src.TokenLastUsedAt)
dest.Tags = append([]string(nil), src.Tags...)
dest.DiskExclude = append([]string(nil), src.DiskExclude...)
dest.IdentityConflict = cloneHostIdentityConflict(src.IdentityConflict)
return dest.NormalizeCollections()
}
func cloneHostIdentityConflict(src *HostIdentityConflict) *HostIdentityConflict {
if src == nil {
return nil
}
dest := *src
dest.Hostnames = append([]string(nil), src.Hostnames...)
dest.ReportIPs = append([]string(nil), src.ReportIPs...)
return &dest
}
func cloneHosts(src []Host) []Host {
if len(src) == 0 {
return nil
}
dest := make([]Host, len(src))
for i, host := range src {
dest[i] = cloneHost(host)
}
return dest
}
func cloneDockerContainerBlockIO(src *DockerContainerBlockIO) *DockerContainerBlockIO {
if src == nil {
return nil
}
dest := *src
dest.ReadRateBytesPerSecond = cloneFloat64Ptr(src.ReadRateBytesPerSecond)
dest.WriteRateBytesPerSecond = cloneFloat64Ptr(src.WriteRateBytesPerSecond)
return &dest
}
func cloneDockerPodmanContainer(src *DockerPodmanContainer) *DockerPodmanContainer {
if src == nil {
return nil
}
dest := *src
return &dest
}
func cloneDockerContainerUpdateStatus(src *DockerContainerUpdateStatus) *DockerContainerUpdateStatus {
if src == nil {
return nil
}
dest := *src
return &dest
}
func cloneDockerContainer(src DockerContainer) DockerContainer {
dest := src
dest.OOMKilled = cloneBoolPtr(src.OOMKilled)
dest.StartedAt = cloneTimePtr(src.StartedAt)
dest.FinishedAt = cloneTimePtr(src.FinishedAt)
dest.Ports = append([]DockerContainerPort(nil), src.Ports...)
dest.Labels = cloneStringMap(src.Labels)
dest.Networks = append([]DockerContainerNetworkLink(nil), src.Networks...)
dest.BlockIO = cloneDockerContainerBlockIO(src.BlockIO)
dest.Mounts = append([]DockerContainerMount(nil), src.Mounts...)
dest.Podman = cloneDockerPodmanContainer(src.Podman)
dest.UpdateStatus = cloneDockerContainerUpdateStatus(src.UpdateStatus)
return dest.NormalizeCollections()
}
func cloneDockerContainers(src []DockerContainer) []DockerContainer {
if len(src) == 0 {
return nil
}
dest := make([]DockerContainer, len(src))
for i, container := range src {
dest[i] = cloneDockerContainer(container)
}
return dest
}
func cloneDockerImages(src []DockerImage) []DockerImage {
if len(src) == 0 {
return nil
}
dest := make([]DockerImage, len(src))
for i, image := range src {
imageCopy := image
imageCopy.RepoTags = append([]string(nil), image.RepoTags...)
imageCopy.RepoDigests = append([]string(nil), image.RepoDigests...)
imageCopy.Labels = cloneStringMap(image.Labels)
dest[i] = imageCopy.NormalizeCollections()
}
return dest
}
func cloneDockerVolumes(src []DockerVolume) []DockerVolume {
if len(src) == 0 {
return nil
}
dest := make([]DockerVolume, len(src))
for i, volume := range src {
volumeCopy := volume
volumeCopy.Labels = cloneStringMap(volume.Labels)
volumeCopy.Options = cloneStringMap(volume.Options)
dest[i] = volumeCopy.NormalizeCollections()
}
return dest
}
func cloneDockerNetworks(src []DockerNetwork) []DockerNetwork {
if len(src) == 0 {
return nil
}
dest := make([]DockerNetwork, len(src))
for i, network := range src {
networkCopy := network
networkCopy.Subnets = append([]DockerNetworkSubnet(nil), network.Subnets...)
networkCopy.Labels = cloneStringMap(network.Labels)
networkCopy.Options = cloneStringMap(network.Options)
dest[i] = networkCopy.NormalizeCollections()
}
return dest
}
func cloneDockerStorageUsage(src *DockerStorageUsage) *DockerStorageUsage {
if src == nil {
return nil
}
dest := *src
return &dest
}
func cloneDockerServiceUpdate(src *DockerServiceUpdate) *DockerServiceUpdate {
if src == nil {
return nil
}
dest := *src
dest.CompletedAt = cloneTimePtr(src.CompletedAt)
return &dest
}
func cloneDockerServices(src []DockerService) []DockerService {
if len(src) == 0 {
return nil
}
dest := make([]DockerService, len(src))
for i, service := range src {
serviceCopy := service
serviceCopy.UpdateStatus = cloneDockerServiceUpdate(service.UpdateStatus)
serviceCopy.Labels = cloneStringMap(service.Labels)
serviceCopy.EndpointPorts = append([]DockerServicePort(nil), service.EndpointPorts...)
serviceCopy.CreatedAt = cloneTimePtr(service.CreatedAt)
serviceCopy.UpdatedAt = cloneTimePtr(service.UpdatedAt)
dest[i] = serviceCopy.NormalizeCollections()
}
return dest
}
func cloneDockerTasks(src []DockerTask) []DockerTask {
if len(src) == 0 {
return nil
}
dest := make([]DockerTask, len(src))
for i, task := range src {
taskCopy := task
taskCopy.UpdatedAt = cloneTimePtr(task.UpdatedAt)
taskCopy.StartedAt = cloneTimePtr(task.StartedAt)
taskCopy.CompletedAt = cloneTimePtr(task.CompletedAt)
dest[i] = taskCopy
}
return dest
}
func cloneDockerNodes(src []DockerNode) []DockerNode {
if len(src) == 0 {
return nil
}
dest := make([]DockerNode, len(src))
for i, node := range src {
nodeCopy := node
nodeCopy.Labels = cloneStringMap(node.Labels)
nodeCopy.EngineLabels = cloneStringMap(node.EngineLabels)
nodeCopy.UpdatedAt = cloneTimePtr(node.UpdatedAt)
dest[i] = nodeCopy.NormalizeCollections()
}
return dest
}
func cloneDockerSecrets(src []DockerSecret) []DockerSecret {
if len(src) == 0 {
return nil
}
dest := make([]DockerSecret, len(src))
for i, secret := range src {
secretCopy := secret
secretCopy.Labels = cloneStringMap(secret.Labels)
secretCopy.UpdatedAt = cloneTimePtr(secret.UpdatedAt)
dest[i] = secretCopy.NormalizeCollections()
}
return dest
}
func cloneDockerConfigs(src []DockerConfig) []DockerConfig {
if len(src) == 0 {
return nil
}
dest := make([]DockerConfig, len(src))
for i, config := range src {
configCopy := config
configCopy.Labels = cloneStringMap(config.Labels)
configCopy.UpdatedAt = cloneTimePtr(config.UpdatedAt)
dest[i] = configCopy.NormalizeCollections()
}
return dest
}
func cloneDockerSwarmInfo(src *DockerSwarmInfo) *DockerSwarmInfo {
if src == nil {
return nil
}
dest := *src
return &dest
}
func cloneDockerHostSecurity(src *DockerHostSecurity) *DockerHostSecurity {
if src == nil {
return nil
}
dest := *src
dest.AuthorizationPlugins = append([]string(nil), src.AuthorizationPlugins...)
return &dest
}
func cloneDockerHostIdentityConflict(src *DockerHostIdentityConflict) *DockerHostIdentityConflict {
if src == nil {
return nil
}
dest := *src
dest.Hostnames = append([]string(nil), src.Hostnames...)
dest.MachineIDs = append([]string(nil), src.MachineIDs...)
return &dest
}
func cloneDockerHostCommandStatus(src *DockerHostCommandStatus) *DockerHostCommandStatus {
if src == nil {
return nil
}
dest := *src
dest.DispatchedAt = cloneTimePtr(src.DispatchedAt)
dest.AcknowledgedAt = cloneTimePtr(src.AcknowledgedAt)
dest.CompletedAt = cloneTimePtr(src.CompletedAt)
dest.FailedAt = cloneTimePtr(src.FailedAt)
dest.ExpiresAt = cloneTimePtr(src.ExpiresAt)
return &dest
}
func cloneDockerHost(src DockerHost) DockerHost {
dest := src
dest.LoadAverage = append([]float64(nil), src.LoadAverage...)
dest.Disks = append([]Disk(nil), src.Disks...)
dest.NetworkInterfaces = cloneHostNetworkInterfaces(src.NetworkInterfaces)
dest.Containers = cloneDockerContainers(src.Containers)
dest.Images = cloneDockerImages(src.Images)
dest.Volumes = cloneDockerVolumes(src.Volumes)
dest.Networks = cloneDockerNetworks(src.Networks)
dest.Services = cloneDockerServices(src.Services)
dest.Tasks = cloneDockerTasks(src.Tasks)
dest.Nodes = cloneDockerNodes(src.Nodes)
dest.Secrets = cloneDockerSecrets(src.Secrets)
dest.Configs = cloneDockerConfigs(src.Configs)
dest.StorageUsage = cloneDockerStorageUsage(src.StorageUsage)
dest.Swarm = cloneDockerSwarmInfo(src.Swarm)
dest.Security = cloneDockerHostSecurity(src.Security)
dest.Temperature = cloneFloat64Ptr(src.Temperature)
dest.TokenLastUsedAt = cloneTimePtr(src.TokenLastUsedAt)
dest.Command = cloneDockerHostCommandStatus(src.Command)
dest.IdentityConflict = cloneDockerHostIdentityConflict(src.IdentityConflict)
return dest.NormalizeCollections()
}
func cloneDockerHosts(src []DockerHost) []DockerHost {
if len(src) == 0 {
return nil
}
dest := make([]DockerHost, len(src))
for i, host := range src {
dest[i] = cloneDockerHost(host)
}
return dest
}
func cloneKubernetesNodes(src []KubernetesNode) []KubernetesNode {
if len(src) == 0 {
return nil
}
dest := make([]KubernetesNode, len(src))
for i, node := range src {
nodeCopy := node
nodeCopy.Roles = append([]string(nil), node.Roles...)
dest[i] = nodeCopy.NormalizeCollections()
}
return dest
}
func cloneKubernetesNamespaces(src []KubernetesNamespace) []KubernetesNamespace {
if len(src) == 0 {
return nil
}
dest := make([]KubernetesNamespace, len(src))
for i, namespace := range src {
namespaceCopy := namespace
namespaceCopy.Labels = cloneStringMap(namespace.Labels)
dest[i] = namespaceCopy.NormalizeCollections()
}
return dest
}
func cloneKubernetesPodContainers(src []KubernetesPodContainer) []KubernetesPodContainer {
return append([]KubernetesPodContainer(nil), src...)
}
func cloneKubernetesPods(src []KubernetesPod) []KubernetesPod {
if len(src) == 0 {
return nil
}
dest := make([]KubernetesPod, len(src))
for i, pod := range src {
podCopy := pod
podCopy.StartTime = cloneTimePtr(pod.StartTime)
podCopy.Labels = cloneStringMap(pod.Labels)
podCopy.Containers = cloneKubernetesPodContainers(pod.Containers)
dest[i] = podCopy.NormalizeCollections()
}
return dest
}
func cloneKubernetesDeployments(src []KubernetesDeployment) []KubernetesDeployment {
if len(src) == 0 {
return nil
}
dest := make([]KubernetesDeployment, len(src))
for i, deployment := range src {
deploymentCopy := deployment
deploymentCopy.Labels = cloneStringMap(deployment.Labels)
dest[i] = deploymentCopy.NormalizeCollections()
}
return dest
}
func cloneKubernetesReplicaSets(src []KubernetesReplicaSet) []KubernetesReplicaSet {
if len(src) == 0 {
return nil
}
dest := make([]KubernetesReplicaSet, len(src))
for i, replicaSet := range src {
copy := replicaSet
copy.Labels = cloneStringMap(replicaSet.Labels)
dest[i] = copy.NormalizeCollections()
}
return dest
}
func cloneKubernetesStatefulSets(src []KubernetesStatefulSet) []KubernetesStatefulSet {
if len(src) == 0 {
return nil
}
dest := make([]KubernetesStatefulSet, len(src))
for i, statefulSet := range src {
copy := statefulSet
copy.Labels = cloneStringMap(statefulSet.Labels)
dest[i] = copy.NormalizeCollections()
}
return dest
}
func cloneKubernetesDaemonSets(src []KubernetesDaemonSet) []KubernetesDaemonSet {
if len(src) == 0 {
return nil
}
dest := make([]KubernetesDaemonSet, len(src))
for i, daemonSet := range src {
copy := daemonSet
copy.Labels = cloneStringMap(daemonSet.Labels)
dest[i] = copy.NormalizeCollections()
}
return dest
}
func cloneKubernetesServices(src []KubernetesService) []KubernetesService {
if len(src) == 0 {
return nil
}
dest := make([]KubernetesService, len(src))
for i, service := range src {
copy := service
copy.ExternalIPs = append([]string(nil), service.ExternalIPs...)
copy.Ports = append([]KubernetesServicePort(nil), service.Ports...)
copy.Selector = cloneStringMap(service.Selector)
copy.Labels = cloneStringMap(service.Labels)
dest[i] = copy.NormalizeCollections()
}
return dest
}
func cloneKubernetesJobs(src []KubernetesJob) []KubernetesJob {
if len(src) == 0 {
return nil
}
dest := make([]KubernetesJob, len(src))
for i, job := range src {
copy := job
copy.StartTime = cloneTimePtr(job.StartTime)
copy.CompletionTime = cloneTimePtr(job.CompletionTime)
copy.Labels = cloneStringMap(job.Labels)
dest[i] = copy.NormalizeCollections()
}
return dest
}
func cloneKubernetesCronJobs(src []KubernetesCronJob) []KubernetesCronJob {
if len(src) == 0 {
return nil
}
dest := make([]KubernetesCronJob, len(src))
for i, cronJob := range src {
copy := cronJob
copy.LastScheduleTime = cloneTimePtr(cronJob.LastScheduleTime)
copy.LastSuccessfulTime = cloneTimePtr(cronJob.LastSuccessfulTime)
copy.Labels = cloneStringMap(cronJob.Labels)
dest[i] = copy.NormalizeCollections()
}
return dest
}
func cloneKubernetesIngresses(src []KubernetesIngress) []KubernetesIngress {
if len(src) == 0 {
return nil
}
dest := make([]KubernetesIngress, len(src))
for i, ingress := range src {
copy := ingress
copy.Hosts = append([]string(nil), ingress.Hosts...)
copy.Addresses = append([]string(nil), ingress.Addresses...)
copy.Labels = cloneStringMap(ingress.Labels)
dest[i] = copy.NormalizeCollections()
}
return dest
}
func cloneKubernetesEndpointSlices(src []KubernetesEndpointSlice) []KubernetesEndpointSlice {
if len(src) == 0 {
return nil
}
dest := make([]KubernetesEndpointSlice, len(src))
for i, endpointSlice := range src {
copy := endpointSlice
copy.Ports = append([]KubernetesEndpointPort(nil), endpointSlice.Ports...)
copy.Labels = cloneStringMap(endpointSlice.Labels)
dest[i] = copy.NormalizeCollections()
}
return dest
}
func cloneKubernetesNetworkPolicies(src []KubernetesNetworkPolicy) []KubernetesNetworkPolicy {
if len(src) == 0 {
return nil
}
dest := make([]KubernetesNetworkPolicy, len(src))
for i, policy := range src {
copy := policy
copy.PolicyTypes = append([]string(nil), policy.PolicyTypes...)
copy.Labels = cloneStringMap(policy.Labels)
dest[i] = copy.NormalizeCollections()
}
return dest
}
func cloneKubernetesPersistentVolumes(src []KubernetesPersistentVolume) []KubernetesPersistentVolume {
if len(src) == 0 {
return nil
}
dest := make([]KubernetesPersistentVolume, len(src))
for i, volume := range src {
copy := volume
copy.AccessModes = append([]string(nil), volume.AccessModes...)
copy.Labels = cloneStringMap(volume.Labels)
dest[i] = copy.NormalizeCollections()
}
return dest
}
func cloneKubernetesPersistentVolumeClaims(src []KubernetesPersistentVolumeClaim) []KubernetesPersistentVolumeClaim {
if len(src) == 0 {
return nil
}
dest := make([]KubernetesPersistentVolumeClaim, len(src))
for i, claim := range src {
copy := claim
copy.AccessModes = append([]string(nil), claim.AccessModes...)
copy.Labels = cloneStringMap(claim.Labels)
dest[i] = copy.NormalizeCollections()
}
return dest
}
func cloneKubernetesStorageClasses(src []KubernetesStorageClass) []KubernetesStorageClass {
if len(src) == 0 {
return nil
}
dest := make([]KubernetesStorageClass, len(src))
for i, class := range src {
copy := class
copy.AllowVolumeExpansion = cloneBoolPtr(class.AllowVolumeExpansion)
copy.ParameterKeys = append([]string(nil), class.ParameterKeys...)
copy.Labels = cloneStringMap(class.Labels)
dest[i] = copy.NormalizeCollections()
}
return dest
}
func cloneKubernetesConfigMaps(src []KubernetesConfigMap) []KubernetesConfigMap {
if len(src) == 0 {
return nil
}
dest := make([]KubernetesConfigMap, len(src))
for i, configMap := range src {
copy := configMap
copy.DataKeys = append([]string(nil), configMap.DataKeys...)
copy.BinaryDataKeys = append([]string(nil), configMap.BinaryDataKeys...)
copy.Labels = cloneStringMap(configMap.Labels)
dest[i] = copy.NormalizeCollections()
}
return dest
}
func cloneKubernetesSecrets(src []KubernetesSecret) []KubernetesSecret {
if len(src) == 0 {
return nil
}
dest := make([]KubernetesSecret, len(src))
for i, secret := range src {
copy := secret
copy.DataKeys = append([]string(nil), secret.DataKeys...)
copy.Labels = cloneStringMap(secret.Labels)
dest[i] = copy.NormalizeCollections()
}
return dest
}
func cloneKubernetesServiceAccounts(src []KubernetesServiceAccount) []KubernetesServiceAccount {
if len(src) == 0 {
return nil
}
dest := make([]KubernetesServiceAccount, len(src))
for i, account := range src {
copy := account
copy.AutomountServiceAccountToken = cloneBoolPtr(account.AutomountServiceAccountToken)
copy.ImagePullSecrets = append([]string(nil), account.ImagePullSecrets...)
copy.Labels = cloneStringMap(account.Labels)
dest[i] = copy.NormalizeCollections()
}
return dest
}
func cloneKubernetesResourceQuotas(src []KubernetesResourceQuota) []KubernetesResourceQuota {
if len(src) == 0 {
return nil
}
dest := make([]KubernetesResourceQuota, len(src))
for i, quota := range src {
copy := quota
copy.Hard = cloneStringMap(quota.Hard)
copy.Used = cloneStringMap(quota.Used)
copy.Labels = cloneStringMap(quota.Labels)
dest[i] = copy.NormalizeCollections()
}
return dest
}
func cloneKubernetesLimitRanges(src []KubernetesLimitRange) []KubernetesLimitRange {
if len(src) == 0 {
return nil
}
dest := make([]KubernetesLimitRange, len(src))
for i, limitRange := range src {
copy := limitRange
copy.LimitTypes = append([]string(nil), limitRange.LimitTypes...)
copy.Labels = cloneStringMap(limitRange.Labels)
dest[i] = copy.NormalizeCollections()
}
return dest
}
func cloneKubernetesPodDisruptionBudgets(src []KubernetesPodDisruptionBudget) []KubernetesPodDisruptionBudget {
if len(src) == 0 {
return nil
}
dest := make([]KubernetesPodDisruptionBudget, len(src))
for i, budget := range src {
copy := budget
copy.Labels = cloneStringMap(budget.Labels)
dest[i] = copy.NormalizeCollections()
}
return dest
}
func cloneKubernetesHorizontalPodAutoscalers(src []KubernetesHorizontalPodAutoscaler) []KubernetesHorizontalPodAutoscaler {
if len(src) == 0 {
return nil
}
dest := make([]KubernetesHorizontalPodAutoscaler, len(src))
for i, autoscaler := range src {
copy := autoscaler
copy.MetricTypes = append([]string(nil), autoscaler.MetricTypes...)
copy.Labels = cloneStringMap(autoscaler.Labels)
dest[i] = copy.NormalizeCollections()
}
return dest
}
func cloneKubernetesEvents(src []KubernetesEvent) []KubernetesEvent {
if len(src) == 0 {
return nil
}
dest := make([]KubernetesEvent, len(src))
for i, event := range src {
copy := event
copy.FirstSeen = cloneTimePtr(event.FirstSeen)
copy.LastSeen = cloneTimePtr(event.LastSeen)
copy.EventTime = cloneTimePtr(event.EventTime)
dest[i] = copy
}
return dest
}
func cloneKubernetesCluster(src KubernetesCluster) KubernetesCluster {
dest := src
dest.Nodes = cloneKubernetesNodes(src.Nodes)
dest.Namespaces = cloneKubernetesNamespaces(src.Namespaces)
dest.Pods = cloneKubernetesPods(src.Pods)
dest.Deployments = cloneKubernetesDeployments(src.Deployments)
dest.ReplicaSets = cloneKubernetesReplicaSets(src.ReplicaSets)
dest.StatefulSets = cloneKubernetesStatefulSets(src.StatefulSets)
dest.DaemonSets = cloneKubernetesDaemonSets(src.DaemonSets)
dest.Services = cloneKubernetesServices(src.Services)
dest.Jobs = cloneKubernetesJobs(src.Jobs)
dest.CronJobs = cloneKubernetesCronJobs(src.CronJobs)
dest.Ingresses = cloneKubernetesIngresses(src.Ingresses)
dest.EndpointSlices = cloneKubernetesEndpointSlices(src.EndpointSlices)
dest.NetworkPolicies = cloneKubernetesNetworkPolicies(src.NetworkPolicies)
dest.PersistentVolumes = cloneKubernetesPersistentVolumes(src.PersistentVolumes)
dest.PersistentVolumeClaims = cloneKubernetesPersistentVolumeClaims(src.PersistentVolumeClaims)
dest.StorageClasses = cloneKubernetesStorageClasses(src.StorageClasses)
dest.ConfigMaps = cloneKubernetesConfigMaps(src.ConfigMaps)
dest.Secrets = cloneKubernetesSecrets(src.Secrets)
dest.ServiceAccounts = cloneKubernetesServiceAccounts(src.ServiceAccounts)
dest.ResourceQuotas = cloneKubernetesResourceQuotas(src.ResourceQuotas)
dest.LimitRanges = cloneKubernetesLimitRanges(src.LimitRanges)
dest.PodDisruptionBudgets = cloneKubernetesPodDisruptionBudgets(src.PodDisruptionBudgets)
dest.HorizontalPodAutoscalers = cloneKubernetesHorizontalPodAutoscalers(src.HorizontalPodAutoscalers)
dest.Events = cloneKubernetesEvents(src.Events)
// RBAC slices: NormalizeCollections() mutates these via index
// assignment (c.Roles[i] = c.Roles[i].NormalizeCollections()), so they
// must not be aliased to the source slice or concurrent clones will
// race on the shared underlying array.
dest.Roles = append([]KubernetesRole(nil), src.Roles...)
dest.ClusterRoles = append([]KubernetesClusterRole(nil), src.ClusterRoles...)
dest.RoleBindings = append([]KubernetesRoleBinding(nil), src.RoleBindings...)
dest.ClusterRoleBindings = append([]KubernetesClusterRoleBinding(nil), src.ClusterRoleBindings...)
dest.TokenLastUsedAt = cloneTimePtr(src.TokenLastUsedAt)
return dest.NormalizeCollections()
}
func cloneKubernetesClusters(src []KubernetesCluster) []KubernetesCluster {
if len(src) == 0 {
return nil
}
dest := make([]KubernetesCluster, len(src))
for i, cluster := range src {
dest[i] = cloneKubernetesCluster(cluster)
}
return dest
}
func cloneStorage(src Storage) Storage {
dest := src
dest.Nodes = append([]string(nil), src.Nodes...)
dest.NodeIDs = append([]string(nil), src.NodeIDs...)
dest.AliasIDs = append([]string(nil), src.AliasIDs...)
dest.ZFSPool = cloneZFSPool(src.ZFSPool)
return dest.NormalizeCollections()
}
func cloneZFSPool(src *ZFSPool) *ZFSPool {
if src == nil {
return nil
}
dest := *src
dest.Devices = append([]ZFSDevice(nil), src.Devices...)
normalized := dest.NormalizeCollections()
return &normalized
}
func cloneStorages(src []Storage) []Storage {
if len(src) == 0 {
return nil
}
dest := make([]Storage, len(src))
for i, storage := range src {
dest[i] = cloneStorage(storage)
}
return dest
}
func cloneCephCluster(src CephCluster) CephCluster {
dest := src
dest.InstanceAliases = append([]string(nil), src.InstanceAliases...)
dest.Pools = append([]CephPool(nil), src.Pools...)
dest.Services = append([]CephServiceStatus(nil), src.Services...)
return dest.NormalizeCollections()
}
func cloneCephClusters(src []CephCluster) []CephCluster {
if len(src) == 0 {
return nil
}
dest := make([]CephCluster, len(src))
for i, cluster := range src {
dest[i] = cloneCephCluster(cluster)
}
return dest
}
func clonePhysicalDisk(src PhysicalDisk) PhysicalDisk {
dest := src
dest.SmartAttributes = cloneSMARTAttributes(src.SmartAttributes)
dest.Collection = diskinventory.CloneStatus(src.Collection)
if src.IO != nil {
ioCopy := *src.IO
dest.IO = &ioCopy
}
return dest
}
func clonePhysicalDisks(src []PhysicalDisk) []PhysicalDisk {
if len(src) == 0 {
return nil
}
dest := make([]PhysicalDisk, len(src))
for i, disk := range src {
dest[i] = clonePhysicalDisk(disk)
}
return dest
}
func clonePBSDatastores(src []PBSDatastore) []PBSDatastore {
if len(src) == 0 {
return nil
}
dest := make([]PBSDatastore, len(src))
for i, datastore := range src {
datastoreCopy := datastore
datastoreCopy.Namespaces = append([]PBSNamespace(nil), datastore.Namespaces...)
dest[i] = datastoreCopy.NormalizeCollections()
}
return dest
}
func clonePBSInstance(src PBSInstance) PBSInstance {
dest := src
dest.Datastores = clonePBSDatastores(src.Datastores)
dest.BackupJobs = append([]PBSBackupJob(nil), src.BackupJobs...)
dest.SyncJobs = append([]PBSSyncJob(nil), src.SyncJobs...)
dest.VerifyJobs = append([]PBSVerifyJob(nil), src.VerifyJobs...)
dest.PruneJobs = append([]PBSPruneJob(nil), src.PruneJobs...)
dest.GarbageJobs = append([]PBSGarbageJob(nil), src.GarbageJobs...)
dest.JobHealthEvidence = append([]PBSJobHealthEvidence(nil), src.JobHealthEvidence...)
return dest.NormalizeCollections()
}
func clonePBSInstances(src []PBSInstance) []PBSInstance {
if len(src) == 0 {
return nil
}
dest := make([]PBSInstance, len(src))
for i, instance := range src {
dest[i] = clonePBSInstance(instance)
}
return dest
}
func clonePMGNodeStatus(src []PMGNodeStatus) []PMGNodeStatus {
if len(src) == 0 {
return nil
}
dest := make([]PMGNodeStatus, len(src))
for i, node := range src {
nodeCopy := node
if node.QueueStatus != nil {
queueCopy := *node.QueueStatus
nodeCopy.QueueStatus = &queueCopy
}
dest[i] = nodeCopy
}
return dest
}
func clonePMGMailStats(src *PMGMailStats) *PMGMailStats {
if src == nil {
return nil
}
dest := *src
return &dest
}
func clonePMGQuarantine(src *PMGQuarantineTotals) *PMGQuarantineTotals {
if src == nil {
return nil
}
dest := *src
return &dest
}
func clonePMGInstance(src PMGInstance) PMGInstance {
dest := src
dest.Nodes = clonePMGNodeStatus(src.Nodes)
dest.MailStats = clonePMGMailStats(src.MailStats)
dest.MailCount = append([]PMGMailCountPoint(nil), src.MailCount...)
dest.SpamDistribution = append([]PMGSpamBucket(nil), src.SpamDistribution...)
dest.Quarantine = clonePMGQuarantine(src.Quarantine)
dest.RelayDomains = append([]PMGRelayDomain(nil), src.RelayDomains...)
dest.DomainStats = append([]PMGDomainStat(nil), src.DomainStats...)
return dest.NormalizeCollections()
}
func clonePMGInstances(src []PMGInstance) []PMGInstance {
if len(src) == 0 {
return nil
}
dest := make([]PMGInstance, len(src))
for i, instance := range src {
dest[i] = clonePMGInstance(instance)
}
return dest
}
func clonePBSBackup(src PBSBackup) PBSBackup {
dest := src
dest.Files = append([]string(nil), src.Files...)
// Deep-copy VerificationRaw to avoid shared map references
if m, ok := src.VerificationRaw.(map[string]interface{}); ok {
cp := make(map[string]interface{}, len(m))
for k, v := range m {
cp[k] = v
}
dest.VerificationRaw = cp
}
// string and nil cases are immutable - shallow copy is fine
return dest.NormalizeCollections()
}
func clonePBSBackups(src []PBSBackup) []PBSBackup {
if len(src) == 0 {
return nil
}
dest := make([]PBSBackup, len(src))
for i, backup := range src {
dest[i] = clonePBSBackup(backup)
}
return dest
}
func clonePMGBackups(src []PMGBackup) []PMGBackup {
return append([]PMGBackup(nil), src...)
}
func clonePVEBackups(src PVEBackups) PVEBackups {
return PVEBackups{
BackupTasks: append([]BackupTask(nil), src.BackupTasks...),
StorageBackups: append([]StorageBackup(nil), src.StorageBackups...),
GuestSnapshots: append([]GuestSnapshot(nil), src.GuestSnapshots...),
}.NormalizeCollections()
}
func clonePerformance(src Performance) Performance {
return Performance{
APICallDuration: cloneStringFloat64Map(src.APICallDuration),
LastPollDuration: src.LastPollDuration,
PollingStartTime: src.PollingStartTime,
TotalAPICalls: src.TotalAPICalls,
FailedAPICalls: src.FailedAPICalls,
}.NormalizeCollections()
}
func cloneAlert(src Alert) Alert {
dest := src
dest.AckTime = cloneTimePtr(src.AckTime)
if src.Metadata != nil {
dest.Metadata = make(map[string]interface{}, len(src.Metadata))
for k, v := range src.Metadata {
dest.Metadata[k] = v
}
}
return dest
}
func cloneAlerts(src []Alert) []Alert {
if len(src) == 0 {
return nil
}
dest := make([]Alert, len(src))
for i, alert := range src {
dest[i] = cloneAlert(alert)
}
return dest
}
func cloneResolvedAlerts(src []ResolvedAlert) []ResolvedAlert {
if len(src) == 0 {
return nil
}
dest := make([]ResolvedAlert, len(src))
for i, alert := range src {
alertCopy := alert
alertCopy.Alert = cloneAlert(alert.Alert)
dest[i] = alertCopy
}
return dest
}
func cloneReplicationJob(src ReplicationJob) ReplicationJob {
dest := src
dest.LastSyncTime = cloneTimePtr(src.LastSyncTime)
dest.NextSyncTime = cloneTimePtr(src.NextSyncTime)
dest.RateLimitMbps = cloneFloat64Ptr(src.RateLimitMbps)
return dest
}
func cloneReplicationJobs(src []ReplicationJob) []ReplicationJob {
if len(src) == 0 {
return nil
}
dest := make([]ReplicationJob, len(src))
for i, job := range src {
dest[i] = cloneReplicationJob(job)
}
return dest
}
func cloneMetricValues(src map[string]interface{}) map[string]interface{} {
if len(src) == 0 {
return nil
}
dest := make(map[string]interface{}, len(src))
for key, value := range src {
dest[key] = cloneDynamicValue(value)
}
return dest
}
func cloneDynamicValue(value interface{}) interface{} {
if value == nil {
return nil
}
v := reflect.ValueOf(value)
switch v.Kind() {
case reflect.Map:
if v.IsNil() {
return value
}
cloned := reflect.MakeMapWithSize(v.Type(), v.Len())
iter := v.MapRange()
for iter.Next() {
clonedValue := cloneDynamicValue(iter.Value().Interface())
cloned.SetMapIndex(iter.Key(), valueForType(v.Type().Elem(), clonedValue))
}
return cloned.Interface()
case reflect.Slice:
if v.IsNil() {
return value
}
cloned := reflect.MakeSlice(v.Type(), v.Len(), v.Len())
for i := 0; i < v.Len(); i++ {
clonedValue := cloneDynamicValue(v.Index(i).Interface())
cloned.Index(i).Set(valueForType(v.Type().Elem(), clonedValue))
}
return cloned.Interface()
case reflect.Pointer:
if v.IsNil() {
return value
}
clonedElem := cloneDynamicValue(v.Elem().Interface())
ptr := reflect.New(v.Type().Elem())
ptr.Elem().Set(valueForType(v.Type().Elem(), clonedElem))
return ptr.Interface()
default:
return value
}
}
func valueForType(targetType reflect.Type, value interface{}) reflect.Value {
if value == nil {
return reflect.Zero(targetType)
}
result := reflect.ValueOf(value)
if result.Type().AssignableTo(targetType) {
return result
}
if result.Type().ConvertibleTo(targetType) {
return result.Convert(targetType)
}
return reflect.Zero(targetType)
}
func cloneMetric(src Metric) Metric {
dest := src
dest.Values = cloneMetricValues(src.Values)
return dest.NormalizeCollections()
}
func cloneMetrics(src []Metric) []Metric {
if len(src) == 0 {
return nil
}
dest := make([]Metric, len(src))
for i, metric := range src {
dest[i] = cloneMetric(metric)
}
return dest
}