-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Clean up and reorganize some of the fields sent in the alert. When tr…
…iggering an alert, use the same dedupkey that from other checks with the same PagerDuty integration key, even if it's a different check. Added automated tests to verify JSON parsing of poorly-documented webhook payload from Freshping
- Loading branch information
Showing
15 changed files
with
502 additions
and
127 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> | ||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Freshping/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> |
3 changes: 2 additions & 1 deletion
3
FreshPager/Configuration.cs → FreshPager/Data/Configuration.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,8 @@ | ||
namespace FreshPager; | ||
namespace FreshPager.Data; | ||
|
||
public class Configuration { | ||
|
||
public IDictionary<string, string> pagerDutyIntegrationKeysByService { get; } = new Dictionary<string, string>(); | ||
public ushort httpServerPort { get; set; } = 37374; | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
using System.Text.Json; | ||
using System.Text.Json.Serialization; | ||
|
||
namespace FreshPager.Data.Marshal; | ||
|
||
public class StringToOptionalIntConverter: JsonConverter<int?> { | ||
|
||
public override int? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { | ||
return int.TryParse(reader.GetString(), out int number) ? number : null; | ||
} | ||
|
||
public override void Write(Utf8JsonWriter writer, int? value, JsonSerializerOptions options) => throw new NotImplementedException(); | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
using System.Text.Json; | ||
using System.Text.Json.Serialization; | ||
|
||
namespace FreshPager.Data.Marshal; | ||
|
||
public abstract class StringToTimespanConverter: JsonConverter<TimeSpan> { | ||
|
||
public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => | ||
reader.GetString() is { } rawString ? intToTimeSpan(double.Parse(rawString)) : default; | ||
|
||
protected abstract TimeSpan intToTimeSpan(double number); | ||
|
||
public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options) => throw new NotImplementedException(); | ||
|
||
public class FromSeconds: StringToTimespanConverter { | ||
|
||
protected override TimeSpan intToTimeSpan(double number) => TimeSpan.FromSeconds(number); | ||
|
||
} | ||
|
||
public class FromMilliseconds: StringToTimespanConverter { | ||
|
||
protected override TimeSpan intToTimeSpan(double number) => TimeSpan.FromMilliseconds(number); | ||
|
||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
using FreshPager.Data.Marshal; | ||
using System.Text.Json.Serialization; | ||
|
||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. | ||
|
||
namespace FreshPager.Data; | ||
|
||
public class WebhookPayload { | ||
|
||
/// <summary> | ||
/// <para>The title/subject/summary of the event</para> | ||
/// <para>Examples:</para> | ||
/// <para>Aldaviva HTTP (https://aldaviva.com) is DOWN.</para> | ||
/// <para>Aldaviva SMTP (tcp://aldaviva.com:25) is UP.</para> | ||
/// </summary> | ||
[JsonPropertyName("text")] | ||
public string eventTitle { get; set; } | ||
|
||
/// <summary> | ||
/// <para>Numeric ID of the check</para> | ||
/// <para>Examples:</para> | ||
/// <para>36897</para> | ||
/// <para>829684</para> | ||
/// </summary> | ||
[JsonPropertyName("check_id")] | ||
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] | ||
public int checkId { get; set; } | ||
|
||
/// <summary> | ||
/// <para>The friendly check name</para> | ||
/// <para>Examples:</para> | ||
/// <para>Aldaviva HTTP</para> | ||
/// <para>Aldaviva SMTP</para> | ||
/// </summary> | ||
[JsonPropertyName("check_name")] | ||
public string checkName { get; set; } | ||
|
||
/// <summary> | ||
/// <para>The URL that was hit to do the health check</para> | ||
/// <para>Examples:</para> | ||
/// <para>https://aldaviva.com</para> | ||
/// <para>tcp://aldaviva.com:25</para> | ||
/// </summary> | ||
[JsonPropertyName("check_url")] | ||
public Uri checkedUrl { get; set; } | ||
|
||
/// <summary> | ||
/// <para>How long the health check had been willing to wait for a response (not how long it actually waited)</para> | ||
/// <para>Example:</para> | ||
/// <para>0:30:00</para> | ||
/// </summary> | ||
[JsonPropertyName("request_timeout")] | ||
[JsonConverter(typeof(StringToTimespanConverter.FromSeconds))] | ||
public TimeSpan requestTimeout { get; set; } | ||
|
||
/// <summary> | ||
/// <para>Examples:</para> | ||
/// <para>US East (N. Virginia)</para> | ||
/// <para>Asia Pacific (Tokyo)</para> | ||
/// <para>EU (Ireland)</para> | ||
/// <para>Asia Pacific (Singapore)</para> | ||
/// <para>Canada (Central)</para> | ||
/// <para>Asia Pacific (Sydney)</para> | ||
/// <para>US West (Oregon)</para> | ||
/// <para>Asia Pacific (Mumbai)</para> | ||
/// <para>South America (Sao Paulo)</para> | ||
/// <para>EU (London)</para> | ||
/// </summary> | ||
[JsonPropertyName("request_location")] | ||
public string requestLocation { get; set; } | ||
|
||
/// <summary> | ||
/// <para>Example:</para> | ||
/// <para>2024-06-28T18:07:46.709971+00:00</para> | ||
/// </summary> | ||
[JsonPropertyName("request_datetime")] | ||
public DateTimeOffset requestDateTime { get; set; } | ||
|
||
/// <summary> | ||
/// <para>Examples:</para> | ||
/// <para><c>None</c> (check is down due to a socket/processing exception like a timeout)</para> | ||
/// <para><c>200</c> (HTTP check is up)</para> | ||
/// <para><c>1</c> (TCP check is up)</para> | ||
/// </summary> | ||
[JsonPropertyName("response_status_code")] | ||
[JsonConverter(typeof(StringToOptionalIntConverter))] | ||
public int? responseStatusCode { get; set; } | ||
|
||
/// <summary> | ||
/// <para>Examples:</para> | ||
/// <para>Connection Timeout</para> | ||
/// <para>Not Responding</para> | ||
/// <para>Available</para> | ||
/// </summary> | ||
[JsonPropertyName("response_summary")] | ||
public string responseSummary { get; set; } | ||
|
||
public bool isServiceUp => responseSummary == "Available"; | ||
|
||
/// <summary> | ||
/// <para>Examples:</para> | ||
/// <para>Not Responding</para> | ||
/// <para>Available</para> | ||
/// </summary> | ||
[JsonPropertyName("response_state")] | ||
public string responseState { get; set; } | ||
|
||
/// <summary> | ||
/// <para>How long it actually took for the health check to get a response.</para> | ||
/// <para>For the maximum time the check was willing to wait, see <see cref="requestTimeout"/>.</para> | ||
/// <para>Examples:</para> | ||
/// <para>30003</para> | ||
/// <para>17</para> | ||
/// </summary> | ||
[JsonPropertyName("response_time")] | ||
[JsonConverter(typeof(StringToTimespanConverter.FromMilliseconds))] | ||
public TimeSpan responseTime { get; set; } | ||
|
||
[JsonPropertyName("event_data")] | ||
[JsonInclude] | ||
private EventData eventData { get; set; } | ||
|
||
public string organizationSubdomain => eventData.organizationSubdomain; | ||
public DateTimeOffset eventCreationDateTime => eventData.eventCreationDateTime; | ||
public int eventId => eventData.eventId; | ||
public int organizationId => eventData.organizationId; | ||
public int webhookId => eventData.webhookId; | ||
public EventFilter eventFilter => eventData.eventFilter; | ||
|
||
public override string ToString() => | ||
$"{nameof(eventTitle)}: {eventTitle}, {nameof(checkId)}: {checkId}, {nameof(checkName)}: {checkName}, {nameof(checkedUrl)}: {checkedUrl}, {nameof(requestTimeout)}: {requestTimeout}, {nameof(requestLocation)}: {requestLocation}, {nameof(requestDateTime)}: {requestDateTime}, {nameof(responseStatusCode)}: {responseStatusCode}, {nameof(responseSummary)}: {responseSummary}, {nameof(isServiceUp)}: {isServiceUp}, {nameof(responseState)}: {responseState}, {nameof(responseTime)}: {responseTime}, {nameof(organizationSubdomain)}: {organizationSubdomain}, {nameof(eventCreationDateTime)}: {eventCreationDateTime}, {nameof(eventId)}: {eventId}, {nameof(organizationId)}: {organizationId}, {nameof(webhookId)}: {webhookId}, {nameof(eventFilter)}: {eventFilter}"; | ||
|
||
public class EventData { | ||
|
||
/// <summary> | ||
/// <para>The subdomain of the Freshping organization, also known as Freshping URL (not the Account Name) </para> | ||
/// <para>Examples:</para> | ||
/// <para>aldaviva</para> | ||
/// </summary> | ||
[JsonPropertyName("org_name")] | ||
public string organizationSubdomain { get; set; } | ||
|
||
/// <summary> | ||
/// <para>Example:</para> | ||
/// <para>2024-06-27T23:49:30.033405+00:00</para> | ||
/// </summary> | ||
[JsonPropertyName("event_created_on")] | ||
public DateTimeOffset eventCreationDateTime { get; set; } | ||
|
||
/// <summary> | ||
/// <para>Unique ID for each webhook message sent</para> | ||
/// <para>Examples:</para> | ||
/// <para>17960894</para> | ||
/// <para>17960760</para> | ||
/// </summary> | ||
[JsonPropertyName("event_id")] | ||
public int eventId { get; set; } | ||
|
||
/// <summary> | ||
/// <para>Example:</para> | ||
/// <para>10593</para> | ||
/// </summary> | ||
[JsonPropertyName("org_id")] | ||
public int organizationId { get; set; } | ||
|
||
/// <summary> | ||
/// <para>Examples:</para> | ||
/// <para><c>AL</c> (all events)</para> | ||
/// <para><c>AT</c> (up and down events)</para> | ||
/// <para><c>PE</c> (performance degraded events)</para> | ||
/// <para><c>PS</c> (paused and restarted events)</para> | ||
/// </summary> | ||
[JsonPropertyName("webhook_type")] | ||
[JsonInclude] | ||
private string eventFilterRaw { get; set; } | ||
|
||
/// <exception cref="ArgumentOutOfRangeException" accessor="get"></exception> | ||
public EventFilter eventFilter => eventFilterRaw switch { | ||
"AL" => EventFilter.ALL, | ||
"AT" => EventFilter.UP_DOWN, | ||
"PE" => EventFilter.DEGRADED_PERFORMANCE, | ||
"PS" => EventFilter.PAUSED_UNPAUSED, | ||
_ => throw new ArgumentOutOfRangeException(nameof(eventFilterRaw), eventFilterRaw, $"Unrecognized webhook_type value '{eventFilterRaw}'") | ||
}; | ||
|
||
/// <summary> | ||
/// <para>The unique ID of the webhook integration</para> | ||
/// <para>Example:</para> | ||
/// <para>35191</para> | ||
/// </summary> | ||
[JsonPropertyName("webhook_id")] | ||
public int webhookId { get; set; } | ||
|
||
} | ||
|
||
public enum EventFilter { | ||
|
||
ALL, | ||
UP_DOWN, | ||
DEGRADED_PERFORMANCE, | ||
PAUSED_UNPAUSED | ||
|
||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.