Skip to content

kbagent notification: write path (create/delete subscriptions) to fix what audit finds #690

Description

@MichalProchazkaP3

Problem

kbagent notification (from #600 / PR #615, #618) is read-only: list and detail over the Notification service's /project-subscriptions. That closed the audit gap, but auditing surfaces things you then want to fix, and there's no way to do that from the CLI.

Concrete motivating case: a fleet-wide Flow-notification audit across 20 projects found 10 /project-subscriptions still naming departed employees / a decommissioned mailbox (michal.hruska@keboola.com, monika.feigler@keboola.com, lenka.hancarova@p3parks.com). All 10 happened to target already-orphaned configs this time (harmless), but that's luck, not policy — the next one could easily be attached to a live production flow, and there'd still be nothing to do about it except open the UI by hand.

What exists in the API (confirmed against the public swagger, https://notification.eu-central-1.keboola.com/docs/swagger.yaml)

  • POST /project-subscriptions — create a subscription. security: [StorageApiTokenAuth, StorageApiBearerToken, ...], identical to the read path already implemented — no elevated scope needed, same as the correction already made in kbagent notification: fleet-wide audit of Flow Notification subscriptions (Notification Service API) #600's discussion.
  • DELETE /project-subscriptions/{subscriptionId} — delete by ID. Same auth. 204 on success, 404 if it doesn't exist for the current project.
  • No PATCH/PUT exists. "Fix a recipient" is necessarily delete-then-recreate, not an in-place edit — worth designing the CLI/service surface around that from the start rather than pretending an update primitive exists.
  • NewProjectSubscription request body: required event (kebab-case string, e.g. job-failed) and recipient ({channel: "email", address} or {channel: "webhook", url}); optional filters (array of {field, value, operator} — same dotted-path fields list/detail already parse, e.g. job.configuration.id, job.component.id, branch.id) and expiresAt.

Proposed solution

Extend the exact layers #615 already built — no new abstraction:

  1. client/notifications.py (_NotificationsMixin): add

    def create_project_subscription(self, event: str, recipient: dict, filters: list[dict] | None = None, expires_at: str | None = None) -> dict:
        body = {"event": event, "recipient": recipient}
        if filters: body["filters"] = filters
        if expires_at: body["expiresAt"] = expires_at
        return self._notification_request("POST", "/project-subscriptions", json=body).json()
    
    def delete_project_subscription(self, subscription_id: str) -> None:
        path = f"/project-subscriptions/{quote(str(subscription_id), safe='')}"
        self._notification_request("DELETE", path)

    Mirrors the existing list_project_subscriptions / get_project_subscription in the same file.

  2. services/notification_service.py (NotificationService): add

    • create_subscription(alias, event, component_id, config_id, channel, address_or_url, branch_id=None, ...) — builds the filters list from component_id/config_id/branch_id the same way list_subscriptions already resolves config names, so the caller passes friendly parameters instead of hand-rolling job.configuration.id-style filter dicts.
    • delete_subscription(alias, subscription_id) — thin wrapper, no name resolution needed.
    • replace_subscription_recipient(alias, subscription_id, new_address) — convenience for the actual motivating use case: get_project_subscription → delete → recreate with the same event/filters/expiresAt but the new recipient. This is the one that turns "10 stale addresses found" into "10 fixed" in one command per row instead of manual delete+recreate every time.
  3. commands/notification.py (notification_app): add

    kbagent notification create --project ALIAS --event NAME --component-id ID --config-id ID [--branch ID] --channel email|webhook --address ADDR [--expires-at TS]
    kbagent notification delete --project ALIAS --subscription-id ID [--yes]
    kbagent notification replace-recipient --project ALIAS --subscription-id ID --address NEW_ADDR [--yes]
    

    delete and replace-recipient are destructive/mutating — register them under the same --deny-writes permission gate as flow update/config update, not the read-only bucket list/detail currently sit in.

Non-goals

  • POST /notifications (send a one-off notification right now) is a different endpoint entirely and requires a Manage API application token with notifications:push-event scope — already called out as out-of-scope in kbagent notification: fleet-wide audit of Flow Notification subscriptions (Notification Service API) #600's discussion thread, still true here.
  • No in-place update exists in the API, so don't build a CLI update that pretends otherwise — replace-recipient above is explicit about being delete+recreate under the hood (and inherits a new subscription_id, worth surfacing in the output so scripts don't cache the old one).

Why it matters

Closes the loop #600 opened: list/detail let you find problems (stale addresses, orphaned subscriptions targeting deleted configs) across every project in one command; today, fixing what you found still means opening the Keboola UI once per subscription. Same audit-then-fix workflow this org already uses for flow update/config update.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions