consolidation of some of the modules, localization fixes, product advisories work, qa work
This commit is contained in:
343
src/BinaryIndex/StellaOps.Symbols.Server/Program.cs
Normal file
343
src/BinaryIndex/StellaOps.Symbols.Server/Program.cs
Normal file
@@ -0,0 +1,343 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
using StellaOps.Auth.ServerIntegration;
|
||||
using StellaOps.Auth.ServerIntegration.Tenancy;
|
||||
using StellaOps.Symbols.Core.Abstractions;
|
||||
using StellaOps.Symbols.Core.Models;
|
||||
using StellaOps.Symbols.Infrastructure;
|
||||
using StellaOps.Symbols.Infrastructure.Hashing;
|
||||
using StellaOps.Symbols.Marketplace.Scoring;
|
||||
using StellaOps.Symbols.Server.Contracts;
|
||||
using StellaOps.Symbols.Server.Endpoints;
|
||||
using StellaOps.Symbols.Server.Security;
|
||||
|
||||
using StellaOps.Router.AspNet;
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Authentication and Authorization
|
||||
builder.Services.AddStellaOpsResourceServerAuthentication(
|
||||
builder.Configuration,
|
||||
configure: options =>
|
||||
{
|
||||
options.RequiredScopes.Clear();
|
||||
});
|
||||
builder.Services.AddStellaOpsTenantServices();
|
||||
|
||||
builder.Services.AddAuthorization(options =>
|
||||
{
|
||||
options.AddStellaOpsScopePolicy(SymbolsPolicies.Read, StellaOpsScopes.SymbolsRead);
|
||||
options.AddStellaOpsScopePolicy(SymbolsPolicies.Write, StellaOpsScopes.SymbolsWrite);
|
||||
});
|
||||
|
||||
// Symbols services (in-memory for development)
|
||||
builder.Services.AddSymbolsInMemory();
|
||||
|
||||
// Marketplace services
|
||||
builder.Services.AddSingleton<ISymbolSourceTrustScorer, DefaultSymbolSourceTrustScorer>();
|
||||
builder.Services.AddSingleton<StellaOps.Symbols.Marketplace.Repositories.ISymbolSourceReadRepository, StellaOps.Symbols.Server.Endpoints.InMemorySymbolSourceReadRepository>();
|
||||
builder.Services.AddSingleton<StellaOps.Symbols.Marketplace.Repositories.IMarketplaceCatalogRepository, StellaOps.Symbols.Server.Endpoints.InMemoryMarketplaceCatalogRepository>();
|
||||
|
||||
builder.Services.AddOpenApi();
|
||||
|
||||
builder.Services.AddStellaOpsCors(builder.Environment, builder.Configuration);
|
||||
|
||||
// Stella Router integration
|
||||
var routerEnabled = builder.Services.AddRouterMicroservice(
|
||||
builder.Configuration,
|
||||
serviceName: "symbols",
|
||||
version: System.Reflection.CustomAttributeExtensions.GetCustomAttribute<System.Reflection.AssemblyInformationalVersionAttribute>(System.Reflection.Assembly.GetExecutingAssembly())?.InformationalVersion ?? "1.0.0",
|
||||
routerOptionsSection: "Router");
|
||||
builder.TryAddStellaOpsLocalBinding("symbols");
|
||||
var app = builder.Build();
|
||||
app.LogStellaOpsLocalHostname("symbols");
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.MapOpenApi();
|
||||
}
|
||||
|
||||
app.UseStellaOpsCors();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.UseStellaOpsTenantMiddleware();
|
||||
app.TryUseStellaRouter(routerEnabled);
|
||||
|
||||
// Health endpoint (anonymous)
|
||||
app.MapGet("/health", () =>
|
||||
{
|
||||
return TypedResults.Ok(new SymbolsHealthResponse(
|
||||
Status: "healthy",
|
||||
Version: "1.0.0",
|
||||
Timestamp: DateTimeOffset.UtcNow,
|
||||
Metrics: null));
|
||||
})
|
||||
.AllowAnonymous()
|
||||
.WithName("GetHealth")
|
||||
.WithSummary("Health check endpoint");
|
||||
|
||||
// Upload symbol manifest
|
||||
app.MapPost("/v1/symbols/manifests", async Task<Results<Created<UploadSymbolManifestResponse>, ProblemHttpResult>> (
|
||||
HttpContext httpContext,
|
||||
UploadSymbolManifestRequest request,
|
||||
ISymbolRepository repository,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (!TryGetTenant(httpContext, out var tenantProblem, out var tenantId))
|
||||
{
|
||||
return tenantProblem!;
|
||||
}
|
||||
|
||||
var symbols = request.Symbols.Select(s => new SymbolEntry
|
||||
{
|
||||
Address = s.Address,
|
||||
Size = s.Size,
|
||||
MangledName = s.MangledName,
|
||||
DemangledName = s.DemangledName,
|
||||
Type = s.Type,
|
||||
Binding = s.Binding,
|
||||
SourceFile = s.SourceFile,
|
||||
SourceLine = s.SourceLine,
|
||||
ContentHash = s.ContentHash
|
||||
}).ToList();
|
||||
|
||||
var sourceMappings = request.SourceMappings?.Select(m => new SourceMapping
|
||||
{
|
||||
CompiledPath = m.CompiledPath,
|
||||
SourcePath = m.SourcePath,
|
||||
ContentHash = m.ContentHash
|
||||
}).ToList();
|
||||
|
||||
var manifestId = ComputeManifestId(request.DebugId, tenantId, symbols);
|
||||
|
||||
var manifest = new SymbolManifest
|
||||
{
|
||||
ManifestId = manifestId,
|
||||
DebugId = request.DebugId,
|
||||
CodeId = request.CodeId,
|
||||
BinaryName = request.BinaryName,
|
||||
Platform = request.Platform,
|
||||
Format = request.Format,
|
||||
Symbols = symbols,
|
||||
SourceMappings = sourceMappings,
|
||||
TenantId = tenantId,
|
||||
CreatedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
await repository.StoreManifestAsync(manifest, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var response = new UploadSymbolManifestResponse(
|
||||
ManifestId: manifestId,
|
||||
DebugId: request.DebugId,
|
||||
BinaryName: request.BinaryName,
|
||||
BlobUri: manifest.BlobUri,
|
||||
SymbolCount: symbols.Count,
|
||||
CreatedAt: manifest.CreatedAt);
|
||||
|
||||
return TypedResults.Created($"/v1/symbols/manifests/{manifestId}", response);
|
||||
})
|
||||
.RequireAuthorization(SymbolsPolicies.Write)
|
||||
.WithName("UploadSymbolManifest")
|
||||
.WithSummary("Upload a symbol manifest")
|
||||
.Produces(StatusCodes.Status201Created)
|
||||
.ProducesProblem(StatusCodes.Status400BadRequest);
|
||||
|
||||
// Get manifest by ID
|
||||
app.MapGet("/v1/symbols/manifests/{manifestId}", async Task<Results<Ok<SymbolManifestDetailResponse>, NotFound, ProblemHttpResult>> (
|
||||
string manifestId,
|
||||
ISymbolRepository repository,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
var manifest = await repository.GetManifestAsync(manifestId, cancellationToken).ConfigureAwait(false);
|
||||
if (manifest is null)
|
||||
{
|
||||
return TypedResults.NotFound();
|
||||
}
|
||||
|
||||
var response = MapToDetailResponse(manifest);
|
||||
return TypedResults.Ok(response);
|
||||
})
|
||||
.RequireAuthorization(SymbolsPolicies.Read)
|
||||
.WithName("GetSymbolManifest")
|
||||
.WithSummary("Get symbol manifest by ID")
|
||||
.Produces(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.ProducesProblem(StatusCodes.Status400BadRequest);
|
||||
|
||||
// Query manifests
|
||||
app.MapGet("/v1/symbols/manifests", async Task<Results<Ok<SymbolManifestListResponse>, ProblemHttpResult>> (
|
||||
HttpContext httpContext,
|
||||
ISymbolRepository repository,
|
||||
string? debugId,
|
||||
string? codeId,
|
||||
string? binaryName,
|
||||
string? platform,
|
||||
int? limit,
|
||||
int? offset,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (!TryGetTenant(httpContext, out var tenantProblem, out var tenantId))
|
||||
{
|
||||
return tenantProblem!;
|
||||
}
|
||||
|
||||
var query = new SymbolQuery
|
||||
{
|
||||
TenantId = tenantId,
|
||||
DebugId = debugId,
|
||||
CodeId = codeId,
|
||||
BinaryName = binaryName,
|
||||
Platform = platform,
|
||||
Limit = limit ?? 50,
|
||||
Offset = offset ?? 0
|
||||
};
|
||||
|
||||
var result = await repository.QueryManifestsAsync(query, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var summaries = result.Manifests.Select(m => new SymbolManifestSummary(
|
||||
ManifestId: m.ManifestId,
|
||||
DebugId: m.DebugId,
|
||||
CodeId: m.CodeId,
|
||||
BinaryName: m.BinaryName,
|
||||
Platform: m.Platform,
|
||||
Format: m.Format,
|
||||
SymbolCount: m.Symbols.Count,
|
||||
HasDsse: !string.IsNullOrEmpty(m.DsseDigest),
|
||||
CreatedAt: m.CreatedAt)).ToList();
|
||||
|
||||
return TypedResults.Ok(new SymbolManifestListResponse(
|
||||
Manifests: summaries,
|
||||
TotalCount: result.TotalCount,
|
||||
Offset: result.Offset,
|
||||
Limit: result.Limit));
|
||||
})
|
||||
.RequireAuthorization(SymbolsPolicies.Read)
|
||||
.WithName("QuerySymbolManifests")
|
||||
.WithSummary("Query symbol manifests")
|
||||
.Produces(StatusCodes.Status200OK)
|
||||
.ProducesProblem(StatusCodes.Status400BadRequest);
|
||||
|
||||
// Resolve symbols
|
||||
app.MapPost("/v1/symbols/resolve", async Task<Results<Ok<ResolveSymbolsResponse>, ProblemHttpResult>> (
|
||||
HttpContext httpContext,
|
||||
ResolveSymbolsRequest request,
|
||||
ISymbolResolver resolver,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (!TryGetTenant(httpContext, out var tenantProblem, out var tenantId))
|
||||
{
|
||||
return tenantProblem!;
|
||||
}
|
||||
|
||||
var resolutions = await resolver.ResolveBatchAsync(
|
||||
request.DebugId,
|
||||
request.Addresses,
|
||||
tenantId,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var dtos = resolutions.Select(r => new SymbolResolutionDto(
|
||||
Address: r.Address,
|
||||
Found: r.Found,
|
||||
MangledName: r.Symbol?.MangledName,
|
||||
DemangledName: r.Symbol?.DemangledName,
|
||||
Offset: r.Offset,
|
||||
SourceFile: r.Symbol?.SourceFile,
|
||||
SourceLine: r.Symbol?.SourceLine,
|
||||
Confidence: r.Confidence)).ToList();
|
||||
|
||||
return TypedResults.Ok(new ResolveSymbolsResponse(
|
||||
DebugId: request.DebugId,
|
||||
Resolutions: dtos));
|
||||
})
|
||||
.RequireAuthorization(SymbolsPolicies.Read)
|
||||
.WithName("ResolveSymbols")
|
||||
.WithSummary("Resolve symbol addresses")
|
||||
.Produces(StatusCodes.Status200OK)
|
||||
.ProducesProblem(StatusCodes.Status400BadRequest);
|
||||
|
||||
// Symbol source and marketplace endpoints
|
||||
app.MapSymbolSourceEndpoints();
|
||||
|
||||
// Get manifests by debug ID
|
||||
app.MapGet("/v1/symbols/by-debug-id/{debugId}", async Task<Results<Ok<SymbolManifestListResponse>, ProblemHttpResult>> (
|
||||
HttpContext httpContext,
|
||||
string debugId,
|
||||
ISymbolRepository repository,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (!TryGetTenant(httpContext, out var tenantProblem, out var tenantId))
|
||||
{
|
||||
return tenantProblem!;
|
||||
}
|
||||
|
||||
var manifests = await repository.GetManifestsByDebugIdAsync(debugId, tenantId, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var summaries = manifests.Select(m => new SymbolManifestSummary(
|
||||
ManifestId: m.ManifestId,
|
||||
DebugId: m.DebugId,
|
||||
CodeId: m.CodeId,
|
||||
BinaryName: m.BinaryName,
|
||||
Platform: m.Platform,
|
||||
Format: m.Format,
|
||||
SymbolCount: m.Symbols.Count,
|
||||
HasDsse: !string.IsNullOrEmpty(m.DsseDigest),
|
||||
CreatedAt: m.CreatedAt)).ToList();
|
||||
|
||||
return TypedResults.Ok(new SymbolManifestListResponse(
|
||||
Manifests: summaries,
|
||||
TotalCount: summaries.Count,
|
||||
Offset: 0,
|
||||
Limit: summaries.Count));
|
||||
})
|
||||
.RequireAuthorization(SymbolsPolicies.Read)
|
||||
.WithName("GetManifestsByDebugId")
|
||||
.WithSummary("Get manifests by debug ID")
|
||||
.Produces(StatusCodes.Status200OK)
|
||||
.ProducesProblem(StatusCodes.Status400BadRequest);
|
||||
|
||||
app.TryRefreshStellaRouterEndpoints(routerEnabled);
|
||||
app.Run();
|
||||
|
||||
static bool TryGetTenant(HttpContext httpContext, out ProblemHttpResult? problem, out string tenantId)
|
||||
{
|
||||
tenantId = string.Empty;
|
||||
if (!httpContext.Request.Headers.TryGetValue("X-Stella-Tenant", out var tenantValues) ||
|
||||
string.IsNullOrWhiteSpace(tenantValues))
|
||||
{
|
||||
problem = TypedResults.Problem(statusCode: StatusCodes.Status400BadRequest, title: "missing_tenant");
|
||||
return false;
|
||||
}
|
||||
|
||||
tenantId = tenantValues.ToString();
|
||||
problem = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
static string ComputeManifestId(string debugId, string tenantId, IReadOnlyList<SymbolEntry> symbols)
|
||||
{
|
||||
return SymbolHashing.ComputeManifestId(debugId, tenantId, symbols);
|
||||
}
|
||||
|
||||
static SymbolManifestDetailResponse MapToDetailResponse(SymbolManifest manifest)
|
||||
{
|
||||
return new SymbolManifestDetailResponse(
|
||||
ManifestId: manifest.ManifestId,
|
||||
DebugId: manifest.DebugId,
|
||||
CodeId: manifest.CodeId,
|
||||
BinaryName: manifest.BinaryName,
|
||||
Platform: manifest.Platform,
|
||||
Format: manifest.Format,
|
||||
TenantId: manifest.TenantId,
|
||||
BlobUri: manifest.BlobUri,
|
||||
DsseDigest: manifest.DsseDigest,
|
||||
RekorLogIndex: manifest.RekorLogIndex,
|
||||
SymbolCount: manifest.Symbols.Count,
|
||||
Symbols: manifest.Symbols.Select(s => new SymbolEntryDto(
|
||||
s.Address, s.Size, s.MangledName, s.DemangledName,
|
||||
s.Type, s.Binding, s.SourceFile, s.SourceLine, s.ContentHash)).ToList(),
|
||||
SourceMappings: manifest.SourceMappings?.Select(m => new SourceMappingDto(
|
||||
m.CompiledPath, m.SourcePath, m.ContentHash)).ToList(),
|
||||
CreatedAt: manifest.CreatedAt);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user