The enitre fucking administration controller

This commit is contained in:
Cyberboss
2018-07-25 12:07:24 -04:00
parent 7226ccc229
commit 830471f4b7
6 changed files with 211 additions and 5 deletions
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using Tgstation.Server.Api.Rights;
namespace Tgstation.Server.Api.Models
@@ -7,6 +6,12 @@ namespace Tgstation.Server.Api.Models
/// <inheritdoc />
public sealed class Administration
{
/// <summary>
/// The GitHub repository the server is built to recieve updates from
/// </summary>
[Permissions(DenyWrite = true)]
public Uri TrackedRepositoryUrl { get; set; }
/// <summary>
/// The latest available version of the Tgstation.Server.Host assembly from the upstream repository. If <see cref="Version.Minor"/> is higher than <see cref="CurrentVersion"/>'s the update cannot be applied due to API changes
/// </summary>
@@ -19,7 +19,7 @@ namespace Tgstation.Server.Api.Rights
/// <summary>
/// User can gracefully restart the host
/// </summary>
SoftStop = 2,
RestartHost = 2,
/// <summary>
/// User can change <see cref="Models.Administration.CurrentVersion"/>
/// </summary>
@@ -0,0 +1,28 @@
namespace Tgstation.Server.Host.Configuration
{
/// <summary>
/// Configuration for the automatic update system
/// </summary>
sealed class UpdatesConfiguration
{
/// <summary>
/// The key for the <see cref="Microsoft.Extensions.Configuration.IConfigurationSection"/> the <see cref="UpdatesConfiguration"/> resides in
/// </summary>
public const string Section = "Updates";
/// <summary>
/// The <see cref="Octokit.Repository.Id"/> of the tgstation-server fork to recieve updates from
/// </summary>
public long GitHubRepositoryId { get; set; }
/// <summary>
/// Prefix before the <see cref="System.Version"/> of TGS published in git tags
/// </summary>
public string GitTagPrefix { get; set; }
/// <summary>
/// Asset package containing the new <see cref="Host"/> assembly in zip form
/// </summary>
public string UpdatePackageAssetName { get; set; }
}
}
@@ -0,0 +1,160 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Octokit;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Rights;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.Security;
namespace Tgstation.Server.Host.Controllers
{
/// <summary>
/// <see cref="ModelController{TModel}"/> for <see cref="Administration"/>
/// </summary>
[Route("/" + nameof(Administration))]
sealed class AdministrationController : ModelController<Administration>
{
/// <summary>
/// HTTP 429 status code
/// </summary>
const int RateLimitHttpStatusCode = 429;
/// <summary>
/// The <see cref="IGitHubClient"/> for the <see cref="AdministrationController"/>
/// </summary>
readonly IGitHubClient gitHubClient;
/// <summary>
/// The <see cref="IServerUpdater"/> for the <see cref="AdministrationController"/>
/// </summary>
readonly IServerUpdater serverUpdater;
/// <summary>
/// The <see cref="IApplication"/> for the <see cref="AdministrationController"/>
/// </summary>
readonly IApplication application;
/// <summary>
/// The <see cref="IIOManager"/> for the <see cref="AdministrationController"/>
/// </summary>
readonly IIOManager ioManager;
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="AdministrationController"/>
/// </summary>
readonly ILogger<AdministrationController> logger;
/// <summary>
/// The <see cref="UpdatesConfiguration"/> for the <see cref="AdministrationController"/>
/// </summary>
readonly UpdatesConfiguration updatesConfiguration;
/// <summary>
/// Construct an <see cref="AdministrationController"/>
/// </summary>
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> for the <see cref="ApiController"/></param>
/// <param name="authenticationContextFactory">The <see cref="IAuthenticationContextFactory"/> for the <see cref="ApiController"/></param>
/// <param name="gitHubClient">The value of <see cref="gitHubClient"/></param>
/// <param name="serverUpdater">The value of <see cref="serverUpdater"/></param>
/// <param name="application">The value of <see cref="application"/></param>
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
/// <param name="logger">The value of <see cref="logger"/></param>
/// <param name="updatesConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing value of <see cref="updatesConfiguration"/></param>
public AdministrationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IGitHubClient gitHubClient, IServerUpdater serverUpdater, IApplication application, IIOManager ioManager, ILogger<AdministrationController> logger, IOptions<UpdatesConfiguration> updatesConfigurationOptions) : base(databaseContext, authenticationContextFactory)
{
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.gitHubClient = gitHubClient ?? throw new ArgumentNullException(nameof(gitHubClient));
this.serverUpdater = serverUpdater ?? throw new ArgumentNullException(nameof(serverUpdater));
this.application = application ?? throw new ArgumentNullException(nameof(application));
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
updatesConfiguration = updatesConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(updatesConfigurationOptions));
}
StatusCodeResult RateLimit(RateLimitExceededException exception)
{
logger.LogWarning("Exceeded GitHub rate limit!");
var secondsString = Math.Ceiling((exception.Reset - DateTimeOffset.Now).TotalSeconds).ToString(CultureInfo.InvariantCulture);
Response.Headers.Add("Retry-After", new Microsoft.Extensions.Primitives.StringValues { });
return StatusCode(RateLimitHttpStatusCode);
}
/// <inheritdoc />
[TgsAuthorize]
public override async Task<IActionResult> Read(CancellationToken cancellationToken)
{
var model = new Administration
{
CurrentVersion = application.Version
};
try
{
var repositoryTask = gitHubClient.Repository.Get(updatesConfiguration.GitHubRepositoryId);
var releases = (await gitHubClient.Repository.Release.GetAll(updatesConfiguration.GitHubRepositoryId).ConfigureAwait(false)).Where(x => x.TagName.StartsWith(updatesConfiguration.GitTagPrefix, StringComparison.InvariantCulture));
Version greatestVersion = null;
foreach (var I in releases)
if (Version.TryParse(I.TagName.Replace(updatesConfiguration.GitTagPrefix, String.Empty), out var version)
&& version.Major == application.Version.Major
&& (greatestVersion == null || version > greatestVersion))
greatestVersion = version;
model.LatestVersion = greatestVersion;
model.TrackedRepositoryUrl = new Uri((await repositoryTask.ConfigureAwait(false)).HtmlUrl);
}
catch (RateLimitExceededException e)
{
return RateLimit(e);
}
return Json(model);
}
/// <inheritdoc />
[TgsAuthorize(AdministrationRights.ChangeVersion)]
public override async Task<IActionResult> Update([FromBody] Administration model, CancellationToken cancellationToken)
{
if (model == null)
throw new ArgumentNullException(nameof(model));
if (model.CurrentVersion == null)
return BadRequest(new { message = "Missing new version!" });
if (model.CurrentVersion.Major != application.Version.Major)
return BadRequest(new { message = "Cannot update to a different suite version!" });
IEnumerable<Release> releases;
try
{
releases = (await gitHubClient.Repository.Release.GetAll(updatesConfiguration.GitHubRepositoryId).ConfigureAwait(false)).Where(x => x.TagName.StartsWith(updatesConfiguration.GitTagPrefix, StringComparison.InvariantCulture));
}
catch (RateLimitExceededException e)
{
return RateLimit(e);
}
foreach (var release in releases)
if (Version.TryParse(release.TagName.Replace(updatesConfiguration.GitTagPrefix, String.Empty), out var version) && version == model.CurrentVersion)
{
var asset = release.Assets.Where(x => x.Name == updatesConfiguration.UpdatePackageAssetName).FirstOrDefault();
if (asset == default)
continue;
var assetBytes = await ioManager.DownloadFile(new Uri(asset.Url), cancellationToken).ConfigureAwait(false);
await serverUpdater.ApplyUpdate(assetBytes, ioManager, cancellationToken).ConfigureAwait(false);
return Ok(); //gtfo of here before all the cancellation tokens fire
}
return StatusCode((int)HttpStatusCode.Gone);
}
}
}
+11 -3
View File
@@ -9,6 +9,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using Octokit;
using System;
using System.Globalization;
using System.IdentityModel.Tokens.Jwt;
@@ -29,6 +30,11 @@ namespace Tgstation.Server.Host.Core
/// <inheritdoc />
sealed class Application : IApplication
{
/// <summary>
/// Prefix for string version names
/// </summary>
const string VersionPrefix = "/tg/station server";
/// <inheritdoc />
public string HostingPath => serverAddresses.Addresses.First();
@@ -64,7 +70,7 @@ namespace Tgstation.Server.Host.Core
this.hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
Version = Assembly.GetExecutingAssembly().GetName().Version;
VersionString = String.Format(CultureInfo.InvariantCulture, "/tg/station server v{0}", Version);
VersionString = String.Format(CultureInfo.InvariantCulture, "{0} v{1}", VersionPrefix, Version);
}
/// <summary>
@@ -77,7 +83,8 @@ namespace Tgstation.Server.Host.Core
{
if (services == null)
throw new ArgumentNullException(nameof(services));
var workingDir = Environment.CurrentDirectory;
services.Configure<UpdatesConfiguration>(configuration.GetSection(UpdatesConfiguration.Section));
var databaseConfigurationSection = configuration.GetSection(DatabaseConfiguration.Section);
services.Configure<DatabaseConfiguration>(databaseConfigurationSection);
@@ -146,9 +153,10 @@ namespace Tgstation.Server.Host.Core
services.AddSingleton<ICryptographySuite, CryptographySuite>();
services.AddSingleton<IDatabaseSeeder, DatabaseSeeder>();
services.AddSingleton<IPasswordHasher<User>, PasswordHasher<User>>();
services.AddSingleton<IPasswordHasher<Models.User>, PasswordHasher<Models.User>>();
services.AddSingleton<ITokenFactory, TokenFactory>();
services.AddSingleton<ISynchronousIOManager, SynchronousIOManager>();
services.AddSingleton<IGitHubClient>(x => new GitHubClient(new ProductHeaderValue(VersionPrefix, Version.ToString())));
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
@@ -18,6 +18,11 @@
}
}
},
"Updates": {
"GitHubRepositoryId": 92952846,
"GitTagPrefix": "tgstation-server-v",
"UpdatePackageAssetName": "ServerUpdatePackage.zip"
},
"Database": {
"DatabaseType": "SqlServer",
"ResetAdminPassword": false,