Migrating to v0.48.0
Sparkwing v0.48.0 makes a schedule say where it fires, pins what an armed
schedule runs, and lets a controller evaluate schedules pushed to it. Three
things change under an adopter: every on.schedule entry needs where, the
runs store advances to schema 34 and refuses binaries older than this release,
and sparkwing crons install now pins the pipeline it arms. Each section below
is a mechanical edit or a one-time command.
on.schedule entries say where they fire
on.schedule is now a list of named entries, and every entry declares the
side that fires it.
-
Before: one cadence per pipeline, written as a cron string or a mapping of
cron,tz,overlapandcatch_up. Where it fired was decided entirely by which hosts had runsparkwing crons install. In Go,pipelines.Triggers.Schedulewas a*pipelines.ScheduleTrigger.on: schedule: cron: "0 3 * * *" tz: America/Denver -
After:
on.scheduletakes one mapping or a list of mappings. Each entry addswhere(required,localorcontroller), an optionalname, and optionalargskeyed by CLI flag name. The bare cron string is gone: a scalar cannot carrywhere, so it is refused at load with a message naming the mapping form.on: schedule: - name: host cron: "0 3 * * *" tz: America/Denver where: local - name: cluster cron: "0 3 * * *" where: controller args: region: us-eastIn Go,
pipelines.Triggers.Scheduleis apipelines.ScheduleTriggers, a slice ofpipelines.ScheduleTrigger. -
Migration: a schedule still written as a bare cron string becomes a mapping, and every schedule already declared needs
where: localadded to keep firing from the host that arms it. That edit is deliberate:wherehas no default, so a config that does not say where a cadence fires is rejected at load rather than guessed at. A pipeline that declares more than one entry also needs anameon each, unique within the pipeline, matching^[a-z0-9][a-z0-9-]*$and at most 40 characters; a lone entry is nameddefault.Go callers read
len(t.Schedule) > 0where they readt.Schedule != nil, and range the slice where they readt.Schedule.Cron:for i := range p.On.Schedule { entry := &p.On.Schedule[i] fmt.Println(entry.EffectiveName(), entry.Cron, entry.Where) }pipelines.Triggersnow holds a slice, so it is no longer comparable with==; compare the fields that matter instead. -
Why: a cron runs unattended, so the config has to say which side runs it. One pipeline also has more than one useful cadence -- a quick sweep every quarter hour and a deep one nightly -- and naming the entries lets each carry its own arguments and be paused, resumed and run on its own.
Runs-store schema 34: named, locked schedules
-
Before: Schema 32 held one schedule per repository checkout and pipeline, keyed
UNIQUE(repo_path, pipeline). A schedule ran the cadence the repository declared, from whatever the checkout held at the moment the tick fired, with no arguments and no host-side edits. -
After: Schema 34 keeps several schedules for one pipeline and pins what they run.
cron_schedulesgainsschedule_name(defaultfor the lone schedule of a pipeline),where_(localorcontroller),args(a JSON object of CLI argument name to value), the lock --locked_ref,locked_binaryandlocked_digest, all empty while the schedule follows the checkout -- and sevenoverride_*columns holding this host's edit of the declaration together with the declaration it was set against. The unique key widens to(repo_path, pipeline, schedule_name), which arrives asidx_cron_schedules_repo_pipeline_name: SQLite rebuilds the table to widen it, Postgres drops the old constraint and creates the index.cron_schedulesalso gainsgit_branch, the branch a schedule pushed to a controller was read from, empty for one a host armed from a working tree.cron_firesgainsargs, the arguments the launch was given. Schema 34 is a repair pass over 33: it addsgit_branchand re-runs every one of 33's additions, so a database an intermediate build stamped 33 is brought to the full shape rather than left short of a column or the widened index. -
Migration: The migration declares the schema requirement
cron-schedule-names-v1, so a binary older than this release refuses the store once it has been opened by this one. The refusal is deliberate: an older binary still keys schedules by(repo_path, pipeline), so it would overwrite a named or a pushed row through the key it believes in, and it would fire a pinned schedule by compiling the checkout the pin exists to ignore.Upgrade every pinned SDK on a machine in one sitting before opening the store with this release:
sparkwing repos update # move every registered repo's SDK pin sparkwing crons install # re-pin, so the timer runs the new binarysparkwing crons installmatters on any host with an OS timer: the unit runs the binary recorded when it was written, and a timer still pointing at an older one will refuse the store every minute.Existing schedules and their fire history survive the widening intact.
Armed schedules are pinned
sparkwing crons install now keeps the pipeline binary it compiled and runs
that file at every fire, instead of compiling the checkout each minute.
-
Before: a schedule read the checkout at the moment it fired, so pulling a branch changed what ran that night.
-
After: install records the compiled binary under
<sparkwing home>/crons/along with the checkout'sHEAD, and the fire executes it. Re-running install is the explicit update: it compiles again, replaces the binary and moves the recorded commit.crons install --followandcrons unlock <name>keep the old behaviour for a schedule;crons lock <name>pins one again. -
Migration: nothing breaks and nothing is pinned by the upgrade. A schedule armed by an earlier release keeps following the checkout until
sparkwing crons installis run again on that host, which is the act that pins it.sparkwing crons statussays how many schedules follow the checkout and how many are pinned, so a host can be checked without re-arming it. -
Why: a cron fires unattended. Someone updating a checkout during the day should not change what runs at three in the morning without saying so.
The pin covers the pipeline and everything compiled into it. Scripts and binaries the pipeline executes from the checkout or from
PATHare outside it, and stay whatever the machine holds when the run reaches them.