Implemented node crud and status health check. Added daemon status health endpoint. Refactored project structure. Added sidebar items and ui views
This commit is contained in:
8
MoonlightServers.Daemon/Configuration/RemoteOptions.cs
Normal file
8
MoonlightServers.Daemon/Configuration/RemoteOptions.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace MoonlightServers.Daemon.Configuration;
|
||||
|
||||
public class RemoteOptions
|
||||
{
|
||||
public string EndpointUrl { get; set; }
|
||||
public string TokenId { get; set; }
|
||||
public string Token { get; set; }
|
||||
}
|
||||
30
MoonlightServers.Daemon/Http/Controllers/HealthController.cs
Normal file
30
MoonlightServers.Daemon/Http/Controllers/HealthController.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MoonlightServers.Daemon.Services;
|
||||
using MoonlightServers.DaemonShared.Http.Daemon;
|
||||
|
||||
namespace MoonlightServers.Daemon.Http.Controllers;
|
||||
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("api/health")]
|
||||
public class HealthController : Controller
|
||||
{
|
||||
private readonly RemoteService RemoteService;
|
||||
|
||||
public HealthController(RemoteService remoteService)
|
||||
{
|
||||
RemoteService = remoteService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<HealthDto>> GetAsync()
|
||||
{
|
||||
var remoteStatusCode = await RemoteService.CheckReachabilityAsync();
|
||||
|
||||
return new HealthDto()
|
||||
{
|
||||
RemoteStatusCode = remoteStatusCode
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.Security.Claims;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace MoonlightServers.Daemon.Implementations.TokenScheme;
|
||||
|
||||
public class TokenSchemeHandler : AuthenticationHandler<TokenSchemeOptions>
|
||||
{
|
||||
public const string SchemeName = "MoonlightServers.Token";
|
||||
|
||||
public TokenSchemeHandler(
|
||||
IOptionsMonitor<TokenSchemeOptions> options,
|
||||
ILoggerFactory logger,
|
||||
UrlEncoder encoder
|
||||
) : base(options, logger, encoder)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
if (!Context.Request.Headers.TryGetValue(HeaderNames.Authorization, out var authHeaderValues))
|
||||
return Task.FromResult(AuthenticateResult.Fail("No authorization header present"));
|
||||
|
||||
if (authHeaderValues.Count != 1)
|
||||
return Task.FromResult(AuthenticateResult.Fail("No authorization value present"));
|
||||
|
||||
var authHeaderValue = authHeaderValues[0];
|
||||
|
||||
if (string.IsNullOrEmpty(authHeaderValue))
|
||||
return Task.FromResult(AuthenticateResult.Fail("No authorization value present"));
|
||||
|
||||
if (authHeaderValue != Options.Token)
|
||||
return Task.FromResult(AuthenticateResult.Fail("Invalid token provided"));
|
||||
|
||||
return Task.FromResult(
|
||||
AuthenticateResult.Success(new AuthenticationTicket(
|
||||
new ClaimsPrincipal(new ClaimsIdentity([], nameof(TokenSchemeHandler))),
|
||||
nameof(TokenSchemeHandler)
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
|
||||
namespace MoonlightServers.Daemon.Implementations.TokenScheme;
|
||||
|
||||
public class TokenSchemeOptions : AuthenticationSchemeOptions
|
||||
{
|
||||
public string Token { get; set; }
|
||||
}
|
||||
@@ -28,4 +28,8 @@
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MoonlightServers.DaemonShared\MoonlightServers.DaemonShared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using Microsoft.Extensions.Logging.Console;
|
||||
using MoonlightServers.Daemon.Configuration;
|
||||
using MoonlightServers.Daemon.Helpers;
|
||||
using MoonlightServers.Daemon.Implementations.TokenScheme;
|
||||
using MoonlightServers.Daemon.ServerSystem;
|
||||
using MoonlightServers.Daemon.ServerSystem.Implementations.Docker;
|
||||
using MoonlightServers.Daemon.ServerSystem.Implementations.Local;
|
||||
using MoonlightServers.Daemon.Services;
|
||||
using MoonlightServers.DaemonShared.Http;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@@ -18,7 +21,28 @@ builder.Services.AddSingleton<ServerService>();
|
||||
builder.Services.AddDockerServices();
|
||||
builder.Services.AddLocalServices();
|
||||
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddHttpClient();
|
||||
builder.Services.AddSingleton<RemoteService>();
|
||||
|
||||
builder.Services.AddOptions<RemoteOptions>().BindConfiguration("Moonlight:Remote");
|
||||
|
||||
var remoteOptions = new RemoteOptions();
|
||||
builder.Configuration.Bind("Moonlight:Remote", remoteOptions);
|
||||
|
||||
builder.Services.AddControllers()
|
||||
.AddApplicationPart(typeof(Program).Assembly)
|
||||
.AddJsonOptions(options =>
|
||||
{
|
||||
options.JsonSerializerOptions.TypeInfoResolverChain.Add(SerializationContext.Default);
|
||||
});
|
||||
|
||||
builder.Services.AddAuthentication(TokenSchemeHandler.SchemeName)
|
||||
.AddScheme<TokenSchemeOptions, TokenSchemeHandler>(TokenSchemeHandler.SchemeName, options =>
|
||||
{
|
||||
options.Token = remoteOptions!.Token;
|
||||
});
|
||||
|
||||
builder.Logging.AddFilter("MoonlightServers.Daemon.Implementations.TokenScheme.TokenSchemeHandler", LogLevel.Warning);
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
@@ -50,8 +74,6 @@ Task.Run(async () =>
|
||||
await server.StopAsync();
|
||||
|
||||
Console.ReadLine();
|
||||
|
||||
await serverService.DeleteAsync("a0e3ddb4-2c72-4f4c-bc49-35650a4bc5c0");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
117
MoonlightServers.Daemon/Services/RemoteService.cs
Normal file
117
MoonlightServers.Daemon/Services/RemoteService.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using MoonlightServers.Daemon.Configuration;
|
||||
using MoonlightServers.DaemonShared.Http;
|
||||
|
||||
namespace MoonlightServers.Daemon.Services;
|
||||
|
||||
public class RemoteService
|
||||
{
|
||||
private readonly IOptions<RemoteOptions> Options;
|
||||
private readonly ILogger<RemoteService> Logger;
|
||||
private readonly IHttpClientFactory ClientFactory;
|
||||
|
||||
public RemoteService(
|
||||
IOptions<RemoteOptions> options,
|
||||
IHttpClientFactory clientFactory,
|
||||
ILogger<RemoteService> logger
|
||||
)
|
||||
{
|
||||
Options = options;
|
||||
ClientFactory = clientFactory;
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
public async Task<int> CheckReachabilityAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var client = ClientFactory.CreateClient();
|
||||
|
||||
var request = CreateBaseRequest(HttpMethod.Get, "api/remote/servers/nodes/ping");
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
return (int)response.StatusCode;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogTrace(e, "An error occured while checking if remote is reachable");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private HttpRequestMessage CreateBaseRequest(
|
||||
[StringSyntax(StringSyntaxAttribute.Uri)]
|
||||
HttpMethod method,
|
||||
string endpoint
|
||||
)
|
||||
{
|
||||
var request = new HttpRequestMessage();
|
||||
|
||||
request.Headers.Add(HeaderNames.Authorization, $"{Options.Value.TokenId} {Options.Value.Token}");
|
||||
request.RequestUri = new Uri(new Uri(Options.Value.EndpointUrl), endpoint);
|
||||
request.Method = method;
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private async Task EnsureSuccessAsync(HttpResponseMessage message)
|
||||
{
|
||||
if (message.IsSuccessStatusCode)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var problemDetails = await message.Content.ReadFromJsonAsync<ProblemDetails>(
|
||||
SerializationContext.Default.Options
|
||||
);
|
||||
|
||||
if (problemDetails == null)
|
||||
{
|
||||
// If we cant handle problem details, we handle it natively
|
||||
message.EnsureSuccessStatusCode();
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse into exception
|
||||
throw new RemoteException(
|
||||
problemDetails.Type,
|
||||
problemDetails.Title,
|
||||
problemDetails.Status,
|
||||
problemDetails.Detail,
|
||||
problemDetails.Errors
|
||||
);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
// If we cant handle problem details, we handle it natively
|
||||
message.EnsureSuccessStatusCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class RemoteException : Exception
|
||||
{
|
||||
public string Type { get; }
|
||||
public string Title { get; }
|
||||
public int Status { get; }
|
||||
public string? Detail { get; }
|
||||
public Dictionary<string, string[]>? Errors { get; }
|
||||
|
||||
public RemoteException(
|
||||
string type,
|
||||
string title,
|
||||
int status,
|
||||
string? detail = null,
|
||||
Dictionary<string, string[]>? errors = null)
|
||||
: base(detail ?? title)
|
||||
{
|
||||
Type = type;
|
||||
Title = title;
|
||||
Status = status;
|
||||
Detail = detail;
|
||||
Errors = errors;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user