up
Some checks failed
api-governance / spectral-lint (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Some checks failed
api-governance / spectral-lint (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Scanner.WebService.Contracts;
|
||||
|
||||
public sealed record EntropyLayerRequest(
|
||||
[property: JsonPropertyName("layerDigest")] string LayerDigest,
|
||||
[property: JsonPropertyName("opaqueRatio")] double OpaqueRatio,
|
||||
[property: JsonPropertyName("opaqueBytes")] long OpaqueBytes,
|
||||
[property: JsonPropertyName("totalBytes")] long TotalBytes);
|
||||
|
||||
public sealed record EntropyIngestRequest(
|
||||
[property: JsonPropertyName("imageOpaqueRatio")] double ImageOpaqueRatio,
|
||||
[property: JsonPropertyName("layers")] IReadOnlyList<EntropyLayerRequest> Layers);
|
||||
@@ -7,6 +7,7 @@ public sealed record ScanStatusResponse(
|
||||
DateTimeOffset CreatedAt,
|
||||
DateTimeOffset UpdatedAt,
|
||||
string? FailureReason,
|
||||
EntropyStatusDto? Entropy,
|
||||
SurfacePointersDto? Surface,
|
||||
ReplayStatusDto? Replay);
|
||||
|
||||
@@ -14,6 +15,16 @@ public sealed record ScanStatusTarget(
|
||||
string? Reference,
|
||||
string? Digest);
|
||||
|
||||
public sealed record EntropyStatusDto(
|
||||
double ImageOpaqueRatio,
|
||||
IReadOnlyList<EntropyLayerStatusDto> Layers);
|
||||
|
||||
public sealed record EntropyLayerStatusDto(
|
||||
string LayerDigest,
|
||||
double OpaqueRatio,
|
||||
long OpaqueBytes,
|
||||
long TotalBytes);
|
||||
|
||||
public sealed record ReplayStatusDto(
|
||||
string ManifestHash,
|
||||
IReadOnlyList<ReplayBundleStatusDto> Bundles);
|
||||
|
||||
@@ -7,6 +7,7 @@ public sealed record ScanSnapshot(
|
||||
DateTimeOffset CreatedAt,
|
||||
DateTimeOffset UpdatedAt,
|
||||
string? FailureReason,
|
||||
EntropySnapshot? Entropy,
|
||||
ReplayArtifacts? Replay);
|
||||
|
||||
public sealed record ReplayArtifacts(
|
||||
@@ -18,3 +19,13 @@ public sealed record ReplayBundleSummary(
|
||||
string Digest,
|
||||
string CasUri,
|
||||
long SizeBytes);
|
||||
|
||||
public sealed record EntropySnapshot(
|
||||
double ImageOpaqueRatio,
|
||||
IReadOnlyList<EntropyLayerSnapshot> Layers);
|
||||
|
||||
public sealed record EntropyLayerSnapshot(
|
||||
string LayerDigest,
|
||||
double OpaqueRatio,
|
||||
long OpaqueBytes,
|
||||
long TotalBytes);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO.Pipelines;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
@@ -45,6 +46,13 @@ internal static class ScanEndpoints
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.RequireAuthorization(ScannerPolicies.ScansRead);
|
||||
|
||||
scans.MapPost("/{scanId}/entropy", HandleAttachEntropyAsync)
|
||||
.WithName("scanner.scans.entropy")
|
||||
.Produces(StatusCodes.Status202Accepted)
|
||||
.Produces(StatusCodes.Status400BadRequest)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.RequireAuthorization(ScannerPolicies.ScansWrite);
|
||||
|
||||
scans.MapGet("/{scanId}/events", HandleProgressStreamAsync)
|
||||
.WithName("scanner.scans.events")
|
||||
.Produces(StatusCodes.Status200OK)
|
||||
@@ -203,12 +211,79 @@ internal static class ScanEndpoints
|
||||
CreatedAt: snapshot.CreatedAt,
|
||||
UpdatedAt: snapshot.UpdatedAt,
|
||||
FailureReason: snapshot.FailureReason,
|
||||
Entropy: snapshot.Entropy is null
|
||||
? null
|
||||
: new EntropyStatusDto(
|
||||
snapshot.Entropy.ImageOpaqueRatio,
|
||||
snapshot.Entropy.Layers
|
||||
.Select(l => new EntropyLayerStatusDto(l.LayerDigest, l.OpaqueRatio, l.OpaqueBytes, l.TotalBytes))
|
||||
.ToArray()),
|
||||
Surface: surfacePointers,
|
||||
Replay: snapshot.Replay is null ? null : MapReplay(snapshot.Replay));
|
||||
|
||||
return Json(response, StatusCodes.Status200OK);
|
||||
}
|
||||
|
||||
private static async Task<IResult> HandleAttachEntropyAsync(
|
||||
string scanId,
|
||||
EntropyIngestRequest request,
|
||||
IScanCoordinator coordinator,
|
||||
HttpContext context,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(coordinator);
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
if (!ScanId.TryParse(scanId, out var parsed))
|
||||
{
|
||||
return ProblemResultFactory.Create(
|
||||
context,
|
||||
ProblemTypes.Validation,
|
||||
"Invalid scan identifier",
|
||||
StatusCodes.Status400BadRequest,
|
||||
detail: "Scan identifier is required.");
|
||||
}
|
||||
|
||||
if (request.Layers is null || request.Layers.Count == 0)
|
||||
{
|
||||
return ProblemResultFactory.Create(
|
||||
context,
|
||||
ProblemTypes.Validation,
|
||||
"Entropy layers are required",
|
||||
StatusCodes.Status400BadRequest);
|
||||
}
|
||||
|
||||
var layers = request.Layers
|
||||
.Where(l => !string.IsNullOrWhiteSpace(l.LayerDigest))
|
||||
.Select(l => new EntropyLayerSnapshot(
|
||||
l.LayerDigest.Trim(),
|
||||
l.OpaqueRatio,
|
||||
l.OpaqueBytes,
|
||||
l.TotalBytes))
|
||||
.ToArray();
|
||||
|
||||
if (layers.Length == 0)
|
||||
{
|
||||
return ProblemResultFactory.Create(
|
||||
context,
|
||||
ProblemTypes.Validation,
|
||||
"Entropy layers are required",
|
||||
StatusCodes.Status400BadRequest);
|
||||
}
|
||||
|
||||
var snapshot = new EntropySnapshot(
|
||||
request.ImageOpaqueRatio,
|
||||
layers);
|
||||
|
||||
var attached = await coordinator.AttachEntropyAsync(parsed, snapshot, cancellationToken).ConfigureAwait(false);
|
||||
if (!attached)
|
||||
{
|
||||
return Results.NotFound();
|
||||
}
|
||||
|
||||
return Results.Accepted();
|
||||
}
|
||||
|
||||
private static async Task<IResult> HandleProgressStreamAsync(
|
||||
string scanId,
|
||||
string? format,
|
||||
|
||||
@@ -11,4 +11,6 @@ public interface IScanCoordinator
|
||||
ValueTask<ScanSnapshot?> TryFindByTargetAsync(string? reference, string? digest, CancellationToken cancellationToken);
|
||||
|
||||
ValueTask<bool> AttachReplayAsync(ScanId scanId, ReplayArtifacts replay, CancellationToken cancellationToken);
|
||||
|
||||
ValueTask<bool> AttachEntropyAsync(ScanId scanId, EntropySnapshot entropy, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -45,15 +45,16 @@ public sealed class InMemoryScanCoordinator : IScanCoordinator
|
||||
scanId,
|
||||
normalizedTarget,
|
||||
ScanStatus.Pending,
|
||||
now,
|
||||
now,
|
||||
now,
|
||||
null,
|
||||
null,
|
||||
null)),
|
||||
(_, existing) =>
|
||||
{
|
||||
if (submission.Force)
|
||||
{
|
||||
var snapshot = existing.Snapshot with
|
||||
(_, existing) =>
|
||||
{
|
||||
if (submission.Force)
|
||||
{
|
||||
var snapshot = existing.Snapshot with
|
||||
{
|
||||
Status = ScanStatus.Pending,
|
||||
UpdatedAt = now,
|
||||
@@ -134,6 +135,30 @@ public sealed class InMemoryScanCoordinator : IScanCoordinator
|
||||
return ValueTask.FromResult(true);
|
||||
}
|
||||
|
||||
public ValueTask<bool> AttachEntropyAsync(ScanId scanId, EntropySnapshot entropy, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entropy);
|
||||
|
||||
if (!scans.TryGetValue(scanId.Value, out var existing))
|
||||
{
|
||||
return ValueTask.FromResult(false);
|
||||
}
|
||||
|
||||
var updated = existing.Snapshot with
|
||||
{
|
||||
Entropy = entropy,
|
||||
UpdatedAt = timeProvider.GetUtcNow()
|
||||
};
|
||||
|
||||
scans[scanId.Value] = new ScanEntry(updated);
|
||||
progressPublisher.Publish(scanId, updated.Status.ToString(), "entropy-attached", new Dictionary<string, object?>
|
||||
{
|
||||
["entropy.imageOpaqueRatio"] = entropy.ImageOpaqueRatio,
|
||||
["entropy.layers"] = entropy.Layers.Count
|
||||
});
|
||||
return ValueTask.FromResult(true);
|
||||
}
|
||||
|
||||
private void IndexTarget(string scanId, ScanTarget target)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(target.Digest))
|
||||
|
||||
Reference in New Issue
Block a user