Skip to content

[external API] alerts: the renamening #8169

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 36 commits into
base: main
Choose a base branch
from
Open

Conversation

hawkw
Copy link
Member

@hawkw hawkw commented May 15, 2025

In conversation with @ahl, we have determined that the external API for webhooks added in #7277 should be changed to focus on "alerts" as the first-class user-facing concept, with "webhooks" as one delivery mechanism for alerts. This way, we can talk about alerts as an entity in the API that exist independently of webhooks that deliver alerts, and the same alert types can be shared with other alert delivery mechanisms if any are added in the future.

What we currently refer to as "webhook events" and "webhook event classes" are therefore renamed to "alerts" and "alert classes". The current concept of "webhook receivers" is generalized to an "alert receiver" resource, of which webhook receivers are (currently) the only subtype. This way, if we add other mechanisms of delivering alerts in the future (email, first-class Slack integration, etc), we can introduce new subtypes of alert receivers. I've restructured the API to have both /v1/alert-receivers/... and /v1/webhook-receivers/... routes, with operations common to all alert receivers (list, view, add/remove subscriptions, delete) under the alert-receivers route, and operations related to webhook-specific configuration (add/remove secrets, probe, deliveries) under the webhook-receivers route. I've also changed the AlertReceiver view to have a "kind" enum that stores the subtype-specific configuration; currently, this will only ever be "webhook", but I thought it was worth doing this now to make future additions cause less breakage for API consumers.

This is, admittedly, a somewhat large diff, but fortunately, most of it is just renaming stuff and moving it around. Reviewers can focus more or less exclusively to the changes to the external API routes and models, and maybe the database migrations. Any mistakes while renaming and moving things around have already been caught by the Rust compiler. :)

@hawkw hawkw requested review from ahl, smklein and david-crespo May 15, 2025 19:11
@david-crespo
Copy link
Contributor

I think this is good. One thing that throws me off (but I could definitely get used it) is that you create a webhook receiver with POST /v1/webhook-receivers but then you list and view it with /v1/alert-receivers. I can see why it works that way, and maybe with two kinds of receiver, the structure would be more obvious. On the other hand it's rare for end users to be looking at a list like what's in nexus_tags.txt. The docs site sidebar is the closest thing, but the structure there is not as visible as I'd like, mostly because we're using plain english for the titles, which I don't think we can avoid.

image

Clone, Debug, Queryable, Selectable, Insertable, Serialize, Deserialize,
)]
#[diesel(table_name = alert_subscription)]
pub struct AlertRxSubscription {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for myself: this was moved from nexus/db-model/src/webhook_rx.rs

hawkw and others added 3 commits May 16, 2025 09:53
Co-authored-by: Sean Klein <sean@oxide.computer>
Co-authored-by: Sean Klein <sean@oxide.computer>
Co-authored-by: Sean Klein <sean@oxide.computer>
@hawkw
Copy link
Member Author

hawkw commented May 16, 2025

@david-crespo re:

One thing that throws me off (but I could definitely get used it) is that you create a webhook receiver with POST /v1/webhook-receivers but then you list and view it with /v1/alert-receivers. I can see why it works that way, and maybe with two kinds of receiver, the structure would be more obvious.

Yeah, I agree that this feels a bit weird.

One thing I considered doing is also having a GET /v1/webhook-receivers route to list/view webhook receivers only, in addition to the GET routes for /v1/alert-receivers. That would return the webhook-specific models, and the view route would 404 if you requested a receiver ID/name that was a type other than webhook. I can see a couple advantages of this: it makes the API feel a bit more "complete", and it also provides you a way to get a webhook-receiver-specific model without having to handle the enum if you know the receiver you want is a webhook receiver (and similarly, it gives you a way to list only webhook receivers). On the other hand, it means we have two separate routes that list/view the same entities, which could be confusing for users, and it requires us to maintain more endpoints. What do you think? Is it worth adding routes like that?

@hawkw
Copy link
Member Author

hawkw commented May 16, 2025

Oh, @smklein, one other thing: there are a couple of places where we now have tables that have a few columns with unfortunate names ("event_class" and "event_id" rather than "alert_class" and "alert_id") because CRDB doesn't support renaming columns idempotently. Do you think it's worth changing the migrations to drop those tables and create new ones with nicer names, instead of just renaming the table?

Copy link
Contributor

@ahl ahl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

took a pass comparing what we had determined in the CLI with the changes you're proposing to the API

@@ -3660,79 +3693,48 @@ pub trait NexusExternalApi {
/// queued for re-delivery.
#[endpoint {
method = POST,
path = "/v1/webhooks/receivers/{receiver}/probe",
tags = ["system/webhooks"],
path = "/v1/webhook-receivers/{receiver}/probe",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you consider nesting webhook-receivers under alerts?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I had initially wanted to do /v1/alert-receivers/webhooks. however, we can't nest routes under a route which can also look up a resource by name. because there's a /v1/alert-receivers/{name-or-id} route, we can't also have a /v1/alert-receivers/webhooks/{name-or-id} route, since it's unclear whether /webhooks should be treated as a receiver name or as a routable path segment.

i did also consider nesting all of this stuff under a top-level /v1/alerts, so /v1/alerts/receivers, /v1/alerts/webhook-receivers, and so on. however, /v1/alerts is also the route for looking up the actual alert resource (currently used for resend but maybe also for actually getting the payload etc in future). alerts are only looked up by UUID and never by name, so we could nest other routes under /v1/alerts, but it felt a bit weird, and i didn't want to put the alert-lookup route under /v1/alerts/alerts because...that's gross.

also, @david-crespo had previously told me that we try to keep the public API as "flat" as possible rather than nesting, and i think the /v1/alerts, /v1/alert-receivers, /v1/alert-deliveries etc structure is closer to what we've done elsewhere? if either of you have suggestions for a better structure given all that, though, i'm all ears!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@david-crespo can you comment? It seems like "as flat as possible... but not flatter" might be the addendum.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case I think the middle paragraph is the real constraint, rather than the aesthetic norm about flatness. We don't have anywhere else in the API where we support both /v1/alerts/1f733f7b-b2eb-429c-8b3a-69203cb55cd7 and /v1/alerts/receivers/.... I think that's worse than what we currently have in this PR. Though overall the difficulty here I think is the same one I was pointing to in #8169 (comment) — the structure doesn't feel quite natural, though I concede that's an initial impression and I am totally open to the idea that a) it's not yet worth the effort required to get it to feel intuitive, since we don't know how the feature will evolve, and b) initial impression is less important than whether it's usable after spending a little time wiht it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is also the option of double-nesting, i.e. having /v1/alerts/alerts/1f733f7b-b2eb-429c-8b3a-69203cb55cd7 and /v1/alerts/receivers/my-cool-receiver, I suppose. But, the "alerts/alerts" feels unpleasant.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in my humble opinion, no to alerts/alerts

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, i really didn't like it either...

Comment on lines 3595 to 3611
#[endpoint {
method = PUT,
path = "/v1/webhooks/receivers/{receiver}",
tags = ["system/webhooks"],
method = POST,
path = "/v1/alert-receivers/{receiver}/subscriptions",
tags = ["system/alerts"],
}]
async fn webhook_receiver_update(
async fn alert_receiver_subscription_add(
rqctx: RequestContext<Self::Context>,
path_params: Path<params::WebhookReceiverSelector>,
params: TypedBody<params::WebhookReceiverUpdate>,
) -> Result<HttpResponseUpdatedNoContent, HttpError>;
path_params: Path<params::AlertReceiverSelector>,
params: TypedBody<params::AlertSubscriptionCreate>,
) -> Result<HttpResponseCreated<views::AlertSubscriptionCreated>, HttpError>;

/// Delete webhook receiver
/// Remove alert receiver subscription
#[endpoint {
method = DELETE,
path = "/v1/webhooks/receivers/{receiver}",
tags = ["system/webhooks"],
path = "/v1/alert-receivers/{receiver}/subscriptions/{subscription}",
tags = ["system/alerts"],
}]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you made what I thought was a good suggestion in the CLI to call these oxide alert receiver subscribe and oxide alert receiver unsubscribe. Do you now prefer subscription add/remove? I think I prefer the former, but my only strong preference is that CLI and API match in this regard.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i like the "subscribe"/"unsubscribe" naming for the CLI. per this comment from @david-crespo we prefer to use verbs like "add"/"remove" in API routes because they're consistent with other API operations, rather than descriptive verbs like "subscribe"/"unsubscribe". personally i think i would strongly prefer the more descriptive verbs in the CLI and don't have strong preferences about whether we should also use that in the API...perhaps it's more important for both to be consistent with each other than to use the more descriptive verbs, i dunno...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds fine; just wanted to make sure we're making the decision eyes open.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i definitely think there's value in using the same verbs in the CLI and the API, because then the CLI becomes a sort of teaching tool for the API: if you've done something manually, you know exactly where to look if you want to do it programmatically. but, i'm not totally sure how strongly we've weighed that against other concerns in the past. are there any cases where we've intentionally chosen to use different verbs (or nouns!) in the CLI, or are they always the same?

@ahl
Copy link
Contributor

ahl commented May 16, 2025

@david-crespo re:

One thing that throws me off (but I could definitely get used it) is that you create a webhook receiver with POST /v1/webhook-receivers but then you list and view it with /v1/alert-receivers. I can see why it works that way, and maybe with two kinds of receiver, the structure would be more obvious.

What about creation via /v1/alert-receivers/webhook POST?

One thing I considered doing is also having a GET /v1/webhook-receivers route to list/view webhook receivers only, in addition to the GET routes for /v1/alert-receivers.

My suggestion is that we do one or the other for now i.e. "list all receivers" (which happen to just be webhooks) or "list webhook receivers" (which happen to be all receivers). In the future, I can't imagine the utility for listing JUST the webhooks, but I have been known to lack imagination!

That would return the webhook-specific models, and the view route would 404 if you requested a receiver ID/name that was a type other than webhook. I can see a couple advantages of this: it makes the API feel a bit more "complete", and it also provides you a way to get a webhook-receiver-specific model without having to handle the enum if you know the receiver you want is a webhook receiver (and similarly, it gives you a way to list only webhook receivers). On the other hand, it means we have two separate routes that list/view the same entities, which could be confusing for users, and it requires us to maintain more endpoints. What do you think? Is it worth adding routes like that?

Given that a second flavor of webhook is speculation at this point, I would suggest just pick something and re-evaluate when we have something more concrete in the future.

@hawkw
Copy link
Member Author

hawkw commented May 16, 2025

@david-crespo re:

One thing that throws me off (but I could definitely get used it) is that you create a webhook receiver with POST /v1/webhook-receivers but then you list and view it with /v1/alert-receivers. I can see why it works that way, and maybe with two kinds of receiver, the structure would be more obvious.

What about creation via /v1/alert-receivers/webhook POST?

Unfortunately we can't have a route like that, as receivers are looked up by name, and there's an ambiguity as to whether /webhook is interpreted as a name of a receiver to look up or a fixed path segment (as i discussed in #8169 (comment))

@hawkw hawkw requested a review from ahl May 19, 2025 18:29
@hawkw hawkw force-pushed the eliza/s/webhook/alert branch from 9d384ea to 56399ec Compare May 19, 2025 19:23
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.

4 participants