diff --git a/Cargo.lock b/Cargo.lock index 8c374f5609..d58f6a0956 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6297,6 +6297,7 @@ dependencies = [ "test-strategy", "thiserror 1.0.69", "update-engine", + "url", "uuid", ] @@ -10191,6 +10192,7 @@ dependencies = [ "schemars_derive", "serde", "serde_json", + "url", "uuid", ] @@ -12612,6 +12614,7 @@ dependencies = [ "form_urlencoded", "idna 1.0.3", "percent-encoding", + "serde", ] [[package]] diff --git a/nexus/external-api/output/nexus_tags.txt b/nexus/external-api/output/nexus_tags.txt index a979a9804b..0b97958e11 100644 --- a/nexus/external-api/output/nexus_tags.txt +++ b/nexus/external-api/output/nexus_tags.txt @@ -231,6 +231,16 @@ API operations found with tag "system/status" OPERATION ID METHOD URL PATH ping GET /v1/ping +API operations found with tag "system/webhooks" +OPERATION ID METHOD URL PATH +webhook_create POST /experimental/v1/webhooks +webhook_delete DELETE /experimental/v1/webhooks/{webhook_id} +webhook_delivery_list GET /experimental/v1/webhooks/{webhook_id}/deliveries +webhook_delivery_resend POST /experimental/v1/webhooks/{webhook_id}/deliveries/{delivery_id}/resend +webhook_secrets_add POST /experimental/v1/webhooks/{webhook_id}/secrets +webhook_secrets_list GET /experimental/v1/webhooks/{webhook_id}/secrets +webhook_view GET /experimental/v1/webhooks/{webhook_id} + API operations found with tag "vpcs" OPERATION ID METHOD URL PATH internet_gateway_create POST /v1/internet-gateways diff --git a/nexus/external-api/src/lib.rs b/nexus/external-api/src/lib.rs index e2b53a7e6f..db8abefbd1 100644 --- a/nexus/external-api/src/lib.rs +++ b/nexus/external-api/src/lib.rs @@ -151,6 +151,12 @@ pub const API_VERSION: &str = "20241204.0.0"; url = "http://docs.oxide.computer/api/vpcs" } }, + "system/webhooks" = { + description = "Webhooks deliver notifications for audit log events and fault management alerts.", + external_docs = { + url = "http://docs.oxide.computer/api/webhooks" + } + }, "system/probes" = { description = "Probes for testing network connectivity", external_docs = { @@ -3088,6 +3094,87 @@ pub trait NexusExternalApi { rqctx: RequestContext, params: TypedBody, ) -> Result, HttpError>; + + // Webhooks (experimental) + + /// Get the configuration for a webhook. + #[endpoint { + method = GET, + path = "/experimental/v1/webhooks/{webhook_id}", + tags = ["system/webhooks"], + }] + async fn webhook_view( + rqctx: RequestContext, + path_params: Path, + ) -> Result, HttpError>; + + /// Create a new webhook receiver. + #[endpoint { + method = POST, + path = "/experimental/v1/webhooks", + tags = ["system/webhooks"], + }] + async fn webhook_create( + rqctx: RequestContext, + params: TypedBody, + ) -> Result, HttpError>; + + /// Delete a webhook receiver. + #[endpoint { + method = DELETE, + path = "/experimental/v1/webhooks/{webhook_id}", + tags = ["system/webhooks"], + }] + async fn webhook_delete( + rqctx: RequestContext, + path_params: Path, + ) -> Result; + + /// List the IDs of secrets for a webhook receiver. + #[endpoint { + method = GET, + path = "/experimental/v1/webhooks/{webhook_id}/secrets", + tags = ["system/webhooks"], + }] + async fn webhook_secrets_list( + rqctx: RequestContext, + path_params: Path, + ) -> Result, HttpError>; + + /// Add a secret to a webhook. + #[endpoint { + method = POST, + path = "/experimental/v1/webhooks/{webhook_id}/secrets", + tags = ["system/webhooks"], + }] + async fn webhook_secrets_add( + rqctx: RequestContext, + path_params: Path, + params: TypedBody, + ) -> Result, HttpError>; + + /// List delivery attempts to a webhook receiver. + #[endpoint { + method = GET, + path = "/experimental/v1/webhooks/{webhook_id}/deliveries", + tags = ["system/webhooks"], + }] + async fn webhook_delivery_list( + rqctx: RequestContext, + path_params: Path, + query_params: Query, + ) -> Result>, HttpError>; + + /// Request re-delivery of a webhook event. + #[endpoint { + method = POST, + path = "/experimental/v1/webhooks/{webhook_id}/deliveries/{delivery_id}/resend", + tags = ["system/webhooks"], + }] + async fn webhook_delivery_resend( + rqctx: RequestContext, + path_params: Path, + ) -> Result, HttpError>; } /// Perform extra validations on the OpenAPI spec. diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 740895b7e4..cb15f4d8fb 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -6298,4 +6298,169 @@ impl NexusExternalApi for NexusExternalApiImpl { ) -> Result, HttpError> { device_auth::device_access_token(rqctx, params.into_inner()).await } + + async fn webhook_view( + rqctx: RequestContext, + _path_params: Path, + ) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + + let opctx = + crate::context::op_context_for_external_api(&rqctx).await?; + + Err(nexus + .unimplemented_todo(&opctx, crate::app::Unimpl::Public) + .await + .into()) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn webhook_create( + rqctx: RequestContext, + _params: TypedBody, + ) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + + let opctx = + crate::context::op_context_for_external_api(&rqctx).await?; + + Err(nexus + .unimplemented_todo(&opctx, crate::app::Unimpl::Public) + .await + .into()) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn webhook_delete( + rqctx: RequestContext, + _path_params: Path, + ) -> Result { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + + let opctx = + crate::context::op_context_for_external_api(&rqctx).await?; + + Err(nexus + .unimplemented_todo(&opctx, crate::app::Unimpl::Public) + .await + .into()) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn webhook_secrets_list( + rqctx: RequestContext, + _path_params: Path, + ) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + + let opctx = + crate::context::op_context_for_external_api(&rqctx).await?; + + Err(nexus + .unimplemented_todo(&opctx, crate::app::Unimpl::Public) + .await + .into()) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + /// Add a secret to a webhook. + async fn webhook_secrets_add( + rqctx: RequestContext, + _path_params: Path, + _params: TypedBody, + ) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + + let opctx = + crate::context::op_context_for_external_api(&rqctx).await?; + + Err(nexus + .unimplemented_todo(&opctx, crate::app::Unimpl::Public) + .await + .into()) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn webhook_delivery_list( + rqctx: RequestContext, + _path_params: Path, + _query_params: Query, + ) -> Result>, HttpError> + { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + + let opctx = + crate::context::op_context_for_external_api(&rqctx).await?; + + Err(nexus + .unimplemented_todo(&opctx, crate::app::Unimpl::Public) + .await + .into()) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn webhook_delivery_resend( + rqctx: RequestContext, + _path_params: Path, + ) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + + let opctx = + crate::context::op_context_for_external_api(&rqctx).await?; + + Err(nexus + .unimplemented_todo(&opctx, crate::app::Unimpl::Public) + .await + .into()) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } } diff --git a/nexus/types/Cargo.toml b/nexus/types/Cargo.toml index 8990b0b83b..4455c7b842 100644 --- a/nexus/types/Cargo.toml +++ b/nexus/types/Cargo.toml @@ -29,7 +29,7 @@ openssl.workspace = true oxql-types.workspace = true oxnet.workspace = true parse-display.workspace = true -schemars = { workspace = true, features = ["chrono", "uuid1"] } +schemars = { workspace = true, features = ["chrono", "uuid1", "url"] } serde.workspace = true serde_json.workspace = true serde_with.workspace = true @@ -41,6 +41,7 @@ thiserror.workspace = true newtype-uuid.workspace = true update-engine.workspace = true uuid.workspace = true +url = { workspace = true, features = ["serde"] } api_identity.workspace = true gateway-client.workspace = true diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index 9b4c2474ad..53650ac875 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -28,6 +28,7 @@ use std::collections::BTreeMap; use std::collections::BTreeSet; use std::collections::HashMap; use std::{net::IpAddr, str::FromStr}; +use url::Url; use uuid::Uuid; macro_rules! path_param { @@ -91,6 +92,7 @@ path_param!(ProbePath, probe, "probe"); path_param!(CertificatePath, certificate, "certificate"); id_path_param!(GroupPath, group_id, "group"); +id_path_param!(WebhookPath, webhook_id, "webhook"); // TODO: The hardware resources should be represented by its UUID or a hardware // ID that can be used to deterministically generate the UUID. @@ -2277,3 +2279,24 @@ pub struct DeviceAccessTokenRequest { pub device_code: String, pub client_id: Uuid, } + +// Webhooks + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct WebhookCreate { + pub name: String, + pub endpoint: Url, + pub secrets: Vec, + pub events: Vec, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct WebhookSecret { + pub secret: String, +} + +#[derive(Deserialize, JsonSchema)] +pub struct WebhookDeliveryPath { + pub webhook_id: Uuid, + pub delivery_id: Uuid, +} diff --git a/nexus/types/src/external_api/views.rs b/nexus/types/src/external_api/views.rs index 0fd45c0666..59888a4e9b 100644 --- a/nexus/types/src/external_api/views.rs +++ b/nexus/types/src/external_api/views.rs @@ -16,6 +16,7 @@ use omicron_common::api::external::{ IdentityMetadata, InstanceState, Name, ObjectIdentity, RoleName, SimpleIdentity, }; +use omicron_uuid_kinds::{EventUuid, WebhookUuid}; use oxnet::{Ipv4Net, Ipv6Net}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -24,6 +25,7 @@ use std::collections::BTreeSet; use std::fmt; use std::net::IpAddr; use strum::{EnumIter, IntoEnumIterator}; +use url::Url; use uuid::Uuid; use super::params::PhysicalDiskKind; @@ -1025,3 +1027,96 @@ pub struct OxqlQueryResult { /// Tables resulting from the query, each containing timeseries. pub tables: Vec, } + +// WEBHOOKS + +/// The configuration for a webhook. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct Webhook { + pub id: WebhookUuid, + pub name: String, + pub endpoint: Url, + pub secrets: Vec, + // XXX(eliza): should eventually be an enum? + pub events: Vec, + // TODO(eliza): roles? +} + +/// A list of the IDs of secrets associated with a webhook. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct WebhookSecrets { + pub secrets: Vec, +} + +/// The public ID of a secret key assigned to a webhook. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct WebhookSecretId { + pub id: String, +} + +/// A delivery attempt for a webhook event. + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct WebhookDelivery { + /// The UUID of this delivery attempt. + pub id: Uuid, + + /// The UUID of the webhook receiver that this event was delivered to. + pub webhook_id: WebhookUuid, + + /// The event class. + pub event: String, + + /// The UUID of the event. + pub event_id: EventUuid, + + /// The state of the delivery attempt. + pub state: WebhookDeliveryState, + + /// The time at which the webhook delivery was attempted, or `null` if + /// webhook delivery has not yet been attempted (`state` is "pending"). + pub time_sent: Option>, + + /// Describes the response returned by the receiver endpoint. + /// + /// This is present if the webhook has been delivered successfully, or if the + /// endpoint returned an HTTP error (`state` is "delivered" or + /// "failed_http_error"). This is `null` if the webhook has not yet been + /// delivered, or if the endpoint was unreachable (`state` is "pending" or + /// "failed_unreachable"). + pub response: Option, + + /// The UUID of a previous delivery attempt that this is a repeat of, if + /// this was a resending of a previous delivery. If this is the first time + /// this event has been delivered, this is `null`. + pub resent_for: Option, +} + +/// The state of a webhook delivery attempt. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum WebhookDeliveryState { + /// The webhook event has not yet been delivered. + Pending, + /// The webhook event has been delivered successfully. + Delivered, + /// A webhook request was sent to the endpoint, and it + /// returned a HTTP error status code indicating an error. + FailedHttpError, + /// The webhook request could not be sent to the receiver endpoint. + FailedUnreachable, +} + +/// The response received from a webhook receiver endpoint. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct WebhookDeliveryResponse { + /// The HTTP status code returned from the webhook endpoint. + pub status: u16, + /// The response time of the webhook endpoint, in milliseconds. + pub duration_ms: usize, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct WebhookDeliveryId { + pub delivery_id: Uuid, +} diff --git a/openapi/nexus.json b/openapi/nexus.json index c0b6a96fcf..6f8ba8b2a0 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -301,6 +301,314 @@ } } }, + "/experimental/v1/webhooks": { + "post": { + "tags": [ + "system/webhooks" + ], + "summary": "Create a new webhook receiver.", + "operationId": "webhook_create", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookCreate" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Webhook" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/experimental/v1/webhooks/{webhook_id}": { + "get": { + "tags": [ + "system/webhooks" + ], + "summary": "Get the configuration for a webhook.", + "operationId": "webhook_view", + "parameters": [ + { + "in": "path", + "name": "webhook_id", + "description": "ID of the webhook", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Webhook" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "tags": [ + "system/webhooks" + ], + "summary": "Delete a webhook receiver.", + "operationId": "webhook_delete", + "parameters": [ + { + "in": "path", + "name": "webhook_id", + "description": "ID of the webhook", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/experimental/v1/webhooks/{webhook_id}/deliveries": { + "get": { + "tags": [ + "system/webhooks" + ], + "summary": "List delivery attempts to a webhook receiver.", + "operationId": "webhook_delivery_list", + "parameters": [ + { + "in": "path", + "name": "webhook_id", + "description": "ID of the webhook", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + }, + { + "in": "query", + "name": "sort_by", + "schema": { + "$ref": "#/components/schemas/IdSortMode" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookDeliveryResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + } + }, + "/experimental/v1/webhooks/{webhook_id}/deliveries/{delivery_id}/resend": { + "post": { + "tags": [ + "system/webhooks" + ], + "summary": "Request re-delivery of a webhook event.", + "operationId": "webhook_delivery_resend", + "parameters": [ + { + "in": "path", + "name": "delivery_id", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "in": "path", + "name": "webhook_id", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookDeliveryId" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/experimental/v1/webhooks/{webhook_id}/secrets": { + "get": { + "tags": [ + "system/webhooks" + ], + "summary": "List the IDs of secrets for a webhook receiver.", + "operationId": "webhook_secrets_list", + "parameters": [ + { + "in": "path", + "name": "webhook_id", + "description": "ID of the webhook", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookSecrets" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "post": { + "tags": [ + "system/webhooks" + ], + "summary": "Add a secret to a webhook.", + "operationId": "webhook_secrets_add", + "parameters": [ + { + "in": "path", + "name": "webhook_id", + "description": "ID of the webhook", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookSecret" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookSecretId" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/login/{silo_name}/saml/{provider_name}": { "post": { "tags": [ @@ -21073,6 +21381,14 @@ } } }, + "TypedUuidForEventKind": { + "type": "string", + "format": "uuid" + }, + "TypedUuidForWebhookKind": { + "type": "string", + "format": "uuid" + }, "UninitializedSled": { "description": "A sled that has not been added to an initialized rack yet", "type": "object", @@ -22483,6 +22799,264 @@ } } }, + "Webhook": { + "description": "The configuration for a webhook.", + "type": "object", + "properties": { + "endpoint": { + "type": "string", + "format": "uri" + }, + "events": { + "type": "array", + "items": { + "type": "string" + } + }, + "id": { + "$ref": "#/components/schemas/TypedUuidForWebhookKind" + }, + "name": { + "type": "string" + }, + "secrets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WebhookSecretId" + } + } + }, + "required": [ + "endpoint", + "events", + "id", + "name", + "secrets" + ] + }, + "WebhookCreate": { + "type": "object", + "properties": { + "endpoint": { + "type": "string", + "format": "uri" + }, + "events": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string" + }, + "secrets": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "endpoint", + "events", + "name", + "secrets" + ] + }, + "WebhookDelivery": { + "description": "A delivery attempt for a webhook event.", + "type": "object", + "properties": { + "event": { + "description": "The event class.", + "type": "string" + }, + "event_id": { + "description": "The UUID of the event.", + "allOf": [ + { + "$ref": "#/components/schemas/TypedUuidForEventKind" + } + ] + }, + "id": { + "description": "The UUID of this delivery attempt.", + "type": "string", + "format": "uuid" + }, + "resent_for": { + "nullable": true, + "description": "The UUID of a previous delivery attempt that this is a repeat of, if this was a resending of a previous delivery. If this is the first time this event has been delivered, this is `null`.", + "type": "string", + "format": "uuid" + }, + "response": { + "nullable": true, + "description": "Describes the response returned by the receiver endpoint.\n\nThis is present if the webhook has been delivered successfully, or if the endpoint returned an HTTP error (`state` is \"delivered\" or \"failed_http_error\"). This is `null` if the webhook has not yet been delivered, or if the endpoint was unreachable (`state` is \"pending\" or \"failed_unreachable\").", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookDeliveryResponse" + } + ] + }, + "state": { + "description": "The state of the delivery attempt.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookDeliveryState" + } + ] + }, + "time_sent": { + "nullable": true, + "description": "The time at which the webhook delivery was attempted, or `null` if webhook delivery has not yet been attempted (`state` is \"pending\").", + "type": "string", + "format": "date-time" + }, + "webhook_id": { + "description": "The UUID of the webhook receiver that this event was delivered to.", + "allOf": [ + { + "$ref": "#/components/schemas/TypedUuidForWebhookKind" + } + ] + } + }, + "required": [ + "event", + "event_id", + "id", + "state", + "webhook_id" + ] + }, + "WebhookDeliveryId": { + "type": "object", + "properties": { + "delivery_id": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "delivery_id" + ] + }, + "WebhookDeliveryResponse": { + "description": "The response received from a webhook receiver endpoint.", + "type": "object", + "properties": { + "duration_ms": { + "description": "The response time of the webhook endpoint, in milliseconds.", + "type": "integer", + "format": "uint", + "minimum": 0 + }, + "status": { + "description": "The HTTP status code returned from the webhook endpoint.", + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + "required": [ + "duration_ms", + "status" + ] + }, + "WebhookDeliveryResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/WebhookDelivery" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "WebhookDeliveryState": { + "description": "The state of a webhook delivery attempt.", + "oneOf": [ + { + "description": "The webhook event has not yet been delivered.", + "type": "string", + "enum": [ + "pending" + ] + }, + { + "description": "The webhook event has been delivered successfully.", + "type": "string", + "enum": [ + "delivered" + ] + }, + { + "description": "A webhook request was sent to the endpoint, and it returned a HTTP error status code indicating an error.", + "type": "string", + "enum": [ + "failed_http_error" + ] + }, + { + "description": "The webhook request could not be sent to the receiver endpoint.", + "type": "string", + "enum": [ + "failed_unreachable" + ] + } + ] + }, + "WebhookSecret": { + "type": "object", + "properties": { + "secret": { + "type": "string" + } + }, + "required": [ + "secret" + ] + }, + "WebhookSecretId": { + "description": "The public ID of a secret key assigned to a webhook.", + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ] + }, + "WebhookSecrets": { + "description": "A list of the IDs of secrets associated with a webhook.", + "type": "object", + "properties": { + "secrets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WebhookSecretId" + } + } + }, + "required": [ + "secrets" + ] + }, "NameOrIdSortMode": { "description": "Supported set of sort modes for scanning by name or id", "oneOf": [ @@ -22509,6 +23083,18 @@ } ] }, + "IdSortMode": { + "description": "Supported set of sort modes for scanning by id only.\n\nCurrently, we only support scanning in ascending order.", + "oneOf": [ + { + "description": "sort in increasing order of \"id\"", + "type": "string", + "enum": [ + "id_ascending" + ] + } + ] + }, "DiskMetricName": { "type": "string", "enum": [ @@ -22528,18 +23114,6 @@ "descending" ] }, - "IdSortMode": { - "description": "Supported set of sort modes for scanning by id only.\n\nCurrently, we only support scanning in ascending order.", - "oneOf": [ - { - "description": "sort in increasing order of \"id\"", - "type": "string", - "enum": [ - "id_ascending" - ] - } - ] - }, "SystemMetricName": { "type": "string", "enum": [ @@ -22718,6 +23292,13 @@ { "name": "system/update" }, + { + "name": "system/webhooks", + "description": "Webhooks deliver notifications for audit log events and fault management alerts.", + "externalDocs": { + "url": "http://docs.oxide.computer/api/webhooks" + } + }, { "name": "vpcs", "description": "Virtual Private Clouds (VPCs) provide isolated network environments for managing and deploying services.", diff --git a/uuid-kinds/src/lib.rs b/uuid-kinds/src/lib.rs index 42c50379ce..d2e448c9c2 100644 --- a/uuid-kinds/src/lib.rs +++ b/uuid-kinds/src/lib.rs @@ -55,6 +55,7 @@ impl_typed_uuid_kind! { DemoSaga => "demo_saga", Downstairs => "downstairs", DownstairsRegion => "downstairs_region", + Event => "event", ExternalIp => "external_ip", Instance => "instance", LoopbackAddress => "loopback_address", @@ -72,5 +73,6 @@ impl_typed_uuid_kind! { UpstairsRepair => "upstairs_repair", UpstairsSession => "upstairs_session", Vnic => "vnic", + Webhook => "webhook", Zpool => "zpool", }