Skip to content
This repository has been archived by the owner on Apr 13, 2020. It is now read-only.

Commit

Permalink
Added StatusPage tracker
Browse files Browse the repository at this point in the history
  • Loading branch information
unaizorrilla committed Jul 3, 2018
1 parent ccbe008 commit 7fe5019
Show file tree
Hide file tree
Showing 14 changed files with 284 additions and 5 deletions.
9 changes: 8 additions & 1 deletion BeatPulse.sln
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{2C74B1B5
build\dependencies.props = build\dependencies.props
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BeatPulse.DynamoDb", "src\BeatPulse.DynamoDb\BeatPulse.DynamoDb.csproj", "{62F2F1D3-B8B5-40BC-BBCF-BE9C0F486325}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BeatPulse.DynamoDb", "src\BeatPulse.DynamoDb\BeatPulse.DynamoDb.csproj", "{62F2F1D3-B8B5-40BC-BBCF-BE9C0F486325}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BeatPulse.StatusPageTracker", "src\BeatPulse.StatusPageTracker\BeatPulse.StatusPageTracker.csproj", "{5FBB54B1-2EDF-4F9E-A3DA-D371022A78D9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -183,6 +185,10 @@ Global
{62F2F1D3-B8B5-40BC-BBCF-BE9C0F486325}.Debug|Any CPU.Build.0 = Debug|Any CPU
{62F2F1D3-B8B5-40BC-BBCF-BE9C0F486325}.Release|Any CPU.ActiveCfg = Release|Any CPU
{62F2F1D3-B8B5-40BC-BBCF-BE9C0F486325}.Release|Any CPU.Build.0 = Release|Any CPU
{5FBB54B1-2EDF-4F9E-A3DA-D371022A78D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5FBB54B1-2EDF-4F9E-A3DA-D371022A78D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5FBB54B1-2EDF-4F9E-A3DA-D371022A78D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5FBB54B1-2EDF-4F9E-A3DA-D371022A78D9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -213,6 +219,7 @@ Global
{AF940A45-3DAD-4297-A558-AAF0B226675D} = {7E4ADA32-6E78-4547-9E7A-8A8F7524D738}
{00A74F39-E0B5-444A-A55A-B721986E1939} = {7E4ADA32-6E78-4547-9E7A-8A8F7524D738}
{62F2F1D3-B8B5-40BC-BBCF-BE9C0F486325} = {1C706B20-C9D9-40ED-A6A8-5007DF1FD2BA}
{5FBB54B1-2EDF-4F9E-A3DA-D371022A78D9} = {1C706B20-C9D9-40ED-A6A8-5007DF1FD2BA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9B397864-402E-48A9-A8DC-0B98151CA4BE}
Expand Down
2 changes: 2 additions & 0 deletions build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ if ($suffix -eq "") {
exec { & dotnet pack .\src\BeatPulse.ApplicationInsightsTracker\BeatPulse.ApplicationInsightsTracker.csproj -c Release -o ..\..\artifacts --include-symbols --no-build }
exec { & dotnet pack .\src\BeatPulse.PrometheusTracker\BeatPulse.PrometheusTracker.csproj -c Release -o ..\..\artifacts --include-symbols --no-build }
exec { & dotnet pack .\src\BeatPulse.DynamoDb\BeatPulse.DynamoDb.csproj -c Release -o ..\..\artifacts --include-symbols --no-build }
exec { & dotnet pack .\src\BeatPulse.StatusPageTracker\BeatPulse.StatusPageTracker.csproj -c Release -o ..\..\artifacts --include-symbols --no-build }
} else {
exec { & dotnet pack .\src\BeatPulse\BeatPulse.csproj -c Release -o ..\..\artifacts --include-symbols --no-build --version-suffix=$suffix }
exec { & dotnet pack .\src\BeatPulse.SqlServer\BeatPulse.SqlServer.csproj -c Release -o ..\..\artifacts --include-symbols --no-build --version-suffix=$suffix }
Expand All @@ -99,5 +100,6 @@ if ($suffix -eq "") {
exec { & dotnet pack .\src\BeatPulse.ApplicationInsightsTracker\BeatPulse.ApplicationInsightsTracker.csproj -c Release -o ..\..\artifacts --include-symbols --no-build --version-suffix=$suffix }
exec { & dotnet pack .\src\BeatPulse.PrometheusTracker\BeatPulse.PrometheusTracker.csproj -c Release -o ..\..\artifacts --include-symbols --no-build --version-suffix=$suffix }
exec { & dotnet pack .\src\BeatPulse.DynamoDb\BeatPulse.DynamoDb.csproj -c Release -o ..\..\artifacts --include-symbols --no-build --version-suffix=$suffix }
exec { & dotnet pack .\src\BeatPulse.StatusPageTracker\BeatPulse.StatusPageTracker.csproj -c Release -o ..\..\artifacts --include-symbols --no-build --version-suffix=$suffix }
}

1 change: 1 addition & 0 deletions build/dependencies.props
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
<BeatPulseAITrackerVersion>2.1.0</BeatPulseAITrackerVersion>
<BeatPulsePrometheusTrackerVersion>2.1.0</BeatPulsePrometheusTrackerVersion>
<BeatPulseAWSDynamoDBPackageVersion>2.1.0</BeatPulseAWSDynamoDBPackageVersion>
<BeatPulseStatusPageTracker>2.1.0</BeatPulseStatusPageTracker>
</PropertyGroup>

<PropertyGroup Label="Package information">
Expand Down
1 change: 1 addition & 0 deletions samples/BeatPulseTrackers/BeatPulseTrackers.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<ItemGroup>
<ProjectReference Include="..\..\src\BeatPulse.ApplicationInsightsTracker\BeatPulse.ApplicationInsightsTracker.csproj" />
<ProjectReference Include="..\..\src\BeatPulse.PrometheusTracker\BeatPulse.PrometheusTracker.csproj" />
<ProjectReference Include="..\..\src\BeatPulse.StatusPageTracker\BeatPulse.StatusPageTracker.csproj" />
<ProjectReference Include="..\..\src\BeatPulse\BeatPulse.csproj" />
</ItemGroup>

Expand Down
21 changes: 17 additions & 4 deletions samples/BeatPulseTrackers/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,33 @@ public void ConfigureServices(IServiceCollection services)
opt.UsePath("catapi");
opt.UseLiveness(new ActionLiveness((httpContext, cancellationToken) =>
{
if (DateTime.Now.Minute == 20)
{
return Task.FromResult(("Service is down!", false));
}

return Task.FromResult(("OK", true));
}));
});

//
//add trackers
//
//
//add trackers
//

setup.AddApplicationInsightsTracker();
//setup.AddApplicationInsightsTracker();

//setup.AddPrometheusTracker(new Uri("http://localhost:9091"), new Dictionary<string, string>()
//{
// {"MachineName",Environment.MachineName}
//});

//setup.AddStatusPageTracker(opt =>
//{
// opt.PageId = "your-page-id";
// opt.ComponentId = "your-component-id";
// opt.ApiKey = "your-api.key";
// opt.IncidentName = "BeatPulse mark this component as outage";
//});
});

services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
Expand Down
19 changes: 19 additions & 0 deletions src/BeatPulse.StatusPageTracker/BeatPulse.StatusPageTracker.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>$(NetStandardTargetVersion)</TargetFramework>
<PackageLicenseUrl>$(PackageLicenseUrl)</PackageLicenseUrl>
<PackageProjectUrl>$(PackageProjectUrl)</PackageProjectUrl>
<PackageTags>BeatPulse;HealthCheck;Beat;Health;StatusPage;StatusPage IO;statuspage.ioTracker;Trackers;</PackageTags>
<Description>BeatPulse.StatusPage is a BeatPulseTracker for StatusPage.IO component status system.</Description>
<Version>$(BeatPulseStatusPageTracker)</Version>
<RepositoryUrl>$(RepositoryUrl)</RepositoryUrl>
<Company>$(Company)</Company>
<Authors>$(Authors)</Authors>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\BeatPulse\BeatPulse.csproj" />
</ItemGroup>

</Project>
19 changes: 19 additions & 0 deletions src/BeatPulse.StatusPageTracker/BeatPulseContextExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using BeatPulse.Core;
using BeatPulse.StatusPageTracker;
using System;

namespace BeatPulse
{
public static class BeatPulseContextExtensions
{
public static BeatPulseContext AddStatusPageTracker(this BeatPulseContext context,Action<StatusPageComponent> setup)
{
var component = new StatusPageComponent();
setup(component);

context.AddTracker(new StatusPageIOTracker(component));

return context;
}
}
}
22 changes: 22 additions & 0 deletions src/BeatPulse.StatusPageTracker/HttpClientExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Net.Http;
using System.Threading.Tasks;

namespace BeatPulse.StatusPageTracker
{
static class HttpClientExtensions
{
public static async Task<HttpResponseMessage> PatchAsync(this HttpClient client, string requestUri, HttpContent content)
{
var method = new HttpMethod("PATCH");

var request = new HttpRequestMessage(method, requestUri)
{
Content = content
};

var response = await client.SendAsync(request);

return response;
}
}
}
101 changes: 101 additions & 0 deletions src/BeatPulse.StatusPageTracker/StatusPageClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace BeatPulse.StatusPageTracker
{
class StatusPageClient
{

private readonly HttpClient _httpClient;
private readonly StatusPageComponent _statusPageComponent;

public StatusPageClient(StatusPageComponent statusPageComponent)
{
_statusPageComponent = statusPageComponent ?? throw new ArgumentNullException(nameof(statusPageComponent));

_httpClient = new HttpClient(
new StatusPageAuthorizationHandler(statusPageComponent.ApiKey))
{
BaseAddress = new Uri("https://api.statuspage.io/v1/")
};
}

public async Task CreateIncident(string failure)
{
var incidentId = await FindUnresolvedComponentIncident();

if (String.IsNullOrWhiteSpace(incidentId))
{
var incident = $"incident[name]={_statusPageComponent.IncidentName}&incident[status]=investigating&incident[body]={failure}&incident[component_ids][]={_statusPageComponent.ComponentId}&incident[components][{_statusPageComponent.ComponentId}]=major_outage";

var content = new StringContent(incident, Encoding.UTF8, "application/x-www-form-urlencoded");

await _httpClient.PostAsync($"pages/{_statusPageComponent.PageId}/incidents.json", content);
}
}

public async Task SolveIncident()
{
var incidentId = await FindUnresolvedComponentIncident();

if (!String.IsNullOrEmpty(incidentId))
{
var content = $"incident[status]=resolved&incident[components][{_statusPageComponent.ComponentId}]=operational";

await _httpClient.PatchAsync($"pages/{_statusPageComponent.PageId}/incidents/{incidentId}.json", new StringContent(content, Encoding.UTF8, "application/x-www-form-urlencoded"));
}
}

async Task<string> FindUnresolvedComponentIncident()
{
var response = await _httpClient.GetAsync($"pages/{_statusPageComponent.PageId}/incidents/unresolved.json");

var unresolved = JsonConvert.DeserializeObject<IEnumerable<WebStatusIncident>>(
await response.Content.ReadAsStringAsync());

if (unresolved.Any())
{
return unresolved.Where(ws => ws.Name.Equals(_statusPageComponent.IncidentName, StringComparison.InvariantCultureIgnoreCase))
.Select(ws => ws.Id)
.SingleOrDefault();
}

return null;
}


private class StatusPageAuthorizationHandler : DelegatingHandler
{
private readonly string _apiKey;

public StatusPageAuthorizationHandler(string apiKey)
{
_apiKey = apiKey ?? throw new ArgumentNullException(nameof(apiKey));
InnerHandler = new HttpClientHandler();
}

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers
.TryAddWithoutValidation("Authorization", $"OAuth {_apiKey}");

return await base.SendAsync(request, cancellationToken);
}
}

private class WebStatusIncident
{
public string Id { get; set; }

public string Name { get; set; }

public string Status { get; set; }
}
}
}
22 changes: 22 additions & 0 deletions src/BeatPulse.StatusPageTracker/StatusPageComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace BeatPulse.StatusPageTracker
{
public class StatusPageComponent
{
public string ApiKey { get; set; }

public string PageId { get; set; }

public string ComponentId { get; set; }

public string IncidentName { get; set; }

public StatusPageComponent()
{
ApiKey = "your-api-key-here";
PageId = "your-pageid-here";
ComponentId = "your-componentid-here";
IncidentName = "your-incidentname-here";

}
}
}
28 changes: 28 additions & 0 deletions src/BeatPulse.StatusPageTracker/StatusPageIOTracker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using BeatPulse.Core;
using System.Threading.Tasks;

namespace BeatPulse.StatusPageTracker
{
class StatusPageIOTracker
: IBeatPulseTracker
{
private readonly StatusPageClient _statusPageClient;

public StatusPageIOTracker(StatusPageComponent statusPageComponent)
{
_statusPageClient = new StatusPageClient(statusPageComponent);
}

public async Task Track(LivenessResult response)
{
if (response.IsHealthy)
{
await _statusPageClient.SolveIncident();
}
else
{
await _statusPageClient.CreateIncident(response.Message);
}
}
}
}
3 changes: 3 additions & 0 deletions src/BeatPulse.StatusPageTracker/__AssemblyOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("UnitTests")]
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using BeatPulse;
using BeatPulse.Core;
using BeatPulse.StatusPageTracker;
using FluentAssertions;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using System.Linq;
using UnitTests.Base;
using Xunit;

namespace UnitTests.BeatPulse.StatusPageTracker
{
public class beat_pulse_context_should
{
[Fact]
public void register_statuspage_tracker()
{
var webHostBuilder = new WebHostBuilder()
.UseBeatPulse()
.UseStartup<DefaultStartup>()
.ConfigureServices(svc =>
{
svc.AddBeatPulse(context =>
{
context.AddStatusPageTracker(setup => { });
});
});

var beatPulseContext = new TestServer(webHostBuilder)
.Host
.Services
.GetService<BeatPulseContext>();

beatPulseContext.GetAllTrackers()
.Where(hc => hc.GetType() == typeof(StatusPageIOTracker))
.Should().HaveCount(1);
}
}
}
1 change: 1 addition & 0 deletions tests/UnitTests/UnitTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<ProjectReference Include="..\..\src\BeatPulse.RabbitMQ\BeatPulse.RabbitMQ.csproj" />
<ProjectReference Include="..\..\src\BeatPulse.Redis\BeatPulse.Redis.csproj" />
<ProjectReference Include="..\..\src\BeatPulse.SqlServer\BeatPulse.SqlServer.csproj" />
<ProjectReference Include="..\..\src\BeatPulse.StatusPageTracker\BeatPulse.StatusPageTracker.csproj" />
<ProjectReference Include="..\..\src\BeatPulse.UI\BeatPulse.UI.csproj" />
<ProjectReference Include="..\..\src\BeatPulse\BeatPulse.csproj" />
</ItemGroup>
Expand Down

0 comments on commit 7fe5019

Please sign in to comment.