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
Policy Lint & Smoke / policy-lint (push) Has been cancelled

This commit is contained in:
master
2025-11-28 18:21:46 +02:00
parent 05da719048
commit d1cbb905f8
103 changed files with 49604 additions and 105 deletions

View File

@@ -82,6 +82,83 @@ internal sealed class ConcelierObservationsClient : IConcelierObservationsClient
return result ?? new AdvisoryObservationsResponse();
}
/// <summary>
/// Gets advisory linkset with conflict information.
/// Per CLI-LNM-22-001.
/// </summary>
public async Task<AdvisoryLinksetResponse> GetLinksetAsync(
AdvisoryLinksetQuery query,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(query);
EnsureConfigured();
var requestUri = BuildLinksetRequestUri(query);
using var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
await AuthorizeRequestAsync(request, cancellationToken).ConfigureAwait(false);
using var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
if (!response.IsSuccessStatusCode)
{
var payload = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
logger.LogError(
"Failed to query linkset (status {StatusCode}). Response: {Payload}",
(int)response.StatusCode,
string.IsNullOrWhiteSpace(payload) ? "<empty>" : payload);
response.EnsureSuccessStatusCode();
}
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
var result = await JsonSerializer
.DeserializeAsync<AdvisoryLinksetResponse>(stream, SerializerOptions, cancellationToken)
.ConfigureAwait(false);
return result ?? new AdvisoryLinksetResponse();
}
/// <summary>
/// Gets a single observation by ID.
/// Per CLI-LNM-22-001.
/// </summary>
public async Task<AdvisoryLinksetObservation?> GetObservationByIdAsync(
string tenant,
string observationId,
CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrWhiteSpace(tenant);
ArgumentException.ThrowIfNullOrWhiteSpace(observationId);
EnsureConfigured();
var requestUri = $"/concelier/observations/{Uri.EscapeDataString(observationId)}?tenant={Uri.EscapeDataString(tenant)}";
using var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
await AuthorizeRequestAsync(request, cancellationToken).ConfigureAwait(false);
using var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
{
return null;
}
if (!response.IsSuccessStatusCode)
{
var payload = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
logger.LogError(
"Failed to get observation (status {StatusCode}). Response: {Payload}",
(int)response.StatusCode,
string.IsNullOrWhiteSpace(payload) ? "<empty>" : payload);
response.EnsureSuccessStatusCode();
}
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
return await JsonSerializer
.DeserializeAsync<AdvisoryLinksetObservation>(stream, SerializerOptions, cancellationToken)
.ConfigureAwait(false);
}
private static string BuildRequestUri(AdvisoryObservationsQuery query)
{
var builder = new StringBuilder("/concelier/observations?tenant=");
@@ -130,6 +207,71 @@ internal sealed class ConcelierObservationsClient : IConcelierObservationsClient
}
}
private static string BuildLinksetRequestUri(AdvisoryLinksetQuery query)
{
var builder = new StringBuilder("/concelier/linkset?tenant=");
builder.Append(Uri.EscapeDataString(query.Tenant));
AppendValues(builder, "observationId", query.ObservationIds);
AppendValues(builder, "alias", query.Aliases);
AppendValues(builder, "purl", query.Purls);
AppendValues(builder, "cpe", query.Cpes);
AppendValues(builder, "source", query.Sources);
if (!string.IsNullOrWhiteSpace(query.Severity))
{
builder.Append("&severity=");
builder.Append(Uri.EscapeDataString(query.Severity));
}
if (query.KevOnly.HasValue)
{
builder.Append("&kevOnly=");
builder.Append(query.KevOnly.Value ? "true" : "false");
}
if (query.HasFix.HasValue)
{
builder.Append("&hasFix=");
builder.Append(query.HasFix.Value ? "true" : "false");
}
if (query.Limit.HasValue && query.Limit.Value > 0)
{
builder.Append("&limit=");
builder.Append(query.Limit.Value.ToString(CultureInfo.InvariantCulture));
}
if (!string.IsNullOrWhiteSpace(query.Cursor))
{
builder.Append("&cursor=");
builder.Append(Uri.EscapeDataString(query.Cursor));
}
return builder.ToString();
static void AppendValues(StringBuilder builder, string name, IReadOnlyList<string> values)
{
if (values is null || values.Count == 0)
{
return;
}
foreach (var value in values)
{
if (string.IsNullOrWhiteSpace(value))
{
continue;
}
builder.Append('&');
builder.Append(name);
builder.Append('=');
builder.Append(Uri.EscapeDataString(value));
}
}
}
private void EnsureConfigured()
{
if (!string.IsNullOrWhiteSpace(options.ConcelierUrl))