Skip to content

Repository files navigation

Incident Clients Gradle Check

Implementation of incident clients for various notification systems.

Supported Clients

Client Module Java Module
PagerDuty V2 Events API incident-pagerduty software.sava.incident_pagerduty
incident.io Incidents V2 incident-io software.sava.incident_io
Generic Webhook / Slack / Telegram incident-webhook software.sava.incident_webhook

All depend on incident-core (software.sava.incident_core), which provides the provider-neutral IncidentClient API described below.

Usage

PagerDuty

Full example

final var client = PagerDutyEventClient.clientBuilder()
    .defaultClientName("CLIENT_NAME")
    .defaultRoutingKey("INTEGRATION_KEY")
    .authToken("AUTH_TOKEN")
    .createClient();

final var payload = PagerDutyEventPayload.build()
    .summary("ex-summary")
    .source("ex-source")
    .severity(PagerDutySeverity.critical)
    .timestamp(ZonedDateTime.now(UTC))
    .component("ex-component")
    .group("ex-group")
    .eventClass("ex-class")
    .customDetails("ex-num-metric", 1)
    .customDetails("ex-boolean", true)
    .customDetails("ex-string", "val")
    .link(PagerDutyLinkRef.build()
        .href("https://github.com/sava-software/incident-client")
        .text("Sava Incident PagerDuty Event Client")
        .create())
    .image(PagerDutyImageRef.build()
        .src("https://www.pagerduty.com/wp-content/uploads/2016/05/pagerduty-logo-green.png")
        .href("https://www.pagerduty.com/")
        .alt("pagerduty")
        .create())
    .create();

final var triggerResponseFuture = client.triggerDefaultRouteEvent(payload);

final var changeEventPayload = PagerDutyChangeEventPayload.build(payload).create();
final var changeEventResponseFuture = client.defaultRouteChangeEvent(changeEventPayload);

final var triggerResponse = triggerResponseFuture.join();

final var ackResponse = client.acknowledgeEvent(triggerResponse.dedupKey()).join();

final var resolveResponse = client.resolveEvent(triggerResponse.dedupKey()).join();

final var changeEventResponse = changeEventResponseFuture.join();

incident.io

Full example

idempotencyKey and visibility are required by the API; a request without them is rejected. Severities, incident types, statuses, roles, and custom fields are all workspace-specific ids — look them up in your incident.io workspace.

try (final var httpClient = HttpClient.newHttpClient()) {
  final var client = IncidentIoClient.clientBuilder()
      .bearerToken("BEARER_TOKEN")
      .httpClient(httpClient)
      .createClient();

  final var request = CreateIncidentRequest.requestBuilder()
      .idempotencyKey(UUID.randomUUID().toString())
      .name("Test Incident")
      .summary("Test Java client")
      .visibility(CreateIncidentRequest.Visibility.PRIVATE)
      .mode(CreateIncidentRequest.Mode.test)
      .severityId("SEVERITY_ID")
      .incidentTypeId("INCIDENT_TYPE_ID")
      .incidentRoleAssignments(List.of(new CreateIncidentRequest.IncidentRoleAssignment(
          "INCIDENT_ROLE_ID",
          CreateIncidentRequest.UserReference.byEmail("responder@example.com"))))
      .customFieldValues(Map.of("CUSTOM_FIELD_ID", "value"))
      .build();

  final var response = client.createIncident(request).join();

  System.out.println(response.reference());
  System.out.println(response.permalink());
}

customFieldValues is sugar for text custom fields; select, catalog, link, and numeric fields use customFieldEntries with a matching CustomFieldValue — see the module README. slackChannelNameOverride, incidentTimestampValues, and retrospectiveIncidentOptions round out IncidentsCreatePayloadV2.

Generic Webhook / Slack / Telegram

Full example

incident-webhook POSTs a structured message to a single configured webhook endpoint — fire-and-forget notification, not incident management. WebhookFormats.GENERIC_JSON (provider id webhook) sends a canonical JSON document of the alert for receivers that do their own mapping; WebhookFormats.SLACK_TEXT (provider id slack) sends a plain {"text":"..."} Slack incoming-webhook message; TelegramTextFormat (provider id telegram) sends a Bot API sendMessage body to https://api.telegram.org/bot<TOKEN>/sendMessage with a configured chatId.

final var client = WebhookClient.clientBuilder()
    .endpoint("https://hooks.slack.com/services/T000/B000/XXXX")
    .createClient();

final IncidentClient incidentClient = client.incidentClient(WebhookFormats.SLACK_TEXT);

Provider-Neutral Usage

Service-level code can be written against IncidentClient and switch providers via configuration. IncidentClients creates a client from configuration alone — provider modules on the module or class path register themselves via ServiceLoader, and the provider config value selects one:

incident.provider=pagerduty
incident.routingKey=INTEGRATION_KEY
incident.provider=incident.io
incident.bearerToken=BEARER_TOKEN
incident.visibility=private
incident.severityIds.CRITICAL=SEVERITY_ID
final IncidentClient incidentClient = IncidentClients.createClient(properties, "incident");

The JSON equivalent wraps the provider's config object, with provider first:

{"provider": "pagerduty", "config": {"routingKey": "INTEGRATION_KEY"}}

Adapters can also be created in code from a native client:

final IncidentClient incidentClient = usePagerDuty
    ? pagerDutyEventClient.asIncidentClient()
    : incidentIoClient.incidentClientBuilder()
        .visibility(CreateIncidentRequest.Visibility.PRIVATE)
        .severityId(IncidentSeverity.CRITICAL, "SEVERITY_ID")
        .createClient();

final var alert = IncidentAlert.build()
    .summary("Validator missed its leader slot")
    .details("No block produced for slot 350000000.")
    .severity(IncidentSeverity.CRITICAL)
    .source("validator-01.example.com")
    .timestamp(ZonedDateTime.now(UTC))
    .customDetail("slot", 350000000L)
    .create();

final var response = incidentClient.reportIncident(alert).join();

if (incidentClient.supportsResolve()) {
  incidentClient.resolveIncident(response.key()).join();
}

The incident.io adapter maps IncidentSeverity values onto workspace severity ids supplied at build time, and has no programmatic resolve: supportsResolve() returns false and resolveIncident(String) fails the returned future with an UnsupportedOperationException. It also needs an incidentTimestampId before it can send IncidentAlert#timestamp() — incident.io timestamps are workspace ids — and carries source() and customDetails() as text appended to the incident summary, since incident.io custom fields are workspace schema objects rather than an arbitrary key/value map. Provider-specific features — PagerDuty links, images, and change events; incident.io custom fields and role assignments — remain on the provider clients.

About

Clients for triggering incidents, e.g., Pagerduty

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages