Schedule & manual triggers

provider: "senro" is the source-neutral shape, for the two invocations with no webhook behind them: a nightly run, and somebody pressing a button.

It is also the shape to reach for when you have the facts but not a webhook body: a cron job, an internal tool, a script that already knows the branch and the changed files.

A scheduled run

{"provider": "senro", "event": "schedule",
 "payload": {"schedule": "0 3 * * *", "params": {"suite": "full"}}}
senro.WithTrigger(ev,
	trigger.OnSchedule("0 3 * * *", trigger.Params{"suite": "full"}),
)
0 3 * * *  cd /srv/app && ./ci --trigger-event /etc/senro/nightly.json

senro grows no scheduler. Something outside it still starts the binary at 03:00: cron, systemd timers, a Kubernetes CronJob, GitHub Actions’ own schedule:. OnSchedule only matches the event that says “this is the 03:00 run”.

The cron string is compared to the event’s own as text, with whitespace normalised. That is what lets two crontab lines pointing at one binary select different work:

0 3 * * *  ./ci --trigger-event nightly.json     # matches OnSchedule("0 3 * * *")
0 * * * *  ./ci --trigger-event hourly.json      # matches OnSchedule("0 * * * *")

senro does not parse cron, so 0 3 * * * and 0 3 * * 0-6 are not equal even though a scheduler would fire them alike. Write the same string in both places.

A manual run

{"provider": "senro", "event": "manual",
 "payload": {"ref": "refs/heads/main", "params": {"reason": "rebuild"}}}
senro.WithTrigger(ev,
	trigger.OnManual(),
)

Anything in params becomes a run parameter, so a condition can read it:

deploy := p.Workflow("deploy", senro.When(senro.ParamIs("reason", "rebuild")))

See Conditions.

The payload fields

FieldWhat it is
refrefs/heads/main, refs/tags/v1.2.3. The kind is read from it.
branchDerived from ref when you leave it out.
tagSame, for a tag ref.
repoacme/app.
default_branchWhat decides whether a push is mode all or affected.
scheduleThe cron string OnSchedule compares against.
filesThe changed-file list Paths filters on.
paramsRun parameters this event contributes.

A field the shape does not have is an error. branches where you meant branch is a message, not a filter that silently matched nothing.

Supplying a file list yourself

Every other source either carries a changed-file list or does not, and you cannot change that. The neutral shape is where you supply one you worked out yourself:

FILES=$(git diff --name-only "$BASE".."$HEAD" | jq -R . | jq -sc .)
jq -n --arg ref "refs/heads/$BRANCH" --argjson files "$FILES" \
  '{provider:"senro", event:"manual", payload:{ref:$ref, files:$files}}' > event.json

trigger.Paths("services/**") now works against it, and so does an affected set.

An empty files list means “nothing changed”, which is a real answer. Leaving files out entirely means “this event does not say”, and Paths against it is an error. The two are not the same.

Where to go next

  • Triggers: the matchers, and what a match carries into the run.
  • The event file: the envelope every source shares.
  • Conditions: reading the params a trigger contributes.