Skip to content

Commit

Permalink
Merge pull request #1842 from tgstation/dev [TGSDeploy]
Browse files Browse the repository at this point in the history
v6.8.0
  • Loading branch information
Cyberboss authored Jul 27, 2024
2 parents 2cc3b1c + 374852f commit 9b0bbc8
Show file tree
Hide file tree
Showing 17 changed files with 183 additions and 97 deletions.
10 changes: 10 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "nuget"
directory: "/"
schedule:
interval: "daily"
labels:
- "Dependencies"
open-pull-requests-limit: 100
26 changes: 16 additions & 10 deletions .github/workflows/ci-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,42 +55,48 @@ jobs:
runs-on: ubuntu-latest
permissions:
pull-requests: write
if: github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.id != github.event.pull_request.base.repo.id && github.event.pull_request.state == 'open'
if: github.event_name == 'pull_request_target' && (github.event.pull_request.head.repo.id != github.event.pull_request.base.repo.id || github.event.pull_request.user.id == 49699333) && github.event.pull_request.state == 'open'
steps:
- name: Comment on new Fork PR
if: github.event.action == 'opened' && !contains(github.event.pull_request.labels.*.name, 'CI Cleared')
if: github.event.action == 'opened' && !contains(github.event.pull_request.labels.*.name, 'CI Cleared') && github.event.pull_request.user.id != 49699333
uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308
with:
message: Thank you for contributing to ${{ github.event.pull_request.base.repo.name }}! The workflow '${{ github.workflow }}' requires repository secrets and will not run without approval. Maintainers can add the `CI Cleared` label to allow it to run. Please note that any changes to the workflow file will not be reflected in the run.

- name: Comment on dependabot PR
if: github.event.action == 'opened' && !contains(github.event.pull_request.labels.*.name, 'CI Cleared') && github.event.pull_request.user.id == 49699333
uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308
with:
message: Set the milestone to the next minor version, check for supply chain attacks, and then add the `CI Cleared` label to allow CI to run.

- name: "Remove Stale 'CI Cleared' Label"
if: github.event.action == 'synchronize' || github.event.action == 'reopened'
uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0
with:
labels: CI Cleared

- name: "Add 'CI Approval Required' Label"
if: (github.event.action == 'synchronize' || github.event.action == 'reopened') || ((github.event.action == 'opened' || github.event.action == 'labeled') && !contains(github.event.pull_request.labels.*.name, 'CI Cleared'))
uses: actions-ecosystem/action-add-labels@bd52874380e3909a1ac983768df6976535ece7f8
- name: "Remove 'CI Approval Required' Label"
if: (github.event.action == 'synchronize' || github.event.action == 'reopened') || ((github.event.action == 'opened' || github.event.action == 'labeled') && contains(github.event.pull_request.labels.*.name, 'CI Cleared'))
uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0
with:
labels: CI Approval Required
github_token: ${{ github.token }}

- name: "Remove 'CI Approval Required' Label"
- name: "Add 'CI Approval Required' Label"
if: (github.event.action == 'synchronize' || github.event.action == 'reopened') || ((github.event.action == 'opened' || github.event.action == 'labeled') && !contains(github.event.pull_request.labels.*.name, 'CI Cleared'))
uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0
uses: actions-ecosystem/action-add-labels@bd52874380e3909a1ac983768df6976535ece7f8
with:
labels: CI Approval Required
github_token: ${{ github.token }}

- name: Fail Clearance Check if PR has Unlabeled new Commits from Fork
- name: Fail Clearance Check if PR has Unlabeled new Commits from User
if: (github.event.action == 'synchronize' || github.event.action == 'reopened') || ((github.event.action == 'opened' || github.event.action == 'labeled') && !contains(github.event.pull_request.labels.*.name, 'CI Cleared'))
run: exit 1

start-ci-run-gate:
name: CI Start Gate
needs: security-checkpoint
runs-on: ubuntu-latest
if: (!(cancelled() || failure()) && (needs.security-checkpoint.result == 'success' || (needs.security-checkpoint.result == 'skipped' && (github.event_name == 'push' || github.event_name == 'schedule' || (github.event.pull_request.head.repo.id == github.event.pull_request.base.repo.id && github.event_name != 'pull_request_target')))))
if: (!(cancelled() || failure()) && (needs.security-checkpoint.result == 'success' || (needs.security-checkpoint.result == 'skipped' && (github.event_name == 'push' || github.event_name == 'schedule' || ((github.event.pull_request.head.repo.id == github.event.pull_request.base.repo.id && github.event.pull_request.user.id != 49699333) && github.event_name != 'pull_request_target')))))
steps:
- name: GitHub Requires at Least One Step for a Job
run: exit 0
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
<p align="center">
<img src =./build/logo.svg>
</p>

# tgstation-server

![CI Pipeline](https://github.com/tgstation/tgstation-server/workflows/CI%20Pipeline/badge.svg) [![codecov](https://codecov.io/gh/tgstation/tgstation-server/branch/master/graph/badge.svg)](https://codecov.io/gh/tgstation/tgstation-server)
Expand Down
8 changes: 4 additions & 4 deletions build/Version.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
<!-- Integration tests will ensure they match across the board -->
<Import Project="WebpanelVersion.props" />
<PropertyGroup>
<TgsCoreVersion>6.7.0</TgsCoreVersion>
<TgsCoreVersion>6.8.0</TgsCoreVersion>
<TgsConfigVersion>5.1.0</TgsConfigVersion>
<TgsApiVersion>10.5.0</TgsApiVersion>
<TgsApiVersion>10.6.0</TgsApiVersion>
<TgsCommonLibraryVersion>7.0.0</TgsCommonLibraryVersion>
<TgsApiLibraryVersion>13.5.0</TgsApiLibraryVersion>
<TgsClientVersion>15.5.0</TgsClientVersion>
<TgsApiLibraryVersion>13.6.0</TgsApiLibraryVersion>
<TgsClientVersion>15.6.0</TgsClientVersion>
<TgsDmapiVersion>7.1.3</TgsDmapiVersion>
<TgsInteropVersion>5.9.0</TgsInteropVersion>
<TgsHostWatchdogVersion>1.4.1</TgsHostWatchdogVersion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="WixToolset.Dtf.CustomAction" Version="4.0.4" />
<PackageReference Include="WixToolset.Dtf.CustomAction" Version="5.0.1" />
</ItemGroup>

<ItemGroup>
Expand Down
6 changes: 6 additions & 0 deletions src/Tgstation.Server.Api/Models/ErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -651,5 +651,11 @@ public enum ErrorCode : uint
/// </summary>
[Description("Could not create dump as dotnet diagnostics threw an exception!")]
DotnetDiagnosticsFailure,

/// <summary>
/// The configured .dme could not be found.
/// </summary>
[Description("Could not load configured .dme due to it being outside the deployment directory! This should be a relative path.")]
DeploymentWrongDme,
}
}
2 changes: 1 addition & 1 deletion src/Tgstation.Server.Api/Tgstation.Server.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<!-- Usage: HTTP constants reference -->
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
<!-- Usage: Decoding the 'nbf' property of JWTs -->
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.0.0" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.0.1" />
<!-- Usage: Data model annotating -->
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
</ItemGroup>
Expand Down
3 changes: 3 additions & 0 deletions src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,9 @@ await eventConsumer.HandleEvent(
else
{
var targetDme = ioManager.ConcatPath(outputDirectory, String.Join('.', job.DmeName, DmeExtension));
if (!await ioManager.PathIsChildOf(outputDirectory, targetDme, cancellationToken))
throw new JobException(ErrorCode.DeploymentWrongDme);

var targetDmeExists = await ioManager.FileExists(targetDme, cancellationToken);
if (!targetDmeExists)
throw new JobException(ErrorCode.DeploymentMissingDme);
Expand Down
108 changes: 41 additions & 67 deletions src/Tgstation.Server.Host/Controllers/InstanceController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ public InstanceController(
IInstanceManager instanceManager,
IJobManager jobManager,
IIOManager ioManager,
IPortAllocator portAllocator,
IPlatformIdentifier platformIdentifier,
IPortAllocator portAllocator,
IPermissionsUpdateNotifyee permissionsUpdateNotifyee,
IOptions<GeneralConfiguration> generalConfigurationOptions,
IOptions<SwarmConfiguration> swarmConfigurationOptions,
Expand Down Expand Up @@ -150,77 +150,52 @@ public async ValueTask<IActionResult> Create([FromBody] InstanceCreateRequest mo
if (earlyOut != null)
return earlyOut;

var unNormalizedPath = model.Path;
var targetInstancePath = NormalizePath(unNormalizedPath);
var targetInstancePath = NormalizePath(model.Path!);
model.Path = targetInstancePath;

var installationDirectoryPath = NormalizePath(DefaultIOManager.CurrentDirectory);

bool InstanceIsChildOf(string otherPath)
{
if (!targetInstancePath.StartsWith(otherPath, StringComparison.Ordinal))
return false;

bool sameLength = targetInstancePath.Length == otherPath.Length;
char dirSeparatorChar = targetInstancePath.ToCharArray()[Math.Min(otherPath.Length, targetInstancePath.Length - 1)];
return sameLength
|| dirSeparatorChar == Path.DirectorySeparatorChar
|| dirSeparatorChar == Path.AltDirectorySeparatorChar;
}

if (InstanceIsChildOf(installationDirectoryPath))
var installationDirectoryPath = DefaultIOManager.CurrentDirectory;
if (await ioManager.PathIsChildOf(installationDirectoryPath, targetInstancePath, cancellationToken))
return Conflict(new ErrorMessageResponse(ErrorCode.InstanceAtConflictingPath));

// Validate it's not a child of any other instance
ulong countOfOtherInstances = 0;
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
{
var newCancellationToken = cts.Token;
try
{
await DatabaseContext
.Instances
.AsQueryable()
.Where(x => x.SwarmIdentifer == swarmConfiguration.Identifier)
.Select(x => new Models.Instance
{
Path = x.Path,
})
.ForEachAsync(
otherInstance =>
{
if (++countOfOtherInstances >= generalConfiguration.InstanceLimit)
earlyOut ??= Conflict(new ErrorMessageResponse(ErrorCode.InstanceLimitReached));
else if (InstanceIsChildOf(otherInstance.Path!))
earlyOut ??= Conflict(new ErrorMessageResponse(ErrorCode.InstanceAtConflictingPath));

if (earlyOut != null && !newCancellationToken.IsCancellationRequested)
cts.Cancel();
},
newCancellationToken);
}
catch (OperationCanceledException)
var instancePaths = await DatabaseContext
.Instances
.AsQueryable()
.Where(x => x.SwarmIdentifer == swarmConfiguration.Identifier)
.Select(x => new Models.Instance
{
cancellationToken.ThrowIfCancellationRequested();
}
}
Path = x.Path,
})
.ToListAsync(cancellationToken);

if (earlyOut != null)
return earlyOut;
if ((instancePaths.Count + 1) >= generalConfiguration.InstanceLimit)
return Conflict(new ErrorMessageResponse(ErrorCode.InstanceLimitReached));

var instancePathChecks = instancePaths
.Select(otherInstance => ioManager.PathIsChildOf(otherInstance.Path!, targetInstancePath, cancellationToken))
.ToArray();

await Task.WhenAll(instancePathChecks);

if (instancePathChecks.Any(task => task.Result))
return Conflict(new ErrorMessageResponse(ErrorCode.InstanceAtConflictingPath));

// Last test, ensure it's in the list of valid paths
if (!(generalConfiguration.ValidInstancePaths?
.Select(path => NormalizePath(path))
.Any(path => InstanceIsChildOf(path)) ?? true))
var pathChecks = generalConfiguration.ValidInstancePaths?
.Select(path => ioManager.PathIsChildOf(path, targetInstancePath, cancellationToken))
.ToArray()
?? Enumerable.Empty<Task<bool>>();
await Task.WhenAll(pathChecks);
if (!pathChecks.All(task => task.Result))
return BadRequest(new ErrorMessageResponse(ErrorCode.InstanceNotAtWhitelistedPath));

async ValueTask<bool> DirExistsAndIsNotEmpty()
{
if (!await ioManager.DirectoryExists(model.Path, cancellationToken))
if (!await ioManager.DirectoryExists(targetInstancePath, cancellationToken))
return false;

var filesTask = ioManager.GetFiles(model.Path, cancellationToken);
var dirsTask = ioManager.GetDirectories(model.Path, cancellationToken);
var filesTask = ioManager.GetFiles(targetInstancePath, cancellationToken);
var dirsTask = ioManager.GetDirectories(targetInstancePath, cancellationToken);

var files = await filesTask;
var dirs = await dirsTask;
Expand All @@ -230,8 +205,8 @@ async ValueTask<bool> DirExistsAndIsNotEmpty()

var dirExistsTask = DirExistsAndIsNotEmpty();
bool attached = false;
if (await ioManager.FileExists(model.Path, cancellationToken) || await dirExistsTask)
if (!await ioManager.FileExists(ioManager.ConcatPath(model.Path, InstanceAttachFileName), cancellationToken))
if (await ioManager.FileExists(targetInstancePath, cancellationToken) || await dirExistsTask)
if (!await ioManager.FileExists(ioManager.ConcatPath(targetInstancePath, InstanceAttachFileName), cancellationToken))
return Conflict(new ErrorMessageResponse(ErrorCode.InstanceAtExistingPath));
else
attached = true;
Expand All @@ -248,7 +223,7 @@ async ValueTask<bool> DirExistsAndIsNotEmpty()
try
{
// actually reserve it now
await ioManager.CreateDirectory(unNormalizedPath, cancellationToken);
await ioManager.CreateDirectory(targetInstancePath, cancellationToken);
await ioManager.DeleteFile(ioManager.ConcatPath(targetInstancePath, InstanceAttachFileName), cancellationToken);
}
catch
Expand Down Expand Up @@ -397,13 +372,13 @@ bool CheckModified<T>(Expression<Func<Api.Models.Instance, T>> expression, Insta
}

string? originalModelPath = null;
string? rawPath = null;
string? normalizedPath = null;
var originalOnline = originalModel.Online!.Value;
if (model.Path != null)
{
rawPath = NormalizePath(model.Path);
normalizedPath = NormalizePath(model.Path);

if (rawPath != originalModel.Path)
if (normalizedPath != originalModel.Path)
{
if (!userRights.HasFlag(InstanceManagerRights.Relocate))
return Forbid();
Expand All @@ -415,7 +390,7 @@ bool CheckModified<T>(Expression<Func<Api.Models.Instance, T>> expression, Insta
return Conflict(new ErrorMessageResponse(ErrorCode.InstanceAtExistingPath));

originalModelPath = originalModel.Path;
originalModel.Path = rawPath;
originalModel.Path = normalizedPath;
}
}

Expand Down Expand Up @@ -505,7 +480,7 @@ await WithComponentInstanceNullable(
var moving = originalModelPath != null;
if (moving)
{
var description = $"Move instance ID {originalModel.Id} from {originalModelPath} to {rawPath}";
var description = $"Move instance ID {originalModel.Id} from {originalModelPath} to {normalizedPath}";
var job = Job.Create(JobCode.Move, AuthenticationContext.User, originalModel, InstanceManagerRights.Relocate);
job.Description = description;

Expand Down Expand Up @@ -823,8 +798,7 @@ InstancePermissionSet InstanceAdminPermissionSet(InstancePermissionSet? permissi
return null;

path = ioManager.ResolvePath(path);
if (platformIdentifier.IsWindows)
path = path.ToUpperInvariant().Replace('\\', '/');
path = platformIdentifier.NormalizePath(path);

return path;
}
Expand Down
26 changes: 16 additions & 10 deletions src/Tgstation.Server.Host/Database/DatabaseSeeder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ sealed class DatabaseSeeder : IDatabaseSeeder
/// </summary>
readonly DatabaseConfiguration databaseConfiguration;

/// <summary>
/// The <see cref="SwarmConfiguration"/> for the <see cref="DatabaseSeeder"/>.
/// </summary>
readonly SwarmConfiguration swarmConfiguration;

/// <summary>
/// Add a default system <see cref="User"/> to a given <paramref name="databaseContext"/>.
/// </summary>
Expand Down Expand Up @@ -83,20 +88,23 @@ static User SeedSystemUser(IDatabaseContext databaseContext, User? tgsUser = nul
/// <param name="platformIdentifier">The value of <see cref="platformIdentifier"/>.</param>
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="generalConfiguration"/>.</param>
/// <param name="databaseConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="databaseConfiguration"/>.</param>
/// <param name="swarmConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="swarmConfiguration"/>.</param>
/// <param name="databaseLogger">The value of <see cref="databaseLogger"/>.</param>
/// <param name="logger">The value of <see cref="logger"/>.</param>
public DatabaseSeeder(
ICryptographySuite cryptographySuite,
IPlatformIdentifier platformIdentifier,
IOptions<GeneralConfiguration> generalConfigurationOptions,
IOptions<DatabaseConfiguration> databaseConfigurationOptions,
IOptions<SwarmConfiguration> swarmConfigurationOptions,
ILogger<DatabaseContext> databaseLogger,
ILogger<DatabaseSeeder> logger)
{
this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite));
this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
databaseConfiguration = databaseConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(databaseConfigurationOptions));
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
swarmConfiguration = swarmConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(swarmConfigurationOptions));
this.databaseLogger = databaseLogger ?? throw new ArgumentNullException(nameof(databaseLogger));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
Expand Down Expand Up @@ -223,16 +231,14 @@ async ValueTask SanitizeDatabase(IDatabaseContext databaseContext, CancellationT
}
}

if (platformIdentifier.IsWindows)
{
// normalize backslashes to forward slashes
var allInstances = await databaseContext
.Instances
.AsQueryable()
.ToListAsync(cancellationToken);
foreach (var instance in allInstances)
instance.Path = instance.Path!.Replace('\\', '/');
}
// normalize backslashes to forward slashes
var allInstances = await databaseContext
.Instances
.AsQueryable()
.Where(instance => instance.SwarmIdentifer == swarmConfiguration.Identifier)
.ToListAsync(cancellationToken);
foreach (var instance in allInstances)
instance.Path = platformIdentifier.NormalizePath(instance.Path!.Replace('\\', '/'));

if (generalConfiguration.ByondTopicTimeout != 0)
{
Expand Down
Loading

0 comments on commit 9b0bbc8

Please sign in to comment.