Configuration as code: bucker.yml
bucker.yml describes an organization's configuration in a file you keep in a
repository and change by pull request. Bucker reads the file, shows you a plan (the
exact list of changes it would make), and changes nothing until you apply that plan.
A file can manage:
- org retention, per data stream;
- for each project, its alert rules, cron monitors, DSN key rate limits, its quota override, and its ownership rules;
- the chat alert routes and ceilings from version 1 of the file. These are planned here but applied from chat. See Chat routes.
The commands
bucker config export > bucker.yml # what the org is configured to do today
bucker config plan bucker.yml # the diff; writes nothing
bucker config apply bucker.yml # re-plans, then applies exactly that plan
The CLI wraps three API routes, which are also the JSON import and export:
GET /orgs/{org}/config[?project=<slug>] export, as a version-2 document
POST /orgs/{org}/config/plan { "source": "<file text>" } or { "config": {…} }
POST /orgs/{org}/config/apply the same body, plus an optional "planHash"
A plan and an apply body carry the document in exactly one of two forms:
source: the file's raw text, as YAML or JSON;config: the document, already decoded.
JSON is valid YAML, so an exported document can be saved as bucker.yml without
changes. If you plan an exported document against the org it came from, the plan has no
changes.
Plan
{
"changes": [
{ "action": "create", "resource": "monitor", "key": "checkout/nightly-billing",
"summary": "create monitor \"nightly-billing\"" },
{ "action": "unchanged", "resource": "project_key", "key": "checkout/default",
"summary": "DSN key \"default\"" }
],
"errors": [],
"planHash": "9f2c…"
}
actionis one ofcreate,update,deleteorunchanged.resourceis one ofretention,alert_rule,monitor,project_key,quota,ownership_rule,chat_routeorchat_ceiling.- If the file fails to parse, the response is still a 200. It contains every error, each
with a path such as
projects.checkout.monitors.0.schedule, and no changes. - A plan never writes. You can run it in CI on every pull request.
Apply
Apply plans the file again, then makes each change. The plan you reviewed and the changes apply makes are the same list.
- Pass
planHashfrom the plan you reviewed. If the file or the org changed since that plan was made, apply returns 409 and changes nothing. Plan again and review the new result. - A plan with errors is refused with 422, and nothing is changed.
- Each change goes through the same write path as the matching API route, so each one
writes its usual audit row (
monitor.create,alert_rule.update,project_key.update, …). Apply also writes oneconfig.applyrow. That row names the plan hash and the list of changes, and it is written before the first change. - The response lists the changes that were applied. Unchanged entries are omitted.
Permissions. Plan and export need read access to the organization. Each project you plan must also be readable by you; a project you cannot read is reported as unknown. Apply needs the narrowest permission for each change, and checks every one before the first write:
| Change | Needs |
|---|---|
| alert rules, monitors, DSN keys, ownership rules | write on the project (a team admin, or an org admin or owner) |
| retention | admin on the org (an org owner) |
| quota | billing management on the org |
If one change needs a permission you lack, apply returns 403 and nothing is changed. A file that only tunes a project's monitors can be applied by that project's team admin. A file that also sets retention needs an owner.
Apply is not one transaction. Each change uses its resource's own write path, including that path's validation and audit row. Planning already checks everything the file controls: the schema, unknown projects, teams and members, ambiguous key names, and alert-rule channels and queries. A write can still fail for a reason the file does not control, for example a quota above the plan's volume. If that happens, apply stops at that change and returns the error. Plan again to see what remains.
The file
version: 2
retention: # org-wide; null returns a stream to the plan's maximum
spanDays: 14
logRecordDays: 14
projects:
checkout: # the project slug
prune: false # see "Deletion" below
alertRules: # the alert-rule API's body, matched by name
- name: New issues in checkout
type: issue
conditions: [{ id: first_seen }]
actions: [{ kind: notify_channel, channelId: <channel id> }]
monitors: # the monitor API's body plus a slug, matched by slug
- slug: nightly-billing
name: Nightly billing
schedule: "0 2 * * *"
timezone: America/New_York
gracePeriodMinutes: 10
keys: # DSN keys, matched by name
- name: default
rateLimitPerMinute: 600
quota:
monthlyEventLimit: 2000000
capsEnabled: true
ownership: # first matching rule wins
- pattern: "src/billing/**"
teams: [payments]
members: [ana@example.com]
- Each resource uses the same schema as its API route. An alert rule is the body of
POST …/alert-rules. A monitor is the body ofPOST …/monitors, plus itsslug. Defaults and validation rules are the route's own, so a rule the API refuses is also refused in the file. - Validation is strict. An unknown key is an error, not ignored. A misspelled
gracePeriodMinutesfails the plan. - A missing section is not managed by the file. If a project block has no
monitors:key, the file leaves that project's monitors alone. You can adopt the file for one concern at a time. - Owners are referenced by name. List teams by slug and members by email. During planning, each name is resolved to an id. A name that does not exist in the organization fails the plan.
- DSN keys are created or updated, never deleted. Deleting a key breaks ingest for
every app that uses it. To stop a key, set
isActive: false. - Org-wide alert rules are not part of the file. These are covered by Org-wide rules. The export also leaves them out.
Deletion
To delete something, the file must say so twice. A project block must:
- list the resource kind, for example
monitors: [...]; and - set
prune: true.
With both, apply deletes that project's alert rules or monitors that the file does not
name. Without prune, unlisted resources are left alone. A file written for a new
project therefore cannot delete the rules of an existing one.
Ownership rules work differently. A file deletes only ownership rules that it created.
Bucker marks those rules with source: bucker.yml. Rules created by a person, and rules
imported from CODEOWNERS, are never deleted from a file. This follows the same rule as
the CODEOWNERS import. If the file lists a rule that already exists, wherever that rule
came from, the plan reports it as unchanged.
Chat routes
Version 1 of bucker.yml has two lists: routes (which chat conversation receives a
project's alerts) and ceilings (per-channel message limits). Version 1 files are still
accepted everywhere. Version 2 keeps both lists unchanged.
The plan shows chat changes, but you apply them in the chat workspace with
/bucker here <project>. A chat binding belongs to one chat installation and is recorded
as a chat action. If the chat part of a file differs from the current state, the plan
reports an error at routes. To apply the rest of the file from the API, make those
changes in chat, or remove routes and ceilings from the file.
Terraform
There is no Terraform provider. bucker.yml plans are reviewable without one. A
provider generated from this schema and the OpenAPI document
will be built if a customer needs one.