Merge pull request #1515 from tgstation/1500-TheRealProblems

GitHub Abstraction Part 1: Centralizing API calls
This commit is contained in:
Jordan Dominion
2023-06-05 17:05:21 -04:00
committed by GitHub
55 changed files with 1508 additions and 408 deletions
+155 -13
View File
@@ -108,6 +108,13 @@ jobs:
sudo apt-get update
sudo apt-get install -y -o APT::Immediate-Configure=0 libc6-i386 libstdc++6:i386 libgcc-s1:i386
- name: Restore BYOND cache
uses: actions/cache@v3
id: cache-byond
with:
path: $HOME/BYOND-${{ env.BYOND_MAJOR }}.${{ env.BYOND_MINOR }}
key: ${{ env.BYOND_MAJOR }}-${{ env.BYOND_MINOR }}-dmapibyond
- name: Install BYOND
if: steps.cache-byond.outputs.cache-hit != 'true'
run: |
@@ -316,12 +323,30 @@ jobs:
watchdog-type: [ 'Basic', 'System' ]
configuration: [ 'Debug', 'Release' ]
runs-on: windows-2019
env:
BYOND_MAJOR: 514
BYOND_MINOR: 1588
steps:
- name: Setup dotnet
uses: actions/setup-dotnet@v2
with:
dotnet-version: ${{ env.TGS_DOTNET_VERSION }}
- name: Restore BYOND cache
uses: actions/cache@v3
id: cache-byond
with:
path: $HOME/BYOND-${{ env.BYOND_MAJOR }}.${{ env.BYOND_MINOR }}
key: ${{ env.BYOND_MAJOR }}-${{ env.BYOND_MINOR }}-livebyond
- name: Download BYOND
if: steps.cache-byond.outputs.cache-hit != 'true'
run: |
echo "Downloading BYOND..."
mkdir -p "$HOME/BYOND-${{ env.BYOND_MAJOR }}.${{ env.BYOND_MINOR }}"
cd "$HOME/BYOND-${{ env.BYOND_MAJOR }}.${{ env.BYOND_MINOR }}"
curl "http://www.byond.com/download/build/${{ env.BYOND_MAJOR }}/${{ env.BYOND_MAJOR }}.${{ env.BYOND_MINOR }}_byond.zip" -o byond.zip
- name: Upgrade NPM
run: npm install -g npm
@@ -403,7 +428,7 @@ jobs:
- name: Store Code Coverage
uses: actions/upload-artifact@v3
with:
name: windows-integration-test-coverage-${{ matrix.configuration }}-${{ matrix.watchdog-type }}
name: windows-integration-test-coverage-${{ matrix.configuration }}-${{ matrix.watchdog-type }}-${{ matrix.database-type }}
path: ./TestResults/
- name: Store OpenAPI Spec
@@ -421,6 +446,7 @@ jobs:
cd ../Tgstation.Server.Host
dotnet publish -c ${{ matrix.configuration }} --no-build -o ../../Artifacts/Service/lib/Default
mv ../../Artifacts/Service/lib/Default/appsettings.yml ../../Artifacts/Service/appsettings.yml
rm ../../Artifacts/Service/lib/Default/Tgstation.Server.Host.exe
- name: Store Server Service
if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'Basic' }}
@@ -481,6 +507,9 @@ jobs:
watchdog-type: [ 'Basic', 'System' ]
configuration: [ 'Debug', 'Release' ]
runs-on: ubuntu-latest
env:
BYOND_MAJOR: 514
BYOND_MINOR: 1588
steps:
- name: Disable ptrace_scope
run: echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
@@ -491,6 +520,21 @@ jobs:
sudo apt-get update
sudo apt-get install -y -o APT::Immediate-Configure=0 libc6-i386 libstdc++6:i386 gdb libgcc-s1:i386
- name: Restore BYOND cache
uses: actions/cache@v3
id: cache-byond
with:
path: $HOME/BYOND-${{ env.BYOND_MAJOR }}.${{ env.BYOND_MINOR }}
key: ${{ env.BYOND_MAJOR }}-${{ env.BYOND_MINOR }}-livebyond
- name: Download BYOND
if: steps.cache-byond.outputs.cache-hit != 'true'
run: |
echo "Downloading BYOND..."
mkdir -p "$HOME/BYOND-${{ env.BYOND_MAJOR }}.${{ env.BYOND_MINOR }}"
cd "$HOME/BYOND-${{ env.BYOND_MAJOR }}.${{ env.BYOND_MINOR }}"
curl "http://www.byond.com/download/build/${{ env.BYOND_MAJOR }}/${{ env.BYOND_MAJOR }}.${{ env.BYOND_MINOR }}_byond_linux.zip" -o byond.zip
- name: Install Node 12.X
uses: actions/setup-node@v3
with:
@@ -566,6 +610,7 @@ jobs:
cd ../Tgstation.Server.Host
dotnet publish -c ${{ matrix.configuration }}NoService --no-build -o ../../Artifacts/Console/lib/Default
mv ../../Artifacts/Console/lib/Default/appsettings.yml ../../Artifacts/Console/appsettings.yml
rm ../../Artifacts/Console/lib/Default/Tgstation.Server.Host
- name: Package Server Update Package
if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'System' && matrix.database-type == 'PostgresSql' }}
@@ -573,6 +618,7 @@ jobs:
cd src/Tgstation.Server.Host
dotnet publish -c ${{ matrix.configuration }}NoService --no-build -o ../../Artifacts/ServerUpdate
rm ../../Artifacts/ServerUpdate/appsettings.yml
rm ../../Artifacts/ServerUpdate/Tgstation.Server.Host
- name: Store Server Console
if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'System' && matrix.database-type == 'MariaDB' }}
@@ -751,29 +797,125 @@ jobs:
name: windows-unit-test-coverage-Release
path: ./code_coverage/unit_tests/windows_unit_tests_release
- name: Retrieve Windows Integration Test Coverage (Debug, Basic)
- name: Retrieve Windows Integration Test Coverage (Debug, Basic, SqlServer)
uses: actions/download-artifact@v3
with:
name: windows-integration-test-coverage-Debug-Basic
path: ./code_coverage/integration_tests/windows_integration_tests_debug_basic
name: windows-integration-test-coverage-Debug-Basic-SqlServer
path: ./code_coverage/integration_tests/windows_integration_tests_debug_basic_sqlserver
- name: Retrieve Windows Integration Test Coverage (Release, Basic)
- name: Retrieve Windows Integration Test Coverage (Release, Basic, SqlServer)
uses: actions/download-artifact@v3
with:
name: windows-integration-test-coverage-Release-Basic
path: ./code_coverage/integration_tests/windows_integration_tests_release_basic
name: windows-integration-test-coverage-Release-Basic-SqlServer
path: ./code_coverage/integration_tests/windows_integration_tests_release_basic_sqlserver
- name: Retrieve Windows Integration Test Coverage (Debug, System)
- name: Retrieve Windows Integration Test Coverage (Debug, System, SqlServer)
uses: actions/download-artifact@v3
with:
name: windows-integration-test-coverage-Debug-System
path: ./code_coverage/integration_tests/windows_integration_tests_debug_system
name: windows-integration-test-coverage-Debug-System-SqlServer
path: ./code_coverage/integration_tests/windows_integration_tests_debug_system_sqlserver
- name: Retrieve Windows Integration Test Coverage (Release, System)
- name: Retrieve Windows Integration Test Coverage (Release, System, SqlServer)
uses: actions/download-artifact@v3
with:
name: windows-integration-test-coverage-Release-System
path: ./code_coverage/integration_tests/windows_integration_tests_release_system
name: windows-integration-test-coverage-Release-System-SqlServer
path: ./code_coverage/integration_tests/windows_integration_tests_release_system_sqlserver
- name: Retrieve Windows Integration Test Coverage (Debug, Basic, MariaDB)
uses: actions/download-artifact@v3
with:
name: windows-integration-test-coverage-Debug-Basic-MariaDB
path: ./code_coverage/integration_tests/windows_integration_tests_debug_basic_mariadb
- name: Retrieve Windows Integration Test Coverage (Release, Basic, MariaDB)
uses: actions/download-artifact@v3
with:
name: windows-integration-test-coverage-Release-Basic-MariaDB
path: ./code_coverage/integration_tests/windows_integration_tests_release_basic_mariadb
- name: Retrieve Windows Integration Test Coverage (Debug, System, MariaDB)
uses: actions/download-artifact@v3
with:
name: windows-integration-test-coverage-Debug-System-MariaDB
path: ./code_coverage/integration_tests/windows_integration_tests_debug_system_mariadb
- name: Retrieve Windows Integration Test Coverage (Release, System, MariaDB)
uses: actions/download-artifact@v3
with:
name: windows-integration-test-coverage-Release-System-MariaDB
path: ./code_coverage/integration_tests/windows_integration_tests_release_system_mariadb
- name: Retrieve Windows Integration Test Coverage (Debug, Basic, MySql)
uses: actions/download-artifact@v3
with:
name: windows-integration-test-coverage-Debug-Basic-MySql
path: ./code_coverage/integration_tests/windows_integration_tests_debug_basic_mysql
- name: Retrieve Windows Integration Test Coverage (Release, Basic, MySql)
uses: actions/download-artifact@v3
with:
name: windows-integration-test-coverage-Release-Basic-MySql
path: ./code_coverage/integration_tests/windows_integration_tests_release_basic_mysql
- name: Retrieve Windows Integration Test Coverage (Debug, System, MySql)
uses: actions/download-artifact@v3
with:
name: windows-integration-test-coverage-Debug-System-MySql
path: ./code_coverage/integration_tests/windows_integration_tests_debug_system_mysql
- name: Retrieve Windows Integration Test Coverage (Release, System, MySql)
uses: actions/download-artifact@v3
with:
name: windows-integration-test-coverage-Release-System-MySql
path: ./code_coverage/integration_tests/windows_integration_tests_release_system_mysql
- name: Retrieve Windows Integration Test Coverage (Debug, Basic, PostgresSql)
uses: actions/download-artifact@v3
with:
name: windows-integration-test-coverage-Debug-Basic-PostgresSql
path: ./code_coverage/integration_tests/windows_integration_tests_debug_basic_postgressql
- name: Retrieve Windows Integration Test Coverage (Release, Basic, PostgresSql)
uses: actions/download-artifact@v3
with:
name: windows-integration-test-coverage-Release-Basic-PostgresSql
path: ./code_coverage/integration_tests/windows_integration_tests_release_basic_postgressql
- name: Retrieve Windows Integration Test Coverage (Debug, System, PostgresSql)
uses: actions/download-artifact@v3
with:
name: windows-integration-test-coverage-Debug-System-PostgresSql
path: ./code_coverage/integration_tests/windows_integration_tests_debug_system_postgressql
- name: Retrieve Windows Integration Test Coverage (Release, System, PostgresSql)
uses: actions/download-artifact@v3
with:
name: windows-integration-test-coverage-Release-System-PostgresSql
path: ./code_coverage/integration_tests/windows_integration_tests_release_system_postgressql
- name: Retrieve Windows Integration Test Coverage (Debug, Basic, Sqlite)
uses: actions/download-artifact@v3
with:
name: windows-integration-test-coverage-Debug-Basic-Sqlite
path: ./code_coverage/integration_tests/windows_integration_tests_debug_basic_sqlite
- name: Retrieve Windows Integration Test Coverage (Release, Basic, Sqlite)
uses: actions/download-artifact@v3
with:
name: windows-integration-test-coverage-Release-Basic-Sqlite
path: ./code_coverage/integration_tests/windows_integration_tests_release_basic_sqlite
- name: Retrieve Windows Integration Test Coverage (Debug, System, Sqlite)
uses: actions/download-artifact@v3
with:
name: windows-integration-test-coverage-Debug-System-Sqlite
path: ./code_coverage/integration_tests/windows_integration_tests_debug_system_sqlite
- name: Retrieve Windows Integration Test Coverage (Release, System, Sqlite)
uses: actions/download-artifact@v3
with:
name: windows-integration-test-coverage-Release-System-Sqlite
path: ./code_coverage/integration_tests/windows_integration_tests_release_system_sqlite
- name: Upload Coverage to CodeCov
uses: codecov/codecov-action@v3
@@ -4,8 +4,6 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Tgstation.Server.Host.Jobs;
namespace Tgstation.Server.Host.Components.Byond
@@ -14,7 +12,7 @@ namespace Tgstation.Server.Host.Components.Byond
/// For managing the BYOND installation.
/// </summary>
/// <remarks>When passing in <see cref="Version"/>s, ensure they are BYOND format versions unless referring to a custom version. This means <see cref="Version.Build"/> should NEVER be 0.</remarks>
public interface IByondManager : IHostedService, IDisposable
public interface IByondManager : IComponentService, IDisposable
{
/// <summary>
/// The currently active BYOND version.
@@ -3,8 +3,6 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Host.Components.Interop;
@@ -13,7 +11,7 @@ namespace Tgstation.Server.Host.Components.Chat
/// <summary>
/// For managing connected chat services.
/// </summary>
public interface IChatManager : IHostedService, IAsyncDisposable
public interface IChatManager : IComponentService, IAsyncDisposable
{
/// <summary>
/// Registers a <paramref name="customCommandHandler"/> to use.
@@ -90,7 +90,7 @@ namespace Tgstation.Server.Host.Components.Deployment
IDmbProvider nextDmbProvider;
/// <summary>
/// If the <see cref="DmbFactory"/> is "started" via <see cref="Microsoft.Extensions.Hosting.IHostedService"/>.
/// If the <see cref="DmbFactory"/> is "started" via <see cref="IComponentService"/>.
/// </summary>
bool started;
@@ -2,8 +2,6 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Components.Deployment
@@ -11,7 +9,7 @@ namespace Tgstation.Server.Host.Components.Deployment
/// <summary>
/// Factory for <see cref="IDmbProvider"/>s.
/// </summary>
public interface IDmbFactory : ILatestCompileJobProvider, IHostedService, IDisposable
public interface IDmbFactory : ILatestCompileJobProvider, IComponentService, IDisposable
{
/// <summary>
/// Get a <see cref="Task"/> that completes when the result of a call to <see cref="LockNextDmb"/> will be different than the previous call if any.
@@ -8,13 +8,13 @@ using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Octokit;
using Tgstation.Server.Host.Components.Repository;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.Utils;
using Tgstation.Server.Host.Utils.GitHub;
namespace Tgstation.Server.Host.Components.Deployment.Remote
{
@@ -29,26 +29,26 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
readonly IDatabaseContextFactory databaseContextFactory;
/// <summary>
/// The <see cref="IGitHubClientFactory"/> for the <see cref="GitHubRemoteDeploymentManager"/>.
/// The <see cref="IGitHubServiceFactory"/> for the <see cref="GitHubRemoteDeploymentManager"/>.
/// </summary>
readonly IGitHubClientFactory gitHubClientFactory;
readonly IGitHubServiceFactory gitHubServiceFactory;
/// <summary>
/// Initializes a new instance of the <see cref="GitHubRemoteDeploymentManager"/> class.
/// </summary>
/// <param name="databaseContextFactory">The value of <see cref="databaseContextFactory"/>.</param>
/// <param name="gitHubClientFactory">The value of <see cref="gitHubClientFactory"/>.</param>
/// <param name="gitHubServiceFactory">The value of <see cref="gitHubServiceFactory"/>.</param>
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="BaseRemoteDeploymentManager"/>.</param>
/// <param name="metadata">The <see cref="Api.Models.Instance"/> for the <see cref="BaseRemoteDeploymentManager"/>.</param>
public GitHubRemoteDeploymentManager(
IDatabaseContextFactory databaseContextFactory,
IGitHubClientFactory gitHubClientFactory,
IGitHubServiceFactory gitHubServiceFactory,
ILogger<GitHubRemoteDeploymentManager> logger,
Api.Models.Instance metadata)
: base(logger, metadata)
{
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory));
this.gitHubServiceFactory = gitHubServiceFactory ?? throw new ArgumentNullException(nameof(gitHubServiceFactory));
}
/// <inheritdoc />
@@ -74,15 +74,23 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
.FirstAsync(cancellationToken));
var instanceAuthenticated = repositorySettings.AccessToken != null;
var gitHubClient = !instanceAuthenticated
? gitHubClientFactory.CreateClient()
: gitHubClientFactory.CreateClient(repositorySettings.AccessToken);
IAuthenticatedGitHubService authenticatedGitHubService;
IGitHubService gitHubService;
if (instanceAuthenticated)
{
authenticatedGitHubService = gitHubServiceFactory.CreateService(repositorySettings.AccessToken);
gitHubService = authenticatedGitHubService;
}
else
{
authenticatedGitHubService = null;
gitHubService = gitHubServiceFactory.CreateService();
}
var repositoryTask = gitHubClient
.Repository
.Get(
remoteInformation.RemoteRepositoryOwner,
remoteInformation.RemoteRepositoryName);
var repositoryIdTask = gitHubService.GetRepositoryId(
remoteInformation.RemoteRepositoryOwner,
remoteInformation.RemoteRepositoryName,
cancellationToken);
if (!repositorySettings.CreateGitHubDeployments.Value)
Logger.LogTrace("Not creating deployment");
@@ -91,59 +99,45 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
else
{
Logger.LogTrace("Creating deployment...");
Octokit.Deployment deployment;
try
{
deployment = await gitHubClient
.Repository
.Deployment
.Create(
remoteInformation.RemoteRepositoryOwner,
remoteInformation.RemoteRepositoryName,
new NewDeployment(compileJob.RevisionInformation.CommitSha)
{
AutoMerge = false,
Description = "TGS Game Deployment",
Environment = $"TGS: {Metadata.Name}",
ProductionEnvironment = true,
RequiredContexts = new Collection<string>(),
})
.WithToken(cancellationToken);
compileJob.GitHubDeploymentId = await authenticatedGitHubService.CreateDeployment(
new NewDeployment(compileJob.RevisionInformation.CommitSha)
{
AutoMerge = false,
Description = "TGS Game Deployment",
Environment = $"TGS: {Metadata.Name}",
ProductionEnvironment = true,
RequiredContexts = new Collection<string>(),
},
remoteInformation.RemoteRepositoryOwner,
remoteInformation.RemoteRepositoryName,
cancellationToken);
Logger.LogDebug("Created deployment ID {deploymentId}", deployment.Id);
Logger.LogDebug("Created deployment ID {deploymentId}", compileJob.GitHubDeploymentId);
await gitHubClient
.Repository
.Deployment
.Status
.Create(
remoteInformation.RemoteRepositoryOwner,
remoteInformation.RemoteRepositoryName,
deployment.Id,
new NewDeploymentStatus(DeploymentState.InProgress)
{
Description = "The project is being deployed",
AutoInactive = false,
})
.WithToken(cancellationToken);
await authenticatedGitHubService.CreateDeploymentStatus(
new NewDeploymentStatus(DeploymentState.InProgress)
{
Description = "The project is being deployed",
AutoInactive = false,
},
remoteInformation.RemoteRepositoryOwner,
remoteInformation.RemoteRepositoryName,
compileJob.GitHubDeploymentId.Value,
cancellationToken);
compileJob.GitHubDeploymentId = deployment.Id;
Logger.LogTrace("In-progress deployment status created");
}
catch (ApiException ex)
{
Logger.LogError(ex, "Unable to create deployment!");
Logger.LogWarning(ex, "Unable to create deployment!");
}
}
try
{
var gitHubRepo = await repositoryTask
.WithToken(cancellationToken)
;
compileJob.GitHubRepoId = gitHubRepo.Id;
compileJob.GitHubRepoId = await repositoryIdTask;
Logger.LogTrace("Set GitHub ID as {gitHubRepoId}", compileJob.GitHubRepoId);
}
catch (RateLimitExceededException ex) when (!repositorySettings.CreateGitHubDeployments.Value)
@@ -206,16 +200,13 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
return Array.Empty<TestMerge>();
}
var gitHubClient = repositorySettings.AccessToken != null
? gitHubClientFactory.CreateClient(repositorySettings.AccessToken)
: gitHubClientFactory.CreateClient();
var gitHubService = repositorySettings.AccessToken != null
? gitHubServiceFactory.CreateService(repositorySettings.AccessToken)
: gitHubServiceFactory.CreateService();
var tasks = revisionInformation
.ActiveTestMerges
.Select(x => gitHubClient
.PullRequest
.Get(repository.RemoteRepositoryOwner, repository.RemoteRepositoryName, x.TestMerge.Number)
.WithToken(cancellationToken));
.Select(x => gitHubService.GetPullRequest(repository.RemoteRepositoryOwner, repository.RemoteRepositoryName, x.TestMerge.Number, cancellationToken));
try
{
await Task.WhenAll(tasks);
@@ -260,13 +251,11 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
int testMergeNumber,
CancellationToken cancellationToken)
{
var gitHubClient = gitHubClientFactory.CreateClient(repositorySettings.AccessToken);
var gitHubService = gitHubServiceFactory.CreateService(repositorySettings.AccessToken);
try
{
await gitHubClient.Issue.Comment.Create(remoteRepositoryOwner, remoteRepositoryName, testMergeNumber, comment)
.WithToken(cancellationToken)
;
await gitHubService.CommentOnIssue(remoteRepositoryOwner, remoteRepositoryName, comment, testMergeNumber, cancellationToken);
}
catch (ApiException e)
{
@@ -351,21 +340,16 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
return;
}
var gitHubClient = gitHubClientFactory.CreateClient(gitHubAccessToken);
var gitHubService = gitHubServiceFactory.CreateService(gitHubAccessToken);
await gitHubClient
.Repository
.Deployment
.Status
.Create(
compileJob.GitHubRepoId.Value,
compileJob.GitHubDeploymentId.Value,
new NewDeploymentStatus(deploymentState)
{
Description = description,
})
.WithToken(cancellationToken)
;
await gitHubService.CreateDeploymentStatus(
new NewDeploymentStatus(deploymentState)
{
Description = description,
},
compileJob.GitHubRepoId.Value,
compileJob.GitHubDeploymentId.Value,
cancellationToken);
}
}
}
@@ -5,7 +5,7 @@ using Microsoft.Extensions.Logging;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Components.Repository;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Utils;
using Tgstation.Server.Host.Utils.GitHub;
namespace Tgstation.Server.Host.Components.Deployment.Remote
{
@@ -20,7 +20,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
/// <summary>
/// The <see cref="IDatabaseContextFactory"/> for the <see cref="RemoteDeploymentManagerFactory"/>.
/// </summary>
readonly IGitHubClientFactory gitHubClientFactory;
readonly IGitHubServiceFactory gitHubServiceFactory;
/// <summary>
/// The <see cref="IGitRemoteFeaturesFactory"/> for the <see cref="RemoteDeploymentManagerFactory"/>.
@@ -41,19 +41,19 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
/// Initializes a new instance of the <see cref="RemoteDeploymentManagerFactory"/> class.
/// </summary>
/// <param name="databaseContextFactory">The value of <see cref="databaseContextFactory"/>.</param>
/// <param name="gitHubClientFactory">The value of <see cref="gitHubClientFactory"/>.</param>
/// <param name="gitHubServiceFactory">The value of <see cref="gitHubServiceFactory"/>.</param>
/// <param name="gitRemoteFeaturesFactory">The value of <see cref="gitRemoteFeaturesFactory"/>.</param>
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/>.</param>
/// <param name="logger">The value of <see cref="logger"/>.</param>
public RemoteDeploymentManagerFactory(
IDatabaseContextFactory databaseContextFactory,
IGitHubClientFactory gitHubClientFactory,
IGitHubServiceFactory gitHubServiceFactory,
IGitRemoteFeaturesFactory gitRemoteFeaturesFactory,
ILoggerFactory loggerFactory,
ILogger<RemoteDeploymentManagerFactory> logger)
{
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory));
this.gitHubServiceFactory = gitHubServiceFactory ?? throw new ArgumentNullException(nameof(gitHubServiceFactory));
this.gitRemoteFeaturesFactory = gitRemoteFeaturesFactory ?? throw new ArgumentNullException(nameof(gitRemoteFeaturesFactory));
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
@@ -70,7 +70,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
{
RemoteGitProvider.GitHub => new GitHubRemoteDeploymentManager(
databaseContextFactory,
gitHubClientFactory,
gitHubServiceFactory,
loggerFactory.CreateLogger<GitHubRemoteDeploymentManager>(),
metadata),
RemoteGitProvider.GitLab => new GitLabRemoteDeploymentManager(
@@ -0,0 +1,11 @@
using Microsoft.Extensions.Hosting;
namespace Tgstation.Server.Host.Components
{
/// <summary>
/// Represents a component meant to be started and stopped by its parent component.
/// </summary>
public interface IComponentService : IHostedService
{
}
}
@@ -1,13 +1,11 @@
using System;
using Microsoft.Extensions.Hosting;
namespace Tgstation.Server.Host.Components
{
/// <summary>
/// Component version of <see cref="IInstanceCore"/>.
/// </summary>
interface IInstance : IInstanceCore, IHostedService, IAsyncDisposable
interface IInstance : IInstanceCore, IComponentService, IAsyncDisposable
{
}
}
@@ -1,7 +1,5 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Tgstation.Server.Host.Components.Interop.Bridge;
using Tgstation.Server.Host.IO;
@@ -10,7 +8,7 @@ namespace Tgstation.Server.Host.Components
/// <summary>
/// Factory for creating <see cref="IInstance"/>s.
/// </summary>
interface IInstanceFactory : IHostedService
interface IInstanceFactory : IComponentService
{
/// <summary>
/// Create an <see cref="IInstance"/>.
@@ -58,9 +58,9 @@ namespace Tgstation.Server.Host.Components
readonly IAssemblyInformationProvider assemblyInformationProvider;
/// <summary>
/// The <see cref="IJobManager"/> for the <see cref="InstanceManager"/>.
/// The <see cref="IJobService"/> for the <see cref="InstanceManager"/>.
/// </summary>
readonly IJobManager jobManager;
readonly IJobService jobService;
/// <summary>
/// The <see cref="IServerControl"/> for the <see cref="InstanceManager"/>.
@@ -149,7 +149,7 @@ namespace Tgstation.Server.Host.Components
/// <param name="ioManager">The value of <paramref name="ioManager"/>.</param>
/// <param name="databaseContextFactory">The value of <paramref name="databaseContextFactory"/>.</param>
/// <param name="assemblyInformationProvider">The value of <see cref="assemblyInformationProvider"/>.</param>
/// <param name="jobManager">The value of <see cref="jobManager"/>.</param>
/// <param name="jobService">The value of <see cref="jobService"/>.</param>
/// <param name="serverControl">The value of <see cref="serverControl"/>.</param>
/// <param name="systemIdentityFactory">The value of <see cref="systemIdentityFactory"/>.</param>
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/>.</param>
@@ -163,7 +163,7 @@ namespace Tgstation.Server.Host.Components
IIOManager ioManager,
IDatabaseContextFactory databaseContextFactory,
IAssemblyInformationProvider assemblyInformationProvider,
IJobManager jobManager,
IJobService jobService,
IServerControl serverControl,
ISystemIdentityFactory systemIdentityFactory,
IAsyncDelayer asyncDelayer,
@@ -177,7 +177,7 @@ namespace Tgstation.Server.Host.Components
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
this.jobService = jobService ?? throw new ArgumentNullException(nameof(jobService));
this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory));
this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
@@ -344,7 +344,7 @@ namespace Tgstation.Server.Host.Components
})
.ToListAsync(cancellationToken);
foreach (var job in jobs)
tasks.Add(jobManager.CancelJob(job, user, true, cancellationToken));
tasks.Add(jobService.CancelJob(job, user, true, cancellationToken));
});
await Task.WhenAll(tasks);
@@ -444,7 +444,7 @@ namespace Tgstation.Server.Host.Components
}
var instanceFactoryStopTask = instanceFactory.StopAsync(cancellationToken);
await jobManager.StopAsync(cancellationToken);
await jobService.StopAsync(cancellationToken);
async Task OfflineInstanceImmediate(IInstance instance, CancellationToken cancellationToken)
{
@@ -563,7 +563,7 @@ namespace Tgstation.Server.Host.Components
.ToListAsync(cancellationToken));
var factoryStartup = instanceFactory.StartAsync(cancellationToken);
var jobManagerStartup = jobManager.StartAsync(cancellationToken);
var jobManagerStartup = jobService.StartAsync(cancellationToken);
await Task.WhenAll(instanceEnumeration, factoryStartup, jobManagerStartup);
@@ -582,7 +582,7 @@ namespace Tgstation.Server.Host.Components
await Task.WhenAll(instanceOnliningTasks);
jobManager.Activate(this);
jobService.Activate(this);
logger.LogInformation("Server ready!");
readyTcs.SetResult();
@@ -7,8 +7,7 @@ using Octokit;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.Utils;
using Tgstation.Server.Host.Utils.GitHub;
namespace Tgstation.Server.Host.Components.Repository
{
@@ -33,20 +32,20 @@ namespace Tgstation.Server.Host.Components.Repository
public override string RemoteRepositoryName { get; }
/// <summary>
/// The <see cref="IGitHubClientFactory"/> for the <see cref="GitHubRemoteFeatures"/>.
/// The <see cref="IGitHubServiceFactory"/> for the <see cref="GitHubRemoteFeatures"/>.
/// </summary>
readonly IGitHubClientFactory gitHubClientFactory;
readonly IGitHubServiceFactory gitHubServiceFactory;
/// <summary>
/// Initializes a new instance of the <see cref="GitHubRemoteFeatures"/> class.
/// </summary>
/// <param name="gitHubClientFactory">The value of <see cref="gitHubClientFactory"/>.</param>
/// <param name="gitHubServiceFactory">The value of <see cref="gitHubServiceFactory"/>.</param>
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="GitRemoteFeaturesBase"/>.</param>
/// <param name="remoteUrl">The remote repository <see cref="Uri"/>.</param>
public GitHubRemoteFeatures(IGitHubClientFactory gitHubClientFactory, ILogger<GitHubRemoteFeatures> logger, Uri remoteUrl)
public GitHubRemoteFeatures(IGitHubServiceFactory gitHubServiceFactory, ILogger<GitHubRemoteFeatures> logger, Uri remoteUrl)
: base(logger, remoteUrl)
{
this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory));
this.gitHubServiceFactory = gitHubServiceFactory ?? throw new ArgumentNullException(nameof(gitHubServiceFactory));
if (remoteUrl == null)
throw new ArgumentNullException(nameof(remoteUrl));
@@ -63,20 +62,16 @@ namespace Tgstation.Server.Host.Components.Repository
RepositorySettings repositorySettings,
CancellationToken cancellationToken)
{
var gitHubClient = repositorySettings.AccessToken != null
? gitHubClientFactory.CreateClient(repositorySettings.AccessToken)
: gitHubClientFactory.CreateClient();
var gitHubService = repositorySettings.AccessToken != null
? gitHubServiceFactory.CreateService(repositorySettings.AccessToken)
: gitHubServiceFactory.CreateService();
PullRequest pr = null;
ApiException exception = null;
string errorMessage = null;
try
{
pr = await gitHubClient
.PullRequest
.Get(RemoteRepositoryOwner, RemoteRepositoryName, parameters.Number)
.WithToken(cancellationToken)
;
pr = await gitHubService.GetPullRequest(RemoteRepositoryOwner, RemoteRepositoryName, parameters.Number, cancellationToken);
}
catch (RateLimitExceededException ex)
{
@@ -3,7 +3,7 @@
using Microsoft.Extensions.Logging;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Utils;
using Tgstation.Server.Host.Utils.GitHub;
namespace Tgstation.Server.Host.Components.Repository
{
@@ -11,9 +11,9 @@ namespace Tgstation.Server.Host.Components.Repository
sealed class GitRemoteFeaturesFactory : IGitRemoteFeaturesFactory
{
/// <summary>
/// The <see cref="IGitHubClientFactory"/> for the <see cref="GitRemoteFeaturesFactory"/>.
/// The <see cref="IGitHubServiceFactory"/> for the <see cref="GitRemoteFeaturesFactory"/>.
/// </summary>
readonly IGitHubClientFactory gitHubClientFactory;
readonly IGitHubServiceFactory gitHubServiceFactory;
/// <summary>
/// The <see cref="ILoggerFactory"/> for the <see cref="GitRemoteFeaturesFactory"/>.
@@ -28,15 +28,15 @@ namespace Tgstation.Server.Host.Components.Repository
/// <summary>
/// Initializes a new instance of the <see cref="GitRemoteFeaturesFactory"/> class.
/// </summary>
/// <param name="gitHubClientFactory">The value of <see cref="gitHubClientFactory"/>.</param>
/// <param name="gitHubServiceFactory">The value of <see cref="gitHubServiceFactory"/>.</param>
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/>.</param>
/// <param name="logger">The value of <see cref="logger"/>.</param>
public GitRemoteFeaturesFactory(
IGitHubClientFactory gitHubClientFactory,
IGitHubServiceFactory gitHubServiceFactory,
ILoggerFactory loggerFactory,
ILogger<GitRemoteFeaturesFactory> logger)
{
this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory));
this.gitHubServiceFactory = gitHubServiceFactory ?? throw new ArgumentNullException(nameof(gitHubServiceFactory));
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
@@ -52,7 +52,7 @@ namespace Tgstation.Server.Host.Components.Repository
return remoteGitProvider switch
{
RemoteGitProvider.GitHub => new GitHubRemoteFeatures(
gitHubClientFactory,
gitHubServiceFactory,
loggerFactory.CreateLogger<GitHubRemoteFeatures>(),
primaryRemote),
RemoteGitProvider.GitLab => new GitLabRemoteFeatures(
@@ -46,7 +46,7 @@ namespace Tgstation.Server.Host.Components.Repository
var repo = await Task.Factory.StartNew<LibGit2Sharp.IRepository>(
() =>
{
logger.LogTrace("Creating libgit2 repostory at {repoPath}...", path);
logger.LogTrace("Creating libgit2 repository at {repoPath}...", path);
return new LibGit2Sharp.Repository(path);
},
cancellationToken,
@@ -967,7 +967,8 @@ namespace Tgstation.Server.Host.Components.Repository
OnPackBuilderProgress = (stage, current, total) =>
{
var baseProgress = stage == PackBuilderStage.Counting ? 0 : 0.5;
progressReporter.ReportProgress(baseProgress + (0.5 * ((double)current / total)));
var addon = total > 0 && current <= total ? (0.5 * ((double)current / total)) : 0;
progressReporter.ReportProgress(baseProgress + addon);
return !cancellationToken.IsCancellationRequested;
},
OnNegotiationCompletedBeforePush = (a) =>
@@ -3,6 +3,7 @@ using System.Threading;
using System.Threading.Tasks;
using LibGit2Sharp;
using Microsoft.Extensions.Logging;
using Tgstation.Server.Api.Models;
@@ -142,7 +143,7 @@ namespace Tgstation.Server.Host.Components.Repository
{
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken))
{
logger.LogTrace("Semaphore acquired");
logger.LogTrace("Semaphore acquired for clone");
var repositoryPath = ioManager.ResolvePath();
if (!await ioManager.DirectoryExists(repositoryPath, cancellationToken))
try
@@ -167,8 +168,7 @@ namespace Tgstation.Server.Host.Components.Repository
url,
cloneOptions,
repositoryPath,
cancellationToken)
;
cancellationToken);
}
catch
{
@@ -192,14 +192,14 @@ namespace Tgstation.Server.Host.Components.Repository
return null;
}
}
logger.LogInformation("Clone complete!");
}
finally
{
logger.LogTrace("Semaphore released after clone");
CloneInProgress = false;
}
logger.LogInformation("Clone complete!");
return await LoadRepository(cancellationToken);
}
@@ -210,11 +210,13 @@ namespace Tgstation.Server.Host.Components.Repository
lock (semaphore)
if (CloneInProgress)
throw new JobException(ErrorCode.RepoCloning);
await semaphore.WaitAsync(cancellationToken);
try
{
await semaphore.WaitAsync(cancellationToken);
try
{
logger.LogTrace("Semaphore acquired for load");
var libGit2Repo = await repositoryFactory.CreateFromPath(ioManager.ResolvePath(), cancellationToken);
return new Repository(
@@ -235,6 +237,7 @@ namespace Tgstation.Server.Host.Components.Repository
}
catch
{
logger.LogTrace("Releasing semaphore as load failed");
semaphore.Release();
throw;
}
@@ -250,10 +253,17 @@ namespace Tgstation.Server.Host.Components.Repository
public async Task DeleteRepository(CancellationToken cancellationToken)
{
logger.LogInformation("Deleting repository...");
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken))
try
{
logger.LogTrace("Semaphore acquired, deleting Repository directory...");
await ioManager.DeleteDirectory(ioManager.ResolvePath(), cancellationToken);
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken))
{
logger.LogTrace("Semaphore acquired, deleting Repository directory...");
await ioManager.DeleteDirectory(ioManager.ResolvePath(), cancellationToken);
}
}
finally
{
logger.LogTrace("Semaphore released after delete attempt");
}
}
}
@@ -3,8 +3,6 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Host.Components.Events;
@@ -15,7 +13,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
/// <summary>
/// For managing the Configuration directory.
/// </summary>
public interface IConfiguration : IHostedService, IEventConsumer, IDisposable
public interface IConfiguration : IComponentService, IEventConsumer, IDisposable
{
/// <summary>
/// Copies all files in the CodeModifications directory to <paramref name="destination"/>.
@@ -2,8 +2,6 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Host.Components.Events;
@@ -14,7 +12,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// <summary>
/// Runs and monitors the twin server controllers.
/// </summary>
public interface IWatchdog : IHostedService, IAsyncDisposable, IEventConsumer, IRenameNotifyee
public interface IWatchdog : IComponentService, IAsyncDisposable, IEventConsumer, IRenameNotifyee
{
/// <summary>
/// The current <see cref="WatchdogStatus"/>.
@@ -39,11 +39,5 @@
/// Asset package containing the new <see cref="Host"/> assembly in zip form.
/// </summary>
public string UpdatePackageAssetName { get; set; } = DefaultUpdatePackageAssetName;
/// <summary>
/// Dump all retrieved releases from the GitHub API when a requested release is not found.
/// </summary>
/// <remarks>This is an internal config and may be adjusted or removed without a version change.</remarks>
public bool DumpReleasesOnNotFound { get; set; }
}
}
@@ -19,12 +19,11 @@ using Tgstation.Server.Api.Rights;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Security;
using Tgstation.Server.Host.System;
using Tgstation.Server.Host.Transfer;
using Tgstation.Server.Host.Utils;
using Tgstation.Server.Host.Utils.GitHub;
namespace Tgstation.Server.Host.Controllers
{
@@ -40,9 +39,9 @@ namespace Tgstation.Server.Host.Controllers
const string OctokitException = "Bad GitHub API response, check configuration!";
/// <summary>
/// The <see cref="IGitHubClientFactory"/> for the <see cref="AdministrationController"/>.
/// The <see cref="IGitHubService"/> for the <see cref="AdministrationController"/>.
/// </summary>
readonly IGitHubClientFactory gitHubClientFactory;
readonly IGitHubService gitHubService;
/// <summary>
/// The <see cref="IServerControl"/> for the <see cref="AdministrationController"/>.
@@ -74,11 +73,6 @@ namespace Tgstation.Server.Host.Controllers
/// </summary>
readonly IFileTransferTicketProvider fileTransferService;
/// <summary>
/// The <see cref="UpdatesConfiguration"/> for the <see cref="AdministrationController"/>.
/// </summary>
readonly UpdatesConfiguration updatesConfiguration;
/// <summary>
/// The <see cref="FileLoggingConfiguration"/> for the <see cref="AdministrationController"/>.
/// </summary>
@@ -89,7 +83,7 @@ namespace Tgstation.Server.Host.Controllers
/// </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="gitHubClientFactory">The value of <see cref="gitHubClientFactory"/>.</param>
/// <param name="gitHubService">The value of <see cref="gitHubService"/>.</param>
/// <param name="serverControl">The value of <see cref="serverControl"/>.</param>
/// <param name="serverUpdateInitiator">The value of <see cref="serverUpdateInitiator"/>.</param>
/// <param name="assemblyInformationProvider">The value of <see cref="assemblyInformationProvider"/>.</param>
@@ -97,12 +91,11 @@ namespace Tgstation.Server.Host.Controllers
/// <param name="platformIdentifier">The value of <see cref="platformIdentifier"/>.</param>
/// <param name="fileTransferService">The value of <see cref="fileTransferService"/>.</param>
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="ApiController"/>.</param>
/// <param name="updatesConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing value of <see cref="updatesConfiguration"/>.</param>
/// <param name="fileLoggingConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing value of <see cref="fileLoggingConfiguration"/>.</param>
public AdministrationController(
IDatabaseContext databaseContext,
IAuthenticationContextFactory authenticationContextFactory,
IGitHubClientFactory gitHubClientFactory,
IGitHubService gitHubService,
IServerControl serverControl,
IServerUpdateInitiator serverUpdateInitiator,
IAssemblyInformationProvider assemblyInformationProvider,
@@ -110,7 +103,6 @@ namespace Tgstation.Server.Host.Controllers
IPlatformIdentifier platformIdentifier,
IFileTransferTicketProvider fileTransferService,
ILogger<AdministrationController> logger,
IOptions<UpdatesConfiguration> updatesConfigurationOptions,
IOptions<FileLoggingConfiguration> fileLoggingConfigurationOptions)
: base(
databaseContext,
@@ -118,14 +110,13 @@ namespace Tgstation.Server.Host.Controllers
logger,
true)
{
this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory));
this.gitHubService = gitHubService ?? throw new ArgumentNullException(nameof(gitHubService));
this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
this.serverUpdateInitiator = serverUpdateInitiator ?? throw new ArgumentNullException(nameof(serverUpdateInitiator));
this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
this.fileTransferService = fileTransferService ?? throw new ArgumentNullException(nameof(fileTransferService));
updatesConfiguration = updatesConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(updatesConfigurationOptions));
fileLoggingConfiguration = fileLoggingConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(fileLoggingConfigurationOptions));
}
@@ -150,26 +141,19 @@ namespace Tgstation.Server.Host.Controllers
Uri repoUrl = null;
try
{
var gitHubClient = gitHubClientFactory.CreateClient();
var repositoryTask = gitHubClient
.Repository
.Get(updatesConfiguration.GitHubRepositoryId)
.WithToken(cancellationToken);
var releases = (await gitHubClient
.Repository
.Release
.GetAll(updatesConfiguration.GitHubRepositoryId)
.WithToken(cancellationToken))
.Where(x => x.TagName.StartsWith(
updatesConfiguration.GitTagPrefix,
StringComparison.InvariantCulture));
var repositoryUrlTask = gitHubService.GetUpdatesRepositoryUrl(cancellationToken);
var releases = await gitHubService.GetTgsReleases(cancellationToken);
foreach (var release in releases)
if (Version.TryParse(release.TagName.Replace(updatesConfiguration.GitTagPrefix, String.Empty, StringComparison.Ordinal), out var version)
&& version.Major > 3 // Forward/backward compatible but not before TGS4
foreach (var kvp in releases)
{
var version = kvp.Key;
var release = kvp.Value;
if (version.Major > 3 // Forward/backward compatible but not before TGS4
&& (greatestVersion == null || version > greatestVersion))
greatestVersion = version;
repoUrl = new Uri((await repositoryTask).HtmlUrl);
}
repoUrl = await repositoryUrlTask;
}
catch (NotFoundException e)
{
@@ -367,13 +367,15 @@ namespace Tgstation.Server.Host.Core
// configure misc services
services.AddSingleton<IProcessExecutor, ProcessExecutor>();
services.AddSingleton<ISynchronousIOManager, SynchronousIOManager>();
services.AddSingleton<IFileDownloader, FileDownloader>();
services.AddFileDownloader();
services.AddSingleton<IServerPortProvider, ServerPortProivder>();
services.AddSingleton<ITopicClientFactory, TopicClientFactory>();
services.AddSingleton<IGitHubClientFactory, GitHubClientFactory>();
services.AddGitHub();
// configure root services
services.AddSingleton<IJobManager, JobManager>();
services.AddSingleton<IJobService, JobService>();
services.AddSingleton<IJobManager>(x => x.GetRequiredService<IJobService>());
services.AddSingleton<InstanceManager>();
services.AddSingleton<IBridgeDispatcher>(x => x.GetRequiredService<InstanceManager>());
+32 -59
View File
@@ -8,10 +8,9 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Swarm;
using Tgstation.Server.Host.Utils;
using Tgstation.Server.Host.Utils.GitHub;
namespace Tgstation.Server.Host.Core
{
@@ -19,9 +18,9 @@ namespace Tgstation.Server.Host.Core
sealed class ServerUpdater : IServerUpdater, IServerUpdateExecutor
{
/// <summary>
/// The <see cref="IGitHubClientFactory"/> for the <see cref="ServerUpdater"/>.
/// The <see cref="IGitHubService"/> for the <see cref="ServerUpdater"/>.
/// </summary>
readonly IGitHubClientFactory gitHubClientFactory;
readonly IGitHubService gitHubService;
/// <summary>
/// The <see cref="IIOManager"/> for the <see cref="ServerUpdater"/>.
@@ -61,7 +60,7 @@ namespace Tgstation.Server.Host.Core
/// <summary>
/// Initializes a new instance of the <see cref="ServerUpdater"/> class.
/// </summary>
/// <param name="gitHubClientFactory">The value of <see cref="gitHubClientFactory"/>.</param>
/// <param name="gitHubService">The value of <see cref="gitHubService"/>.</param>
/// <param name="ioManager">The value of <see cref="ioManager"/>.</param>
/// <param name="fileDownloader">The value of <see cref="fileDownloader"/>.</param>
/// <param name="serverControl">The value of <see cref="serverControl"/>.</param>
@@ -69,7 +68,7 @@ namespace Tgstation.Server.Host.Core
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="generalConfiguration"/>.</param>
/// <param name="updatesConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="updatesConfiguration"/>.</param>
public ServerUpdater(
IGitHubClientFactory gitHubClientFactory,
IGitHubService gitHubService,
IIOManager ioManager,
IFileDownloader fileDownloader,
IServerControl serverControl,
@@ -77,7 +76,7 @@ namespace Tgstation.Server.Host.Core
IOptions<GeneralConfiguration> generalConfigurationOptions,
IOptions<UpdatesConfiguration> updatesConfigurationOptions)
{
this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory));
this.gitHubService = gitHubService ?? throw new ArgumentNullException(nameof(gitHubService));
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.fileDownloader = fileDownloader ?? throw new ArgumentNullException(nameof(fileDownloader));
this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
@@ -224,64 +223,38 @@ namespace Tgstation.Server.Host.Core
async Task<ServerUpdateResult> BeginUpdateImpl(ISwarmService swarmService, Version newVersion, bool recursed, CancellationToken cancellationToken)
{
logger.LogDebug("Looking for GitHub releases version {version}...", newVersion);
var gitHubClient = gitHubClientFactory.CreateClient();
var releases = await gitHubClient
.Repository
.Release
.GetAll(updatesConfiguration.GitHubRepositoryId)
.WithToken(cancellationToken);
logger.LogTrace("Received {releaseCount} total releases from GitHub", releases.Count);
var filteredReleases = releases
.Where(x => x.TagName.StartsWith(updatesConfiguration.GitTagPrefix, StringComparison.InvariantCulture))
.ToList();
logger.LogTrace(
"Filtered to {releaseCount} releases matching the configured tag prefix of \"{tagPrefix}\"",
filteredReleases.Count,
updatesConfiguration.GitTagPrefix);
foreach (var release in filteredReleases)
if (Version.TryParse(
release.TagName.Replace(
updatesConfiguration.GitTagPrefix, String.Empty, StringComparison.Ordinal),
out var version))
var releases = await gitHubService.GetTgsReleases(cancellationToken);
foreach (var kvp in releases)
{
var version = kvp.Key;
var release = kvp.Value;
if (version == newVersion)
{
if (version == newVersion)
var asset = release.Assets.Where(x => x.Name.Equals(updatesConfiguration.UpdatePackageAssetName, StringComparison.Ordinal)).FirstOrDefault();
if (asset == default)
continue;
serverUpdateOperation = new ServerUpdateOperation
{
var asset = release.Assets.Where(x => x.Name.Equals(updatesConfiguration.UpdatePackageAssetName, StringComparison.Ordinal)).FirstOrDefault();
if (asset == default)
continue;
TargetVersion = version,
UpdateZipUrl = new Uri(asset.BrowserDownloadUrl),
SwarmService = swarmService,
};
serverUpdateOperation = new ServerUpdateOperation
{
TargetVersion = version,
UpdateZipUrl = new Uri(asset.BrowserDownloadUrl),
SwarmService = swarmService,
};
try
{
if (!serverControl.TryStartUpdate(this, version))
return ServerUpdateResult.UpdateInProgress;
}
finally
{
serverUpdateOperation = null;
}
return ServerUpdateResult.Started;
try
{
if (!serverControl.TryStartUpdate(this, version))
return ServerUpdateResult.UpdateInProgress;
}
finally
{
serverUpdateOperation = null;
}
}
else
logger.LogDebug("Unparsable release tag: {releaseTag}", release.TagName);
if (updatesConfiguration.DumpReleasesOnNotFound)
logger.LogInformation(
"Found releases:{newline}\t{releases}",
Environment.NewLine,
String.Join($"{Environment.NewLine}\t", releases.Select(x => x.TagName).OrderBy(x => x)));
return ServerUpdateResult.Started;
}
}
if (!recursed)
{
@@ -14,7 +14,9 @@ using Serilog.Sinks.Elasticsearch;
using Tgstation.Server.Host.Components.Chat.Providers;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Utils;
using Tgstation.Server.Host.Utils.GitHub;
namespace Tgstation.Server.Host.Extensions
{
@@ -26,13 +28,31 @@ namespace Tgstation.Server.Host.Extensions
/// <summary>
/// The <see cref="IProviderFactory"/> implementation used in calls to <see cref="AddChatProviderFactory(IServiceCollection)"/>.
/// </summary>
static Type chatProviderFactoryType = typeof(ProviderFactory);
static Type chatProviderFactoryType;
/// <summary>
/// The <see cref="IGitHubServiceFactory"/> implementation used in calls to <see cref="AddGitHub(IServiceCollection)"/>.
/// </summary>
static Type gitHubServiceFactoryType;
/// <summary>
/// The <see cref="IFileDownloader"/> implementation used in calls to <see cref="AddFileDownloader(IServiceCollection)"/>.
/// </summary>
static Type fileDownloaderType;
/// <summary>
/// A <see cref="ServiceDescriptor"/> for an additional <see cref="ILoggerProvider"/> to use.
/// </summary>
static ServiceDescriptor additionalLoggerProvider;
/// <summary>
/// Initializes static members of the <see cref="ServiceCollectionExtensions"/> class.
/// </summary>
static ServiceCollectionExtensions()
{
UseDefaultServices();
}
/// <summary>
/// Change the <see cref="Type"/> used as an implementation for calls to <see cref="AddChatProviderFactory(IServiceCollection)"/>.
/// </summary>
@@ -42,6 +62,56 @@ namespace Tgstation.Server.Host.Extensions
chatProviderFactoryType = typeof(TProviderFactory);
}
/// <summary>
/// Change the <see cref="Type"/> used as an implementation for calls to <see cref="AddGitHub(IServiceCollection)"/>.
/// </summary>
/// <typeparam name="TGitHubServiceFactory">The <see cref="IGitHubServiceFactory"/> implementation to use.</typeparam>
public static void UseGitHubServiceFactory<TGitHubServiceFactory>() where TGitHubServiceFactory : IGitHubServiceFactory
{
gitHubServiceFactoryType = typeof(TGitHubServiceFactory);
}
/// <summary>
/// Change the <see cref="Type"/> used as an implementation for calls to <see cref="AddGitHub(IServiceCollection)"/>.
/// </summary>
/// <typeparam name="TFileDownloader">The <see cref="IFileDownloader"/> implementation to use.</typeparam>
public static void UseFileDownloader<TFileDownloader>() where TFileDownloader : IFileDownloader
{
fileDownloaderType = typeof(TFileDownloader);
}
/// <summary>
/// Adds a <see cref="IFileDownloader"/> implementation to the given <paramref name="serviceCollection"/>.
/// </summary>
/// <param name="serviceCollection">The <see cref="IServiceCollection"/> to configure.</param>
/// <returns><paramref name="serviceCollection"/>.</returns>
public static IServiceCollection AddFileDownloader(this IServiceCollection serviceCollection)
{
if (serviceCollection == null)
throw new ArgumentNullException(nameof(serviceCollection));
serviceCollection.AddSingleton(typeof(IFileDownloader), fileDownloaderType);
return serviceCollection;
}
/// <summary>
/// Adds a <see cref="IGitHubServiceFactory"/> implementation to the given <paramref name="serviceCollection"/>.
/// </summary>
/// <param name="serviceCollection">The <see cref="IServiceCollection"/> to configure.</param>
/// <returns><paramref name="serviceCollection"/>.</returns>
public static IServiceCollection AddGitHub(this IServiceCollection serviceCollection)
{
if (serviceCollection == null)
throw new ArgumentNullException(nameof(serviceCollection));
serviceCollection.AddSingleton<IGitHubClientFactory, GitHubClientFactory>();
serviceCollection.AddSingleton(typeof(IGitHubServiceFactory), gitHubServiceFactoryType);
serviceCollection.AddSingleton(x => x.GetRequiredService<IGitHubServiceFactory>().CreateService());
return serviceCollection;
}
/// <summary>
/// Add an additional <see cref="ILoggerProvider"/> to <see cref="IServiceCollection"/>s that call <see cref="SetupLogging(IServiceCollection, Action{LoggerConfiguration}, Action{LoggerSinkConfiguration}, ElasticsearchConfiguration)"/>.
/// </summary>
@@ -60,6 +130,9 @@ namespace Tgstation.Server.Host.Extensions
/// <returns><paramref name="serviceCollection"/>.</returns>
public static IServiceCollection AddChatProviderFactory(this IServiceCollection serviceCollection)
{
if (serviceCollection == null)
throw new ArgumentNullException(nameof(serviceCollection));
return serviceCollection.AddSingleton(typeof(IProviderFactory), chatProviderFactoryType);
}
@@ -154,5 +227,15 @@ namespace Tgstation.Server.Host.Extensions
if (additionalLoggerProvider != null)
builder.Services.TryAddEnumerable(additionalLoggerProvider);
});
/// <summary>
/// Set the modifiable services to their default types.
/// </summary>
static void UseDefaultServices()
{
UseChatProviderFactory<ProviderFactory>();
UseGitHubServiceFactory<GitHubServiceFactory>();
UseFileDownloader<FileDownloader>();
}
}
}
@@ -1,9 +1,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Host.Components;
using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Jobs
@@ -11,7 +9,7 @@ namespace Tgstation.Server.Host.Jobs
/// <summary>
/// Manages the runtime of <see cref="Job"/>s.
/// </summary>
public interface IJobManager : IHostedService
public interface IJobManager
{
/// <summary>
/// Set the <see cref="JobResponse.Progress"/> and <see cref="JobResponse.Stage"/> for a given <paramref name="apiResponse"/>.
@@ -47,11 +45,5 @@ namespace Tgstation.Server.Host.Jobs
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the updated <paramref name="job"/> if it was cancelled, <see langword="null"/> if it couldn't be found.</returns>
Task<Job> CancelJob(Job job, User user, bool blocking, CancellationToken cancellationToken);
/// <summary>
/// Activate the <see cref="IJobManager"/>.
/// </summary>
/// <param name="instanceCoreProvider">The <see cref="IInstanceCoreProvider"/> for the <see cref="IJobManager"/>.</param>
void Activate(IInstanceCoreProvider instanceCoreProvider);
}
}
@@ -0,0 +1,16 @@
using Tgstation.Server.Host.Components;
namespace Tgstation.Server.Host.Jobs
{
/// <summary>
/// The service that manages everything to do with jobs.
/// </summary>
public interface IJobService : IJobManager, IComponentService
{
/// <summary>
/// Activate the <see cref="IJobManager"/>.
/// </summary>
/// <param name="instanceCoreProvider">The <see cref="IInstanceCoreProvider"/> for the <see cref="IJobManager"/>.</param>
void Activate(IInstanceCoreProvider instanceCoreProvider);
}
}
@@ -17,22 +17,22 @@ using Tgstation.Server.Host.Utils;
namespace Tgstation.Server.Host.Jobs
{
/// <inheritdoc />
sealed class JobManager : IJobManager, IDisposable
sealed class JobService : IJobService, IDisposable
{
/// <summary>
/// The <see cref="IServiceProvider"/> for the <see cref="JobManager"/>.
/// The <see cref="IServiceProvider"/> for the <see cref="JobService"/>.
/// </summary>
readonly IDatabaseContextFactory databaseContextFactory;
/// <summary>
/// The <see cref="ILoggerFactory"/> for the <see cref="JobManager"/>.
/// The <see cref="ILoggerFactory"/> for the <see cref="JobService"/>.
/// </summary>
readonly ILoggerFactory loggerFactory;
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="JobManager"/>.
/// The <see cref="ILogger"/> for the <see cref="JobService"/>.
/// </summary>
readonly ILogger<JobManager> logger;
readonly ILogger<JobService> logger;
/// <summary>
/// <see cref="Dictionary{TKey, TValue}"/> of <see cref="Job"/> <see cref="Api.Models.EntityId.Id"/>s to running <see cref="JobHandler"/>s.
@@ -60,15 +60,15 @@ namespace Tgstation.Server.Host.Jobs
volatile bool noMoreJobsShouldStart;
/// <summary>
/// Initializes a new instance of the <see cref="JobManager"/> class.
/// Initializes a new instance of the <see cref="JobService"/> class.
/// </summary>
/// <param name="databaseContextFactory">The value of <see cref="databaseContextFactory"/>.</param>
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/>.</param>
/// <param name="logger">The value of <see cref="logger"/>.</param>
public JobManager(
public JobService(
IDatabaseContextFactory databaseContextFactory,
ILoggerFactory loggerFactory,
ILogger<JobManager> logger)
ILogger<JobService> logger)
{
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
@@ -8,7 +8,7 @@ using Octokit;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Utils;
using Tgstation.Server.Host.Utils.GitHub;
namespace Tgstation.Server.Host.Security.OAuth
{
@@ -21,9 +21,9 @@ namespace Tgstation.Server.Host.Security.OAuth
public OAuthProvider Provider => OAuthProvider.GitHub;
/// <summary>
/// The <see cref="IGitHubClientFactory"/> for the <see cref="GitHubOAuthValidator"/>.
/// The <see cref="IGitHubServiceFactory"/> for the <see cref="GitHubOAuthValidator"/>.
/// </summary>
readonly IGitHubClientFactory gitHubClientFactory;
readonly IGitHubServiceFactory gitHubServiceFactory;
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="GitHubOAuthValidator"/>.
@@ -38,15 +38,15 @@ namespace Tgstation.Server.Host.Security.OAuth
/// <summary>
/// Initializes a new instance of the <see cref="GitHubOAuthValidator"/> class.
/// </summary>
/// <param name="gitHubClientFactory">The value of <see cref="gitHubClientFactory"/>.</param>
/// <param name="gitHubServiceFactory">The value of <see cref="gitHubServiceFactory"/>.</param>
/// <param name="logger">The value of <see cref="logger"/>.</param>
/// <param name="oAuthConfiguration">The value of <see cref="oAuthConfiguration"/>.</param>
public GitHubOAuthValidator(
IGitHubClientFactory gitHubClientFactory,
IGitHubServiceFactory gitHubServiceFactory,
ILogger<GitHubOAuthValidator> logger,
OAuthConfiguration oAuthConfiguration)
{
this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory));
this.gitHubServiceFactory = gitHubServiceFactory ?? throw new ArgumentNullException(nameof(gitHubServiceFactory));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.oAuthConfiguration = oAuthConfiguration ?? throw new ArgumentNullException(nameof(oAuthConfiguration));
}
@@ -57,35 +57,21 @@ namespace Tgstation.Server.Host.Security.OAuth
if (code == null)
throw new ArgumentNullException(nameof(code));
var client = gitHubClientFactory.CreateClient();
try
{
logger.LogTrace("Validating response code...");
var response = await client
.Oauth
.CreateAccessToken(
new OauthTokenRequest(
oAuthConfiguration.ClientId,
oAuthConfiguration.ClientSecret,
code)
{
RedirectUri = oAuthConfiguration.RedirectUrl,
})
;
var token = response.AccessToken;
var gitHubService = gitHubServiceFactory.CreateService();
var token = await gitHubService.CreateOAuthAccessToken(oAuthConfiguration, code, cancellationToken);
if (token == null)
return null;
var authenticatedClient = gitHubClientFactory.CreateClient(token);
var authenticatedClient = gitHubServiceFactory.CreateService(token);
logger.LogTrace("Getting user details...");
var userDetails = await authenticatedClient
.User
.Current()
;
var userId = await authenticatedClient.GetCurrentUserId(cancellationToken);
return userDetails.Id.ToString(CultureInfo.InvariantCulture);
return userId.ToString(CultureInfo.InvariantCulture);
}
catch (RateLimitExceededException)
{
@@ -10,7 +10,7 @@ using Microsoft.Extensions.Options;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Common;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Utils;
using Tgstation.Server.Host.Utils.GitHub;
namespace Tgstation.Server.Host.Security.OAuth
{
@@ -25,12 +25,12 @@ namespace Tgstation.Server.Host.Security.OAuth
/// <summary>
/// Initializes a new instance of the <see cref="OAuthProviders"/> class.
/// </summary>
/// <param name="gitHubClientFactory">The <see cref="IGitHubClientFactory"/> to use.</param>
/// <param name="gitHubServiceFactory">The <see cref="IGitHubServiceFactory"/> to use.</param>
/// <param name="httpClientFactory">The <see cref="IAbstractHttpClientFactory"/> to use.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/> to use.</param>
/// <param name="securityConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the <see cref="SecurityConfiguration"/> to use.</param>
public OAuthProviders(
IGitHubClientFactory gitHubClientFactory,
IGitHubServiceFactory gitHubServiceFactory,
IAbstractHttpClientFactory httpClientFactory,
ILoggerFactory loggerFactory,
IOptions<SecurityConfiguration> securityConfigurationOptions)
@@ -49,7 +49,7 @@ namespace Tgstation.Server.Host.Security.OAuth
if (securityConfiguration.OAuth.TryGetValue(OAuthProvider.GitHub, out var gitHubConfig))
validatorsBuilder.Add(
new GitHubOAuthValidator(
gitHubClientFactory,
gitHubServiceFactory,
loggerFactory.CreateLogger<GitHubOAuthValidator>(),
gitHubConfig));
@@ -9,7 +9,7 @@ using Octokit;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.Utils
namespace Tgstation.Server.Host.Utils.GitHub
{
/// <inheritdoc />
sealed class GitHubClientFactory : IGitHubClientFactory
@@ -67,7 +67,9 @@ namespace Tgstation.Server.Host.Utils
public IGitHubClient CreateClient() => GetOrCreateClient(generalConfiguration.GitHubAccessToken);
/// <inheritdoc />
public IGitHubClient CreateClient(string accessToken) => GetOrCreateClient(accessToken ?? throw new ArgumentNullException(nameof(accessToken)));
public IGitHubClient CreateClient(string accessToken)
=> GetOrCreateClient(
accessToken ?? throw new ArgumentNullException(nameof(accessToken)));
/// <summary>
/// Retrieve a <see cref="GitHubClient"/> from the <see cref="clientCache"/> or add a new one based on a given <paramref name="accessToken"/>.
@@ -0,0 +1,280 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Octokit;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Extensions;
namespace Tgstation.Server.Host.Utils.GitHub
{
/// <summary>
/// Service for interacting with the GitHub API. Authenticated or otherwise.
/// </summary>
sealed class GitHubService : IAuthenticatedGitHubService
{
/// <summary>
/// The <see cref="IGitHubClient"/> for the <see cref="GitHubService"/>.
/// </summary>
readonly IGitHubClient gitHubClient;
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="GitHubService"/>.
/// </summary>
readonly ILogger<GitHubService> logger;
/// <summary>
/// The <see cref="UpdatesConfiguration"/> for the <see cref="GitHubService"/>.
/// </summary>
readonly UpdatesConfiguration updatesConfiguration;
/// <summary>
/// Initializes a new instance of the <see cref="GitHubService"/> class.
/// </summary>
/// <param name="gitHubClient">The value of <see cref="gitHubClient"/>.</param>
/// <param name="logger">The value of <see cref="logger"/>.</param>
/// <param name="updatesConfiguration">The value of <see cref="updatesConfiguration"/>.</param>
public GitHubService(IGitHubClient gitHubClient, ILogger<GitHubService> logger, UpdatesConfiguration updatesConfiguration)
{
this.gitHubClient = gitHubClient ?? throw new ArgumentNullException(nameof(gitHubClient));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.updatesConfiguration = updatesConfiguration ?? throw new ArgumentNullException(nameof(updatesConfiguration));
}
/// <inheritdoc />
public async Task<string> CreateOAuthAccessToken(OAuthConfiguration oAuthConfiguration, string code, CancellationToken cancellationToken)
{
if (oAuthConfiguration == null)
throw new ArgumentNullException(nameof(oAuthConfiguration));
if (code == null)
throw new ArgumentNullException(nameof(code));
logger.LogTrace("CreateOAuthAccessToken");
var response = await gitHubClient
.Oauth
.CreateAccessToken(
new OauthTokenRequest(
oAuthConfiguration.ClientId,
oAuthConfiguration.ClientSecret,
code)
{
RedirectUri = oAuthConfiguration.RedirectUrl,
})
.WithToken(cancellationToken);
var token = response.AccessToken;
return token;
}
/// <inheritdoc />
public async Task<Dictionary<Version, Release>> GetTgsReleases(CancellationToken cancellationToken)
{
logger.LogTrace("GetTgsReleases");
var allReleases = await gitHubClient
.Repository
.Release
.GetAll(updatesConfiguration.GitHubRepositoryId)
.WithToken(cancellationToken);
logger.LogTrace("{totalReleases} total releases", allReleases.Count);
var releases = allReleases
.Where(release =>
{
if (!release.PublishedAt.HasValue)
{
logger.LogDebug("Release tag without PublishedAt: {releaseTag}", release.TagName);
return false;
}
if (!release.TagName.StartsWith(updatesConfiguration.GitTagPrefix, StringComparison.InvariantCulture))
return false;
return true;
})
.GroupBy(release =>
{
if (!Version.TryParse(release.TagName.Replace(updatesConfiguration.GitTagPrefix, String.Empty, StringComparison.Ordinal), out var version))
{
logger.LogDebug("Unparsable release tag: {releaseTag}", release.TagName);
return null;
}
return version;
})
.Where(grouping => grouping.Key != null)
// GitHub can return the same result twice or some other nonsense
.Select(grouping => Tuple.Create(grouping.Key, grouping.OrderBy(x => x.PublishedAt.Value).First()))
.ToDictionary(tuple => tuple.Item1, tuple => tuple.Item2);
logger.LogTrace("{parsedReleases} parsed releases", releases.Count);
return releases;
}
/// <inheritdoc />
public async Task<Uri> GetUpdatesRepositoryUrl(CancellationToken cancellationToken)
{
logger.LogTrace("GetUpdatesRepositoryUrl");
var repository = await gitHubClient
.Repository
.Get(updatesConfiguration.GitHubRepositoryId)
.WithToken(cancellationToken);
var repoUrl = new Uri(repository.HtmlUrl);
logger.LogTrace("Maps to {repostioryUrl}", repoUrl);
return repoUrl;
}
/// <inheritdoc />
public async Task<int> GetCurrentUserId(CancellationToken cancellationToken)
{
logger.LogTrace("CreateOAuthAccessToken");
var userDetails = await gitHubClient.User.Current().WithToken(cancellationToken);
return userDetails.Id;
}
/// <inheritdoc />
public Task CommentOnIssue(string repoOwner, string repoName, string comment, int issueNumber, CancellationToken cancellationToken)
{
if (repoOwner == null)
throw new ArgumentNullException(nameof(repoOwner));
if (repoName == null)
throw new ArgumentNullException(nameof(repoName));
if (comment == null)
throw new ArgumentNullException(nameof(comment));
logger.LogTrace("CommentOnIssue");
return gitHubClient
.Issue
.Comment
.Create(
repoOwner,
repoName,
issueNumber,
comment)
.WithToken(cancellationToken);
}
/// <inheritdoc />
public async Task<long> GetRepositoryId(string repoOwner, string repoName, CancellationToken cancellationToken)
{
if (repoOwner == null)
throw new ArgumentNullException(nameof(repoOwner));
if (repoName == null)
throw new ArgumentNullException(nameof(repoName));
logger.LogTrace("GetRepositoryId");
var repo = await gitHubClient
.Repository
.Get(
repoOwner,
repoName)
.WithToken(cancellationToken);
return repo.Id;
}
/// <inheritdoc />
public async Task<int> CreateDeployment(NewDeployment newDeployment, string repoOwner, string repoName, CancellationToken cancellationToken)
{
if (newDeployment == null)
throw new ArgumentNullException(nameof(newDeployment));
if (repoOwner == null)
throw new ArgumentNullException(nameof(repoOwner));
if (repoName == null)
throw new ArgumentNullException(nameof(repoName));
logger.LogTrace("CreateDeployment");
var deployment = await gitHubClient
.Repository
.Deployment
.Create(
repoOwner,
repoName,
newDeployment)
.WithToken(cancellationToken);
return deployment.Id;
}
/// <inheritdoc />
public Task CreateDeploymentStatus(NewDeploymentStatus newDeploymentStatus, string repoOwner, string repoName, int deploymentId, CancellationToken cancellationToken)
{
if (newDeploymentStatus == null)
throw new ArgumentNullException(nameof(newDeploymentStatus));
if (repoOwner == null)
throw new ArgumentNullException(nameof(repoOwner));
if (repoName == null)
throw new ArgumentNullException(nameof(repoName));
logger.LogTrace("CreateDeploymentStatus");
return gitHubClient
.Repository
.Deployment
.Status
.Create(
repoOwner,
repoName,
deploymentId,
newDeploymentStatus)
.WithToken(cancellationToken);
}
/// <inheritdoc />
public Task CreateDeploymentStatus(NewDeploymentStatus newDeploymentStatus, long repoId, int deploymentId, CancellationToken cancellationToken)
{
if (newDeploymentStatus == null)
throw new ArgumentNullException(nameof(newDeploymentStatus));
logger.LogTrace("CreateDeploymentStatus");
return gitHubClient
.Repository
.Deployment
.Status
.Create(
repoId,
deploymentId,
newDeploymentStatus)
.WithToken(cancellationToken);
}
/// <inheritdoc />
public Task<PullRequest> GetPullRequest(string repoOwner, string repoName, int pullRequestNumber, CancellationToken cancellationToken)
{
if (repoOwner == null)
throw new ArgumentNullException(nameof(repoOwner));
if (repoName == null)
throw new ArgumentNullException(nameof(repoName));
logger.LogTrace("GetPullRequest");
return gitHubClient
.Repository
.PullRequest
.Get(
repoOwner,
repoName,
pullRequestNumber)
.WithToken(cancellationToken);
}
}
}
@@ -0,0 +1,66 @@
using System;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Octokit;
using Tgstation.Server.Host.Configuration;
namespace Tgstation.Server.Host.Utils.GitHub
{
/// <inheritdoc />
sealed class GitHubServiceFactory : IGitHubServiceFactory
{
/// <summary>
/// The <see cref="IGitHubClientFactory"/> for the <see cref="GitHubServiceFactory"/>.
/// </summary>
readonly IGitHubClientFactory gitHubClientFactory;
/// <summary>
/// The <see cref="ILoggerFactory"/> for the <see cref="GitHubServiceFactory"/>.
/// </summary>
readonly ILoggerFactory loggerFactory;
/// <summary>
/// The <see cref="UpdatesConfiguration"/> for the <see cref="GitHubServiceFactory"/>.
/// </summary>
readonly UpdatesConfiguration updatesConfiguration;
/// <summary>
/// Initializes a new instance of the <see cref="GitHubServiceFactory"/> class.
/// </summary>
/// <param name="gitHubClientFactory">The value of <see cref="gitHubClientFactory"/>.</param>
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/>.</param>
/// <param name="updatesConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing value of <see cref="updatesConfiguration"/>.</param>
public GitHubServiceFactory(
IGitHubClientFactory gitHubClientFactory,
ILoggerFactory loggerFactory,
IOptions<UpdatesConfiguration> updatesConfigurationOptions)
{
this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory));
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
updatesConfiguration = updatesConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(updatesConfigurationOptions));
}
/// <inheritdoc />
public IGitHubService CreateService() => CreateServiceImpl(gitHubClientFactory.CreateClient());
/// <inheritdoc />
public IAuthenticatedGitHubService CreateService(string accessToken)
=> CreateServiceImpl(
gitHubClientFactory.CreateClient(
accessToken ?? throw new ArgumentNullException(nameof(accessToken))));
/// <summary>
/// Create a <see cref="GitHubService"/>.
/// </summary>
/// <param name="gitHubClient">The <see cref="IGitHubClient"/> for the <see cref="GitHubService"/>.</param>
/// <returns>A new <see cref="GitHubService"/>.</returns>
GitHubService CreateServiceImpl(IGitHubClient gitHubClient)
=> new (
gitHubClient,
loggerFactory.CreateLogger<GitHubService>(),
updatesConfiguration);
}
}
@@ -0,0 +1,55 @@
using System.Threading;
using System.Threading.Tasks;
using Octokit;
namespace Tgstation.Server.Host.Utils.GitHub
{
/// <summary>
/// <see cref="IGitHubService"/> that exposes functions that require authentication.
/// </summary>
public interface IAuthenticatedGitHubService : IGitHubService
{
/// <summary>
/// Create a comment on a given <paramref name="issueNumber"/>.
/// </summary>
/// <param name="repoOwner">The owner of the target repository.</param>
/// <param name="repoName">The name of the target repository.</param>
/// <param name="comment">The text of the comment.</param>
/// <param name="issueNumber">The number of the issue to comment on.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
Task CommentOnIssue(string repoOwner, string repoName, string comment, int issueNumber, CancellationToken cancellationToken);
/// <summary>
/// Create a <paramref name="newDeployment"/> on a target repostiory.
/// </summary>
/// <param name="newDeployment">The <see cref="NewDeployment"/>.</param>
/// <param name="repoOwner">The owner of the target repository.</param>
/// <param name="repoName">The name of the target repository.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the new deployment's ID.</returns>
Task<int> CreateDeployment(NewDeployment newDeployment, string repoOwner, string repoName, CancellationToken cancellationToken);
/// <summary>
/// Create a <paramref name="newDeploymentStatus"/> on a target deployment.
/// </summary>
/// <param name="newDeploymentStatus">The <see cref="NewDeploymentStatus"/>.</param>
/// <param name="repoOwner">The owner of the target repository.</param>
/// <param name="repoName">The name of the target repository.</param>
/// <param name="deploymentId">The ID of the parent deployment.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
Task CreateDeploymentStatus(NewDeploymentStatus newDeploymentStatus, string repoOwner, string repoName, int deploymentId, CancellationToken cancellationToken);
/// <summary>
/// Create a <paramref name="newDeploymentStatus"/> on a target deployment.
/// </summary>
/// <param name="newDeploymentStatus">The <see cref="NewDeploymentStatus"/>.</param>
/// <param name="repoId">The ID of the target repository.</param>
/// <param name="deploymentId">The ID of the parent deployment.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
Task CreateDeploymentStatus(NewDeploymentStatus newDeploymentStatus, long repoId, int deploymentId, CancellationToken cancellationToken);
}
}
@@ -1,6 +1,6 @@
using Octokit;
namespace Tgstation.Server.Host.Utils
namespace Tgstation.Server.Host.Utils.GitHub
{
/// <summary>
/// For creating <see cref="IGitHubClient"/>s.
@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Octokit;
using Tgstation.Server.Host.Configuration;
namespace Tgstation.Server.Host.Utils.GitHub
{
/// <summary>
/// Service for interacting with the GitHub API.
/// </summary>
public interface IGitHubService
{
/// <summary>
/// Gets the <see cref="Uri"/> of the repository designated as the updates repository.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="Uri"/> of the designated updates repository.</returns>
Task<Uri> GetUpdatesRepositoryUrl(CancellationToken cancellationToken);
/// <summary>
/// Get all valid TGS <see cref="Release"/>s from the configured update source.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="Dictionary{TKey, TValue}"/> of TGS <see cref="Release"/>s keyed by their <see cref="Version"/>.</returns>
/// <remarks>GitHub has been known to return incomplete results from the API with this call.</remarks>
Task<Dictionary<Version, Release>> GetTgsReleases(CancellationToken cancellationToken);
/// <summary>
/// Attempt to get an OAuth token from a given <paramref name="code"/>.
/// </summary>
/// <param name="oAuthConfiguration">The <see cref="OAuthConfiguration"/>. Must have <see cref="OAuthConfiguration.RedirectUrl"/>, <see cref="OAuthConfigurationBase.ClientId"/> and <see cref="OAuthConfigurationBase.ClientSecret"/> set.</param>
/// <param name="code">The OAuth response code.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="string"/> representing the returned OAuth code from GitHub on success, <see langword="null"/> otherwise.</returns>
Task<string> CreateOAuthAccessToken(OAuthConfiguration oAuthConfiguration, string code, CancellationToken cancellationToken);
/// <summary>
/// Get a target repostiory's ID.
/// </summary>
/// <param name="repoOwner">The owner of the target repository.</param>
/// <param name="repoName">The name of the target repository.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the target repository's ID.</returns>
Task<long> GetRepositoryId(string repoOwner, string repoName, CancellationToken cancellationToken);
/// <summary>
/// Get the current user's ID.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the current user's ID.</returns>
Task<int> GetCurrentUserId(CancellationToken cancellationToken);
/// <summary>
/// Get a given <paramref name="pullRequestNumber"/>.
/// </summary>
/// <param name="repoOwner">The owner of the target repository.</param>
/// <param name="repoName">The name of the target repository.</param>
/// <param name="pullRequestNumber">The target <see cref="PullRequest.Number"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the target <see cref="PullRequest"/>.</returns>
Task<PullRequest> GetPullRequest(string repoOwner, string repoName, int pullRequestNumber, CancellationToken cancellationToken);
}
}
@@ -0,0 +1,21 @@
namespace Tgstation.Server.Host.Utils.GitHub
{
/// <summary>
/// Factory for <see cref="IGitHubService"/>s.
/// </summary>
public interface IGitHubServiceFactory
{
/// <summary>
/// Create a <see cref="IGitHubService"/>.
/// </summary>
/// <returns>A new <see cref="IGitHubService"/>.</returns>
public IGitHubService CreateService();
/// <summary>
/// Create an <see cref="IAuthenticatedGitHubService"/>.
/// </summary>
/// <param name="accessToken">The access token to use for communication with GitHub.</param>
/// <returns>A new <see cref="IAuthenticatedGitHubService"/>.</returns>
public IAuthenticatedGitHubService CreateService(string accessToken);
}
}
@@ -18,11 +18,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers.Tests
[TestClass]
public sealed class TestDiscordProvider
{
ChatBot testToken1;
IJobManager mockJobManager;
static ChatBot testToken1;
static IJobManager mockJobManager;
[TestInitialize]
public void Initialize()
[ClassInitialize]
public static void Initialize(TestContext _)
{
var actualToken = Environment.GetEnvironmentVariable("TGS_TEST_DISCORD_TOKEN");
if (!String.IsNullOrWhiteSpace(actualToken))
@@ -10,10 +10,10 @@ namespace Tgstation.Server.Host.IO.Tests
[TestClass]
public sealed class TestSymlinkFactory
{
ISymlinkFactory symlinkFactory;
static ISymlinkFactory symlinkFactory;
[TestInitialize]
public void SelectFactory()
[ClassInitialize]
public static void SelectFactory(TestContext _)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
symlinkFactory = new WindowsSymlinkFactory();
@@ -13,12 +13,12 @@ namespace Tgstation.Server.Host.Swarm.Tests
[TestClass]
public sealed class TestSwarmProtocol
{
readonly HashSet<ushort> usedPorts = new ();
ILoggerFactory loggerFactory;
ILogger<TestSwarmProtocol> logger;
static readonly HashSet<ushort> usedPorts = new ();
static ILoggerFactory loggerFactory;
static ILogger<TestSwarmProtocol> logger;
[TestInitialize]
public void Initialize()
[ClassInitialize]
public static void Initialize(TestContext _)
{
loggerFactory = LoggerFactory.Create(builder =>
{
@@ -29,8 +29,8 @@ namespace Tgstation.Server.Host.Swarm.Tests
logger = loggerFactory.CreateLogger<TestSwarmProtocol>();
}
[TestCleanup]
public void Shutdown()
[ClassCleanup]
public static void Shutdown()
{
usedPorts.Clear();
loggerFactory.Dispose();
@@ -15,10 +15,10 @@ namespace Tgstation.Server.Host.System.Tests
[TestClass]
public sealed class TestProcessFeatures
{
IProcessFeatures features;
static IProcessFeatures features;
[TestInitialize]
public void Init()
[ClassInitialize]
public static void Init(TestContext _)
{
features = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? (IProcessFeatures)new WindowsProcessFeatures(Mock.Of<ILogger<WindowsProcessFeatures>>())
@@ -13,15 +13,15 @@ using Octokit;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.Utils.Tests
namespace Tgstation.Server.Host.Utils.GitHub.Tests
{
[TestClass]
public sealed class TestGitHubClientFactory
{
ILoggerFactory loggerFactory;
static ILoggerFactory loggerFactory;
[TestInitialize]
public void Initialize()
[ClassInitialize]
public static void Initialize(TestContext _)
{
loggerFactory = LoggerFactory.Create(builder =>
{
@@ -30,8 +30,8 @@ namespace Tgstation.Server.Host.Utils.Tests
});
}
[TestCleanup]
public void Cleanup()
[ClassCleanup]
public static void Cleanup()
{
loggerFactory.Dispose();
}
@@ -0,0 +1,57 @@
using System;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Octokit;
using Tgstation.Server.Host.Configuration;
namespace Tgstation.Server.Host.Utils.GitHub.Tests
{
[TestClass]
public sealed class TestGitHubServiceFactory
{
[TestMethod]
public void TestConstructor()
{
Assert.ThrowsException<ArgumentNullException>(() => new GitHubServiceFactory(null, null, null));
Assert.ThrowsException<ArgumentNullException>(() => new GitHubServiceFactory(Mock.Of<IGitHubClientFactory>(), null, null));
Assert.ThrowsException<ArgumentNullException>(() => new GitHubServiceFactory(Mock.Of<IGitHubClientFactory>(), Mock.Of<ILoggerFactory>(), null));
var mockOptions = new Mock<IOptions<UpdatesConfiguration>>();
mockOptions.SetupGet(x => x.Value).Returns(new UpdatesConfiguration());
_ = new GitHubServiceFactory(Mock.Of<IGitHubClientFactory>(), Mock.Of<ILoggerFactory>(), mockOptions.Object);
}
[TestMethod]
public void TestCreateService()
{
var mockFactory = new Mock<IGitHubClientFactory>();
mockFactory.Setup(x => x.CreateClient()).Returns(Mock.Of<IGitHubClient>()).Verifiable();
var mockToken = "asdf";
mockFactory.Setup(x => x.CreateClient(mockToken)).Returns(Mock.Of<IGitHubClient>()).Verifiable();
var mockOptions = new Mock<IOptions<UpdatesConfiguration>>();
mockOptions.SetupGet(x => x.Value).Returns(new UpdatesConfiguration());
var factory = new GitHubServiceFactory(mockFactory.Object, Mock.Of<ILoggerFactory>(), mockOptions.Object);
Assert.ThrowsException<ArgumentNullException>(() => factory.CreateService(null));
Assert.AreEqual(0, mockFactory.Invocations.Count);
var result1 = factory.CreateService();
Assert.IsNotNull(result1);
var result2 = factory.CreateService(mockToken);
Assert.IsNotNull(result2);
mockFactory.VerifyAll();
}
}
}
@@ -0,0 +1,153 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Tgstation.Server.Common;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.System;
using Tgstation.Server.Tests.Live.Instance;
namespace Tgstation.Server.Tests.Live
{
sealed class CachingFileDownloader : IFileDownloader
{
static readonly Dictionary<string, Tuple<string, bool>> cachedPaths = new Dictionary<string, Tuple<string, bool>>();
readonly ILogger<CachingFileDownloader> logger;
public CachingFileDownloader(ILogger<CachingFileDownloader> logger)
{
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
logger.LogTrace("Created");
}
public static async Task InitializeAndInject(CancellationToken cancellationToken)
{
if (LiveTestUtils.RunningInGitHubActions)
{
// actions caches BYOND for us
var url = new Uri(
$"https://secure.byond.com/download/build/{ByondTest.TestVersion.Major}/{ByondTest.TestVersion.Major}.{ByondTest.TestVersion.Minor}_byond{(!new PlatformIdentifier().IsWindows ? "_linux" : String.Empty)}.zip");
var path = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
$"BYOND-{ByondTest.TestVersion.Major}.{ByondTest.TestVersion.Minor}",
"byond.zip");
Assert.IsTrue(File.Exists(path));
System.Console.WriteLine($"CACHE PREWARMED: {url}");
cachedPaths.Add(url.ToString(), Tuple.Create(path, false));
}
// predownload the target github release update asset
var gitHubToken = Environment.GetEnvironmentVariable("TGS_TEST_GITHUB_TOKEN");
if (String.IsNullOrWhiteSpace(gitHubToken))
gitHubToken = null;
// this can fail, try a few times
var succeeded = false;
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConsole();
builder.SetMinimumLevel(LogLevel.Trace);
});
var logger = loggerFactory.CreateLogger("CachingFileDownloader");
for (var i = 0; i < 10; ++i)
try
{
var url = new Uri($"https://github.com/tgstation/tgstation-server/releases/download/tgstation-server-v{TestLiveServer.TestUpdateVersion}/ServerUpdatePackage.zip");
await using var stream = await CacheFile(logger, url, gitHubToken, !LiveTestUtils.RunningInGitHubActions, cancellationToken);
succeeded = true;
break;
}
catch (Exception ex)
{
logger.LogWarning(ex, $"TEST: FAILED TO CACHE GITHUB RELEASE.");
}
Assert.IsTrue(succeeded);
ServiceCollectionExtensions.UseFileDownloader<CachingFileDownloader>();
}
public static void Cleanup()
{
lock (cachedPaths)
{
foreach (var pathAndDelete in cachedPaths.Values)
if (pathAndDelete.Item2)
try
{
File.Delete(pathAndDelete.Item1);
}
catch
{
}
cachedPaths.Clear();
}
}
public async Task<MemoryStream> DownloadFile(Uri url, string bearerToken, CancellationToken cancellationToken)
{
Tuple<string, bool> tuple;
lock (cachedPaths)
if (!cachedPaths.TryGetValue(url.ToString(), out tuple))
{
logger.LogInformation("Cache miss: {url}", url);
tuple = null;
}
if (tuple == null)
return await CacheFile(logger, url, bearerToken, true, cancellationToken);
logger.LogTrace("Cache hit: {url}", url);
var bytes = await new DefaultIOManager().ReadAllBytes(tuple.Item1, cancellationToken);
return new MemoryStream(bytes);
}
static async Task<MemoryStream> CacheFile(ILogger logger, Uri url, string bearerToken, bool temporal, CancellationToken cancellationToken)
{
var downloader = new FileDownloader(
new HttpClientFactory(
new AssemblyInformationProvider().ProductInfoHeaderValue),
new Logger<FileDownloader>(
LiveTestUtils.CreateLoggerFactoryForLogger(
logger,
out _)));
var download = await downloader.DownloadFile(url, bearerToken, cancellationToken);
try
{
var path = Path.GetTempFileName();
try
{
await using var fs = new DefaultIOManager().CreateAsyncSequentialWriteStream(path);
await download.CopyToAsync(fs, cancellationToken);
lock (cachedPaths)
cachedPaths.Add(url.ToString(), Tuple.Create(path, temporal));
logger.LogTrace("Cached to {path}", path);
}
catch
{
File.Delete(path);
throw;
}
download.Seek(0, SeekOrigin.Begin);
return download;
}
catch
{
await download.DisposeAsync();
throw;
}
}
}
}
@@ -43,21 +43,6 @@ namespace Tgstation.Server.Tests.Live
ulong channelIdAllocator;
public static ILoggerFactory CreateLoggerFactoryForLogger(ILogger logger, out Mock<ILoggerFactory> mockLoggerFactory)
{
mockLoggerFactory = new Mock<ILoggerFactory>();
mockLoggerFactory.Setup(x => x.CreateLogger(It.IsAny<string>())).Returns(() =>
{
var temp = logger;
logger = null;
Assert.IsNotNull(temp);
return temp;
})
.Verifiable();
return mockLoggerFactory.Object;
}
static IAsyncDelayer CreateMockDelayer()
{
// at time of writing, this is used exclusively for the reconnection interval which works in minutes
@@ -79,7 +64,7 @@ namespace Tgstation.Server.Tests.Live
ICryptographySuite cryptographySuite,
IReadOnlyCollection<ICommand> commands,
Random random)
: base(jobManager, CreateMockDelayer(), new Logger<DummyChatProvider>(CreateLoggerFactoryForLogger(logger, out var mockLoggerFactory)), chatBot)
: base(jobManager, CreateMockDelayer(), new Logger<DummyChatProvider>(LiveTestUtils.CreateLoggerFactoryForLogger(logger, out var mockLoggerFactory)), chatBot)
{
mockLoggerFactory.VerifyAll();
this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite));
@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq;
using Octokit;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.Security;
using Tgstation.Server.Host.System;
using Tgstation.Server.Host.Utils.GitHub;
namespace Tgstation.Server.Tests.Live
{
sealed class DummyGitHubService : IAuthenticatedGitHubService
{
static Dictionary<Version, Release> releasesDictionary;
static PullRequest testPr;
readonly ICryptographySuite cryptographySuite;
readonly ILogger<DummyGitHubService> logger;
public static async Task InitializeAndInject(CancellationToken cancellationToken)
{
var mockOptions = new Mock<IOptions<GeneralConfiguration>>();
mockOptions.SetupGet(x => x.Value).Returns(new GeneralConfiguration
{
GitHubAccessToken = Environment.GetEnvironmentVariable("TGS_TEST_GITHUB_TOKEN")
});
var gitHubClientFactory = new GitHubClientFactory(new AssemblyInformationProvider(), Mock.Of<ILogger<GitHubClientFactory>>(), mockOptions.Object);
var gitHubClient = gitHubClientFactory.CreateClient();
Release targetRelease;
do
{
var releases = await gitHubClient
.Repository
.Release
.GetAll("tgstation", "tgstation-server")
.WithToken(cancellationToken);
targetRelease = releases.FirstOrDefault(release => release.TagName == $"{new UpdatesConfiguration().GitTagPrefix}{TestLiveServer.TestUpdateVersion}");
}
while (targetRelease == null);
releasesDictionary = new Dictionary<Version, Release>
{
{ TestLiveServer.TestUpdateVersion, targetRelease }
};
testPr = await gitHubClient
.PullRequest
.Get("Cyberboss", "common_core", 2)
.WithToken(cancellationToken);
ServiceCollectionExtensions.UseGitHubServiceFactory<DummyGitHubServiceFactory>();
}
public DummyGitHubService(ICryptographySuite cryptographySuite, ILogger<DummyGitHubService> logger)
{
this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
logger.LogTrace("Created");
}
public Task CommentOnIssue(string repoOwner, string repoName, string comment, int issueNumber, CancellationToken cancellationToken)
{
logger.LogTrace("CommentOnIssue");
return Task.CompletedTask;
}
public Task<int> CreateDeployment(NewDeployment newDeployment, string repoOwner, string repoName, CancellationToken cancellationToken)
{
logger.LogTrace("CreateDeployment");
return Task.FromResult(new Random().Next()); ;
}
public Task CreateDeploymentStatus(NewDeploymentStatus newDeploymentStatus, string repoOwner, string repoName, int deploymentId, CancellationToken cancellationToken)
{
logger.LogTrace("CreateDeploymentStatus");
return Task.CompletedTask;
}
public Task CreateDeploymentStatus(NewDeploymentStatus newDeploymentStatus, long repoId, int deploymentId, CancellationToken cancellationToken)
{
logger.LogTrace("CreateDeploymentStatus");
return Task.CompletedTask;
}
public Task<string> CreateOAuthAccessToken(OAuthConfiguration oAuthConfiguration, string code, CancellationToken cancellationToken)
{
logger.LogTrace("CreateOAuthAccessToken");
return Task.FromResult(cryptographySuite.GetSecureString());
}
public Task<int> GetCurrentUserId(CancellationToken cancellationToken)
{
logger.LogTrace("GetCurrentUserId");
return Task.FromResult(new Random().Next());
}
public Task<long> GetRepositoryId(string repoOwner, string repoName, CancellationToken cancellationToken)
{
logger.LogTrace("GetRepositoryId");
return Task.FromResult(new Random().NextInt64());
}
public Task<Uri> GetUpdatesRepositoryUrl(CancellationToken cancellationToken)
{
logger.LogTrace("GetUpdatesRepositoryUrl");
return Task.FromResult(new Uri("https://github.com/tgstation/tgstation-server"));
}
public Task<PullRequest> GetPullRequest(string repoOwner, string repoName, int pullRequestNumber, CancellationToken cancellationToken)
{
logger.LogTrace("GetPullRequest");
return Task.FromResult(testPr);
}
public Task<Dictionary<Version, Release>> GetTgsReleases(CancellationToken cancellationToken)
{
logger.LogTrace("GetTgsReleases");
return Task.FromResult(releasesDictionary);
}
}
}
@@ -0,0 +1,33 @@
using System;
using Microsoft.Extensions.Logging;
using Tgstation.Server.Host.Security;
using Tgstation.Server.Host.Utils.GitHub;
namespace Tgstation.Server.Tests.Live
{
sealed class DummyGitHubServiceFactory : IGitHubServiceFactory
{
readonly ICryptographySuite cryptographySuite;
readonly ILogger<DummyGitHubService> logger;
public DummyGitHubServiceFactory(ICryptographySuite cryptographySuite, ILogger<DummyGitHubService> logger)
{
this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public IGitHubService CreateService() => CreateDummyService();
public IAuthenticatedGitHubService CreateService(string accessToken)
{
if (accessToken == null)
throw new ArgumentNullException(nameof(accessToken));
return CreateDummyService();
}
DummyGitHubService CreateDummyService() => new DummyGitHubService(cryptographySuite, logger);
}
}
@@ -14,7 +14,6 @@ using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Request;
using Tgstation.Server.Client;
using Tgstation.Server.Client.Components;
using Tgstation.Server.Common;
using Tgstation.Server.Host.Components.Byond;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.IO;
@@ -27,13 +26,15 @@ namespace Tgstation.Server.Tests.Live.Instance
public static readonly Version TestVersion = new(514, 1588);
readonly IByondClient byondClient;
readonly IFileDownloader fileDownloader;
readonly Api.Models.Instance metadata;
public ByondTest(IByondClient byondClient, IJobsClient jobsClient, Api.Models.Instance metadata)
public ByondTest(IByondClient byondClient, IJobsClient jobsClient, IFileDownloader fileDownloader, Api.Models.Instance metadata)
: base(jobsClient)
{
this.byondClient = byondClient ?? throw new ArgumentNullException(nameof(byondClient));
this.fileDownloader = fileDownloader ?? throw new ArgumentNullException(nameof(fileDownloader));
this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata));
}
@@ -159,9 +160,6 @@ namespace Tgstation.Server.Tests.Live.Instance
generalConfigOptionsMock.SetupGet(x => x.Value).Returns(new GeneralConfiguration());
var assemblyInformationProvider = new AssemblyInformationProvider();
var fileDownloader = new FileDownloader(
new HttpClientFactory(assemblyInformationProvider.ProductInfoHeaderValue),
Mock.Of<ILogger<FileDownloader>>());
IByondInstaller byondInstaller = new PlatformIdentifier().IsWindows
? new WindowsByondInstaller(
@@ -5,6 +5,7 @@ using System.Threading.Tasks;
using Tgstation.Server.Client;
using Tgstation.Server.Client.Components;
using Tgstation.Server.Host.Components;
using Tgstation.Server.Host.IO;
namespace Tgstation.Server.Tests.Live.Instance
{
@@ -12,20 +13,22 @@ namespace Tgstation.Server.Tests.Live.Instance
{
readonly IInstanceClient instanceClient;
readonly IInstanceManagerClient instanceManagerClient;
readonly IFileDownloader fileDownloader;
readonly InstanceManager instanceManager;
readonly ushort serverPort;
public InstanceTest(IInstanceClient instanceClient, IInstanceManagerClient instanceManagerClient, InstanceManager instanceManager, ushort serverPort)
public InstanceTest(IInstanceClient instanceClient, IInstanceManagerClient instanceManagerClient, IFileDownloader fileDownloader, InstanceManager instanceManager, ushort serverPort)
{
this.instanceClient = instanceClient ?? throw new ArgumentNullException(nameof(instanceClient));
this.instanceManagerClient = instanceManagerClient ?? throw new ArgumentNullException(nameof(instanceManagerClient));
this.fileDownloader = fileDownloader ?? throw new ArgumentNullException(nameof(fileDownloader));
this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager));
this.serverPort = serverPort;
}
public async Task RunTests(CancellationToken cancellationToken, bool highPrioDD, bool lowPrioDeployment)
{
var byondTest = new ByondTest(instanceClient.Byond, instanceClient.Jobs, instanceClient.Metadata);
var byondTest = new ByondTest(instanceClient.Byond, instanceClient.Jobs, fileDownloader, instanceClient.Metadata);
var chatTest = new ChatTest(instanceClient.ChatBots, instanceManagerClient, instanceClient.Jobs, instanceClient.Metadata);
var configTest = new ConfigurationTest(instanceClient.Configuration, instanceClient.Metadata);
var repoTest = new RepositoryTest(instanceClient.Repository, instanceClient.Jobs);
@@ -34,7 +37,7 @@ namespace Tgstation.Server.Tests.Live.Instance
var byondTask = byondTest.Run(cancellationToken, out var firstInstall);
var chatTask = chatTest.RunPreWatchdog(cancellationToken);
var repoLongJob = repoTest.RunLongClone(cancellationToken);
var repoLongJob = await repoTest.RunLongClone(cancellationToken);
await dmTest.RunPreRepoClone(cancellationToken);
await repoTest.AbortLongCloneAndCloneSomethingQuick(repoLongJob, cancellationToken);
@@ -23,7 +23,7 @@ namespace Tgstation.Server.Tests.Live.Instance
this.repositoryClient = repositoryClient ?? throw new ArgumentNullException(nameof(repositoryClient));
}
public async Task<JobResponse> RunLongClone(CancellationToken cancellationToken)
public async Task<Task<JobResponse>> RunLongClone(CancellationToken cancellationToken)
{
var workingBranch = "master";
@@ -35,24 +35,40 @@ namespace Tgstation.Server.Tests.Live.Instance
};
var clone = await repositoryClient.Clone(cloneRequest, cancellationToken);
await ApiAssert.ThrowsException<ConflictException>(() => repositoryClient.Read(cancellationToken), ErrorCode.RepoCloning);
Assert.IsNotNull(clone);
Assert.AreEqual(cloneRequest.Origin, clone.Origin);
Assert.AreEqual(workingBranch, clone.Reference);
Assert.IsNull(clone.RevisionInformation);
Assert.IsNotNull(clone.ActiveJob);
// throwing this small jobs consistency test in here
await Task.Delay(TimeSpan.FromSeconds(15), cancellationToken);
var activeJobs = await JobsClient.ListActive(null, cancellationToken);
var allJobs = await JobsClient.List(null, cancellationToken);
return Rest();
Assert.IsTrue(activeJobs.Any(x => x.Id == clone.ActiveJob.Id));
Assert.IsTrue(allJobs.Any(x => x.Id == clone.ActiveJob.Id));
Assert.IsTrue(activeJobs.First(x => x.Id == clone.ActiveJob.Id).Progress.HasValue);
Assert.IsTrue(allJobs.First(x => x.Id == clone.ActiveJob.Id).Progress.HasValue);
async Task<JobResponse> Rest()
{
await ApiAssert.ThrowsException<ConflictException>(() => repositoryClient.Read(cancellationToken), ErrorCode.RepoCloning);
Assert.IsNotNull(clone);
Assert.AreEqual(cloneRequest.Origin, clone.Origin);
Assert.AreEqual(workingBranch, clone.Reference);
Assert.IsNull(clone.RevisionInformation);
Assert.IsNotNull(clone.ActiveJob);
return clone.ActiveJob;
// throwing this small jobs consistency test in here
await Task.Delay(TimeSpan.FromSeconds(15), cancellationToken);
var activeJobs = await JobsClient.ListActive(null, cancellationToken);
var allJobs = await JobsClient.List(null, cancellationToken);
Assert.IsTrue(activeJobs.Any(x => x.Id == clone.ActiveJob.Id));
Assert.IsTrue(allJobs.Any(x => x.Id == clone.ActiveJob.Id));
var targetActiveJob = activeJobs.First(x => x.Id == clone.ActiveJob.Id);
if (!targetActiveJob.Progress.HasValue)
{
// give it 15 more seconds
targetActiveJob = await WaitForJobProgress(targetActiveJob, 15, cancellationToken);
allJobs = await JobsClient.List(null, cancellationToken);
}
Assert.IsTrue(targetActiveJob.Progress.HasValue);
Assert.IsTrue(allJobs.First(x => x.Id == clone.ActiveJob.Id).Progress.HasValue);
return clone.ActiveJob;
}
}
public async Task AbortLongCloneAndCloneSomethingQuick(Task<JobResponse> longCloneJob, CancellationToken cancellationToken)
@@ -70,7 +86,7 @@ namespace Tgstation.Server.Tests.Live.Instance
Assert.IsNotNull(secondRead);
Assert.IsNull(secondRead.ActiveJob);
const string Origin = "https://github.com/tgstation/common_core";
const string Origin = "https://github.com/Cyberboss/common_core";
var cloneRequest = new RepositoryCreateRequest
{
Origin = new Uri(Origin),
@@ -108,7 +124,20 @@ namespace Tgstation.Server.Tests.Live.Instance
// Back
updated = await Checkout(new RepositoryUpdateRequest { Reference = "master" }, false, true, cancellationToken);
var prNumber = 37;
// enable the good shit if possible
if (LiveTestUtils.RunningInGitHubActions
|| String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("TGS_TEST_GITHUB_TOKEN"))
|| Environment.MachineName.Equals("CYBERSTATIONXVI", StringComparison.OrdinalIgnoreCase))
await repositoryClient.Update(new RepositoryUpdateRequest
{
CreateGitHubDeployments = true,
PostTestMergeComment = true,
PushTestMergeCommits = true,
AccessUser = "Cyberboss",
AccessToken = Environment.GetEnvironmentVariable("TGS_TEST_GITHUB_TOKEN"),
}, cancellationToken);
var prNumber = 2;
await TestMergeTests(updated, prNumber, cancellationToken);
}
@@ -164,7 +193,7 @@ namespace Tgstation.Server.Tests.Live.Instance
Assert.IsNotNull(withMerge.RevisionInformation.PrimaryTestMerge.TitleAtMerge);
Assert.IsNotNull(withMerge.RevisionInformation.PrimaryTestMerge.BodyAtMerge);
if (withMerge.RevisionInformation.PrimaryTestMerge.Url != "GITHUB API ERROR: RATE LIMITED")
Assert.AreEqual($"https://github.com/tgstation/common_core/pull/{prNumber}", withMerge.RevisionInformation.PrimaryTestMerge.Url);
Assert.AreEqual($"https://github.com/Cyberboss/common_core/pull/{prNumber}", withMerge.RevisionInformation.PrimaryTestMerge.Url);
Assert.AreEqual(orignCommit, withMerge.RevisionInformation.OriginCommitSha);
Assert.AreNotEqual(orignCommit, withMerge.RevisionInformation.CommitSha);
@@ -570,7 +570,7 @@ namespace Tgstation.Server.Tests.Live.Instance
while (!cancellationToken.IsCancellationRequested)
{
var currentSize = baseSize + (int)Math.Pow(2, nextPow);
var topicRequestResult = await TopicClient.SendTopic(
var topicRequestResult = await TopicClientNoLogger.SendTopic(
IPAddress.Loopback,
$"tgs_integration_test_tactics4={TopicClient.SanitizeString(currentSize.ToString())}",
TestLiveServer.DDPort,
@@ -923,7 +923,7 @@ namespace Tgstation.Server.Tests.Live.Instance
ReceiveTimeout = TimeSpan.FromSeconds(30),
ConnectTimeout = TimeSpan.FromSeconds(30),
DisconnectTimeout = TimeSpan.FromSeconds(30)
}, new Logger<TopicClient>(DummyChatProvider.CreateLoggerFactoryForLogger(loggerFactory.CreateLogger("WatchdogTest.TopicClient"), out var mockLoggerFactory)));
}, new Logger<TopicClient>(LiveTestUtils.CreateLoggerFactoryForLogger(loggerFactory.CreateLogger("WatchdogTest.TopicClient"), out var mockLoggerFactory)));
public static readonly TopicClient TopicClientNoLogger = new(new SocketParameters
{
@@ -0,0 +1,29 @@
using System;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
namespace Tgstation.Server.Tests.Live
{
static class LiveTestUtils
{
public static bool RunningInGitHubActions { get; } = !String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("GITHUB_RUN_ID"));
public static ILoggerFactory CreateLoggerFactoryForLogger(ILogger logger, out Mock<ILoggerFactory> mockLoggerFactory)
{
mockLoggerFactory = new Mock<ILoggerFactory>();
mockLoggerFactory.Setup(x => x.CreateLogger(It.IsAny<string>())).Returns(() =>
{
var temp = logger;
logger = null;
Assert.IsNotNull(temp);
return temp;
})
.Verifiable();
return mockLoggerFactory.Object;
}
}
}
@@ -49,7 +49,7 @@ namespace Tgstation.Server.Tests.Live
SerilogContextHelper.AddSwarmNodeIdentifierToTemplate();
}
public LiveTestingServer(SwarmConfiguration swarmConfiguration, bool enableOAuth, ushort port = 5010, bool dumpOnMissingUpdate = true)
public LiveTestingServer(SwarmConfiguration swarmConfiguration, bool enableOAuth, ushort port = 5010)
{
Directory = Environment.GetEnvironmentVariable("TGS_TEST_TEMP_DIRECTORY");
if (string.IsNullOrWhiteSpace(Directory))
@@ -89,9 +89,8 @@ namespace Tgstation.Server.Tests.Live
// neither of these should really matter but it's better that we test them
// high prio DD might help with some topic flakiness actually
// github doesn't allow nicing on linux though
var runningInGitHubActions = !String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("GITHUB_RUN_ID"));
var windows = new Host.System.PlatformIdentifier().IsWindows;
var nicingAllowed = windows || !runningInGitHubActions;
var nicingAllowed = windows || !LiveTestUtils.RunningInGitHubActions;
HighPriorityDreamDaemon = nicingAllowed;
LowPriorityDeployments = nicingAllowed;
@@ -116,9 +115,6 @@ namespace Tgstation.Server.Tests.Live
$"Session:LowPriorityDeploymentProcesses={LowPriorityDeployments}",
};
if (dumpOnMissingUpdate)
args.Add("Updates:DumpReleasesOnNotFound=true");
swarmArgs = new List<string>();
if (swarmConfiguration != null)
{
@@ -3,6 +3,8 @@ using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Tgstation.Server.Api;
using Tgstation.Server.Client;
using Tgstation.Server.Common;
@@ -29,6 +31,8 @@ namespace Tgstation.Server.Tests.Live
var now = DateTimeOffset.UtcNow;
Console.WriteLine($"TEST ERROR RATE LIMITED: {ex}");
if (!LiveTestUtils.RunningInGitHubActions)
Assert.Inconclusive("Rate limited by GitHub!");
var sleepTime = ex.RetryAfter.Value - now;
Console.WriteLine($"Sleeping for {sleepTime.TotalMinutes} minutes and retrying...");
@@ -44,7 +44,7 @@ namespace Tgstation.Server.Tests.Live
public static ushort DDPort { get; } = FreeTcpPort();
public static ushort DMPort { get; } = GetDMPort();
readonly Version TestUpdateVersion = new(5, 11, 0);
public static readonly Version TestUpdateVersion = new(5, 11, 0);
readonly IServerClientFactory clientFactory = new ServerClientFactory(new ProductHeaderValue(Assembly.GetExecutingAssembly().GetName().Name, Assembly.GetExecutingAssembly().GetName().Version.ToString()));
@@ -79,9 +79,14 @@ namespace Tgstation.Server.Tests.Live
}
}
[TestInitialize]
public async Task Initialize()
[ClassInitialize]
public static async Task Initialize(TestContext _)
{
if (LiveTestUtils.RunningInGitHubActions || String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("TGS_TEST_GITHUB_TOKEN")))
await DummyGitHubService.InitializeAndInject(default);
await CachingFileDownloader.InitializeAndInject(default);
await DummyChatProvider.RandomDisconnections(true, default);
ServerClientFactory.ApiClientFactory = new RateLimitRetryingApiClientFactory();
@@ -146,6 +151,12 @@ namespace Tgstation.Server.Tests.Live
}
}
[ClassCleanup]
public static void Cleanup()
{
CachingFileDownloader.Cleanup();
}
[TestMethod]
public async Task TestUpdateProtocolAndDisabledOAuth()
{
@@ -155,7 +166,6 @@ namespace Tgstation.Server.Tests.Live
var serverTask = server.Run(cancellationToken);
try
{
var testUpdateVersion = new Version(4, 3, 0);
using (var adminClient = await CreateAdminClient(server.Url, cancellationToken))
{
// Disabled OAuth test
@@ -178,10 +188,21 @@ namespace Tgstation.Server.Tests.Live
//attempt to update to stable
await adminClient.Administration.Update(new ServerUpdateRequest
{
NewVersion = testUpdateVersion
NewVersion = TestUpdateVersion
}, cancellationToken);
var serverInfo = await adminClient.ServerInformation(cancellationToken);
Assert.IsTrue(serverInfo.UpdateInProgress);
try
{
var serverInfoTask = adminClient.ServerInformation(cancellationToken);
var completedTask = await Task.WhenAny(serverTask, serverInfoTask);
if (completedTask == serverInfoTask)
{
var serverInfo = await serverInfoTask;
Assert.IsTrue(serverInfo.UpdateInProgress);
}
}
catch (ServiceUnavailableException) { }
catch (HttpRequestException) { }
}
//wait up to 3 minutes for the dl and install
@@ -195,7 +216,7 @@ namespace Tgstation.Server.Tests.Live
Assert.IsTrue(File.Exists(updatedAssemblyPath), "Updated assembly missing!");
var updatedAssemblyVersion = FileVersionInfo.GetVersionInfo(updatedAssemblyPath);
Assert.AreEqual(testUpdateVersion, Version.Parse(updatedAssemblyVersion.FileVersion).Semver());
Assert.AreEqual(TestUpdateVersion, Version.Parse(updatedAssemblyVersion.FileVersion).Semver());
}
finally
{
@@ -218,7 +239,7 @@ namespace Tgstation.Server.Tests.Live
[TestMethod]
public async Task TestUpdateBadVersion()
{
using var server = new LiveTestingServer(null, false, dumpOnMissingUpdate: false);
using var server = new LiveTestingServer(null, false);
using var serverCts = new CancellationTokenSource();
var cancellationToken = serverCts.Token;
var serverTask = server.Run(cancellationToken);
@@ -746,8 +767,8 @@ namespace Tgstation.Server.Tests.Live
Assert.AreEqual(ProcessPriorityClass.Normal, currentProcess.PriorityClass);
}
const int MaximumTestMinutes = 30;
using var hardCancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(MaximumTestMinutes));
var maximumTestMinutes = LiveTestUtils.RunningInGitHubActions ? 90 : 20;
using var hardCancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(maximumTestMinutes));
var hardCancellationToken = hardCancellationTokenSource.Token;
ServiceCollectionExtensions.UseAdditionalLoggerProvider<HardFailLoggerProvider>();
@@ -880,7 +901,17 @@ namespace Tgstation.Server.Tests.Live
Assert.IsTrue(Directory.Exists(instanceClient.Metadata.Path));
var instanceTests = FailFast(new InstanceTest(instanceClient, adminClient.Instances, GetInstanceManager(), (ushort)server.Url.Port).RunTests(cancellationToken, server.HighPriorityDreamDaemon, server.LowPriorityDeployments));
var instanceTests = FailFast(
new InstanceTest(
instanceClient,
adminClient.Instances,
((Host.Server)server.RealServer).Host.Services.GetRequiredService<Host.IO.IFileDownloader>(),
GetInstanceManager(),
(ushort)server.Url.Port)
.RunTests(
cancellationToken,
server.HighPriorityDreamDaemon,
server.LowPriorityDeployments));
await Task.WhenAll(rootTest, adminTest, instancesTest, instanceTests, usersTest);
@@ -1133,9 +1164,7 @@ namespace Tgstation.Server.Tests.Live
url,
DefaultCredentials.AdminUserName,
DefaultCredentials.DefaultAdminUserPassword,
attemptLoginRefresh: false,
cancellationToken: cancellationToken)
;
cancellationToken: cancellationToken);
}
catch (HttpRequestException)
{
@@ -12,10 +12,10 @@ namespace Tgstation.Server.Tests
[TestClass]
public sealed class TestDMApiConstants
{
string[] definesFileLines;
static string[] definesFileLines;
[TestInitialize]
public async Task Initialize()
[ClassInitialize]
public static async Task Initialize(TestContext _)
{
definesFileLines = await File.ReadAllLinesAsync("../../../../../src/DMAPI/tgs/v5/_defines.dm");
}
+4 -4
View File
@@ -20,12 +20,12 @@ namespace Tgstation.Server.Tests
[TestCategory("SkipWhenLiveUnitTesting")]
public sealed class TestVersions
{
XNamespace xmlNamespace;
static XNamespace xmlNamespace;
XElement versionsPropertyGroup;
static XElement versionsPropertyGroup;
[TestInitialize]
public void Init()
[ClassInitialize]
public static void Init(TestContext _)
{
var doc = XDocument.Load("../../../../../build/Version.props");
var project = doc.Root;