package updater import ( "context" "fmt" "felis.lolicon.best/internal/updates" ) // RoutingSource is the production updates.ReleaseSource. updates.Run calls a single // source for every non-pinned component, so this one dispatches each component to its // configured upstream by the topology: the PaperMC Fill API for Velocity, the GitHub // Releases API for felis-api, k3s, cloudflared and the Temurin JRE, and the // PostgreSQL project's release table for the database. type RoutingSource struct { routes map[string]Spec paper paperMC gh github pg postgresFeed // notes holds what a lookup learned beyond the version, keyed by component: today // only an end-of-life PostgreSQL major. updates.Run calls Latest sequentially. notes map[string]string } // NewRoutingSource builds the router from a topology. Pinned specs are indexed too // but never reached (Run skips pinned components before calling Latest). func NewRoutingSource(specs []Spec) *RoutingSource { routes := make(map[string]Spec, len(specs)) for _, s := range specs { routes[s.Name] = s } return &RoutingSource{routes: routes, paper: newPaperMC(), gh: newGitHub(), pg: newPostgresFeed(), notes: map[string]string{}} } // Notes returns what the last lookups learned beyond each version, keyed by // component, for the caller to print under the report. func (r *RoutingSource) Notes() map[string]string { return r.notes } // Latest implements updates.ReleaseSource. An unknown component name is an error, not // a silent zero, so a topology/route mismatch is loud. func (r *RoutingSource) Latest(ctx context.Context, comp updates.Component) (updates.Version, error) { spec, ok := r.routes[comp.Name] if !ok { return updates.Version{}, fmt.Errorf("updater: no source route for %q", comp.Name) } switch spec.Source { case sourcePaperMC: return r.paper.latestStable(ctx, spec.Coord) case sourceGitHub: return r.gh.latestStable(ctx, spec.Coord) case sourceTemurin: // The feature release the host runs picks the repository: a newer feature is a // release decision (the installer's pin), never a patch to report. return r.gh.latestTemurin(ctx, comp.Current.Major) case sourcePostgres: rel, err := r.pg.latest(ctx, comp.Current) if err != nil { return updates.Version{}, err } if rel.note != "" { r.notes[comp.Name] = rel.note } return rel.latest, nil case sourceNone: // A pinned component (Run never reaches this, but be explicit and loud). return updates.Version{}, fmt.Errorf("updater: %q is pinned and has no release source", comp.Name) default: return updates.Version{}, fmt.Errorf("updater: component %q has an unknown source kind", comp.Name) } }