Files
git.stella-ops.org/src/StellaOps.Zastava.Observer/Worker/RuntimeEventFactory.cs
master 625299fa2b up
2025-10-24 19:19:23 +03:00

149 lines
5.3 KiB
C#

using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using StellaOps.Zastava.Core.Contracts;
using StellaOps.Zastava.Observer.Configuration;
using StellaOps.Zastava.Observer.ContainerRuntime;
using StellaOps.Zastava.Observer.ContainerRuntime.Cri;
using StellaOps.Zastava.Observer.Runtime;
namespace StellaOps.Zastava.Observer.Worker;
internal static class RuntimeEventFactory
{
public static RuntimeEventEnvelope Create(
ContainerLifecycleEvent lifecycleEvent,
ContainerRuntimeEndpointOptions endpoint,
CriRuntimeIdentity identity,
string tenant,
string nodeName,
RuntimeProcessCapture? capture = null,
RuntimePosture? posture = null,
IReadOnlyList<RuntimeEvidence>? additionalEvidence = null)
{
ArgumentNullException.ThrowIfNull(lifecycleEvent);
ArgumentNullException.ThrowIfNull(endpoint);
ArgumentNullException.ThrowIfNull(identity);
ArgumentNullException.ThrowIfNull(tenant);
ArgumentNullException.ThrowIfNull(nodeName);
var snapshot = lifecycleEvent.Snapshot;
var workloadLabels = snapshot.Labels ?? new Dictionary<string, string>(StringComparer.Ordinal);
var annotations = snapshot.Annotations is null
? new Dictionary<string, string>(StringComparer.Ordinal)
: new Dictionary<string, string>(snapshot.Annotations, StringComparer.Ordinal);
var platform = ResolvePlatform(workloadLabels, endpoint);
var runtimeEvent = new RuntimeEvent
{
EventId = ComputeEventId(nodeName, lifecycleEvent),
When = lifecycleEvent.Timestamp,
Kind = lifecycleEvent.Kind == ContainerLifecycleEventKind.Start
? RuntimeEventKind.ContainerStart
: RuntimeEventKind.ContainerStop,
Tenant = tenant,
Node = nodeName,
Runtime = new RuntimeEngine
{
Engine = endpoint.Engine.ToEngineString(),
Version = identity.RuntimeVersion
},
Workload = new RuntimeWorkload
{
Platform = platform,
Namespace = TryGet(workloadLabels, CriLabelKeys.PodNamespace),
Pod = TryGet(workloadLabels, CriLabelKeys.PodName),
Container = TryGet(workloadLabels, CriLabelKeys.ContainerName) ?? snapshot.Name,
ContainerId = $"{endpoint.Engine.ToEngineString()}://{snapshot.Id}",
ImageRef = ResolveImageRef(snapshot),
Owner = null
},
Process = capture?.Process,
LoadedLibraries = capture?.Libraries ?? Array.Empty<RuntimeLoadedLibrary>(),
Posture = posture,
Evidence = MergeEvidence(capture?.Evidence, additionalEvidence),
Annotations = annotations.Count == 0 ? null : new SortedDictionary<string, string>(annotations, StringComparer.Ordinal)
};
return RuntimeEventEnvelope.Create(runtimeEvent, ZastavaContractVersions.RuntimeEvent);
}
private static string ResolvePlatform(IReadOnlyDictionary<string, string> labels, ContainerRuntimeEndpointOptions endpoint)
{
if (labels.ContainsKey(CriLabelKeys.PodName))
{
return "kubernetes";
}
return endpoint.Engine.ToEngineString();
}
private static IReadOnlyList<RuntimeEvidence> MergeEvidence(
IReadOnlyList<RuntimeEvidence>? primary,
IReadOnlyList<RuntimeEvidence>? secondary)
{
if ((primary is null || primary.Count == 0) && (secondary is null || secondary.Count == 0))
{
return Array.Empty<RuntimeEvidence>();
}
if (secondary is null || secondary.Count == 0)
{
return primary ?? Array.Empty<RuntimeEvidence>();
}
if (primary is null || primary.Count == 0)
{
return secondary;
}
var merged = new List<RuntimeEvidence>(primary.Count + secondary.Count);
merged.AddRange(primary);
merged.AddRange(secondary);
return merged;
}
private static string? ResolveImageRef(CriContainerInfo snapshot)
{
if (!string.IsNullOrWhiteSpace(snapshot.ImageRef))
{
return snapshot.ImageRef;
}
return snapshot.Image;
}
private static string? TryGet(IReadOnlyDictionary<string, string> dictionary, string key)
{
if (dictionary.TryGetValue(key, out var value) && !string.IsNullOrWhiteSpace(value))
{
return value;
}
return null;
}
private static string ComputeEventId(string nodeName, ContainerLifecycleEvent lifecycleEvent)
{
var builder = new StringBuilder()
.Append(nodeName)
.Append('|')
.Append(lifecycleEvent.Snapshot.Id)
.Append('|')
.Append(lifecycleEvent.Timestamp.ToUniversalTime().Ticks)
.Append('|')
.Append((int)lifecycleEvent.Kind);
var bytes = Encoding.UTF8.GetBytes(builder.ToString());
Span<byte> hash = stackalloc byte[16];
if (!MD5.TryHashData(bytes, hash, out _))
{
using var md5 = MD5.Create();
hash = md5.ComputeHash(bytes).AsSpan(0, 16);
}
var guid = new Guid(hash);
return guid.ToString("N");
}
}