up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-13 00:20:26 +02:00
parent e1f1bef4c1
commit 564df71bfb
2376 changed files with 334389 additions and 328032 deletions

View File

@@ -1,54 +1,54 @@
using System.Diagnostics.CodeAnalysis;
namespace StellaOps.Concelier.Connector.Vndr.Vmware.Configuration;
public sealed class VmwareOptions
{
public const string HttpClientName = "source.vmware";
public Uri IndexUri { get; set; } = new("https://example.invalid/vmsa/index.json", UriKind.Absolute);
public TimeSpan InitialBackfill { get; set; } = TimeSpan.FromDays(30);
public TimeSpan ModifiedTolerance { get; set; } = TimeSpan.FromHours(2);
public int MaxAdvisoriesPerFetch { get; set; } = 50;
public TimeSpan RequestDelay { get; set; } = TimeSpan.FromMilliseconds(250);
public TimeSpan HttpTimeout { get; set; } = TimeSpan.FromMinutes(2);
[MemberNotNull(nameof(IndexUri))]
public void Validate()
{
if (IndexUri is null || !IndexUri.IsAbsoluteUri)
{
throw new InvalidOperationException("VMware index URI must be absolute.");
}
if (InitialBackfill <= TimeSpan.Zero)
{
throw new InvalidOperationException("Initial backfill must be positive.");
}
if (ModifiedTolerance < TimeSpan.Zero)
{
throw new InvalidOperationException("Modified tolerance cannot be negative.");
}
if (MaxAdvisoriesPerFetch <= 0)
{
throw new InvalidOperationException("Max advisories per fetch must be greater than zero.");
}
if (RequestDelay < TimeSpan.Zero)
{
throw new InvalidOperationException("Request delay cannot be negative.");
}
if (HttpTimeout <= TimeSpan.Zero)
{
throw new InvalidOperationException("HTTP timeout must be positive.");
}
}
}
using System.Diagnostics.CodeAnalysis;
namespace StellaOps.Concelier.Connector.Vndr.Vmware.Configuration;
public sealed class VmwareOptions
{
public const string HttpClientName = "source.vmware";
public Uri IndexUri { get; set; } = new("https://example.invalid/vmsa/index.json", UriKind.Absolute);
public TimeSpan InitialBackfill { get; set; } = TimeSpan.FromDays(30);
public TimeSpan ModifiedTolerance { get; set; } = TimeSpan.FromHours(2);
public int MaxAdvisoriesPerFetch { get; set; } = 50;
public TimeSpan RequestDelay { get; set; } = TimeSpan.FromMilliseconds(250);
public TimeSpan HttpTimeout { get; set; } = TimeSpan.FromMinutes(2);
[MemberNotNull(nameof(IndexUri))]
public void Validate()
{
if (IndexUri is null || !IndexUri.IsAbsoluteUri)
{
throw new InvalidOperationException("VMware index URI must be absolute.");
}
if (InitialBackfill <= TimeSpan.Zero)
{
throw new InvalidOperationException("Initial backfill must be positive.");
}
if (ModifiedTolerance < TimeSpan.Zero)
{
throw new InvalidOperationException("Modified tolerance cannot be negative.");
}
if (MaxAdvisoriesPerFetch <= 0)
{
throw new InvalidOperationException("Max advisories per fetch must be greater than zero.");
}
if (RequestDelay < TimeSpan.Zero)
{
throw new InvalidOperationException("Request delay cannot be negative.");
}
if (HttpTimeout <= TimeSpan.Zero)
{
throw new InvalidOperationException("HTTP timeout must be positive.");
}
}
}

View File

@@ -1,172 +1,172 @@
using System;
using System.Collections.Generic;
using System.Linq;
using StellaOps.Concelier.Bson;
namespace StellaOps.Concelier.Connector.Vndr.Vmware.Internal;
internal sealed record VmwareCursor(
DateTimeOffset? LastModified,
IReadOnlyCollection<string> ProcessedIds,
IReadOnlyCollection<Guid> PendingDocuments,
IReadOnlyCollection<Guid> PendingMappings,
IReadOnlyDictionary<string, VmwareFetchCacheEntry> FetchCache)
{
private static readonly IReadOnlyCollection<Guid> EmptyGuidList = Array.Empty<Guid>();
private static readonly IReadOnlyCollection<string> EmptyStringList = Array.Empty<string>();
private static readonly IReadOnlyDictionary<string, VmwareFetchCacheEntry> EmptyFetchCache =
new Dictionary<string, VmwareFetchCacheEntry>(StringComparer.OrdinalIgnoreCase);
public static VmwareCursor Empty { get; } = new(null, EmptyStringList, EmptyGuidList, EmptyGuidList, EmptyFetchCache);
public BsonDocument ToBsonDocument()
{
var document = new BsonDocument
{
["pendingDocuments"] = new BsonArray(PendingDocuments.Select(id => id.ToString())),
["pendingMappings"] = new BsonArray(PendingMappings.Select(id => id.ToString())),
};
if (LastModified.HasValue)
{
document["lastModified"] = LastModified.Value.UtcDateTime;
}
if (ProcessedIds.Count > 0)
{
document["processedIds"] = new BsonArray(ProcessedIds);
}
if (FetchCache.Count > 0)
{
var cacheDocument = new BsonDocument();
foreach (var (key, entry) in FetchCache)
{
cacheDocument[key] = entry.ToBsonDocument();
}
document["fetchCache"] = cacheDocument;
}
return document;
}
public static VmwareCursor FromBson(BsonDocument? document)
{
if (document is null || document.ElementCount == 0)
{
return Empty;
}
var lastModified = document.TryGetValue("lastModified", out var value)
? ParseDate(value)
: null;
var processedIds = document.TryGetValue("processedIds", out var processedValue) && processedValue is BsonArray idsArray
? idsArray.OfType<BsonValue>()
.Where(static x => x.BsonType == BsonType.String)
.Select(static x => x.AsString)
.ToArray()
: EmptyStringList;
var pendingDocuments = ReadGuidArray(document, "pendingDocuments");
var pendingMappings = ReadGuidArray(document, "pendingMappings");
var fetchCache = ReadFetchCache(document);
return new VmwareCursor(lastModified, processedIds, pendingDocuments, pendingMappings, fetchCache);
}
public VmwareCursor WithLastModified(DateTimeOffset timestamp, IEnumerable<string> processedIds)
=> this with
{
LastModified = timestamp.ToUniversalTime(),
ProcessedIds = processedIds?.Where(static id => !string.IsNullOrWhiteSpace(id))
.Select(static id => id.Trim())
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray() ?? EmptyStringList,
};
public VmwareCursor WithPendingDocuments(IEnumerable<Guid> ids)
=> this with { PendingDocuments = ids?.Distinct().ToArray() ?? EmptyGuidList };
public VmwareCursor WithPendingMappings(IEnumerable<Guid> ids)
=> this with { PendingMappings = ids?.Distinct().ToArray() ?? EmptyGuidList };
public VmwareCursor WithFetchCache(IDictionary<string, VmwareFetchCacheEntry>? cache)
{
if (cache is null || cache.Count == 0)
{
return this with { FetchCache = EmptyFetchCache };
}
return this with { FetchCache = new Dictionary<string, VmwareFetchCacheEntry>(cache, StringComparer.OrdinalIgnoreCase) };
}
public bool TryGetFetchCache(string key, out VmwareFetchCacheEntry entry)
{
if (FetchCache.Count == 0)
{
entry = VmwareFetchCacheEntry.Empty;
return false;
}
return FetchCache.TryGetValue(key, out entry!);
}
public VmwareCursor AddProcessedId(string id)
{
if (string.IsNullOrWhiteSpace(id))
{
return this;
}
var set = new HashSet<string>(ProcessedIds, StringComparer.OrdinalIgnoreCase) { id.Trim() };
return this with { ProcessedIds = set.ToArray() };
}
private static IReadOnlyCollection<Guid> ReadGuidArray(BsonDocument document, string field)
{
if (!document.TryGetValue(field, out var value) || value is not BsonArray array)
{
return EmptyGuidList;
}
var results = new List<Guid>(array.Count);
foreach (var element in array)
{
if (Guid.TryParse(element.ToString(), out var guid))
{
results.Add(guid);
}
}
return results;
}
private static IReadOnlyDictionary<string, VmwareFetchCacheEntry> ReadFetchCache(BsonDocument document)
{
if (!document.TryGetValue("fetchCache", out var value) || value is not BsonDocument cacheDocument || cacheDocument.ElementCount == 0)
{
return EmptyFetchCache;
}
var cache = new Dictionary<string, VmwareFetchCacheEntry>(StringComparer.OrdinalIgnoreCase);
foreach (var element in cacheDocument.Elements)
{
if (element.Value is BsonDocument entryDocument)
{
cache[element.Name] = VmwareFetchCacheEntry.FromBson(entryDocument);
}
}
return cache;
}
private static DateTimeOffset? ParseDate(BsonValue value)
=> value.BsonType switch
{
BsonType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
BsonType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
_ => null,
};
}
using System;
using System.Collections.Generic;
using System.Linq;
using StellaOps.Concelier.Documents;
namespace StellaOps.Concelier.Connector.Vndr.Vmware.Internal;
internal sealed record VmwareCursor(
DateTimeOffset? LastModified,
IReadOnlyCollection<string> ProcessedIds,
IReadOnlyCollection<Guid> PendingDocuments,
IReadOnlyCollection<Guid> PendingMappings,
IReadOnlyDictionary<string, VmwareFetchCacheEntry> FetchCache)
{
private static readonly IReadOnlyCollection<Guid> EmptyGuidList = Array.Empty<Guid>();
private static readonly IReadOnlyCollection<string> EmptyStringList = Array.Empty<string>();
private static readonly IReadOnlyDictionary<string, VmwareFetchCacheEntry> EmptyFetchCache =
new Dictionary<string, VmwareFetchCacheEntry>(StringComparer.OrdinalIgnoreCase);
public static VmwareCursor Empty { get; } = new(null, EmptyStringList, EmptyGuidList, EmptyGuidList, EmptyFetchCache);
public DocumentObject ToDocumentObject()
{
var document = new DocumentObject
{
["pendingDocuments"] = new DocumentArray(PendingDocuments.Select(id => id.ToString())),
["pendingMappings"] = new DocumentArray(PendingMappings.Select(id => id.ToString())),
};
if (LastModified.HasValue)
{
document["lastModified"] = LastModified.Value.UtcDateTime;
}
if (ProcessedIds.Count > 0)
{
document["processedIds"] = new DocumentArray(ProcessedIds);
}
if (FetchCache.Count > 0)
{
var cacheDocument = new DocumentObject();
foreach (var (key, entry) in FetchCache)
{
cacheDocument[key] = entry.ToDocumentObject();
}
document["fetchCache"] = cacheDocument;
}
return document;
}
public static VmwareCursor FromBson(DocumentObject? document)
{
if (document is null || document.ElementCount == 0)
{
return Empty;
}
var lastModified = document.TryGetValue("lastModified", out var value)
? ParseDate(value)
: null;
var processedIds = document.TryGetValue("processedIds", out var processedValue) && processedValue is DocumentArray idsArray
? idsArray.OfType<DocumentValue>()
.Where(static x => x.DocumentType == DocumentType.String)
.Select(static x => x.AsString)
.ToArray()
: EmptyStringList;
var pendingDocuments = ReadGuidArray(document, "pendingDocuments");
var pendingMappings = ReadGuidArray(document, "pendingMappings");
var fetchCache = ReadFetchCache(document);
return new VmwareCursor(lastModified, processedIds, pendingDocuments, pendingMappings, fetchCache);
}
public VmwareCursor WithLastModified(DateTimeOffset timestamp, IEnumerable<string> processedIds)
=> this with
{
LastModified = timestamp.ToUniversalTime(),
ProcessedIds = processedIds?.Where(static id => !string.IsNullOrWhiteSpace(id))
.Select(static id => id.Trim())
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray() ?? EmptyStringList,
};
public VmwareCursor WithPendingDocuments(IEnumerable<Guid> ids)
=> this with { PendingDocuments = ids?.Distinct().ToArray() ?? EmptyGuidList };
public VmwareCursor WithPendingMappings(IEnumerable<Guid> ids)
=> this with { PendingMappings = ids?.Distinct().ToArray() ?? EmptyGuidList };
public VmwareCursor WithFetchCache(IDictionary<string, VmwareFetchCacheEntry>? cache)
{
if (cache is null || cache.Count == 0)
{
return this with { FetchCache = EmptyFetchCache };
}
return this with { FetchCache = new Dictionary<string, VmwareFetchCacheEntry>(cache, StringComparer.OrdinalIgnoreCase) };
}
public bool TryGetFetchCache(string key, out VmwareFetchCacheEntry entry)
{
if (FetchCache.Count == 0)
{
entry = VmwareFetchCacheEntry.Empty;
return false;
}
return FetchCache.TryGetValue(key, out entry!);
}
public VmwareCursor AddProcessedId(string id)
{
if (string.IsNullOrWhiteSpace(id))
{
return this;
}
var set = new HashSet<string>(ProcessedIds, StringComparer.OrdinalIgnoreCase) { id.Trim() };
return this with { ProcessedIds = set.ToArray() };
}
private static IReadOnlyCollection<Guid> ReadGuidArray(DocumentObject document, string field)
{
if (!document.TryGetValue(field, out var value) || value is not DocumentArray array)
{
return EmptyGuidList;
}
var results = new List<Guid>(array.Count);
foreach (var element in array)
{
if (Guid.TryParse(element.ToString(), out var guid))
{
results.Add(guid);
}
}
return results;
}
private static IReadOnlyDictionary<string, VmwareFetchCacheEntry> ReadFetchCache(DocumentObject document)
{
if (!document.TryGetValue("fetchCache", out var value) || value is not DocumentObject cacheDocument || cacheDocument.ElementCount == 0)
{
return EmptyFetchCache;
}
var cache = new Dictionary<string, VmwareFetchCacheEntry>(StringComparer.OrdinalIgnoreCase);
foreach (var element in cacheDocument.Elements)
{
if (element.Value is DocumentObject entryDocument)
{
cache[element.Name] = VmwareFetchCacheEntry.FromBson(entryDocument);
}
}
return cache;
}
private static DateTimeOffset? ParseDate(DocumentValue value)
=> value.DocumentType switch
{
DocumentType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
DocumentType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
_ => null,
};
}

View File

@@ -1,53 +1,53 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace StellaOps.Concelier.Connector.Vndr.Vmware.Internal;
internal sealed record VmwareDetailDto
{
[JsonPropertyName("id")]
public string AdvisoryId { get; init; } = string.Empty;
[JsonPropertyName("title")]
public string Title { get; init; } = string.Empty;
[JsonPropertyName("summary")]
public string? Summary { get; init; }
[JsonPropertyName("published")]
public DateTimeOffset? Published { get; init; }
[JsonPropertyName("modified")]
public DateTimeOffset? Modified { get; init; }
[JsonPropertyName("cves")]
public IReadOnlyList<string>? CveIds { get; init; }
[JsonPropertyName("affected")]
public IReadOnlyList<VmwareAffectedProductDto>? Affected { get; init; }
[JsonPropertyName("references")]
public IReadOnlyList<VmwareReferenceDto>? References { get; init; }
}
internal sealed record VmwareAffectedProductDto
{
[JsonPropertyName("product")]
public string Product { get; init; } = string.Empty;
[JsonPropertyName("version")]
public string? Version { get; init; }
[JsonPropertyName("fixedVersion")]
public string? FixedVersion { get; init; }
}
internal sealed record VmwareReferenceDto
{
[JsonPropertyName("type")]
public string? Type { get; init; }
[JsonPropertyName("url")]
public string Url { get; init; } = string.Empty;
}
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace StellaOps.Concelier.Connector.Vndr.Vmware.Internal;
internal sealed record VmwareDetailDto
{
[JsonPropertyName("id")]
public string AdvisoryId { get; init; } = string.Empty;
[JsonPropertyName("title")]
public string Title { get; init; } = string.Empty;
[JsonPropertyName("summary")]
public string? Summary { get; init; }
[JsonPropertyName("published")]
public DateTimeOffset? Published { get; init; }
[JsonPropertyName("modified")]
public DateTimeOffset? Modified { get; init; }
[JsonPropertyName("cves")]
public IReadOnlyList<string>? CveIds { get; init; }
[JsonPropertyName("affected")]
public IReadOnlyList<VmwareAffectedProductDto>? Affected { get; init; }
[JsonPropertyName("references")]
public IReadOnlyList<VmwareReferenceDto>? References { get; init; }
}
internal sealed record VmwareAffectedProductDto
{
[JsonPropertyName("product")]
public string Product { get; init; } = string.Empty;
[JsonPropertyName("version")]
public string? Version { get; init; }
[JsonPropertyName("fixedVersion")]
public string? FixedVersion { get; init; }
}
internal sealed record VmwareReferenceDto
{
[JsonPropertyName("type")]
public string? Type { get; init; }
[JsonPropertyName("url")]
public string Url { get; init; } = string.Empty;
}

View File

@@ -1,88 +1,88 @@
using System;
using StellaOps.Concelier.Bson;
using StellaOps.Concelier.Storage;
namespace StellaOps.Concelier.Connector.Vndr.Vmware.Internal;
internal sealed record VmwareFetchCacheEntry(string? Sha256, string? ETag, DateTimeOffset? LastModified)
{
public static VmwareFetchCacheEntry Empty { get; } = new(string.Empty, null, null);
public BsonDocument ToBsonDocument()
{
var document = new BsonDocument
{
["sha256"] = Sha256 ?? string.Empty,
};
if (!string.IsNullOrWhiteSpace(ETag))
{
document["etag"] = ETag;
}
if (LastModified.HasValue)
{
document["lastModified"] = LastModified.Value.UtcDateTime;
}
return document;
}
public static VmwareFetchCacheEntry FromBson(BsonDocument document)
{
var sha256 = document.TryGetValue("sha256", out var shaValue) ? shaValue.ToString() : string.Empty;
string? etag = null;
if (document.TryGetValue("etag", out var etagValue) && !etagValue.IsBsonNull)
{
etag = etagValue.ToString();
}
DateTimeOffset? lastModified = null;
if (document.TryGetValue("lastModified", out var lastModifiedValue))
{
lastModified = lastModifiedValue.BsonType switch
{
BsonType.DateTime => DateTime.SpecifyKind(lastModifiedValue.ToUniversalTime(), DateTimeKind.Utc),
BsonType.String when DateTimeOffset.TryParse(lastModifiedValue.AsString, out var parsed) => parsed.ToUniversalTime(),
_ => null,
};
}
return new VmwareFetchCacheEntry(sha256, etag, lastModified);
}
public static VmwareFetchCacheEntry FromDocument(DocumentRecord document)
{
ArgumentNullException.ThrowIfNull(document);
return new VmwareFetchCacheEntry(
document.Sha256,
document.Etag,
document.LastModified?.ToUniversalTime());
}
public bool Matches(DocumentRecord document)
{
ArgumentNullException.ThrowIfNull(document);
if (!string.IsNullOrEmpty(Sha256) && !string.IsNullOrEmpty(document.Sha256)
&& string.Equals(Sha256, document.Sha256, StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (!string.IsNullOrEmpty(ETag) && !string.IsNullOrEmpty(document.Etag)
&& string.Equals(ETag, document.Etag, StringComparison.Ordinal))
{
return true;
}
if (LastModified.HasValue && document.LastModified.HasValue
&& LastModified.Value.ToUniversalTime() == document.LastModified.Value.ToUniversalTime())
{
return true;
}
return false;
}
}
using System;
using StellaOps.Concelier.Documents;
using StellaOps.Concelier.Storage;
namespace StellaOps.Concelier.Connector.Vndr.Vmware.Internal;
internal sealed record VmwareFetchCacheEntry(string? Sha256, string? ETag, DateTimeOffset? LastModified)
{
public static VmwareFetchCacheEntry Empty { get; } = new(string.Empty, null, null);
public DocumentObject ToDocumentObject()
{
var document = new DocumentObject
{
["sha256"] = Sha256 ?? string.Empty,
};
if (!string.IsNullOrWhiteSpace(ETag))
{
document["etag"] = ETag;
}
if (LastModified.HasValue)
{
document["lastModified"] = LastModified.Value.UtcDateTime;
}
return document;
}
public static VmwareFetchCacheEntry FromBson(DocumentObject document)
{
var sha256 = document.TryGetValue("sha256", out var shaValue) ? shaValue.ToString() : string.Empty;
string? etag = null;
if (document.TryGetValue("etag", out var etagValue) && !etagValue.IsDocumentNull)
{
etag = etagValue.ToString();
}
DateTimeOffset? lastModified = null;
if (document.TryGetValue("lastModified", out var lastModifiedValue))
{
lastModified = lastModifiedValue.DocumentType switch
{
DocumentType.DateTime => DateTime.SpecifyKind(lastModifiedValue.ToUniversalTime(), DateTimeKind.Utc),
DocumentType.String when DateTimeOffset.TryParse(lastModifiedValue.AsString, out var parsed) => parsed.ToUniversalTime(),
_ => null,
};
}
return new VmwareFetchCacheEntry(sha256, etag, lastModified);
}
public static VmwareFetchCacheEntry FromDocument(DocumentRecord document)
{
ArgumentNullException.ThrowIfNull(document);
return new VmwareFetchCacheEntry(
document.Sha256,
document.Etag,
document.LastModified?.ToUniversalTime());
}
public bool Matches(DocumentRecord document)
{
ArgumentNullException.ThrowIfNull(document);
if (!string.IsNullOrEmpty(Sha256) && !string.IsNullOrEmpty(document.Sha256)
&& string.Equals(Sha256, document.Sha256, StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (!string.IsNullOrEmpty(ETag) && !string.IsNullOrEmpty(document.Etag)
&& string.Equals(ETag, document.Etag, StringComparison.Ordinal))
{
return true;
}
if (LastModified.HasValue && document.LastModified.HasValue
&& LastModified.Value.ToUniversalTime() == document.LastModified.Value.ToUniversalTime())
{
return true;
}
return false;
}
}

View File

@@ -1,16 +1,16 @@
using System;
using System.Text.Json.Serialization;
namespace StellaOps.Concelier.Connector.Vndr.Vmware.Internal;
internal sealed record VmwareIndexItem
{
[JsonPropertyName("id")]
public string Id { get; init; } = string.Empty;
[JsonPropertyName("url")]
public string DetailUrl { get; init; } = string.Empty;
[JsonPropertyName("modified")]
public DateTimeOffset? Modified { get; init; }
}
using System;
using System.Text.Json.Serialization;
namespace StellaOps.Concelier.Connector.Vndr.Vmware.Internal;
internal sealed record VmwareIndexItem
{
[JsonPropertyName("id")]
public string Id { get; init; } = string.Empty;
[JsonPropertyName("url")]
public string DetailUrl { get; init; } = string.Empty;
[JsonPropertyName("modified")]
public DateTimeOffset? Modified { get; init; }
}

View File

@@ -1,235 +1,235 @@
using System;
using System.Collections.Generic;
using System.Linq;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Connector.Common;
using StellaOps.Concelier.Connector.Common.Packages;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.PsirtFlags;
namespace StellaOps.Concelier.Connector.Vndr.Vmware.Internal;
internal static class VmwareMapper
{
public static (Advisory Advisory, PsirtFlagRecord Flag) Map(VmwareDetailDto dto, DocumentRecord document, DtoRecord dtoRecord)
{
ArgumentNullException.ThrowIfNull(dto);
ArgumentNullException.ThrowIfNull(document);
ArgumentNullException.ThrowIfNull(dtoRecord);
var recordedAt = dtoRecord.ValidatedAt.ToUniversalTime();
var fetchProvenance = new AdvisoryProvenance(VmwareConnectorPlugin.SourceName, "document", document.Uri, document.FetchedAt.ToUniversalTime());
var mappingProvenance = new AdvisoryProvenance(VmwareConnectorPlugin.SourceName, "mapping", dto.AdvisoryId, recordedAt);
var aliases = BuildAliases(dto);
var references = BuildReferences(dto, recordedAt);
var affectedPackages = BuildAffectedPackages(dto, recordedAt);
var advisory = new Advisory(
dto.AdvisoryId,
dto.Title,
dto.Summary,
language: "en",
dto.Published?.ToUniversalTime(),
dto.Modified?.ToUniversalTime(),
severity: null,
exploitKnown: false,
aliases,
references,
affectedPackages,
cvssMetrics: Array.Empty<CvssMetric>(),
provenance: new[] { fetchProvenance, mappingProvenance });
var flag = new PsirtFlagRecord(
dto.AdvisoryId,
"VMware",
VmwareConnectorPlugin.SourceName,
dto.AdvisoryId,
recordedAt);
return (advisory, flag);
}
private static IEnumerable<string> BuildAliases(VmwareDetailDto dto)
{
var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { dto.AdvisoryId };
if (dto.CveIds is not null)
{
foreach (var cve in dto.CveIds)
{
if (!string.IsNullOrWhiteSpace(cve))
{
set.Add(cve.Trim());
}
}
}
return set;
}
private static IReadOnlyList<AdvisoryReference> BuildReferences(VmwareDetailDto dto, DateTimeOffset recordedAt)
{
if (dto.References is null || dto.References.Count == 0)
{
return Array.Empty<AdvisoryReference>();
}
var references = new List<AdvisoryReference>(dto.References.Count);
foreach (var reference in dto.References)
{
if (string.IsNullOrWhiteSpace(reference.Url))
{
continue;
}
var kind = NormalizeReferenceKind(reference.Type);
var provenance = new AdvisoryProvenance(VmwareConnectorPlugin.SourceName, "reference", reference.Url, recordedAt);
try
{
references.Add(new AdvisoryReference(reference.Url, kind, reference.Type, null, provenance));
}
catch (ArgumentException)
{
// ignore invalid urls
}
}
references.Sort(static (left, right) => StringComparer.OrdinalIgnoreCase.Compare(left.Url, right.Url));
return references.Count == 0 ? Array.Empty<AdvisoryReference>() : references;
}
private static string? NormalizeReferenceKind(string? type)
{
if (string.IsNullOrWhiteSpace(type))
{
return null;
}
return type.Trim().ToLowerInvariant() switch
{
"advisory" => "advisory",
"kb" or "kb_article" => "kb",
"patch" => "patch",
"workaround" => "workaround",
_ => null,
};
}
private static IReadOnlyList<AffectedPackage> BuildAffectedPackages(VmwareDetailDto dto, DateTimeOffset recordedAt)
{
if (dto.Affected is null || dto.Affected.Count == 0)
{
return Array.Empty<AffectedPackage>();
}
var packages = new List<AffectedPackage>(dto.Affected.Count);
foreach (var product in dto.Affected)
{
if (string.IsNullOrWhiteSpace(product.Product))
{
continue;
}
var provenance = new[]
{
new AdvisoryProvenance(VmwareConnectorPlugin.SourceName, "affected", product.Product, recordedAt),
};
var ranges = new List<AffectedVersionRange>();
if (!string.IsNullOrWhiteSpace(product.Version) || !string.IsNullOrWhiteSpace(product.FixedVersion))
{
var rangeProvenance = new AdvisoryProvenance(VmwareConnectorPlugin.SourceName, "range", product.Product, recordedAt);
ranges.Add(new AffectedVersionRange(
rangeKind: "vendor",
introducedVersion: product.Version,
fixedVersion: product.FixedVersion,
lastAffectedVersion: null,
rangeExpression: product.Version,
provenance: rangeProvenance,
primitives: BuildRangePrimitives(product)));
}
packages.Add(new AffectedPackage(
AffectedPackageTypes.Vendor,
product.Product,
platform: null,
versionRanges: ranges,
statuses: Array.Empty<AffectedPackageStatus>(),
provenance: provenance));
}
return packages;
}
private static RangePrimitives? BuildRangePrimitives(VmwareAffectedProductDto product)
{
var extensions = new Dictionary<string, string>(StringComparer.Ordinal);
AddExtension(extensions, "vmware.product", product.Product);
AddExtension(extensions, "vmware.version.raw", product.Version);
AddExtension(extensions, "vmware.fixedVersion.raw", product.FixedVersion);
var semVer = BuildSemVerPrimitive(product.Version, product.FixedVersion);
if (semVer is null && extensions.Count == 0)
{
return null;
}
return new RangePrimitives(semVer, null, null, extensions.Count == 0 ? null : extensions);
}
private static SemVerPrimitive? BuildSemVerPrimitive(string? introduced, string? fixedVersion)
{
var introducedNormalized = NormalizeSemVer(introduced);
var fixedNormalized = NormalizeSemVer(fixedVersion);
if (introducedNormalized is null && fixedNormalized is null)
{
return null;
}
return new SemVerPrimitive(
introducedNormalized,
IntroducedInclusive: true,
fixedNormalized,
FixedInclusive: false,
LastAffected: null,
LastAffectedInclusive: false,
ConstraintExpression: null);
}
private static string? NormalizeSemVer(string? value)
{
if (PackageCoordinateHelper.TryParseSemVer(value, out _, out var normalized) && !string.IsNullOrWhiteSpace(normalized))
{
return normalized;
}
if (Version.TryParse(value, out var parsed))
{
if (parsed.Build >= 0 && parsed.Revision >= 0)
{
return $"{parsed.Major}.{parsed.Minor}.{parsed.Build}.{parsed.Revision}";
}
if (parsed.Build >= 0)
{
return $"{parsed.Major}.{parsed.Minor}.{parsed.Build}";
}
return $"{parsed.Major}.{parsed.Minor}";
}
return null;
}
private static void AddExtension(Dictionary<string, string> extensions, string key, string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return;
}
extensions[key] = value.Trim();
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Connector.Common;
using StellaOps.Concelier.Connector.Common.Packages;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.PsirtFlags;
namespace StellaOps.Concelier.Connector.Vndr.Vmware.Internal;
internal static class VmwareMapper
{
public static (Advisory Advisory, PsirtFlagRecord Flag) Map(VmwareDetailDto dto, DocumentRecord document, DtoRecord dtoRecord)
{
ArgumentNullException.ThrowIfNull(dto);
ArgumentNullException.ThrowIfNull(document);
ArgumentNullException.ThrowIfNull(dtoRecord);
var recordedAt = dtoRecord.ValidatedAt.ToUniversalTime();
var fetchProvenance = new AdvisoryProvenance(VmwareConnectorPlugin.SourceName, "document", document.Uri, document.FetchedAt.ToUniversalTime());
var mappingProvenance = new AdvisoryProvenance(VmwareConnectorPlugin.SourceName, "mapping", dto.AdvisoryId, recordedAt);
var aliases = BuildAliases(dto);
var references = BuildReferences(dto, recordedAt);
var affectedPackages = BuildAffectedPackages(dto, recordedAt);
var advisory = new Advisory(
dto.AdvisoryId,
dto.Title,
dto.Summary,
language: "en",
dto.Published?.ToUniversalTime(),
dto.Modified?.ToUniversalTime(),
severity: null,
exploitKnown: false,
aliases,
references,
affectedPackages,
cvssMetrics: Array.Empty<CvssMetric>(),
provenance: new[] { fetchProvenance, mappingProvenance });
var flag = new PsirtFlagRecord(
dto.AdvisoryId,
"VMware",
VmwareConnectorPlugin.SourceName,
dto.AdvisoryId,
recordedAt);
return (advisory, flag);
}
private static IEnumerable<string> BuildAliases(VmwareDetailDto dto)
{
var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { dto.AdvisoryId };
if (dto.CveIds is not null)
{
foreach (var cve in dto.CveIds)
{
if (!string.IsNullOrWhiteSpace(cve))
{
set.Add(cve.Trim());
}
}
}
return set;
}
private static IReadOnlyList<AdvisoryReference> BuildReferences(VmwareDetailDto dto, DateTimeOffset recordedAt)
{
if (dto.References is null || dto.References.Count == 0)
{
return Array.Empty<AdvisoryReference>();
}
var references = new List<AdvisoryReference>(dto.References.Count);
foreach (var reference in dto.References)
{
if (string.IsNullOrWhiteSpace(reference.Url))
{
continue;
}
var kind = NormalizeReferenceKind(reference.Type);
var provenance = new AdvisoryProvenance(VmwareConnectorPlugin.SourceName, "reference", reference.Url, recordedAt);
try
{
references.Add(new AdvisoryReference(reference.Url, kind, reference.Type, null, provenance));
}
catch (ArgumentException)
{
// ignore invalid urls
}
}
references.Sort(static (left, right) => StringComparer.OrdinalIgnoreCase.Compare(left.Url, right.Url));
return references.Count == 0 ? Array.Empty<AdvisoryReference>() : references;
}
private static string? NormalizeReferenceKind(string? type)
{
if (string.IsNullOrWhiteSpace(type))
{
return null;
}
return type.Trim().ToLowerInvariant() switch
{
"advisory" => "advisory",
"kb" or "kb_article" => "kb",
"patch" => "patch",
"workaround" => "workaround",
_ => null,
};
}
private static IReadOnlyList<AffectedPackage> BuildAffectedPackages(VmwareDetailDto dto, DateTimeOffset recordedAt)
{
if (dto.Affected is null || dto.Affected.Count == 0)
{
return Array.Empty<AffectedPackage>();
}
var packages = new List<AffectedPackage>(dto.Affected.Count);
foreach (var product in dto.Affected)
{
if (string.IsNullOrWhiteSpace(product.Product))
{
continue;
}
var provenance = new[]
{
new AdvisoryProvenance(VmwareConnectorPlugin.SourceName, "affected", product.Product, recordedAt),
};
var ranges = new List<AffectedVersionRange>();
if (!string.IsNullOrWhiteSpace(product.Version) || !string.IsNullOrWhiteSpace(product.FixedVersion))
{
var rangeProvenance = new AdvisoryProvenance(VmwareConnectorPlugin.SourceName, "range", product.Product, recordedAt);
ranges.Add(new AffectedVersionRange(
rangeKind: "vendor",
introducedVersion: product.Version,
fixedVersion: product.FixedVersion,
lastAffectedVersion: null,
rangeExpression: product.Version,
provenance: rangeProvenance,
primitives: BuildRangePrimitives(product)));
}
packages.Add(new AffectedPackage(
AffectedPackageTypes.Vendor,
product.Product,
platform: null,
versionRanges: ranges,
statuses: Array.Empty<AffectedPackageStatus>(),
provenance: provenance));
}
return packages;
}
private static RangePrimitives? BuildRangePrimitives(VmwareAffectedProductDto product)
{
var extensions = new Dictionary<string, string>(StringComparer.Ordinal);
AddExtension(extensions, "vmware.product", product.Product);
AddExtension(extensions, "vmware.version.raw", product.Version);
AddExtension(extensions, "vmware.fixedVersion.raw", product.FixedVersion);
var semVer = BuildSemVerPrimitive(product.Version, product.FixedVersion);
if (semVer is null && extensions.Count == 0)
{
return null;
}
return new RangePrimitives(semVer, null, null, extensions.Count == 0 ? null : extensions);
}
private static SemVerPrimitive? BuildSemVerPrimitive(string? introduced, string? fixedVersion)
{
var introducedNormalized = NormalizeSemVer(introduced);
var fixedNormalized = NormalizeSemVer(fixedVersion);
if (introducedNormalized is null && fixedNormalized is null)
{
return null;
}
return new SemVerPrimitive(
introducedNormalized,
IntroducedInclusive: true,
fixedNormalized,
FixedInclusive: false,
LastAffected: null,
LastAffectedInclusive: false,
ConstraintExpression: null);
}
private static string? NormalizeSemVer(string? value)
{
if (PackageCoordinateHelper.TryParseSemVer(value, out _, out var normalized) && !string.IsNullOrWhiteSpace(normalized))
{
return normalized;
}
if (Version.TryParse(value, out var parsed))
{
if (parsed.Build >= 0 && parsed.Revision >= 0)
{
return $"{parsed.Major}.{parsed.Minor}.{parsed.Build}.{parsed.Revision}";
}
if (parsed.Build >= 0)
{
return $"{parsed.Major}.{parsed.Minor}.{parsed.Build}";
}
return $"{parsed.Major}.{parsed.Minor}";
}
return null;
}
private static void AddExtension(Dictionary<string, string> extensions, string key, string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return;
}
extensions[key] = value.Trim();
}
}

View File

@@ -1,46 +1,46 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Concelier.Core.Jobs;
namespace StellaOps.Concelier.Connector.Vndr.Vmware;
internal static class VmwareJobKinds
{
public const string Fetch = "source:vmware:fetch";
public const string Parse = "source:vmware:parse";
public const string Map = "source:vmware:map";
}
internal sealed class VmwareFetchJob : IJob
{
private readonly VmwareConnector _connector;
public VmwareFetchJob(VmwareConnector connector)
=> _connector = connector ?? throw new ArgumentNullException(nameof(connector));
public Task ExecuteAsync(JobExecutionContext context, CancellationToken cancellationToken)
=> _connector.FetchAsync(context.Services, cancellationToken);
}
internal sealed class VmwareParseJob : IJob
{
private readonly VmwareConnector _connector;
public VmwareParseJob(VmwareConnector connector)
=> _connector = connector ?? throw new ArgumentNullException(nameof(connector));
public Task ExecuteAsync(JobExecutionContext context, CancellationToken cancellationToken)
=> _connector.ParseAsync(context.Services, cancellationToken);
}
internal sealed class VmwareMapJob : IJob
{
private readonly VmwareConnector _connector;
public VmwareMapJob(VmwareConnector connector)
=> _connector = connector ?? throw new ArgumentNullException(nameof(connector));
public Task ExecuteAsync(JobExecutionContext context, CancellationToken cancellationToken)
=> _connector.MapAsync(context.Services, cancellationToken);
}
using System;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Concelier.Core.Jobs;
namespace StellaOps.Concelier.Connector.Vndr.Vmware;
internal static class VmwareJobKinds
{
public const string Fetch = "source:vmware:fetch";
public const string Parse = "source:vmware:parse";
public const string Map = "source:vmware:map";
}
internal sealed class VmwareFetchJob : IJob
{
private readonly VmwareConnector _connector;
public VmwareFetchJob(VmwareConnector connector)
=> _connector = connector ?? throw new ArgumentNullException(nameof(connector));
public Task ExecuteAsync(JobExecutionContext context, CancellationToken cancellationToken)
=> _connector.FetchAsync(context.Services, cancellationToken);
}
internal sealed class VmwareParseJob : IJob
{
private readonly VmwareConnector _connector;
public VmwareParseJob(VmwareConnector connector)
=> _connector = connector ?? throw new ArgumentNullException(nameof(connector));
public Task ExecuteAsync(JobExecutionContext context, CancellationToken cancellationToken)
=> _connector.ParseAsync(context.Services, cancellationToken);
}
internal sealed class VmwareMapJob : IJob
{
private readonly VmwareConnector _connector;
public VmwareMapJob(VmwareConnector connector)
=> _connector = connector ?? throw new ArgumentNullException(nameof(connector));
public Task ExecuteAsync(JobExecutionContext context, CancellationToken cancellationToken)
=> _connector.MapAsync(context.Services, cancellationToken);
}

View File

@@ -1,3 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("StellaOps.Concelier.Connector.Vndr.Vmware.Tests")]
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("StellaOps.Concelier.Connector.Vndr.Vmware.Tests")]

View File

@@ -7,8 +7,8 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Concelier.Bson;
using StellaOps.Concelier.Bson.IO;
using StellaOps.Concelier.Documents;
using StellaOps.Concelier.Documents.IO;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Connector.Common;
using StellaOps.Concelier.Connector.Common.Fetch;
@@ -343,7 +343,7 @@ public sealed class VmwareConnector : IFeedConnector
}
var sanitized = JsonSerializer.Serialize(detail, SerializerOptions);
var payload = StellaOps.Concelier.Bson.BsonDocument.Parse(sanitized);
var payload = StellaOps.Concelier.Documents.DocumentObject.Parse(sanitized);
var dtoRecord = new DtoRecord(Guid.NewGuid(), document.Id, SourceName, "vmware.v1", payload, _timeProvider.GetUtcNow());
await _dtoStore.UpsertAsync(dtoRecord, cancellationToken).ConfigureAwait(false);
@@ -448,7 +448,7 @@ public sealed class VmwareConnector : IFeedConnector
private async Task UpdateCursorAsync(VmwareCursor cursor, CancellationToken cancellationToken)
{
var document = cursor.ToBsonDocument();
var document = cursor.ToDocumentObject();
await _stateRepository.UpdateCursorAsync(SourceName, document, _timeProvider.GetUtcNow(), cancellationToken).ConfigureAwait(false);
}
}

View File

@@ -1,20 +1,20 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using StellaOps.Plugin;
namespace StellaOps.Concelier.Connector.Vndr.Vmware;
public sealed class VmwareConnectorPlugin : IConnectorPlugin
{
public string Name => SourceName;
public static string SourceName => "vmware";
public bool IsAvailable(IServiceProvider services) => services is not null;
public IFeedConnector Create(IServiceProvider services)
{
ArgumentNullException.ThrowIfNull(services);
return ActivatorUtilities.CreateInstance<VmwareConnector>(services);
}
}
using System;
using Microsoft.Extensions.DependencyInjection;
using StellaOps.Plugin;
namespace StellaOps.Concelier.Connector.Vndr.Vmware;
public sealed class VmwareConnectorPlugin : IConnectorPlugin
{
public string Name => SourceName;
public static string SourceName => "vmware";
public bool IsAvailable(IServiceProvider services) => services is not null;
public IFeedConnector Create(IServiceProvider services)
{
ArgumentNullException.ThrowIfNull(services);
return ActivatorUtilities.CreateInstance<VmwareConnector>(services);
}
}

View File

@@ -1,53 +1,53 @@
using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using StellaOps.DependencyInjection;
using StellaOps.Concelier.Core.Jobs;
using StellaOps.Concelier.Connector.Vndr.Vmware.Configuration;
namespace StellaOps.Concelier.Connector.Vndr.Vmware;
public sealed class VmwareDependencyInjectionRoutine : IDependencyInjectionRoutine
{
private const string ConfigurationSection = "concelier:sources:vmware";
private const string FetchCron = "10,40 * * * *";
private const string ParseCron = "15,45 * * * *";
private const string MapCron = "20,50 * * * *";
private static readonly TimeSpan FetchTimeout = TimeSpan.FromMinutes(10);
private static readonly TimeSpan ParseTimeout = TimeSpan.FromMinutes(10);
private static readonly TimeSpan MapTimeout = TimeSpan.FromMinutes(15);
private static readonly TimeSpan LeaseDuration = TimeSpan.FromMinutes(5);
public IServiceCollection Register(IServiceCollection services, IConfiguration configuration)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(configuration);
services.AddVmwareConnector(options =>
{
configuration.GetSection(ConfigurationSection).Bind(options);
options.Validate();
});
var scheduler = new JobSchedulerBuilder(services);
scheduler
.AddJob<VmwareFetchJob>(
VmwareJobKinds.Fetch,
cronExpression: FetchCron,
timeout: FetchTimeout,
leaseDuration: LeaseDuration)
.AddJob<VmwareParseJob>(
VmwareJobKinds.Parse,
cronExpression: ParseCron,
timeout: ParseTimeout,
leaseDuration: LeaseDuration)
.AddJob<VmwareMapJob>(
VmwareJobKinds.Map,
cronExpression: MapCron,
timeout: MapTimeout,
leaseDuration: LeaseDuration);
return services;
}
}
using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using StellaOps.DependencyInjection;
using StellaOps.Concelier.Core.Jobs;
using StellaOps.Concelier.Connector.Vndr.Vmware.Configuration;
namespace StellaOps.Concelier.Connector.Vndr.Vmware;
public sealed class VmwareDependencyInjectionRoutine : IDependencyInjectionRoutine
{
private const string ConfigurationSection = "concelier:sources:vmware";
private const string FetchCron = "10,40 * * * *";
private const string ParseCron = "15,45 * * * *";
private const string MapCron = "20,50 * * * *";
private static readonly TimeSpan FetchTimeout = TimeSpan.FromMinutes(10);
private static readonly TimeSpan ParseTimeout = TimeSpan.FromMinutes(10);
private static readonly TimeSpan MapTimeout = TimeSpan.FromMinutes(15);
private static readonly TimeSpan LeaseDuration = TimeSpan.FromMinutes(5);
public IServiceCollection Register(IServiceCollection services, IConfiguration configuration)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(configuration);
services.AddVmwareConnector(options =>
{
configuration.GetSection(ConfigurationSection).Bind(options);
options.Validate();
});
var scheduler = new JobSchedulerBuilder(services);
scheduler
.AddJob<VmwareFetchJob>(
VmwareJobKinds.Fetch,
cronExpression: FetchCron,
timeout: FetchTimeout,
leaseDuration: LeaseDuration)
.AddJob<VmwareParseJob>(
VmwareJobKinds.Parse,
cronExpression: ParseCron,
timeout: ParseTimeout,
leaseDuration: LeaseDuration)
.AddJob<VmwareMapJob>(
VmwareJobKinds.Map,
cronExpression: MapCron,
timeout: MapTimeout,
leaseDuration: LeaseDuration);
return services;
}
}

View File

@@ -1,67 +1,67 @@
using System;
using System.Diagnostics.Metrics;
namespace StellaOps.Concelier.Connector.Vndr.Vmware;
/// <summary>
/// VMware connector metrics (fetch, parse, map).
/// </summary>
public sealed class VmwareDiagnostics : IDisposable
{
public const string MeterName = "StellaOps.Concelier.Connector.Vndr.Vmware";
private const string MeterVersion = "1.0.0";
private readonly Meter _meter;
private readonly Counter<long> _fetchItems;
private readonly Counter<long> _fetchFailures;
private readonly Counter<long> _fetchUnchanged;
private readonly Counter<long> _parseFailures;
private readonly Histogram<long> _mapAffectedCount;
public VmwareDiagnostics()
{
_meter = new Meter(MeterName, MeterVersion);
_fetchItems = _meter.CreateCounter<long>(
name: "vmware.fetch.items",
unit: "documents",
description: "Number of VMware advisory documents fetched.");
_fetchFailures = _meter.CreateCounter<long>(
name: "vmware.fetch.failures",
unit: "operations",
description: "Number of VMware fetch failures.");
_fetchUnchanged = _meter.CreateCounter<long>(
name: "vmware.fetch.unchanged",
unit: "documents",
description: "Number of VMware advisories skipped due to unchanged content.");
_parseFailures = _meter.CreateCounter<long>(
name: "vmware.parse.fail",
unit: "documents",
description: "Number of VMware advisory documents that failed to parse.");
_mapAffectedCount = _meter.CreateHistogram<long>(
name: "vmware.map.affected_count",
unit: "packages",
description: "Distribution of affected-package counts emitted per VMware advisory.");
}
public void FetchItem() => _fetchItems.Add(1);
public void FetchFailure() => _fetchFailures.Add(1);
public void FetchUnchanged() => _fetchUnchanged.Add(1);
public void ParseFailure() => _parseFailures.Add(1);
public void MapAffectedCount(int count)
{
if (count < 0)
{
return;
}
_mapAffectedCount.Record(count);
}
public Meter Meter => _meter;
public void Dispose() => _meter.Dispose();
}
using System;
using System.Diagnostics.Metrics;
namespace StellaOps.Concelier.Connector.Vndr.Vmware;
/// <summary>
/// VMware connector metrics (fetch, parse, map).
/// </summary>
public sealed class VmwareDiagnostics : IDisposable
{
public const string MeterName = "StellaOps.Concelier.Connector.Vndr.Vmware";
private const string MeterVersion = "1.0.0";
private readonly Meter _meter;
private readonly Counter<long> _fetchItems;
private readonly Counter<long> _fetchFailures;
private readonly Counter<long> _fetchUnchanged;
private readonly Counter<long> _parseFailures;
private readonly Histogram<long> _mapAffectedCount;
public VmwareDiagnostics()
{
_meter = new Meter(MeterName, MeterVersion);
_fetchItems = _meter.CreateCounter<long>(
name: "vmware.fetch.items",
unit: "documents",
description: "Number of VMware advisory documents fetched.");
_fetchFailures = _meter.CreateCounter<long>(
name: "vmware.fetch.failures",
unit: "operations",
description: "Number of VMware fetch failures.");
_fetchUnchanged = _meter.CreateCounter<long>(
name: "vmware.fetch.unchanged",
unit: "documents",
description: "Number of VMware advisories skipped due to unchanged content.");
_parseFailures = _meter.CreateCounter<long>(
name: "vmware.parse.fail",
unit: "documents",
description: "Number of VMware advisory documents that failed to parse.");
_mapAffectedCount = _meter.CreateHistogram<long>(
name: "vmware.map.affected_count",
unit: "packages",
description: "Distribution of affected-package counts emitted per VMware advisory.");
}
public void FetchItem() => _fetchItems.Add(1);
public void FetchFailure() => _fetchFailures.Add(1);
public void FetchUnchanged() => _fetchUnchanged.Add(1);
public void ParseFailure() => _parseFailures.Add(1);
public void MapAffectedCount(int count)
{
if (count < 0)
{
return;
}
_mapAffectedCount.Record(count);
}
public Meter Meter => _meter;
public void Dispose() => _meter.Dispose();
}

View File

@@ -1,39 +1,39 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using StellaOps.Concelier.Connector.Common.Http;
using StellaOps.Concelier.Connector.Vndr.Vmware.Configuration;
namespace StellaOps.Concelier.Connector.Vndr.Vmware;
public static class VmwareServiceCollectionExtensions
{
public static IServiceCollection AddVmwareConnector(this IServiceCollection services, Action<VmwareOptions> configure)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(configure);
services.AddOptions<VmwareOptions>()
.Configure(configure)
.PostConfigure(static opts => opts.Validate());
services.AddSourceHttpClient(VmwareOptions.HttpClientName, (sp, clientOptions) =>
{
var options = sp.GetRequiredService<IOptions<VmwareOptions>>().Value;
clientOptions.BaseAddress = new Uri(options.IndexUri.GetLeftPart(UriPartial.Authority));
clientOptions.Timeout = options.HttpTimeout;
clientOptions.UserAgent = "StellaOps.Concelier.VMware/1.0";
clientOptions.AllowedHosts.Clear();
clientOptions.AllowedHosts.Add(options.IndexUri.Host);
clientOptions.DefaultRequestHeaders["Accept"] = "application/json";
});
services.TryAddSingleton<VmwareDiagnostics>();
services.AddTransient<VmwareConnector>();
services.AddTransient<VmwareFetchJob>();
services.AddTransient<VmwareParseJob>();
services.AddTransient<VmwareMapJob>();
return services;
}
}
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using StellaOps.Concelier.Connector.Common.Http;
using StellaOps.Concelier.Connector.Vndr.Vmware.Configuration;
namespace StellaOps.Concelier.Connector.Vndr.Vmware;
public static class VmwareServiceCollectionExtensions
{
public static IServiceCollection AddVmwareConnector(this IServiceCollection services, Action<VmwareOptions> configure)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(configure);
services.AddOptions<VmwareOptions>()
.Configure(configure)
.PostConfigure(static opts => opts.Validate());
services.AddSourceHttpClient(VmwareOptions.HttpClientName, (sp, clientOptions) =>
{
var options = sp.GetRequiredService<IOptions<VmwareOptions>>().Value;
clientOptions.BaseAddress = new Uri(options.IndexUri.GetLeftPart(UriPartial.Authority));
clientOptions.Timeout = options.HttpTimeout;
clientOptions.UserAgent = "StellaOps.Concelier.VMware/1.0";
clientOptions.AllowedHosts.Clear();
clientOptions.AllowedHosts.Add(options.IndexUri.Host);
clientOptions.DefaultRequestHeaders["Accept"] = "application/json";
});
services.TryAddSingleton<VmwareDiagnostics>();
services.AddTransient<VmwareConnector>();
services.AddTransient<VmwareFetchJob>();
services.AddTransient<VmwareParseJob>();
services.AddTransient<VmwareMapJob>();
return services;
}
}