Skip to content

feat(schedules): runcrate schedule (start instances or jobs later, once or on cron) - #5

Merged
heydryft merged 3 commits into
mainfrom
feat/schedules
Oct 20, 2026
Merged

heydryft merged 3 commits into
mainfrom
feat/schedules

Conversation

@heydryft

@heydryft heydryft commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

What it adds

runcrate schedule (alias schedules), against /api/v1/schedules:

  • create --at '<time>' | --cron '<5 fields>' [--tz <IANA>] [--name] [--overlap skip|allow] with the instance create flags (--gpu, --gpu-count, --region, --template, --volume, --type-id, --idle-stop, --max-runtime) or a job command after -- with the job flags (--volume, --image, --env).
    • Kind is job when a command follows --, else instance (which needs --name, like instances create).
    • --max-runtime is required for both kinds and refused locally when missing (instance 5m to 168h, job 1m to 24h, same parsers as instances create and run).
    • --at without an offset is wall time in --tz; --tz defaults to this machine's IANA zone ($TZ, then /etc/localtime) for --at and --cron. The CLI sends run_at as a UTC instant plus timezone. DST is handled without offset math: a skipped time moves forward by the gap (with a note on stderr), a repeated time uses the first one, matching the server's cron rule. Zone data is embedded (time/tzdata) so --tz works on Windows.
    • Mismatched flags are refused (--idle-stop/--type-id on a job, --image/--env on an instance, --overlap with --at, both or neither of --at/--cron, non 5-field cron).
  • list [-q] table: ID, NAME, KIND, WHEN (cron + tz, or once <time> (tz)), NEXT RUN (local time), LAST RESULT, ENABLED.
  • get <id>, pause <id> (PATCH enabled:false), resume <id> (enabled:true), run-now <id>, delete <id> (asks [y/N] unless -y/--yes; without a terminal it asks for --yes).
  • --json on the group for every subcommand, like instances and jobs.
  • v1 errors surface through api.APIError as <message> (HTTP <status>); run-now's 402 BUDGET_REACHED prints the same budget message as instances create.
  • README: new Schedules section.

Refactors for reuse (no behaviour change)

  • buildInstanceRequest and addInstanceOnlyFlags extracted from instances create; addJobOnlyFlags (--image, --env) extracted from run. schedule create builds its spec with these and buildJobRequest, and reuses formatMinutes, parseMinutesFlag and parseMaxRuntime.
  • newTableTo(w) so the list table can be rendered in tests.

Based on main (9bbf731, includes #1 to #4).

How it was tested

go vet ./..., go test ./... (all packages pass), and the CI cross-compile for the five targets. New tests in internal/commands/schedule_test.go, all offline with httptest fakes:

  • --at conversion in Europe/London and America/New_York incl. spring-forward gaps (2026-03-29 01:30 London lands at 02:30 BST) and fall-back repeats (first occurrence), explicit offsets, past and malformed times.
  • $TZ default zone and unknown --tz.
  • Kind detection and full create bodies for an instance cron (with a volume) and a job one-off; 16 refusal cases incl. missing --max-runtime for each kind.
  • Wire requests for create, pause, resume (PATCH bodies), run-now, delete; 422 message surfacing and run-now's 402 BUDGET_REACHED.
  • List table rendering and the delete confirm prompt.

Smoke-run of the built binary with an empty HOME (no credentials, no live API calls): help output, the missing --max-runtime refusal, and the DST note.

Pairs with the runcrate_app schedules PR (feat/launch-schedules).

…delete, run-now

Start an instance or a job later, once (--at) or on a recurring --cron in an IANA zone, against /api/v1/schedules. A command after -- makes it a job; otherwise it starts an instance. --max-runtime is required for both. --at wall times are read in --tz (default this machine's zone) and converted across DST: a skipped time moves forward by the gap, a repeated time runs the first time.

Reuses the instance and job create builders and flags by extracting buildInstanceRequest, addInstanceOnlyFlags and addJobOnlyFlags.
Add run_as and run_as_credential to the Schedule type, show how a schedule launches in schedule get, and show credential_revoked as 'paused: key revoked'.
@heydryft
heydryft merged commit 578fe61 into main Oct 20, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant