up
Some checks failed
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
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Some checks failed
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
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using StellaOps.Concelier.Core.Linksets;
|
||||
using StellaOps.Scanner.WebService.Options;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace StellaOps.Scanner.WebService.Services;
|
||||
|
||||
internal sealed class ConcelierHttpLinksetQueryService : IAdvisoryLinksetQueryService
|
||||
{
|
||||
private readonly HttpClient _client;
|
||||
private readonly ConcelierLinksetOptions _options;
|
||||
|
||||
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web)
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
public ConcelierHttpLinksetQueryService(HttpClient client, IOptions<ConcelierLinksetOptions> options)
|
||||
{
|
||||
_client = client ?? throw new ArgumentNullException(nameof(client));
|
||||
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
public async Task<AdvisoryLinksetQueryResult> QueryAsync(AdvisoryLinksetQueryOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (options.AdvisoryIds is null)
|
||||
{
|
||||
return new AdvisoryLinksetQueryResult(ImmutableArray<AdvisoryLinkset>.Empty, null, false);
|
||||
}
|
||||
|
||||
var results = ImmutableArray.CreateBuilder<AdvisoryLinkset>();
|
||||
foreach (var advisoryId in options.AdvisoryIds)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(advisoryId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var path = $"/v1/lnm/linksets/{Uri.EscapeDataString(advisoryId)}?tenant={Uri.EscapeDataString(options.Tenant)}&includeConflicts=true&includeObservations=false&includeTimeline=false";
|
||||
try
|
||||
{
|
||||
using var response = await _client.GetAsync(path, cancellationToken).ConfigureAwait(false);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var payload = await response.Content.ReadFromJsonAsync<LinksetDetailResponse>(SerializerOptions, cancellationToken).ConfigureAwait(false);
|
||||
if (payload?.Linksets is null || payload.Linksets.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var linkset in payload.Linksets)
|
||||
{
|
||||
results.Add(Map(linkset));
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// swallow and continue; caller will see partial results
|
||||
}
|
||||
}
|
||||
|
||||
var linksets = results.ToImmutable();
|
||||
return new AdvisoryLinksetQueryResult(linksets, null, false);
|
||||
}
|
||||
|
||||
private static AdvisoryLinkset Map(LinksetDto dto)
|
||||
{
|
||||
var normalized = dto.Normalized is null
|
||||
? null
|
||||
: new AdvisoryLinksetNormalized(
|
||||
dto.Normalized.Purls,
|
||||
dto.Normalized.Cpes,
|
||||
dto.Normalized.Versions,
|
||||
dto.Normalized.Ranges,
|
||||
dto.Normalized.Severities);
|
||||
|
||||
var conflicts = dto.Conflicts is null
|
||||
? null
|
||||
: dto.Conflicts.Select(c => new AdvisoryLinksetConflict(c.Field, c.Reason ?? string.Empty, c.Values, c.SourceIds)).ToList();
|
||||
|
||||
return new AdvisoryLinkset(
|
||||
TenantId: dto.Tenant ?? string.Empty,
|
||||
Source: dto.Source ?? string.Empty,
|
||||
AdvisoryId: dto.AdvisoryId ?? string.Empty,
|
||||
ObservationIds: dto.ObservationIds?.ToImmutableArray() ?? ImmutableArray<string>.Empty,
|
||||
Normalized: normalized,
|
||||
Provenance: null,
|
||||
Confidence: dto.Confidence,
|
||||
Conflicts: conflicts,
|
||||
CreatedAt: dto.CreatedAt ?? DateTimeOffset.MinValue,
|
||||
BuiltByJobId: dto.BuiltByJobId);
|
||||
}
|
||||
|
||||
private sealed record LinksetDetailResponse([property: JsonPropertyName("linksets")] LinksetDto[] Linksets);
|
||||
|
||||
private sealed record LinksetDto
|
||||
{
|
||||
[JsonPropertyName("advisoryId")]
|
||||
public string? AdvisoryId { get; init; }
|
||||
|
||||
[JsonPropertyName("source")]
|
||||
public string? Source { get; init; }
|
||||
|
||||
[JsonPropertyName("tenant")]
|
||||
public string? Tenant { get; init; }
|
||||
|
||||
[JsonPropertyName("confidence")]
|
||||
public double? Confidence { get; init; }
|
||||
|
||||
[JsonPropertyName("createdAt")]
|
||||
public DateTimeOffset? CreatedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("builtByJobId")]
|
||||
public string? BuiltByJobId { get; init; }
|
||||
|
||||
[JsonPropertyName("observationIds")]
|
||||
public string[]? ObservationIds { get; init; }
|
||||
|
||||
[JsonPropertyName("normalized")]
|
||||
public LinksetNormalizedDto? Normalized { get; init; }
|
||||
|
||||
[JsonPropertyName("conflicts")]
|
||||
public LinksetConflictDto[]? Conflicts { get; init; }
|
||||
}
|
||||
|
||||
private sealed record LinksetNormalizedDto
|
||||
{
|
||||
[JsonPropertyName("purls")]
|
||||
public IReadOnlyList<string>? Purls { get; init; }
|
||||
|
||||
[JsonPropertyName("cpes")]
|
||||
public IReadOnlyList<string>? Cpes { get; init; }
|
||||
|
||||
[JsonPropertyName("versions")]
|
||||
public IReadOnlyList<string>? Versions { get; init; }
|
||||
|
||||
[JsonPropertyName("ranges")]
|
||||
public IReadOnlyList<Dictionary<string, object?>>? Ranges { get; init; }
|
||||
|
||||
[JsonPropertyName("severities")]
|
||||
public IReadOnlyList<Dictionary<string, object?>>? Severities { get; init; }
|
||||
}
|
||||
|
||||
private sealed record LinksetConflictDto
|
||||
{
|
||||
[JsonPropertyName("field")]
|
||||
public string Field { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("reason")]
|
||||
public string? Reason { get; init; }
|
||||
|
||||
[JsonPropertyName("values")]
|
||||
public IReadOnlyList<string>? Values { get; init; }
|
||||
|
||||
[JsonPropertyName("sourceIds")]
|
||||
public IReadOnlyList<string>? SourceIds { get; init; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user