save development progress

This commit is contained in:
StellaOps Bot
2025-12-25 23:09:58 +02:00
parent d71853ad7e
commit aa70af062e
351 changed files with 37683 additions and 150156 deletions

View File

@@ -18,6 +18,7 @@ using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Core.Canonical;
using StellaOps.Plugin;
namespace StellaOps.Concelier.Connector.Distro.Debian;
@@ -35,6 +36,7 @@ public sealed class DebianConnector : IFeedConnector
private readonly DebianOptions _options;
private readonly TimeProvider _timeProvider;
private readonly ILogger<DebianConnector> _logger;
private readonly ICanonicalAdvisoryService? _canonicalService;
private static readonly Action<ILogger, string, int, Exception?> LogMapped =
LoggerMessage.Define<string, int>(
@@ -51,7 +53,8 @@ public sealed class DebianConnector : IFeedConnector
ISourceStateRepository stateRepository,
IOptions<DebianOptions> options,
TimeProvider? timeProvider,
ILogger<DebianConnector> logger)
ILogger<DebianConnector> logger,
ICanonicalAdvisoryService? canonicalService = null)
{
_fetchService = fetchService ?? throw new ArgumentNullException(nameof(fetchService));
_rawDocumentStorage = rawDocumentStorage ?? throw new ArgumentNullException(nameof(rawDocumentStorage));
@@ -63,6 +66,7 @@ public sealed class DebianConnector : IFeedConnector
_options.Validate();
_timeProvider = timeProvider ?? TimeProvider.System;
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_canonicalService = canonicalService; // Optional - canonical ingest
}
public string SourceName => DebianConnectorPlugin.SourceName;
@@ -427,6 +431,14 @@ public sealed class DebianConnector : IFeedConnector
var advisory = DebianMapper.Map(dto, document, _timeProvider.GetUtcNow());
await _advisoryStore.UpsertAsync(advisory, cancellationToken).ConfigureAwait(false);
await _documentStore.UpdateStatusAsync(documentId, DocumentStatuses.Mapped, cancellationToken).ConfigureAwait(false);
// Ingest to canonical advisory service if available
if (_canonicalService is not null)
{
var rawPayloadJson = dtoRecord.Payload.ToJson(new JsonWriterSettings { OutputMode = JsonOutputMode.RelaxedExtendedJson });
await IngestToCanonicalAsync(advisory, rawPayloadJson, document.FetchedAt, cancellationToken).ConfigureAwait(false);
}
pendingMappings.Remove(documentId);
LogMapped(_logger, dto.AdvisoryId, advisory.AffectedPackages.Length, null);
}
@@ -634,4 +646,90 @@ public sealed class DebianConnector : IFeedConnector
packages,
references);
}
/// <summary>
/// Ingests Debian advisory to canonical advisory service for deduplication.
/// Creates one RawAdvisory per affected package.
/// </summary>
private async Task IngestToCanonicalAsync(
Advisory advisory,
string rawPayloadJson,
DateTimeOffset fetchedAt,
CancellationToken cancellationToken)
{
if (_canonicalService is null || advisory.AffectedPackages.IsEmpty)
{
return;
}
// Find primary CVE from aliases
var cve = advisory.Aliases
.FirstOrDefault(a => a.StartsWith("CVE-", StringComparison.OrdinalIgnoreCase))
?? advisory.AdvisoryKey;
// Extract CWE weaknesses
var weaknesses = advisory.Cwes
.Where(w => w.Identifier.StartsWith("CWE-", StringComparison.OrdinalIgnoreCase))
.Select(w => w.Identifier)
.ToList();
// Create one RawAdvisory per affected package
foreach (var affected in advisory.AffectedPackages)
{
if (string.IsNullOrWhiteSpace(affected.Identifier))
{
continue;
}
// Build version range JSON
string? versionRangeJson = null;
if (!affected.VersionRanges.IsEmpty)
{
var firstRange = affected.VersionRanges[0];
var rangeObj = new
{
introduced = firstRange.IntroducedVersion,
@fixed = firstRange.FixedVersion,
last_affected = firstRange.LastAffectedVersion
};
versionRangeJson = System.Text.Json.JsonSerializer.Serialize(rangeObj);
}
var rawAdvisory = new RawAdvisory
{
SourceAdvisoryId = advisory.AdvisoryKey,
Cve = cve,
AffectsKey = affected.Identifier,
VersionRangeJson = versionRangeJson,
Weaknesses = weaknesses,
PatchLineage = null,
Severity = advisory.Severity,
Title = advisory.Title,
Summary = advisory.Summary,
VendorStatus = VendorStatus.Affected,
RawPayloadJson = rawPayloadJson,
FetchedAt = fetchedAt
};
try
{
var result = await _canonicalService.IngestAsync(SourceName, rawAdvisory, cancellationToken).ConfigureAwait(false);
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug(
"Canonical ingest for {AdvisoryKey}/{AffectsKey}: {Decision} (canonical={CanonicalId})",
advisory.AdvisoryKey, affected.Identifier, result.Decision, result.CanonicalId);
}
}
catch (Exception ex)
{
_logger.LogWarning(
ex,
"Failed to ingest {AdvisoryKey}/{AffectsKey} to canonical service",
advisory.AdvisoryKey, affected.Identifier);
// Don't fail the mapping operation for canonical ingest failures
}
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using StellaOps.Concelier.Documents;
using StellaOps.Concelier.Storage.Contracts;
namespace StellaOps.Concelier.Connector.Distro.Debian.Internal;
@@ -10,6 +11,9 @@ internal sealed record DebianFetchCacheEntry(string? ETag, DateTimeOffset? LastM
public static DebianFetchCacheEntry FromDocument(StellaOps.Concelier.Storage.DocumentRecord document)
=> new(document.Etag, document.LastModified);
public static DebianFetchCacheEntry FromDocument(StorageDocument document)
=> new(document.Etag, document.LastModified);
public static DebianFetchCacheEntry FromDocument(DocumentObject document)
{
if (document is null || document.ElementCount == 0)