package run // import "git.griefed.de/griefed/repoman/internal/run" Package run defines the domain types for migration runs and run events. The DB implementations live in internal/db; the engine lives in internal/migrate. Both import from here to avoid cycles. CONSTANTS const OKLinePrefix = "[OK]" OKLinePrefix is the marker the engine prints at the start of a log line for a repository it freshly migrated (e.g. "[OK] owner/repo"). It is the single source of truth for that convention: the line parser classifies on it, and the recovery loop-guard detects "this run migrated a new repository" by it (such a repo is idempotently skipped on the next attempt, so its presence proves the migration advanced). It lives HERE, in the domain-types package, only because internal/db needs it: RunProgress builds an anchored `message LIKE '[OK]%'` query from it, and db must not import internal/migrate (that would couple storage to the engine). This package is therefore the lowest common one the parser, the engine and the DB layer already share. That is a tolerated exception, not a pattern to copy — the engine's other line markers have no consumer outside internal/migrate and are defined there (see errorLinePrefix in migrate/lineparser.go). Don't move one here for symmetry alone. TYPES type Counters struct { TotalRepos int Migrated int Skipped int Errors int // Warnings counts problems that did not abort a repo's migration but did // stop something from happening — a release listing refused, an asset that // could not be downloaded, a ref push that failed. They are deliberately // separate from Errors so a repo archived on the source (whose webhook // provisioning will always 403) cannot make every run report failure. Warnings int UsersCreated int MirrorsConfigured int ReposSynced int } Counters holds the summary stats parsed from migrate.sh output. type Event struct { ID int64 RunID int64 Timestamp time.Time Level EventLevel Source string Message string } Event is one row from the run_events table. type EventLevel string EventLevel mirrors the run_events.level CHECK constraint. const ( // LevelDebug is diagnostic detail, normally hidden from operators. LevelDebug EventLevel = "debug" // LevelInfo is normal progress output. LevelInfo EventLevel = "info" // LevelWarn flags a non-fatal problem (e.g. a skipped repo). LevelWarn EventLevel = "warn" // LevelError flags a per-repo failure that did not abort the run. LevelError EventLevel = "error" ) type EventRepository interface { // Append writes a batch of events in one transaction. Append(ctx context.Context, events []Event) error // ListForRun returns all events for a run in order, with IDs // greater than afterID. Pass 0 to get all events. ListForRun(ctx context.Context, runID int64, afterID int64) ([]Event, error) // ListForRunPage returns one page of a run's events for the lazy log view: // the newest `limit` events older than `before` (before<=0 means the newest // events overall), returned oldest-first. hasMore reports whether still-older // events exist, so the client knows scroll-up can load another page. This // backs the monitor's "load only what fits, fetch more on scroll-up" view, so // a multi-thousand-line run never ships its whole log in one response. ListForRunPage(ctx context.Context, runID, before int64, limit int) (events []Event, hasMore bool, err error) // RunProgress reports a run's resumable-progress facts for the recovery // loop-guard. hasMigratedRepo is true when the run logged at least one // OKLinePrefix line (a repository it freshly migrated, which the idempotent // engine skips on the next attempt, so the crash point necessarily advances); // lastActivityAt is the timestamp of its most recent event (zero when it logged // none), used to tell a continuation that did sustained idempotent work — e.g. // a multi-GB release-asset sync on an already-present repo, which emits no // OKLinePrefix line — apart from one that crashed almost immediately. RunProgress(ctx context.Context, runID int64) (hasMigratedRepo bool, lastActivityAt time.Time, err error) } EventRepository is the persistence interface for run events. type ExitStatus string ExitStatus is the terminal state of a run. const ( // StatusRunning means the run is still in progress (not terminal). StatusRunning ExitStatus = "running" // StatusSuccess means every repo migrated without error. StatusSuccess ExitStatus = "success" // StatusPartial means the run completed but some repos errored. StatusPartial ExitStatus = "partial" // StatusFailed means the run aborted before completing. StatusFailed ExitStatus = "failed" // StatusCancelled means an operator cancelled the run. StatusCancelled ExitStatus = "cancelled" ) type QueueEntry struct { ID int64 MigrationConfigID int64 TriggeredBy TriggerSource TriggeredByUserID *int64 Mode RunMode QueuedAt time.Time // IsRecovery marks an entry queued to continue an interrupted run after a // restart; it flows into the created run's IsRecovery so the one-retry guard // persists. False for ordinary manual/scheduled triggers. IsRecovery bool } QueueEntry is one row from the run_queue table. type QueueRepository interface { // Enqueue adds an entry. Returns ErrAlreadyQueued if a pending // entry for the same config already exists and dedup=true. Enqueue(ctx context.Context, entry *QueueEntry, dedup bool) error // Pop removes and returns the oldest pending entry. Returns // (nil, nil) when the queue is empty. Pop(ctx context.Context) (*QueueEntry, error) // List returns all pending entries in FIFO order (oldest first) without // removing them, so the UI can show queued-but-unstarted runs. List(ctx context.Context) ([]*QueueEntry, error) // HasForConfig reports whether an entry for one config is waiting. Separate // from List because the callers that ask this — the confirmation wizard's // step and its status poll, which runs every few seconds per open page — // want one boolean, not every pending entry scanned into structs and then // searched. HasForConfig(ctx context.Context, configID int64) (bool, error) // Len returns how many entries are waiting. Len(ctx context.Context) (int, error) } QueueRepository manages the FIFO run queue. type Repository interface { // Create inserts a new run row with status=running and returns its ID. Create(ctx context.Context, r *Run) (int64, error) // GetByID returns one run by ID. GetByID(ctx context.Context, id int64) (*Run, error) // ListForConfig returns runs for one config, newest first. ListForConfig(ctx context.Context, configID int64, limit int) ([]*Run, error) // ListUnfinished returns every run still in StatusRunning. After a clean // boot these are orphans (the worker is single-instance and not yet started, // so none can be live): a previous process was interrupted before it could // finalize the row. Used by Worker.RecoverOrphans. ListUnfinished(ctx context.Context) ([]*Run, error) // UpdateFinished sets finished_at, exit_status, and counter fields. UpdateFinished(ctx context.Context, id int64, at time.Time, status ExitStatus, counters Counters) error // UpdateStatus updates only exit_status. Used for cancellation. UpdateStatus(ctx context.Context, id int64, status ExitStatus) error } Repository is the persistence interface for run records. type Run struct { ID int64 MigrationConfigID *int64 MigrationConfigVersionID *int64 MigrationConfigName string // joined from migration_configs; may be empty for deleted configs TriggeredBy TriggerSource TriggeredByUserID *int64 Mode RunMode StartedAt time.Time FinishedAt *time.Time ExitStatus ExitStatus // IsRecovery marks a run that was automatically queued to continue an // interrupted run after a daemon restart/crash. Such a run is never itself // re-queued on a subsequent interruption (the one-retry guard against a // config that crashes the daemon mid-run); see Worker.RecoverOrphans. IsRecovery bool // Counters — populated from the script's summary block. TotalRepos int Migrated int Skipped int Errors int // Warnings counts non-fatal problems — a refused release listing, an asset // that could not be downloaded, a failed ref push. Separate from Errors so a // repository archived upstream cannot make every run report failure. Warnings int UsersCreated int MirrorsConfigured int ReposSynced int } Run is one row from the runs table. func (r *Run) Duration(now time.Time) time.Duration Duration returns the elapsed time for a finished run, or time since start for a still-running one. func (r *Run) IsFinished() bool IsFinished reports whether the run has reached a terminal state. type RunMode string RunMode distinguishes real runs from previews. const ( // ModeReal performs the migration and writes to Forgejo. ModeReal RunMode = "real" // ModeDryRun previews the migration without writing anything. ModeDryRun RunMode = "dry-run" // ModeFirstRunForcedDry forces a dry run the first time a config is // executed, before the operator has confirmed its target namespace. ModeFirstRunForcedDry RunMode = "first-run-forced-dry" ) type TriggerSource string TriggerSource is how a run was initiated. const ( // TriggerManual marks a run an operator started from the web UI. TriggerManual TriggerSource = "manual" // TriggerScheduled marks a run the cron scheduler started. TriggerScheduled TriggerSource = "scheduled" )