Files
git.stella-ops.org/src/Attestor/StellaOps.Attestor.TileProxy/Program.cs

138 lines
4.3 KiB
C#

// -----------------------------------------------------------------------------
// Program.cs
// Sprint: SPRINT_20260125_002_Attestor_trust_automation
// Task: PROXY-002 - Implement tile-proxy service
// Description: Tile proxy web service entry point
// -----------------------------------------------------------------------------
using Microsoft.Extensions.Options;
using Serilog;
using StellaOps.Attestor.TileProxy;
using StellaOps.Attestor.TileProxy.Endpoints;
using StellaOps.Attestor.TileProxy.Jobs;
using StellaOps.Attestor.TileProxy.Services;
const string ConfigurationSection = "tile_proxy";
var builder = WebApplication.CreateBuilder(args);
// Configure logging
builder.Host.UseSerilog((context, config) =>
{
config
.ReadFrom.Configuration(context.Configuration)
.Enrich.FromLogContext()
.WriteTo.Console(
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}");
});
// Load configuration
builder.Configuration
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables("TILE_PROXY__");
// Configure options
builder.Services.Configure<TileProxyOptions>(builder.Configuration.GetSection(ConfigurationSection));
// Validate options
builder.Services.AddSingleton<IValidateOptions<TileProxyOptions>, TileProxyOptionsValidator>();
// Register services
builder.Services.AddSingleton<ContentAddressedTileStore>();
builder.Services.AddSingleton<TileProxyService>();
// Register sync job as hosted service
builder.Services.AddHostedService<TileSyncJob>();
// Configure HTTP client for upstream
builder.Services.AddHttpClient<TileProxyService>((sp, client) =>
{
var options = sp.GetRequiredService<IOptions<TileProxyOptions>>().Value;
client.BaseAddress = new Uri(options.UpstreamUrl);
client.Timeout = TimeSpan.FromSeconds(options.Request.TimeoutSeconds);
client.DefaultRequestHeaders.Add("User-Agent", "StellaOps-TileProxy/1.0");
});
// Add OpenAPI
builder.Services.AddEndpointsApiExplorer();
var app = builder.Build();
// Validate options on startup
var optionsValidator = app.Services.GetRequiredService<IValidateOptions<TileProxyOptions>>();
var options = app.Services.GetRequiredService<IOptions<TileProxyOptions>>().Value;
var validationResult = optionsValidator.Validate(null, options);
if (validationResult.Failed)
{
throw new InvalidOperationException($"Configuration validation failed: {validationResult.FailureMessage}");
}
// Configure pipeline
app.UseSerilogRequestLogging();
// Map endpoints
app.MapTileProxyEndpoints();
// Startup message
var logger = app.Services.GetRequiredService<ILogger<Program>>();
logger.LogInformation(
"Tile Proxy starting - Upstream: {Upstream}, Cache: {CachePath}",
options.UpstreamUrl,
options.Cache.BasePath);
app.Run();
/// <summary>
/// Options validator for tile proxy configuration.
/// </summary>
public sealed class TileProxyOptionsValidator : IValidateOptions<TileProxyOptions>
{
public ValidateOptionsResult Validate(string? name, TileProxyOptions options)
{
var errors = new List<string>();
if (string.IsNullOrWhiteSpace(options.UpstreamUrl))
{
errors.Add("UpstreamUrl is required");
}
else if (!Uri.TryCreate(options.UpstreamUrl, UriKind.Absolute, out _))
{
errors.Add("UpstreamUrl must be a valid absolute URI");
}
if (string.IsNullOrWhiteSpace(options.Origin))
{
errors.Add("Origin is required");
}
if (options.Cache.MaxSizeGb < 0)
{
errors.Add("Cache.MaxSizeGb cannot be negative");
}
if (options.Cache.CheckpointTtlMinutes < 1)
{
errors.Add("Cache.CheckpointTtlMinutes must be at least 1");
}
if (options.Request.TimeoutSeconds < 1)
{
errors.Add("Request.TimeoutSeconds must be at least 1");
}
if (options.Tuf.Enabled && string.IsNullOrWhiteSpace(options.Tuf.Url))
{
errors.Add("Tuf.Url is required when TUF is enabled");
}
return errors.Count > 0
? ValidateOptionsResult.Fail(errors)
: ValidateOptionsResult.Success;
}
}
public partial class Program
{
}