mirror of
https://github.com/azaion/missions.git
synced 2026-06-21 21:31:07 +00:00
78dea8ebab
ci/woodpecker/push/build-arm Pipeline was successful
Enhanced the .gitignore to exclude test results and updated the Dockerfile to include a new entrypoint script for improved container initialization. Refactored JWT configuration to support additional parameters for automatic refresh intervals, ensuring better control over token management. Updated the ConfigurationResolver to enforce required environment variables without hardcoded fallbacks, enhancing security and flexibility.
8.3 KiB
8.3 KiB
Flow F6 — Service startup + schema migration
One-shot per process start. Idempotent migrator (
CREATE ... IF NOT EXISTS). Post-B9 the migrator additionallyDROP TABLE IF EXISTS orthophotos / gps_correctionsfor fielded edge devices that previously ran the legacy schema.
Description
Program.cs builds the DI graph from environment, runs DatabaseMigrator.Migrate(db) once inside a startup scope, then starts serving HTTP. The migrator only owns the 4 tables this service is responsible for (vehicles, missions, waypoints, map_objects); the borrowed tables (media, annotations, detection) are migrated by their owner services in their own startups.
Preconditions
DATABASE_URL,JWT_ISSUER,JWT_AUDIENCE,JWT_JWKS_URLall resolve viaConfigurationResolver.ResolveRequiredOrThrow. Any missing value aborts startup before the host is built — there are no hardcoded fallbacks.postgres-localis reachable.- The
azaiondatabase itself exists in PostgreSQL (created at provisioning time, NOT by this migrator). admindoes NOT need to be reachable at process start. The JWKS fetch is lazy — it happens on the first protected request, not during the startup sequence diagrammed below.
Sequence Diagram
sequenceDiagram
autonumber
participant Docker as Docker / Watchtower
participant Host as 07_host (Program.cs)
participant Cfg as IConfiguration
participant Identity as 05_identity
participant DI as DI container
participant Migrator as DatabaseMigrator
participant DB as postgres-local
Docker->>Host: ENTRYPOINT dotnet Azaion.Missions.dll
Host->>Cfg: ResolveRequiredOrThrow DATABASE_URL → ConvertPostgresUrl → Npgsql connection string
Host->>Cfg: ResolveRequiredOrThrow JWT_ISSUER, JWT_AUDIENCE, JWT_JWKS_URL — throw on any miss
Host->>Identity: AddJwtAuth(builder.Configuration) — DI registration + ConfigurationManager<JsonWebKeySet> wiring (no network yet)
Host->>Cfg: read CorsConfig:AllowedOrigins, CorsConfig:AllowAnyOrigin
Host->>Cfg: CorsConfigurationValidator.EnsureSafeForEnvironment — throws in Production when origins=[] AND allowAnyOrigin=false
Host->>DI: register CORS policy (permissive OR WithOrigins(...))
Host->>DI: register controllers + middleware + scoped AppDataConnection + scoped services
Host->>DI: build Host
Host->>Migrator: scope.Resolve<AppDataConnection>(); Migrate(db)
Migrator->>DB: CREATE TABLE IF NOT EXISTS vehicles (...)
Migrator->>DB: CREATE TABLE IF NOT EXISTS missions (...)
Migrator->>DB: CREATE TABLE IF NOT EXISTS waypoints (...)
Migrator->>DB: CREATE TABLE IF NOT EXISTS map_objects (...)
Migrator->>DB: CREATE INDEX IF NOT EXISTS ix_missions_vehicle_id, ix_waypoints_mission_id, ix_map_objects_mission_id
Migrator->>DB: DROP TABLE IF EXISTS orthophotos
Migrator->>DB: DROP TABLE IF EXISTS gps_corrections
note right of Migrator: B9 one-shot. Idempotent on devices that already cleaned up.
Migrator-->>Host: void
Host->>Host: emit PermissiveDefaultWarning if implicit-permissive CORS applies (non-Production with empty origins)
Host->>Host: register ErrorHandlingMiddleware FIRST in pipeline
Host->>Host: UseCors / UseAuthentication / UseAuthorization
Host->>Host: MapControllers + MapGet("/health") + UseSwagger
Host->>Docker: app.Run() — listening on 0.0.0.0:8080
Flowchart
flowchart TD
Start([Container start]) --> ResolveDB[ResolveRequiredOrThrow DATABASE_URL]
ResolveDB --> ResolveJwt["ResolveRequiredOrThrow JWT_ISSUER, JWT_AUDIENCE, JWT_JWKS_URL"]
ResolveJwt --> CorsCfg[Read CorsConfig:AllowedOrigins + CorsConfig:AllowAnyOrigin]
CorsCfg --> CorsGate{Production AND origins=[] AND allowAnyOrigin=false?}
CorsGate -->|yes| FailFast([InvalidOperationException — Watchtower restarts])
CorsGate -->|no| RegDI[DI registrations: JWT bearer + JWKS manager, CORS, controllers, scoped DB + services]
RegDI --> Build[Build Host]
Build --> CorsWarn{Implicit-permissive CORS in this env?}
CorsWarn -->|yes| LogWarn[Log PermissiveDefaultWarning]
CorsWarn -->|no| OpenScope[Open startup scope]
LogWarn --> OpenScope
OpenScope --> Migrate[Run DatabaseMigrator.Migrate]
Migrate --> Create["CREATE TABLE IF NOT EXISTS x4 + indexes"]
Create --> Drop["DROP TABLE IF EXISTS orthophotos, gps_corrections (B9)"]
Drop --> Pipeline[Wire pipeline: error MW first, auth, controllers, /health, Swagger]
Pipeline --> Run([app.Run on :8080])
ResolveDB -. on missing value .-> FailFast
ResolveJwt -. on missing value .-> FailFast
Migrate -. on failure .-> Crash([Process exits non-zero — Watchtower restarts])
Data Flow
| Step | From | To | Data | Format |
|---|---|---|---|---|
| 1 | Environment / IConfiguration | Program.cs (via ConfigurationResolver) |
DATABASE_URL, JWT_ISSUER, JWT_AUDIENCE, JWT_JWKS_URL |
string (required) |
| 2 | Environment / IConfiguration | Program.cs |
CorsConfig:AllowedOrigins (string[]), CorsConfig:AllowAnyOrigin (bool) |
optional |
| 3 | Program.cs |
DI container | service registrations + JWT bearer + JWKS ConfigurationManager |
C# code |
| 4 | DatabaseMigrator |
postgres-local |
DDL statements (CREATE / INDEX / DROP) | SQL |
| 5 | Program.cs |
OS / Docker | bind to 0.0.0.0:8080 |
TCP listener |
Error Scenarios
| Error | Where | Detection | Recovery |
|---|---|---|---|
Missing DATABASE_URL / JWT_ISSUER / JWT_AUDIENCE / JWT_JWKS_URL |
ConfigurationResolver.ResolveRequiredOrThrow |
env var + config key both empty/whitespace | Process exits non-zero with InvalidOperationException whose message names the missing env var and config key. Watchtower restarts but the new container hits the same failure. Fix: provide the value via env or appsettings.json |
CORS misconfigured in Production (CorsConfig:AllowedOrigins=[] AND CorsConfig:AllowAnyOrigin!=true) |
CorsConfigurationValidator.EnsureSafeForEnvironment at startup |
hard-fail guard | Process exits with InvalidOperationException("CORS is misconfigured: ..."). Fix: set CorsConfig:AllowedOrigins to the production UI origins, or set CorsConfig:AllowAnyOrigin=true to opt in explicitly |
postgres-local unreachable |
Migrate step | Npgsql IOException / SocketException |
Process exits non-zero. Watchtower restarts; flight-gate prevents restart mid-mission. Fix: ensure postgres-local healthcheck passes before missions starts (compose depends_on with condition: service_healthy) |
azaion database missing |
Migrate step | Npgsql 3D000 (invalid_catalog_name) |
Process exits. Operator must create the database — provisioning concern, not this service. Documented in ../../suite/_docs/00_top_level_architecture.md |
DROP TABLE IF EXISTS orthophotos fails because table is locked by gps-denied |
B9 one-shot | Lock timeout or 55006 |
Process exits, Watchtower restarts in a few seconds. Out-of-band ordering: deploy gps-denied FIRST so it has its own copy of the schema before missions drops the legacy tables. Documented in B9 ticket |
One CREATE TABLE succeeds, the next fails |
Mid-Migrate | Npgsql exception on later statement | Process exits. Each statement is individually idempotent (IF NOT EXISTS) so the next startup retries safely from the start. No partial-migration cleanup needed |
| Wrong PostgreSQL version (e.g., < 13) | Migrate step | Specific syntax errors in newer features | Process exits. Suite-supported version is PG 16+; older devices need a Postgres upgrade |
DATABASE_URL malformed (e.g. user password contains @) |
ConvertPostgresUrl |
parse failure / silent mis-parse | ConvertPostgresUrl does NOT URL-decode user/password — caveat for credentials with @, :, /, %. 07_host Caveats. Mitigation: avoid those chars in passwords, OR pass a raw Npgsql key=value string instead of a URL |
Performance Expectations
| Metric | Target | Notes |
|---|---|---|
| Cold start total time | <2 seconds typical on Jetson Orin | Migrator runs ~10 DDL statements; all are no-ops on a steady-state device |
| Cold start with legacy GPS-Denied tables present | +1 second | First-time-on-device B9 DROP adds two DDL statements |
| Crash recovery (Watchtower restart) | ~10 seconds | Container restart latency dominates |