work work ... haaaard work
This commit is contained in:
@@ -7,22 +7,24 @@ using StellaOps.SbomService.Services;
|
||||
using StellaOps.SbomService.Observability;
|
||||
using StellaOps.SbomService.Repositories;
|
||||
using System.Text.Json;
|
||||
using System.Diagnostics;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Configuration
|
||||
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
|
||||
.AddEnvironmentVariables("SBOM_");
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Configuration
|
||||
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
|
||||
.AddEnvironmentVariables("SBOM_");
|
||||
|
||||
builder.Services.AddOptions();
|
||||
builder.Services.AddLogging();
|
||||
|
||||
builder.Services.AddOptions();
|
||||
builder.Services.AddLogging();
|
||||
|
||||
// Register SBOM query services (InMemory seed; replace with Mongo-backed repository later).
|
||||
builder.Services.AddSingleton<IComponentLookupRepository>(_ => new InMemoryComponentLookupRepository());
|
||||
builder.Services.AddSingleton<IClock, SystemClock>();
|
||||
builder.Services.AddSingleton<ISbomEventStore, InMemorySbomEventStore>();
|
||||
builder.Services.AddSingleton<ISbomEventPublisher>(sp => sp.GetRequiredService<ISbomEventStore>());
|
||||
builder.Services.AddSingleton<ISbomQueryService, InMemorySbomQueryService>();
|
||||
builder.Services.AddSingleton<IEntrypointRepository, InMemoryEntrypointRepository>();
|
||||
|
||||
builder.Services.AddSingleton<IProjectionRepository>(sp =>
|
||||
{
|
||||
@@ -54,16 +56,85 @@ builder.Services.AddSingleton<IProjectionRepository>(sp =>
|
||||
|
||||
return new FileProjectionRepository(string.Empty);
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await next();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[dev-exception] {ex}");
|
||||
throw;
|
||||
}
|
||||
});
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.MapGet("/healthz", () => Results.Ok(new { status = "ok" }));
|
||||
app.MapGet("/readyz", () => Results.Ok(new { status = "warming" }));
|
||||
|
||||
app.MapGet("/entrypoints", async Task<IResult> (
|
||||
[FromServices] IEntrypointRepository repo,
|
||||
[FromQuery] string? tenant,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(tenant))
|
||||
{
|
||||
return Results.BadRequest(new { error = "tenant is required" });
|
||||
}
|
||||
|
||||
var tenantId = tenant.Trim();
|
||||
using var activity = SbomTracing.Source.StartActivity("entrypoints.list", ActivityKind.Server);
|
||||
activity?.SetTag("tenant", tenantId);
|
||||
|
||||
var items = await repo.ListAsync(tenantId, cancellationToken);
|
||||
return Results.Ok(new EntrypointListResponse(tenantId, items));
|
||||
});
|
||||
|
||||
app.MapPost("/entrypoints", async Task<IResult> (
|
||||
[FromServices] IEntrypointRepository repo,
|
||||
[FromBody] EntrypointUpsertRequest request,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Tenant))
|
||||
{
|
||||
return Results.BadRequest(new { error = "tenant is required" });
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.Artifact) || string.IsNullOrWhiteSpace(request.Service) || string.IsNullOrWhiteSpace(request.Path))
|
||||
{
|
||||
return Results.BadRequest(new { error = "artifact, service, and path are required" });
|
||||
}
|
||||
|
||||
var entrypoint = new Entrypoint(
|
||||
request.Artifact.Trim(),
|
||||
request.Service.Trim(),
|
||||
request.Path.Trim(),
|
||||
string.IsNullOrWhiteSpace(request.Scope) ? "runtime" : request.Scope.Trim(),
|
||||
request.RuntimeFlag);
|
||||
|
||||
var tenantId = request.Tenant.Trim();
|
||||
using var activity = SbomTracing.Source.StartActivity("entrypoints.upsert", ActivityKind.Server);
|
||||
activity?.SetTag("tenant", tenantId);
|
||||
activity?.SetTag("artifact", entrypoint.Artifact);
|
||||
activity?.SetTag("service", entrypoint.Service);
|
||||
|
||||
await repo.UpsertAsync(tenantId, entrypoint, cancellationToken);
|
||||
|
||||
var items = await repo.ListAsync(tenantId, cancellationToken);
|
||||
return Results.Ok(new EntrypointListResponse(tenantId, items));
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.MapGet("/healthz", () => Results.Ok(new { status = "ok" }));
|
||||
app.MapGet("/readyz", () => Results.Ok(new { status = "warming" }));
|
||||
|
||||
app.MapGet("/console/sboms", async Task<IResult> (
|
||||
[FromServices] ISbomQueryService service,
|
||||
[FromQuery] string? artifact,
|
||||
[FromQuery] string? license,
|
||||
app.MapGet("/console/sboms", async Task<IResult> (
|
||||
[FromServices] ISbomQueryService service,
|
||||
[FromQuery] string? artifact,
|
||||
[FromQuery] string? license,
|
||||
[FromQuery] string? scope,
|
||||
[FromQuery(Name = "assetTag")] string? assetTag,
|
||||
[FromQuery] string? cursor,
|
||||
@@ -80,15 +151,17 @@ app.MapGet("/console/sboms", async Task<IResult> (
|
||||
return Results.BadRequest(new { error = "cursor must be an integer offset" });
|
||||
}
|
||||
|
||||
var offset = cursor is null ? 0 : int.Parse(cursor, CultureInfo.InvariantCulture);
|
||||
var pageSize = limit ?? 50;
|
||||
|
||||
var start = Stopwatch.GetTimestamp();
|
||||
var result = await service.GetConsoleCatalogAsync(
|
||||
new SbomCatalogQuery(artifact?.Trim(), license?.Trim(), scope?.Trim(), assetTag?.Trim(), pageSize, offset),
|
||||
cancellationToken);
|
||||
|
||||
var elapsedSeconds = Stopwatch.GetElapsedTime(start).TotalSeconds;
|
||||
var offset = cursor is null ? 0 : int.Parse(cursor, CultureInfo.InvariantCulture);
|
||||
var pageSize = limit ?? 50;
|
||||
|
||||
using var activity = SbomTracing.Source.StartActivity("console.sboms", ActivityKind.Server);
|
||||
activity?.SetTag("artifact", artifact);
|
||||
var start = Stopwatch.GetTimestamp();
|
||||
var result = await service.GetConsoleCatalogAsync(
|
||||
new SbomCatalogQuery(artifact?.Trim(), license?.Trim(), scope?.Trim(), assetTag?.Trim(), pageSize, offset),
|
||||
cancellationToken);
|
||||
|
||||
var elapsedSeconds = Stopwatch.GetElapsedTime(start).TotalSeconds;
|
||||
SbomMetrics.PathsLatencySeconds.Record(elapsedSeconds, new TagList
|
||||
{
|
||||
{ "scope", scope ?? string.Empty },
|
||||
@@ -103,10 +176,10 @@ app.MapGet("/console/sboms", async Task<IResult> (
|
||||
return Results.Ok(result.Result);
|
||||
});
|
||||
|
||||
app.MapGet("/components/lookup", async Task<IResult> (
|
||||
[FromServices] ISbomQueryService service,
|
||||
[FromQuery] string? purl,
|
||||
[FromQuery] string? artifact,
|
||||
app.MapGet("/components/lookup", async Task<IResult> (
|
||||
[FromServices] ISbomQueryService service,
|
||||
[FromQuery] string? purl,
|
||||
[FromQuery] string? artifact,
|
||||
[FromQuery] string? cursor,
|
||||
[FromQuery] int? limit,
|
||||
CancellationToken cancellationToken) =>
|
||||
@@ -126,13 +199,16 @@ app.MapGet("/components/lookup", async Task<IResult> (
|
||||
return Results.BadRequest(new { error = "cursor must be an integer offset" });
|
||||
}
|
||||
|
||||
var offset = cursor is null ? 0 : int.Parse(cursor, CultureInfo.InvariantCulture);
|
||||
var pageSize = limit ?? 50;
|
||||
|
||||
var start = Stopwatch.GetTimestamp();
|
||||
var result = await service.GetComponentLookupAsync(
|
||||
new ComponentLookupQuery(purl.Trim(), artifact?.Trim(), pageSize, offset),
|
||||
cancellationToken);
|
||||
var offset = cursor is null ? 0 : int.Parse(cursor, CultureInfo.InvariantCulture);
|
||||
var pageSize = limit ?? 50;
|
||||
|
||||
using var activity = SbomTracing.Source.StartActivity("components.lookup", ActivityKind.Server);
|
||||
activity?.SetTag("purl", purl);
|
||||
activity?.SetTag("artifact", artifact);
|
||||
var start = Stopwatch.GetTimestamp();
|
||||
var result = await service.GetComponentLookupAsync(
|
||||
new ComponentLookupQuery(purl.Trim(), artifact?.Trim(), pageSize, offset),
|
||||
cancellationToken);
|
||||
|
||||
var elapsedSeconds = Stopwatch.GetElapsedTime(start).TotalSeconds;
|
||||
SbomMetrics.PathsLatencySeconds.Record(elapsedSeconds, new TagList
|
||||
@@ -149,13 +225,13 @@ app.MapGet("/components/lookup", async Task<IResult> (
|
||||
return Results.Ok(result.Result);
|
||||
});
|
||||
|
||||
app.MapGet("/sbom/paths", async Task<IResult> (
|
||||
[FromServices] ISbomQueryService service,
|
||||
[FromQuery] string? purl,
|
||||
[FromQuery] string? artifact,
|
||||
[FromQuery] string? scope,
|
||||
[FromQuery(Name = "env")] string? environment,
|
||||
[FromQuery] string? cursor,
|
||||
app.MapGet("/sbom/paths", async Task<IResult> (
|
||||
[FromServices] IServiceProvider services,
|
||||
[FromQuery] string? purl,
|
||||
[FromQuery] string? artifact,
|
||||
[FromQuery] string? scope,
|
||||
[FromQuery(Name = "env")] string? environment,
|
||||
[FromQuery] string? cursor,
|
||||
[FromQuery] int? limit,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
@@ -172,15 +248,16 @@ app.MapGet("/sbom/paths", async Task<IResult> (
|
||||
if (cursor is { Length: > 0 } && !int.TryParse(cursor, NumberStyles.Integer, CultureInfo.InvariantCulture, out _))
|
||||
{
|
||||
return Results.BadRequest(new { error = "cursor must be an integer offset" });
|
||||
}
|
||||
|
||||
var offset = cursor is null ? 0 : int.Parse(cursor, CultureInfo.InvariantCulture);
|
||||
var pageSize = limit ?? 50;
|
||||
|
||||
var start = Stopwatch.GetTimestamp();
|
||||
var result = await service.GetPathsAsync(
|
||||
new SbomPathQuery(purl.Trim(), artifact?.Trim(), scope?.Trim(), environment?.Trim(), pageSize, offset),
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
var offset = cursor is null ? 0 : int.Parse(cursor, CultureInfo.InvariantCulture);
|
||||
var pageSize = limit ?? 50;
|
||||
|
||||
var service = services.GetRequiredService<ISbomQueryService>();
|
||||
var start = Stopwatch.GetTimestamp();
|
||||
var result = await service.GetPathsAsync(
|
||||
new SbomPathQuery(purl.Trim(), artifact?.Trim(), scope?.Trim(), environment?.Trim(), pageSize, offset),
|
||||
cancellationToken);
|
||||
|
||||
var elapsedSeconds = Stopwatch.GetElapsedTime(start).TotalSeconds;
|
||||
SbomMetrics.PathsLatencySeconds.Record(elapsedSeconds, new TagList
|
||||
@@ -250,20 +327,37 @@ app.MapGet("/sboms/{snapshotId}/projection", async Task<IResult> (
|
||||
return Results.BadRequest(new { error = "tenant is required" });
|
||||
}
|
||||
|
||||
var start = Stopwatch.GetTimestamp();
|
||||
var projection = await service.GetProjectionAsync(snapshotId.Trim(), tenantId.Trim(), cancellationToken);
|
||||
if (projection is null)
|
||||
{
|
||||
return Results.NotFound(new { error = "projection not found" });
|
||||
}
|
||||
|
||||
return Results.Ok(new
|
||||
using var activity = SbomTracing.Source.StartActivity("sbom.projection", ActivityKind.Server);
|
||||
activity?.SetTag("tenant", projection.TenantId);
|
||||
activity?.SetTag("snapshotId", projection.SnapshotId);
|
||||
activity?.SetTag("schema", projection.SchemaVersion);
|
||||
|
||||
var payload = new
|
||||
{
|
||||
snapshotId = projection.SnapshotId,
|
||||
tenantId = projection.TenantId,
|
||||
schemaVersion = projection.SchemaVersion,
|
||||
hash = projection.ProjectionHash,
|
||||
projection = projection.Projection
|
||||
});
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(payload);
|
||||
var sizeBytes = System.Text.Encoding.UTF8.GetByteCount(json);
|
||||
SbomMetrics.ProjectionSizeBytes.Record(sizeBytes, new TagList { { "tenant", projection.TenantId } });
|
||||
SbomMetrics.ProjectionLatencySeconds.Record(Stopwatch.GetElapsedTime(start).TotalSeconds,
|
||||
new TagList { { "tenant", projection.TenantId } });
|
||||
SbomMetrics.ProjectionQueryTotal.Add(1, new TagList { { "tenant", projection.TenantId } });
|
||||
|
||||
app.Logger.LogInformation("projection_returned tenant={Tenant} snapshot={Snapshot} size={SizeBytes}", projection.TenantId, projection.SnapshotId, sizeBytes);
|
||||
|
||||
return Results.Ok(payload);
|
||||
});
|
||||
|
||||
app.MapGet("/internal/sbom/events", async Task<IResult> (
|
||||
|
||||
Reference in New Issue
Block a user