Pulse/internal/vmware/activity_changes.go
rcourtman 5016cbc2ba Add vSphere network inventory
Project vCenter network inventory through canonical resources and add the vSphere Networks table backed by vCenter network topology. Align resource presentation coalescing so state and resource APIs share the same host contract.
2026-05-22 20:26:56 +01:00

119 lines
4.6 KiB
Go

package vmware
import (
"sort"
"strings"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
)
// ActivityChanges returns canonical shared-timeline activity derived from the
// cached VMware snapshot.
func (p *Provider) ActivityChanges() []unifiedresources.ResourceChange {
if p == nil || !IsFeatureEnabled() {
return nil
}
snapshot := p.Snapshot()
return activityChangesFromSnapshot(snapshot)
}
// FixtureActivityChanges projects VMware fixture activity into canonical
// timeline changes without consulting the runtime feature flag.
func FixtureActivityChanges(snapshot InventorySnapshot) []unifiedresources.ResourceChange {
return activityChangesFromSnapshot(&snapshot)
}
func activityChangesFromSnapshot(snapshot *InventorySnapshot) []unifiedresources.ResourceChange {
if snapshot == nil {
return nil
}
changes := make([]unifiedresources.ResourceChange, 0)
for _, host := range snapshot.Hosts {
resourceID := vmwareSourceID(snapshot.ConnectionID, "host", host.Host)
changes = append(changes, entityActivityChanges(resourceID, snapshot.ConnectionID, "host", host.Host, host.RecentTasks, host.RecentEvents)...)
}
for _, vm := range snapshot.VMs {
resourceID := vmwareSourceID(snapshot.ConnectionID, "vm", vm.VM)
changes = append(changes, entityActivityChanges(resourceID, snapshot.ConnectionID, "vm", vm.VM, vm.RecentTasks, vm.RecentEvents)...)
}
for _, datastore := range snapshot.Datastores {
resourceID := vmwareSourceID(snapshot.ConnectionID, "datastore", datastore.Datastore)
changes = append(changes, entityActivityChanges(resourceID, snapshot.ConnectionID, "datastore", datastore.Datastore, datastore.RecentTasks, datastore.RecentEvents)...)
}
for _, network := range snapshot.Networks {
resourceID := vmwareSourceID(snapshot.ConnectionID, "network", network.Network)
changes = append(changes, entityActivityChanges(resourceID, snapshot.ConnectionID, "network", network.Network, network.RecentTasks, network.RecentEvents)...)
}
sort.SliceStable(changes, func(i, j int) bool {
if !changes[i].ObservedAt.Equal(changes[j].ObservedAt) {
return changes[i].ObservedAt.After(changes[j].ObservedAt)
}
return changes[i].ID > changes[j].ID
})
return changes
}
func entityActivityChanges(resourceID, connectionID, entityType, managedObjectID string, tasks []InventoryTask, events []InventoryEvent) []unifiedresources.ResourceChange {
out := make([]unifiedresources.ResourceChange, 0, len(tasks)+len(events))
for _, task := range tasks {
change := unifiedresources.BuildPlatformActivityChange(resourceID, unifiedresources.PlatformActivityChange{
SourceAdapter: unifiedresources.AdapterVMware,
ActivityType: "vmware_task",
NativeID: strings.TrimSpace(task.Task),
Title: strings.TrimSpace(task.Name),
State: strings.TrimSpace(task.State),
Message: strings.TrimSpace(task.ErrorMessage),
OccurredAt: firstNonZeroTime(task.CompletedAt, task.StartedAt),
Metadata: map[string]any{
"vmwareConnectionId": strings.TrimSpace(connectionID),
"vmwareEntityType": strings.TrimSpace(entityType),
"vmwareManagedObjectId": strings.TrimSpace(managedObjectID),
"vmwareTask": strings.TrimSpace(task.Task),
"vmwareTaskName": strings.TrimSpace(task.Name),
"vmwareTaskState": strings.TrimSpace(task.State),
"vmwareTaskDescription": strings.TrimSpace(task.DescriptionID),
"vmwareTaskError": strings.TrimSpace(task.ErrorMessage),
},
})
if change != nil {
out = append(out, *change)
}
}
for _, event := range events {
change := unifiedresources.BuildPlatformActivityChange(resourceID, unifiedresources.PlatformActivityChange{
SourceAdapter: unifiedresources.AdapterVMware,
ActivityType: "vmware_event",
NativeID: strings.TrimSpace(event.Event),
Title: firstNonEmptyTrimmed(event.Type),
Message: strings.TrimSpace(event.Message),
Actor: strings.TrimSpace(event.User),
OccurredAt: event.CreatedAt,
Metadata: map[string]any{
"vmwareConnectionId": strings.TrimSpace(connectionID),
"vmwareEntityType": strings.TrimSpace(entityType),
"vmwareManagedObjectId": strings.TrimSpace(managedObjectID),
"vmwareEvent": strings.TrimSpace(event.Event),
"vmwareEventType": strings.TrimSpace(event.Type),
"vmwareEventMessage": strings.TrimSpace(event.Message),
"vmwareEventUser": strings.TrimSpace(event.User),
},
})
if change != nil {
out = append(out, *change)
}
}
return out
}
func firstNonZeroTime(values ...time.Time) time.Time {
for _, value := range values {
if !value.IsZero() {
return value.UTC()
}
}
return time.Time{}
}