Add MergeUsageAnalyzer to detect legacy merge service usage
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

- Implemented MergeUsageAnalyzer to flag usage of AdvisoryMergeService and AddMergeModule.
- Created AnalyzerReleases.Shipped.md and AnalyzerReleases.Unshipped.md for release documentation.
- Added tests for MergeUsageAnalyzer to ensure correct diagnostics for various scenarios.
- Updated project files for analyzers and tests to include necessary dependencies and configurations.
- Introduced a sample report structure for scanner output.
This commit is contained in:
master
2025-11-06 15:03:39 +02:00
parent 5a923d968c
commit 950f238a93
45 changed files with 1291 additions and 623 deletions

View File

@@ -16,20 +16,24 @@ namespace StellaOps.Scanner.WebService.Services;
internal sealed class ReportEventDispatcher : IReportEventDispatcher
{
private const string DefaultTenant = "default";
private const string Source = "scanner.webservice";
private readonly IPlatformEventPublisher _publisher;
private readonly TimeProvider _timeProvider;
private readonly ILogger<ReportEventDispatcher> _logger;
private readonly string[] _apiBaseSegments;
private readonly string _reportsSegment;
private readonly string _policySegment;
public ReportEventDispatcher(
IPlatformEventPublisher publisher,
IOptions<ScannerWebServiceOptions> options,
TimeProvider timeProvider,
private const string DefaultTenant = "default";
private const string Source = "scanner.webservice";
private readonly IPlatformEventPublisher _publisher;
private readonly TimeProvider _timeProvider;
private readonly ILogger<ReportEventDispatcher> _logger;
private readonly string[] _apiBaseSegments;
private readonly string _reportsSegment;
private readonly string _policySegment;
private readonly string[] _consoleBaseSegments;
private readonly string _consoleReportsSegment;
private readonly string _consolePolicySegment;
private readonly string _consoleAttestationsSegment;
public ReportEventDispatcher(
IPlatformEventPublisher publisher,
IOptions<ScannerWebServiceOptions> options,
TimeProvider timeProvider,
ILogger<ReportEventDispatcher> logger)
{
_publisher = publisher ?? throw new ArgumentNullException(nameof(publisher));
@@ -38,17 +42,28 @@ internal sealed class ReportEventDispatcher : IReportEventDispatcher
throw new ArgumentNullException(nameof(options));
}
var apiOptions = options.Value.Api ?? new ScannerWebServiceOptions.ApiOptions();
_apiBaseSegments = SplitSegments(apiOptions.BasePath);
_reportsSegment = string.IsNullOrWhiteSpace(apiOptions.ReportsSegment)
? "reports"
: apiOptions.ReportsSegment.Trim('/');
_policySegment = string.IsNullOrWhiteSpace(apiOptions.PolicySegment)
? "policy"
: apiOptions.PolicySegment.Trim('/');
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
var apiOptions = options.Value.Api ?? new ScannerWebServiceOptions.ApiOptions();
_apiBaseSegments = SplitSegments(apiOptions.BasePath);
_reportsSegment = string.IsNullOrWhiteSpace(apiOptions.ReportsSegment)
? "reports"
: apiOptions.ReportsSegment.Trim('/');
_policySegment = string.IsNullOrWhiteSpace(apiOptions.PolicySegment)
? "policy"
: apiOptions.PolicySegment.Trim('/');
var consoleOptions = options.Value.Console ?? new ScannerWebServiceOptions.ConsoleOptions();
_consoleBaseSegments = SplitSegments(consoleOptions.BasePath);
_consoleReportsSegment = string.IsNullOrWhiteSpace(consoleOptions.ReportsSegment)
? "reports"
: consoleOptions.ReportsSegment.Trim('/');
_consolePolicySegment = string.IsNullOrWhiteSpace(consoleOptions.PolicySegment)
? "policy"
: consoleOptions.PolicySegment.Trim('/');
_consoleAttestationsSegment = string.IsNullOrWhiteSpace(consoleOptions.AttestationsSegment)
? "attestations"
: consoleOptions.AttestationsSegment.Trim('/');
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task PublishAsync(
ReportRequestDto request,
@@ -240,21 +255,21 @@ internal sealed class ReportEventDispatcher : IReportEventDispatcher
};
}
private ReportLinksPayload BuildLinks(HttpContext context, ReportDocumentDto document, DsseEnvelopeDto? envelope)
{
if (!context.Request.Host.HasValue)
{
return new ReportLinksPayload();
}
var reportUi = BuildAbsoluteUri(context, "ui", "reports", document.ReportId);
private ReportLinksPayload BuildLinks(HttpContext context, ReportDocumentDto document, DsseEnvelopeDto? envelope)
{
if (!context.Request.Host.HasValue)
{
return new ReportLinksPayload();
}
var reportUi = BuildAbsoluteUri(context, ConcatSegments(_consoleBaseSegments, _consoleReportsSegment, document.ReportId));
var reportApi = BuildAbsoluteUri(context, ConcatSegments(_apiBaseSegments, _reportsSegment, document.ReportId));
LinkTarget? policyLink = null;
if (!string.IsNullOrWhiteSpace(document.Policy.RevisionId))
{
var policyRevision = document.Policy.RevisionId!;
var policyUi = BuildAbsoluteUri(context, "ui", "policy", "revisions", policyRevision);
var policyUi = BuildAbsoluteUri(context, ConcatSegments(_consoleBaseSegments, _consolePolicySegment, "revisions", policyRevision));
var policyApi = BuildAbsoluteUri(context, ConcatSegments(_apiBaseSegments, _policySegment, "revisions", policyRevision));
policyLink = LinkTarget.Create(policyUi, policyApi);
}
@@ -262,7 +277,7 @@ internal sealed class ReportEventDispatcher : IReportEventDispatcher
LinkTarget? attestationLink = null;
if (envelope is not null)
{
var attestationUi = BuildAbsoluteUri(context, "ui", "attestations", document.ReportId);
var attestationUi = BuildAbsoluteUri(context, ConcatSegments(_consoleBaseSegments, _consoleAttestationsSegment, document.ReportId));
var attestationApi = BuildAbsoluteUri(context, ConcatSegments(_apiBaseSegments, _reportsSegment, document.ReportId, "attestation"));
attestationLink = LinkTarget.Create(attestationUi, attestationApi);
}