Merge pull request #1751 from tgstation/FinalFixRound [TGSDeploy]

v6.0.1: Final round of patches
This commit is contained in:
Jordan Dominion
2023-12-26 13:41:50 -05:00
committed by GitHub
5 changed files with 124 additions and 78 deletions
+11 -7
View File
@@ -185,16 +185,20 @@ If using manual configuration, before starting your container make sure the afor
In order for TGS to use [OpenDream](https://github.com/OpenDreamProject/OpenDream), it requires the full .NET SDK to build whichever version your servers target. Whatever that is, it must be available using the `dotnet` command for whichever user runs TGS.
OpenDream currently requires [.NET SDK 7.0](https://dotnet.microsoft.com/en-us/download/dotnet/7.0) at the time of this writing. You must install this manually.
OpenDream currently requires [.NET SDK 8.0](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) at the time of this writing. You must install this manually outside of TGS (i.e. using your package manager).
On Linux, as long as OpenDream and TGS do not use the same .NET major version, you cannot achieve this with the package manager as they will conflict. The 7.0 SDK can be added to an 8.0 runtime installation via the following steps.
<details>
<summary>How to handle a different SDK version than the ASP.NET runtime of TGS.</summary>
1. Install `tgstation-server` using any of the above methods.
1. [Download the Linux SDK binaries](https://dotnet.microsoft.com/en-us/download/dotnet/7.0) for your selected architecture.
1. Extract everything EXCEPT the `dotnet` executable, `LICENSE.txt`, and `ThirdPartyNotices.txt` in the `.tar.gz` on top of the existing installation directory `/usr/share/dotnet/`
1. Run `sudo chown -R root /usr/share/dotnet`
On Linux, as long as OpenDream and TGS do not use the same .NET major version, you cannot achieve this with the package manager as they will conflict. For example, the 7.0 SDK can be added to an 8.0 runtime installation via the following steps.
You should now be able to run the `dotnet --list-sdks` command and see an entry for `7.0.XXX [/usr/share/dotnet/sdk]`.
1. Install `tgstation-server` using any of the above methods.
1. [Download the Linux SDK binaries](https://dotnet.microsoft.com/en-us/download/dotnet/7.0) for your selected architecture.
1. Extract everything EXCEPT the `dotnet` executable, `LICENSE.txt`, and `ThirdPartyNotices.txt` in the `.tar.gz` on top of the existing installation directory `/usr/share/dotnet/`
1. Run `sudo chown -R root /usr/share/dotnet`
You should now be able to run the `dotnet --list-sdks` command and see an entry for `7.0.XXX [/usr/share/dotnet/sdk]`.
</details>
### Configuring
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -84,7 +85,7 @@ namespace Tgstation.Server.Host.Components.Repository
/// <summary>
/// The <see cref="IIOManager"/> for the <see cref="Repository"/>.
/// </summary>
readonly IIOManager ioMananger;
readonly IIOManager ioManager;
/// <summary>
/// The <see cref="IEventConsumer"/> for the <see cref="Repository"/>.
@@ -106,6 +107,11 @@ namespace Tgstation.Server.Host.Components.Repository
/// </summary>
readonly IGitRemoteFeatures gitRemoteFeatures;
/// <summary>
/// The <see cref="ILibGit2RepositoryFactory"/> used for updating submodules.
/// </summary>
readonly ILibGit2RepositoryFactory submoduleFactory;
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="Repository"/>.
/// </summary>
@@ -121,22 +127,24 @@ namespace Tgstation.Server.Host.Components.Repository
/// </summary>
/// <param name="libGitRepo">The value of <see cref="libGitRepo"/>.</param>
/// <param name="commands">The value of <see cref="commands"/>.</param>
/// <param name="ioMananger">The value of <see cref="ioMananger"/>.</param>
/// <param name="ioManager">The value of <see cref="ioManager"/>.</param>
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/>.</param>
/// <param name="credentialsProvider">The value of <see cref="credentialsProvider"/>.</param>
/// <param name="postWriteHandler">The value of <see cref="postWriteHandler"/>.</param>
/// <param name="gitRemoteFeaturesFactory">The <see cref="IGitRemoteFeaturesFactory"/> to provide the value of <see cref="gitRemoteFeatures"/>.</param>
/// <param name="submoduleFactory">The value of <see cref="submoduleFactory"/>.</param>
/// <param name="logger">The value of <see cref="logger"/>.</param>
/// <param name="generalConfiguration">The value of <see cref="generalConfiguration"/>.</param>
/// <param name="disposeAction">The <see cref="IDisposable.Dispose"/> action for the <see cref="DisposeInvoker"/>.</param>
public Repository(
LibGit2Sharp.IRepository libGitRepo,
ILibGit2Commands commands,
IIOManager ioMananger,
IIOManager ioManager,
IEventConsumer eventConsumer,
ICredentialsProvider credentialsProvider,
IPostWriteHandler postWriteHandler,
IGitRemoteFeaturesFactory gitRemoteFeaturesFactory,
ILibGit2RepositoryFactory submoduleFactory,
ILogger<Repository> logger,
GeneralConfiguration generalConfiguration,
Action disposeAction)
@@ -144,11 +152,12 @@ namespace Tgstation.Server.Host.Components.Repository
{
this.libGitRepo = libGitRepo ?? throw new ArgumentNullException(nameof(libGitRepo));
this.commands = commands ?? throw new ArgumentNullException(nameof(commands));
this.ioMananger = ioMananger ?? throw new ArgumentNullException(nameof(ioMananger));
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
this.credentialsProvider = credentialsProvider ?? throw new ArgumentNullException(nameof(credentialsProvider));
this.postWriteHandler = postWriteHandler ?? throw new ArgumentNullException(nameof(postWriteHandler));
ArgumentNullException.ThrowIfNull(gitRemoteFeaturesFactory);
this.submoduleFactory = submoduleFactory ?? throw new ArgumentNullException(nameof(submoduleFactory));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.generalConfiguration = generalConfiguration ?? throw new ArgumentNullException(nameof(generalConfiguration));
@@ -501,7 +510,7 @@ namespace Tgstation.Server.Host.Components.Repository
{
ArgumentNullException.ThrowIfNull(path);
logger.LogTrace("Copying to {path}...", path);
await ioMananger.CopyDirectory(
await ioManager.CopyDirectory(
new List<string> { ".git" },
(src, dest) =>
{
@@ -510,7 +519,7 @@ namespace Tgstation.Server.Host.Components.Repository
return ValueTask.CompletedTask;
},
ioMananger.ResolvePath(),
ioManager.ResolvePath(),
path,
generalConfiguration.GetCopyDirectoryTaskThrottle(),
cancellationToken);
@@ -657,7 +666,7 @@ namespace Tgstation.Server.Host.Components.Repository
EventType.RepoPreSynchronize,
new List<string>
{
ioMananger.ResolvePath(),
ioManager.ResolvePath(),
},
deploymentPipeline,
cancellationToken);
@@ -987,6 +996,17 @@ namespace Tgstation.Server.Host.Components.Repository
};
}
/// <summary>
/// Gets the path of <see cref="libGitRepo"/>.
/// </summary>
/// <returns>The path of the target <see cref="IRepository"/>.</returns>
string GetRepositoryPath()
=> ioManager.GetDirectoryName(libGitRepo
.Info
.Path
.TrimEnd(Path.DirectorySeparatorChar)
.TrimEnd(Path.AltDirectorySeparatorChar));
/// <summary>
/// Recusively update all <see cref="Submodule"/>s in the <see cref="libGitRepo"/>.
/// </summary>
@@ -996,86 +1016,106 @@ namespace Tgstation.Server.Host.Components.Repository
/// <param name="deploymentPipeline">If any events created should be marked as part of the deployment pipeline.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask"/> representing the running operation.</returns>
async ValueTask UpdateSubmodules(
ValueTask UpdateSubmodules(
JobProgressReporter? progressReporter,
string? username,
string? password,
bool deploymentPipeline,
CancellationToken cancellationToken)
{
var submoduleCount = libGitRepo.Submodules.Count();
if (submoduleCount == 0)
logger.LogTrace("Updating submodules {withOrWithout} credentials...", username == null ? "without" : "with");
async ValueTask RecursiveUpdateSubmodules(LibGit2Sharp.IRepository parentRepository, JobProgressReporter? currentProgressReporter, string parentGitDirectory)
{
logger.LogTrace("No submodules, skipping update");
return;
}
logger.LogTrace("Updating submodules with{orWithout} credentials...", username == null ? "out" : String.Empty);
var factor = 1.0 / submoduleCount / 2;
foreach (var submodule in libGitRepo.Submodules)
{
var submoduleUpdateOptions = new SubmoduleUpdateOptions
var submoduleCount = libGitRepo.Submodules.Count();
if (submoduleCount == 0)
{
Init = true,
OnCheckoutNotify = (_, _) => !cancellationToken.IsCancellationRequested,
};
submoduleUpdateOptions.FetchOptions.Hydrate(
logger,
progressReporter?.CreateSection($"Fetch submodule {submodule.Name}", factor),
credentialsProvider.GenerateCredentialsHandler(username, password),
cancellationToken);
if (progressReporter != null)
submoduleUpdateOptions.OnCheckoutProgress = CheckoutProgressHandler(
progressReporter.CreateSection($"Checkout submodule {submodule.Name}", factor));
logger.LogDebug("Updating submodule {submoduleName}...", submodule.Name);
Task RawSubModuleUpdate() => Task.Factory.StartNew(
() => libGitRepo.Submodules.Update(submodule.Name, submoduleUpdateOptions),
cancellationToken,
DefaultIOManager.BlockingTaskCreationOptions,
TaskScheduler.Current);
try
{
await RawSubModuleUpdate();
logger.LogTrace("No submodules, skipping update");
return;
}
catch (LibGit2SharpException ex)
var factor = 1.0 / submoduleCount / 3;
foreach (var submodule in parentRepository.Submodules)
{
// workaround for https://github.com/libgit2/libgit2/issues/3820
// kill off the modules/ folder in .git and try again
progressReporter?.ReportProgress(null);
credentialsProvider.CheckBadCredentialsException(ex);
logger.LogWarning(ex, "Initial update of submodule {submoduleName} failed. Deleting submodule directories and re-attempting...", submodule.Name);
logger.LogTrace("Entering submodule {name} ({path}) for recursive updates...", submodule.Name, submodule.Path);
var submoduleUpdateOptions = new SubmoduleUpdateOptions
{
Init = true,
OnCheckoutNotify = (_, _) => !cancellationToken.IsCancellationRequested,
};
await Task.WhenAll(
ioMananger.DeleteDirectory($".git/modules/{submodule.Path}", cancellationToken),
ioMananger.DeleteDirectory(submodule.Path, cancellationToken));
submoduleUpdateOptions.FetchOptions.Hydrate(
logger,
currentProgressReporter?.CreateSection($"Fetch submodule {submodule.Name}", factor),
credentialsProvider.GenerateCredentialsHandler(username, password),
cancellationToken);
if (currentProgressReporter != null)
submoduleUpdateOptions.OnCheckoutProgress = CheckoutProgressHandler(
currentProgressReporter.CreateSection($"Checkout submodule {submodule.Name}", factor));
logger.LogDebug("Updating submodule {submoduleName}...", submodule.Name);
Task RawSubModuleUpdate() => Task.Factory.StartNew(
() => parentRepository.Submodules.Update(submodule.Name, submoduleUpdateOptions),
cancellationToken,
DefaultIOManager.BlockingTaskCreationOptions,
TaskScheduler.Current);
logger.LogTrace("Second update attempt for submodule {submoduleName}...", submodule.Name);
try
{
await RawSubModuleUpdate();
}
catch (UserCancelledException)
catch (LibGit2SharpException ex) when (parentRepository == libGitRepo)
{
cancellationToken.ThrowIfCancellationRequested();
}
catch (LibGit2SharpException ex2)
{
credentialsProvider.CheckBadCredentialsException(ex2);
logger.LogTrace(ex2, "Retried update of submodule {submoduleName} failed!", submodule.Name);
throw new AggregateException(ex, ex2);
}
}
// workaround for https://github.com/libgit2/libgit2/issues/3820
// kill off the modules/ folder in .git and try again
currentProgressReporter?.ReportProgress(null);
credentialsProvider.CheckBadCredentialsException(ex);
logger.LogWarning(ex, "Initial update of submodule {submoduleName} failed. Deleting submodule directories and re-attempting...", submodule.Name);
await eventConsumer.HandleEvent(
EventType.RepoSubmoduleUpdate,
new List<string> { submodule.Name },
deploymentPipeline,
cancellationToken);
await Task.WhenAll(
ioManager.DeleteDirectory($".git/modules/{submodule.Path}", cancellationToken),
ioManager.DeleteDirectory(submodule.Path, cancellationToken));
logger.LogTrace("Second update attempt for submodule {submoduleName}...", submodule.Name);
try
{
await RawSubModuleUpdate();
}
catch (UserCancelledException)
{
cancellationToken.ThrowIfCancellationRequested();
}
catch (LibGit2SharpException ex2)
{
credentialsProvider.CheckBadCredentialsException(ex2);
logger.LogTrace(ex2, "Retried update of submodule {submoduleName} failed!", submodule.Name);
throw new AggregateException(ex, ex2);
}
}
await eventConsumer.HandleEvent(
EventType.RepoSubmoduleUpdate,
new List<string> { submodule.Name },
deploymentPipeline,
cancellationToken);
var submodulePath = ioManager.ResolvePath(
ioManager.ConcatPath(
parentGitDirectory,
submodule.Path));
using var submoduleRepo = await submoduleFactory.CreateFromPath(
submodulePath,
cancellationToken);
await RecursiveUpdateSubmodules(
submoduleRepo,
currentProgressReporter?.CreateSection($"Entering submodule \"{submodule.Name}\"...", factor),
submodulePath);
}
}
return RecursiveUpdateSubmodules(libGitRepo, progressReporter, GetRepositoryPath());
}
/// <summary>
@@ -231,6 +231,7 @@ namespace Tgstation.Server.Host.Components.Repository
repositoryFactory,
postWriteHandler,
gitRemoteFeaturesFactory,
repositoryFactory,
repositoryLogger,
generalConfiguration,
() =>
@@ -1112,10 +1112,10 @@ namespace Tgstation.Server.Host.Components.Watchdog
switch (++healthChecksMissed)
{
case 1:
Logger.LogDebug("DEFCON 4: DreamDaemon missed first health check!");
Logger.LogDebug("DEFCON 4: Game server missed first health check!");
break;
case 2:
const string message2 = "DEFCON 3: DreamDaemon has missed 2 health checks!";
const string message2 = "DEFCON 3: Game server has missed 2 health checks!";
Logger.LogInformation(message2);
Chat.QueueWatchdogMessage(message2);
break;
@@ -1123,7 +1123,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
var actionToTake = shouldShutdown
? "shutdown"
: "be restarted";
const string logTemplate1 = "DEFCON 2: DreamDaemon has missed 3 health checks! If it does not respond to the next one, the watchdog will {actionToTake}!";
const string logTemplate1 = "DEFCON 2: Game server has missed 3 health checks! If it does not respond to the next one, the watchdog will {actionToTake}!";
Logger.LogWarning(logTemplate1, actionToTake);
Chat.QueueWatchdogMessage(
logTemplate1.Replace(
@@ -39,6 +39,7 @@ namespace Tgstation.Server.Tests
Mock.Of<ICredentialsProvider>(),
Mock.Of<IPostWriteHandler>(),
Mock.Of<IGitRemoteFeaturesFactory>(),
Mock.Of<ILibGit2RepositoryFactory>(),
Mock.Of<ILogger<Host.Components.Repository.Repository>>(),
new GeneralConfiguration(),
() => { });