Merge branch 'dev' into 1495-NonGitHubUpdates

This commit is contained in:
Dominion
2023-06-10 12:02:48 -04:00
47 changed files with 363 additions and 370 deletions
+30 -7
View File
@@ -41,14 +41,36 @@ The recommended IDE is Visual Studio 2019 which has installation options for bot
In order to run the integration tests you must have the following environment variables set. To run them more accurately, include the optional ones.
- `TGS_TEST_DATABASE_TYPE`: `MySql`, `MariaDB`, `PostgresSql`, or `SqlServer`.
- `TGS_TEST_CONNECTION_STRING`: To a valid database connection string. You can use the setup wizard to create one.
- `TGS_TEST_BRANCH`: Should be either `dev` or `master` depending on what you are working off of. Used for repository tests.
- (Optional) `TGS_TEST_GITHUB_TOKEN`: A GitHub personal access token with no scopes used to bypass rate limits.
- (Optional) The following variables are all interdependent, so if one is set they all must be.
- `TSG_TEST_DISCORD_TOKEN`: To a valid discord bot token.
- `TGS_TEST_DISCORD_TOKEN`: To a valid discord bot token.
- `TGS_TEST_DISCORD_CHANNEL`: To a valid discord channel ID that the above bot can access.
- `TGS_TEST_IRC_CONNECTION_STRING`: To a valid IRC connection string. See the code for [IrcConnectionStringBuilder](../src/Tgstation.Server.Api/Models/IrcConnectionStringBuilder.cs) for details.
- `TGS_TEST_IRC_CHANNEL`: To a valid IRC channel accessible with the above connection.
### Notes About Forks
For the full CI gambit, the following repository configuration must be set:
- Setting `Workflow Permissions` to `Read and write permissions`: Enables CodeQL uploads and GitHub Actions comments.
![image](https://github.com/tgstation/tgstation-server/assets/8171642/ab17fa74-364f-4e66-b7c4-b9bb24c6a599)
- Label `CI Cleared`: To allow PRs from forks to run CI with secrets after approval.
- Variable `TGS_ENABLE_CODE_QL` to `true`: Enables CodeQL scanning in actions.
- Integration [CodeCov](https://github.com/apps/codecov): Enables CodeCov status checks.
- Secret `CODECOV_TOKEN`: A CodeCov repo token to work around https://github.com/codecov/codecov-action/issues/837.
- Secret `LIVE_TESTS_TOKEN`: A GitHub token with read access to the repository and write access to https://github.com/Cyberboss/common_core (TODO: Make the target repository here configurable). Despite it's name, it may be used across the entire test suite.
- Secret `TGS_TEST_DISCORD_TOKEN`: See above note about test environment variables.
- Secret `TGS_TEST_DISCORD_CHANNEL`: See above note about test environment variables.
- Secret `TGS_TEST_IRC_CONNECTION_STRING`: See above note about test environment variables.
- Secret `TGS_TEST_IRC_CHANNEL`: See above note about test environment variables.
If you don't plan on deploying TGS, the following secrets can be omitted:
- Secret `DEV_PUSH_TOKEN`: A GitHub token with write access to the repository. Enables doxygen pushes to `gh-pages` branch, and releases creation.
- Secret `DOCKER_USERNAME`: Login username for Docker image push.
- Secret `DOCKER_PASSWORD`: Login password for Docker image push.
- Secret `NUGET_API_KEY`: Nuget.org API Key for client libraries push.
### Know your Code
- All feature work should be submitted to the `dev` branch for the next minor release.
@@ -224,6 +246,7 @@ We have several subcomponent APIs we ship with the core server that have their o
- HTTP API
- DreamMaker API
- Interop API
- Configuration File
- Host Watchdog
- Web Control Panel
@@ -237,19 +260,19 @@ All versions are stored in the master file [build/Version.props](../build/Versio
The NuGet package Tgstation.Server.Client is another part of the suite which should be versioned separately. However, Tgstation.Server.Api is also a package that is published, and breaking changes can happen independantly of each other.
- Consider Tgstation.Server.Client it's own product, perform major and minor bumps according to semver semantics including the Tgstation.Server.Api code (but not the version).
- Tgstation.Server.Api is a bit tricky as breaking code changes may occur without affecting the actual HTTP contract. For this reason, all code changes that do this should be pushed out as patches, even if they contain breaking changes.
- Tgstation.Server.Api is a bit tricky as breaking code changes may occur without affecting the actual HTTP contract. For this reason, the library itself is versioned separately from the API contract.
## Triage, Deployment, and Releasing
_This section mainly applies to people with write access to the repository. Anyone is free to propose their work and maintainers will triage it appropriately._
When issues affecting the server come in, they should be lebeled appropriately and either put into the `Backlog` milestone or current patch milestone depending on if it's a feature request or bug.
When issues affecting the server come in, they should be labeled appropriately and either put into the `Backlog` milestone or current patch milestone depending on if it's a feature request or bug.
After a minor release, the team should decide at that time what will go into it and setup the milestone accordingly. At this point the `Backlog` label should be removed and replaced with `Ready` and the milestone changed from `Backlog` to `vX.Y.0` with X/Y being the major/minor release versions respectively.
Assign work before beginning on it. When work is started, replace the `Ready` label with the `Work In Progress` label.
Word commit names descriptively. Only submit work through pull requests. When doing so, link the issue you'll be closing and set the milestone appropriately. Don't forget a changelog. **WARNING** Remember to submit patches to the `master` branch. Also at the time of this writing there appears to be an issue where GitHub won't close issues with PRs to the non-default branch. Maintainers may need to do this manually after merging.
Word commit names descriptively. Only submit work through pull requests (With the exception of resolving conflicts in the `master` -> `dev` automatic merge). When doing so, link the issue you'll be closing and set the milestone appropriately. Don't forget a changelog. **WARNING** Remember to submit patches to the `master` branch. Also at the time of this writing there appears to be an issue where GitHub won't close issues with PRs to the non-default branch. Maintainers may need to do this manually after merging. Alternatively, add closing keywords to the commit message and let the `master` -> `dev` merge do the closing.
At the time of this writing, the repository is configured to automate much of the deployment/release process.
@@ -259,10 +282,10 @@ That step should be taken for the latest API and client before releasing the cor
Before releasing the core version, ensure the following:
- For minor releases, ensure your changes are merging `dev` into `master`.
- For minor/major releases, ensure your changes are merging `dev` into `master`.
- Ensure all issues and pull requests in the associated milestone are closed (aside from the PR you are using to cut the release).
To perform the release, merge the PR with `[TGSDeploy]` in the commit message. The build system will handle generating release notes, packaging, and pushing the build to GitHub releases. This will also make it available for servers to self update.
To perform the release, merge the PR with `[TGSDeploy]` in the commit message. The build system will handle generating release notes, packaging, and pushing the build to GitHub releases. This will also make it available for servers to self update. Note that `[TGSDeploy]` only affects commits on the `master` branch.
The build system will also handle closing the current milestone and creating new minor/patch milestones where applicable.
@@ -16,7 +16,7 @@ concurrency:
jobs:
approve-pr-if-dominion-is-author:
name: Approve PR if Dominion is Author
if: github.event.pull_request.user.login == 'Cyberboss'
if: github.event.pull_request.user.login == 'Cyberboss' && (github.event.pull_request.base.repo.owner.login == 'tgstation' || && github.event.pull_request.base.repo.owner.login == 'Cyberboss')
runs-on: ubuntu-latest
steps:
- name: GitHub API Call
+5 -42
View File
@@ -21,6 +21,7 @@ env:
TGS_DOTNET_VERSION: 6.0.x
TGS_TEST_GITHUB_TOKEN: ${{ secrets.LIVE_TESTS_TOKEN }}
TGS_RELEASE_NOTES_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
concurrency:
group: "ci-${{ github.head_ref || github.run_id }}-${{ github.event_name }}"
@@ -57,44 +58,6 @@ jobs:
- name: GitHub Requires at Least One Step for a Job
run: exit 0
analyze:
name: Code Scanning
needs: start-ci-run-gate
if: "!(cancelled() || failure()) && needs.start-ci-run-gate.result == 'success'"
runs-on: ubuntu-latest
steps:
- name: Install Node 12.X
uses: actions/setup-node@v3
with:
node-version: 12.x
- name: Upgrade NPM
run: npm install -g npm
- name: Checkout (Branch)
uses: actions/checkout@v3
if: github.event_name == 'push' || github.event_name == 'schedule'
with:
fetch-depth: 2
- name: Checkout (PR Merge)
uses: actions/checkout@v3
if: github.event_name != 'push' && github.event_name != 'schedule'
with:
fetch-depth: 2
ref: "refs/pull/${{ github.event.number }}/merge"
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: csharp
- name: Autobuild
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
dmapi-build:
name: Build DMAPI
needs: start-ci-run-gate
@@ -114,7 +77,7 @@ jobs:
uses: actions/cache@v3
id: cache-byond
with:
path: $HOME/BYOND-${{ env.BYOND_MAJOR }}.${{ env.BYOND_MINOR }}
path: ~/BYOND-${{ env.BYOND_MAJOR }}.${{ env.BYOND_MINOR }}
key: ${{ env.BYOND_MAJOR }}-${{ env.BYOND_MINOR }}-dmapibyond
- name: Install BYOND
@@ -186,7 +149,7 @@ jobs:
doxyfile-path: 'docs/Doxyfile'
- name: gh-pages push
if: github.event_name == 'push' && github.event.ref == 'refs/heads/dev'
if: github.event_name == 'push' && github.event.ref == 'refs/heads/dev' && env.TGS_RELEASE_NOTES_TOKEN != ''
run: |
git clone -b gh-pages --single-branch "https://git@github.com/tgstation/tgstation-server" $HOME/tgsdox
pushd $HOME/tgsdox
@@ -324,7 +287,7 @@ jobs:
database-type: [ 'SqlServer', 'Sqlite', 'PostgresSql', 'MariaDB', 'MySql' ]
watchdog-type: [ 'Basic', 'System' ]
configuration: [ 'Debug', 'Release' ]
runs-on: windows-2019
runs-on: windows-latest
env:
BYOND_MAJOR: 514
BYOND_MINOR: 1588
@@ -338,7 +301,7 @@ jobs:
uses: actions/cache@v3
id: cache-byond
with:
path: $HOME/BYOND-${{ env.BYOND_MAJOR }}.${{ env.BYOND_MINOR }}
path: ~/BYOND-${{ env.BYOND_MAJOR }}.${{ env.BYOND_MINOR }}
key: ${{ env.BYOND_MAJOR }}-${{ env.BYOND_MINOR }}-livebyond
- name: Download BYOND
+51
View File
@@ -0,0 +1,51 @@
name: 'Code Scanning'
on:
schedule:
- cron: 0 23 * * 1
push:
branches:
- dev
- master
pull_request:
branches:
- dev
- master
concurrency:
group: "code-scanning-${{ github.head_ref || github.run_id }}-${{ github.event_name }}"
cancel-in-progress: true
jobs:
analyze:
name: Code Scanning
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
if: ${{ vars.TGS_ENABLE_CODE_QL }} == 'true'
steps:
- name: Install Node 12.X
uses: actions/setup-node@v3
with:
node-version: 12.x
- name: Upgrade NPM
run: npm install -g npm
- name: Checkout
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: csharp
- name: Autobuild
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:csharp"
+1 -1
View File
@@ -16,6 +16,6 @@ Vulnerabilities should ideally be reported by directly messaging one of the TGS
Here is a list of their discord IDs.
@Cyberboss - Dominion#0444 (<@133295178197893120>)
@Cyberboss - dominion (<@133295178197893120>)
Once reported, they will handle the processing of the security advisory.
+1 -1
View File
@@ -3,7 +3,7 @@
<!-- Integration tests will ensure they match across the board -->
<Import Project="ControlPanelVersion.props" />
<PropertyGroup>
<TgsCoreVersion>5.12.4</TgsCoreVersion>
<TgsCoreVersion>5.12.5</TgsCoreVersion>
<TgsConfigVersion>4.6.0</TgsConfigVersion>
<TgsApiVersion>9.10.2</TgsApiVersion>
<TgsApiLibraryVersion>11.0.0</TgsApiLibraryVersion>
@@ -70,8 +70,7 @@ namespace Tgstation.Server.Host.Components.Byond
IOManager.ConcatPath(
PathToUserByondFolder,
CacheDirectoryName),
cancellationToken)
;
cancellationToken);
}
catch (OperationCanceledException)
{
@@ -383,8 +383,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
cancellationToken,
DefaultIOManager.BlockingTaskCreationOptions,
TaskScheduler.Current)
.WithToken(cancellationToken)
;
.WithToken(cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
@@ -480,8 +479,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
},
cancellationToken,
DefaultIOManager.BlockingTaskCreationOptions,
TaskScheduler.Current)
;
TaskScheduler.Current);
await HardDisconnect(cancellationToken);
}
catch (OperationCanceledException)
@@ -309,7 +309,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
cancellationToken);
// DCT: Always wait for the job to complete here
await jobManager.WaitForJobCompletion(job, null, cancellationToken, default);
await jobManager.WaitForJobCompletion(job, null, cancellationToken, cancellationToken);
}
}
catch (OperationCanceledException e)
@@ -145,8 +145,7 @@ namespace Tgstation.Server.Host.Components.Deployment
job);
await remoteDeploymentManager.StageDeployment(
newProvider.CompileJob,
cancellationToken)
;
cancellationToken);
}
lock (jobLockCounts)
@@ -188,10 +187,8 @@ namespace Tgstation.Server.Host.Components.Deployment
.AsQueryable()
.Where(x => x.Job.Instance.Id == metadata.Id)
.OrderByDescending(x => x.Job.StoppedAt)
.FirstOrDefaultAsync(cancellationToken)
;
})
;
.FirstOrDefaultAsync(cancellationToken);
});
if (cj == default(CompileJob))
return;
@@ -267,8 +264,7 @@ namespace Tgstation.Server.Host.Components.Deployment
ioManager.ConcatPath(
newProvider.Directory,
newProvider.DmbName),
cancellationToken)
;
cancellationToken);
if (!dmbExistsAtRoot)
{
@@ -241,8 +241,7 @@ namespace Tgstation.Server.Host.Components.Deployment
StartupTimeout = x.StartupTimeout,
LogOutput = x.LogOutput,
})
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
if (ddSettings == default)
throw new JobException(ErrorCode.InstanceMissingDreamDaemonSettings);
@@ -250,8 +249,7 @@ namespace Tgstation.Server.Host.Components.Deployment
.DreamMakerSettings
.AsQueryable()
.Where(x => x.InstanceId == metadata.Id)
.FirstAsync(cancellationToken)
;
.FirstAsync(cancellationToken);
if (dreamMakerSettings == default)
throw new JobException(ErrorCode.InstanceMissingDreamMakerSettings);
@@ -267,8 +265,7 @@ namespace Tgstation.Server.Host.Components.Deployment
PushTestMergeCommits = x.PushTestMergeCommits,
PostTestMergeComment = x.PostTestMergeComment,
})
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
if (repositorySettings == default)
throw new JobException(ErrorCode.InstanceMissingRepositorySettings);
@@ -291,8 +288,7 @@ namespace Tgstation.Server.Host.Components.Deployment
.Include(x => x.ActiveTestMerges)
.ThenInclude(x => x.TestMerge)
.ThenInclude(x => x.MergedBy)
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
if (revInfo == default)
{
@@ -319,8 +315,7 @@ namespace Tgstation.Server.Host.Components.Deployment
repo?.Dispose();
throw;
}
})
;
});
var likelyPushedTestMergeCommit =
repositorySettings.PushTestMergeCommits.Value
@@ -336,8 +331,7 @@ namespace Tgstation.Server.Host.Components.Deployment
progressReporter,
averageSpan,
likelyPushedTestMergeCommit,
cancellationToken)
;
cancellationToken);
var activeCompileJob = compileJobConsumer.LatestCompileJob();
try
@@ -379,8 +373,7 @@ namespace Tgstation.Server.Host.Components.Deployment
compileJob.Job = fullJob;
compileJob.RevisionInformation = fullRevInfo;
})
;
});
}
catch (Exception ex)
{
@@ -447,8 +440,7 @@ namespace Tgstation.Server.Host.Components.Deployment
StoppedAt = x.Job.StoppedAt,
StartedAt = x.Job.StartedAt,
})
.ToListAsync(cancellationToken)
;
.ToListAsync(cancellationToken);
TimeSpan? averageSpan = null;
if (previousCompileJobs.Count != 0)
@@ -605,8 +597,7 @@ namespace Tgstation.Server.Host.Components.Deployment
repoOrigin.ToString(),
$"{byondLock.Version.Major}.{byondLock.Version.Minor}",
},
cancellationToken)
;
cancellationToken);
// determine the dme
progressReporter.StageName = "Determining .dme";
@@ -978,8 +969,7 @@ namespace Tgstation.Server.Host.Components.Deployment
remoteDeploymentManager.FailDeployment(
job,
FormatExceptionForUsers(exception),
default))
;
default));
}
}
}
@@ -73,8 +73,7 @@ namespace Tgstation.Server.Host.Components.Deployment
await symlinkFactory.CreateSymbolicLink(
ioManager.ResolvePath(baseProvider.Directory),
ioManager.ResolvePath(LiveGameDirectory),
cancellationToken)
;
cancellationToken);
}
}
}
@@ -185,8 +185,7 @@ namespace Tgstation.Server.Host.Components
Configuration.StartAsync(cancellationToken),
ByondManager.StartAsync(cancellationToken),
Chat.StartAsync(cancellationToken),
dmbFactory.StartAsync(cancellationToken))
;
dmbFactory.StartAsync(cancellationToken));
// dependent on so many things, its just safer this way
await Watchdog.StartAsync(cancellationToken);
@@ -310,8 +309,7 @@ namespace Tgstation.Server.Host.Components
repositorySettings.AccessUser,
repositorySettings.AccessToken,
NextProgressReporter("Fetch Origin"),
cancellationToken)
;
cancellationToken);
var hasDbChanges = false;
RevisionInformation currentRevInfo = null;
@@ -385,8 +383,7 @@ namespace Tgstation.Server.Host.Components
repositorySettings.CommitterName,
repositorySettings.CommitterEmail,
NextProgressReporter("Merge Origin"),
cancellationToken)
;
cancellationToken);
var preserveTestMerges = repositorySettings.AutoUpdatesKeepTestMerges.Value;
var remoteDeploymentManager = remoteDeploymentManagerFactory.CreateRemoteDeploymentManager(
@@ -435,16 +432,14 @@ namespace Tgstation.Server.Host.Components
repositorySettings.AccessToken,
repositorySettings.UpdateSubmodules.Value,
NextProgressReporter(StageName),
cancellationToken)
;
cancellationToken);
var currentHead = repo.Head;
currentRevInfo = await databaseContext.RevisionInformations
.AsQueryable()
.Where(x => x.CommitSha == currentHead && x.Instance.Id == metadata.Id)
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
if (currentHead != startSha && currentRevInfo == default)
await UpdateRevInfo(currentHead, true, null);
@@ -514,11 +509,9 @@ namespace Tgstation.Server.Host.Components
await jobManager.RegisterOperation(
repositoryUpdateJob,
RepositoryAutoUpdateJob,
cancellationToken)
;
cancellationToken);
// DCT: First token will cancel the job, second is for cancelling the cancellation, unwanted
await jobManager.WaitForJobCompletion(repositoryUpdateJob, null, cancellationToken, default);
await jobManager.WaitForJobCompletion(repositoryUpdateJob, null, cancellationToken, cancellationToken);
Job compileProcessJob;
using (var repo = await RepositoryManager.LoadRepository(cancellationToken))
@@ -557,8 +550,7 @@ namespace Tgstation.Server.Host.Components
progressReporter,
jobCancellationToken);
},
cancellationToken)
;
cancellationToken);
}
await jobManager.WaitForJobCompletion(compileProcessJob, null, default, cancellationToken);
@@ -284,8 +284,7 @@ namespace Tgstation.Server.Host.Components
await ioManager.WriteAllBytes(
ioManager.ConcatPath(oldPath, InstanceController.InstanceAttachFileName),
Array.Empty<byte>(),
default)
;
default);
}
catch (Exception tripleEx)
{
@@ -66,8 +66,7 @@ namespace Tgstation.Server.Host.Components.Repository
var mr = await client
.MergeRequests
.GetAsync($"{RemoteRepositoryOwner}/{RemoteRepositoryName}", parameters.Number)
.WithToken(cancellationToken)
;
.WithToken(cancellationToken);
var revisionToUse = parameters.TargetCommitSha == null
|| mr.Sha.StartsWith(parameters.TargetCommitSha, StringComparison.OrdinalIgnoreCase)
@@ -356,8 +356,7 @@ namespace Tgstation.Server.Host.Components.Repository
}),
cancellationToken,
DefaultIOManager.BlockingTaskCreationOptions,
TaskScheduler.Current)
;
TaskScheduler.Current);
if (updateSubmodules)
{
@@ -412,8 +411,7 @@ namespace Tgstation.Server.Host.Components.Repository
},
cancellationToken,
DefaultIOManager.BlockingTaskCreationOptions,
TaskScheduler.Current)
;
TaskScheduler.Current);
if (updateSubmodules)
await UpdateSubmodules(
@@ -463,8 +461,7 @@ namespace Tgstation.Server.Host.Components.Repository
},
cancellationToken,
DefaultIOManager.BlockingTaskCreationOptions,
TaskScheduler.Current)
;
TaskScheduler.Current);
}
/// <inheritdoc />
@@ -485,8 +482,7 @@ namespace Tgstation.Server.Host.Components.Repository
await ResetToSha(
trackedBranch.Tip.Sha,
progressReporter.CreateSection(null, updateSubmodules ? 2.0 / 3 : 1.0),
cancellationToken)
;
cancellationToken);
if (updateSubmodules)
await UpdateSubmodules(
@@ -619,8 +615,7 @@ namespace Tgstation.Server.Host.Components.Repository
},
cancellationToken,
DefaultIOManager.BlockingTaskCreationOptions,
TaskScheduler.Current)
;
TaskScheduler.Current);
if (result.Status == MergeStatus.Conflicts)
{
@@ -684,8 +679,7 @@ namespace Tgstation.Server.Host.Components.Repository
{
ioMananger.ResolvePath(),
},
cancellationToken)
;
cancellationToken);
}
finally
{
@@ -702,8 +696,7 @@ namespace Tgstation.Server.Host.Components.Repository
},
cancellationToken,
DefaultIOManager.BlockingTaskCreationOptions,
TaskScheduler.Current)
;
TaskScheduler.Current);
}
var remainingProgressFactor = 0.9;
@@ -759,8 +752,7 @@ namespace Tgstation.Server.Host.Components.Repository
},
cancellationToken,
DefaultIOManager.BlockingTaskCreationOptions,
TaskScheduler.Current)
;
TaskScheduler.Current);
}
/// <inheritdoc />
@@ -1040,8 +1032,7 @@ namespace Tgstation.Server.Host.Components.Repository
await Task.WhenAll(
ioMananger.DeleteDirectory($".git/modules/{submodule.Path}", cancellationToken),
ioMananger.DeleteDirectory(submodule.Path, cancellationToken))
;
ioMananger.DeleteDirectory(submodule.Path, cancellationToken));
logger.LogTrace("Second update attempt for submodule {submoduleName}...", submodule.Name);
try
@@ -270,8 +270,7 @@ namespace Tgstation.Server.Host.Components.Repository
currentModel.CommitterEmail,
NextProgressReporter("Sychronize"),
true,
cancellationToken)
;
cancellationToken);
postUpdateSha = repo.Head;
}
else
@@ -305,8 +304,7 @@ namespace Tgstation.Server.Host.Components.Repository
currentModel.AccessToken,
updateSubmodules,
NextProgressReporter("Checkout"),
cancellationToken)
;
cancellationToken);
await CallLoadRevInfo(); // we've either seen origin before or what we're checking out is on origin
}
else
@@ -321,8 +319,7 @@ namespace Tgstation.Server.Host.Components.Repository
currentModel.AccessToken,
updateSubmodules,
NextProgressReporter("Reset to Origin"),
cancellationToken)
;
cancellationToken);
await repo.Sychronize(
currentModel.AccessUser,
currentModel.AccessToken,
@@ -330,8 +327,7 @@ namespace Tgstation.Server.Host.Components.Repository
currentModel.CommitterEmail,
NextProgressReporter("Synchronize"),
true,
cancellationToken)
;
cancellationToken);
await CallLoadRevInfo();
// repo head is on origin so force this
@@ -545,8 +541,7 @@ namespace Tgstation.Server.Host.Components.Repository
currentModel.CommitterEmail,
NextProgressReporter("Synchronize"),
false,
cancellationToken)
;
cancellationToken);
await UpdateRevInfo();
}
@@ -444,8 +444,7 @@ namespace Tgstation.Server.Host.Components.Session
{
var commandResult = await SendCommand(
new TopicParameters(port),
cancellationToken)
;
cancellationToken);
if (commandResult?.ErrorMessage != null)
return false;
@@ -478,8 +477,7 @@ namespace Tgstation.Server.Host.Components.Session
ReattachInformation.RebootState = newRebootState;
var result = await SendCommand(
new TopicParameters(newRebootState),
cancellationToken)
;
cancellationToken);
return result?.ErrorMessage == null;
}
@@ -140,8 +140,7 @@ namespace Tgstation.Server.Host.Components.Session
.AsQueryable()
.Where(x => x.Id == metadata.Id)
.Select(x => x.DreamDaemonSettings.TopicRequestTimeout)
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
if (timeoutMilliseconds == default)
{
@@ -238,8 +237,7 @@ namespace Tgstation.Server.Host.Components.Session
if (instant)
await baseQuery
.DeleteAsync(cancellationToken)
;
.DeleteAsync(cancellationToken);
else
{
var results = await baseQuery.ToListAsync(cancellationToken);
@@ -652,8 +652,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
await Task.WhenAll(
ioManager.CreateDirectory(CodeModificationsSubdirectory, cancellationToken),
ioManager.CreateDirectory(EventScriptsSubdirectory, cancellationToken),
ValidateStaticFolder())
;
ValidateStaticFolder());
}
/// <summary>
@@ -102,8 +102,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
await GameIOManager.MoveDirectory(
ActiveSwappable.CompileJob.DirectoryName.ToString(),
ActiveSwappable.Directory,
cancellationToken)
;
cancellationToken);
hardLinkedDmb = ActiveSwappable;
}
@@ -131,8 +130,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
await GameIOManager.MoveDirectory(
hardLink,
originalPosition,
default)
;
default);
}
catch (Exception ex)
{
@@ -465,8 +465,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
var notification = new EventNotification(eventType, parameters);
var result = await activeServer.SendCommand(
new TopicParameters(notification),
cancellationToken)
;
cancellationToken);
HandleChatResponses(result);
}
@@ -853,7 +852,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
var healthCheckSeconds = ActiveLaunchParameters.HeartbeatSeconds.Value;
var healthCheck = healthCheckSeconds == 0
|| !controller.DMApiAvailable
? Extensions.TaskExtensions.InfiniteTask()
? Extensions.TaskExtensions.InfiniteTask
: Task.Delay(
TimeSpan.FromSeconds(healthCheckSeconds),
cancellationToken);
@@ -125,8 +125,7 @@ namespace Tgstation.Server.Host.Controllers
await this.StatusCode(
HttpStatusCode.UpgradeRequired,
new ErrorMessageResponse(ErrorCode.ApiMismatch))
.ExecuteResultAsync(context)
;
.ExecuteResultAsync(context);
return;
}
@@ -142,8 +141,7 @@ namespace Tgstation.Server.Host.Controllers
if (requireHeaders)
{
await HeadersIssue(false)
.ExecuteResultAsync(context)
;
.ExecuteResultAsync(context);
return;
}
}
@@ -218,8 +216,13 @@ namespace Tgstation.Server.Host.Controllers
/// <summary>
/// Generic 501 response.
/// </summary>
/// <param name="ex">The <see cref="NotImplementedException"/> that was thrown.</param>
/// <returns>An <see cref="ObjectResult"/> with <see cref="HttpStatusCode.NotImplemented"/>.</returns>
protected ObjectResult RequiresPosixSystemIdentity() => this.StatusCode(HttpStatusCode.NotImplemented, new ErrorMessageResponse(ErrorCode.RequiresPosixSystemIdentity));
protected ObjectResult RequiresPosixSystemIdentity(NotImplementedException ex)
{
Logger.LogTrace(ex, "System identities not implemented!");
return this.StatusCode(HttpStatusCode.NotImplemented, new ErrorMessageResponse(ErrorCode.RequiresPosixSystemIdentity));
}
/// <summary>
/// Strongly type calls to <see cref="ControllerBase.StatusCode(int)"/>.
@@ -384,8 +387,7 @@ namespace Tgstation.Server.Host.Controllers
{
totalResults = await paginationResult.Results.CountAsync(cancellationToken);
pagedResults = await queriedResults
.ToListAsync(cancellationToken)
;
.ToListAsync(cancellationToken);
}
else
{
@@ -189,8 +189,7 @@ namespace Tgstation.Server.Host.Controllers
.ChatBots
.AsQueryable()
.Where(x => x.Id == id)
.DeleteAsync(cancellationToken))
;
.DeleteAsync(cancellationToken));
return null;
})
@@ -365,8 +364,7 @@ namespace Tgstation.Server.Host.Controllers
await chat.ChangeChannels(current.Id.Value, current.Channels, cancellationToken);
return null;
})
;
});
if (earlyOut != null)
return earlyOut;
@@ -85,12 +85,10 @@ namespace Tgstation.Server.Host.Controllers
model.Path,
systemIdentity,
model.LastReadHash,
cancellationToken)
;
cancellationToken);
return model.LastReadHash == null ? Accepted(newFile) : Json(newFile);
})
;
});
}
catch (IOException e)
{
@@ -100,9 +98,9 @@ namespace Tgstation.Server.Host.Controllers
AdditionalData = e.Message,
});
}
catch (NotImplementedException)
catch (NotImplementedException ex)
{
return RequiresPosixSystemIdentity();
return RequiresPosixSystemIdentity(ex);
}
}
@@ -130,14 +128,12 @@ namespace Tgstation.Server.Host.Controllers
{
var result = await instance
.Configuration
.Read(filePath, systemIdentity, cancellationToken)
;
.Read(filePath, systemIdentity, cancellationToken);
if (result == null)
return this.Gone();
return Json(result);
})
;
});
}
catch (IOException e)
{
@@ -147,9 +143,9 @@ namespace Tgstation.Server.Host.Controllers
AdditionalData = e.Message,
});
}
catch (NotImplementedException)
catch (NotImplementedException ex)
{
return RequiresPosixSystemIdentity();
return RequiresPosixSystemIdentity(ex);
}
}
@@ -184,8 +180,7 @@ namespace Tgstation.Server.Host.Controllers
{
var result = await instance
.Configuration
.ListDirectory(directoryPath, systemIdentity, cancellationToken)
;
.ListDirectory(directoryPath, systemIdentity, cancellationToken);
if (result == null)
return new PaginatableResult<ConfigurationFileResponse>(this.Gone());
@@ -194,10 +189,10 @@ namespace Tgstation.Server.Host.Controllers
.AsQueryable()
.OrderBy(x => x.Path));
}
catch (NotImplementedException)
catch (NotImplementedException ex)
{
return new PaginatableResult<ConfigurationFileResponse>(
RequiresPosixSystemIdentity());
RequiresPosixSystemIdentity(ex));
}
catch (UnauthorizedAccessException)
{
@@ -258,8 +253,7 @@ namespace Tgstation.Server.Host.Controllers
.Configuration
.CreateDirectory(model.Path, systemIdentity, cancellationToken)
? Json(resultModel)
: Created(resultModel))
;
: Created(resultModel));
}
catch (IOException e)
{
@@ -269,9 +263,9 @@ namespace Tgstation.Server.Host.Controllers
Message = e.Message,
});
}
catch (NotImplementedException)
catch (NotImplementedException ex)
{
return RequiresPosixSystemIdentity();
return RequiresPosixSystemIdentity(ex);
}
catch (UnauthorizedAccessException)
{
@@ -309,9 +303,9 @@ namespace Tgstation.Server.Host.Controllers
? NoContent()
: Conflict(new ErrorMessageResponse(ErrorCode.ConfigurationDirectoryNotEmpty)));
}
catch (NotImplementedException)
catch (NotImplementedException ex)
{
return RequiresPosixSystemIdentity();
return RequiresPosixSystemIdentity(ex);
}
catch (UnauthorizedAccessException)
{
@@ -95,8 +95,7 @@ namespace Tgstation.Server.Host.Controllers
await jobManager.RegisterOperation(
job,
(core, databaseContextFactory, paramJob, progressHandler, innerCt) => core.Watchdog.Launch(innerCt),
cancellationToken)
;
cancellationToken);
return Accepted(job.ToApi());
});
@@ -242,8 +241,7 @@ namespace Tgstation.Server.Host.Controllers
await watchdog.ResetRebootState(cancellationToken);
return await ReadImpl(current, cancellationToken);
})
;
});
}
#pragma warning restore CA1506
#pragma warning restore CA1502
@@ -277,8 +275,7 @@ namespace Tgstation.Server.Host.Controllers
await jobManager.RegisterOperation(
job,
(core, paramJob, databaseContextFactory, progressReporter, ct) => core.Watchdog.Restart(false, ct),
cancellationToken)
;
cancellationToken);
return Accepted(job.ToApi());
});
@@ -311,8 +308,7 @@ namespace Tgstation.Server.Host.Controllers
await jobManager.RegisterOperation(
job,
(core, databaseContextFactory, paramJob, progressReporter, ct) => core.Watchdog.CreateDump(ct),
cancellationToken)
;
cancellationToken);
return Accepted(job.ToApi());
});
@@ -337,8 +333,7 @@ namespace Tgstation.Server.Host.Controllers
.AsQueryable()
.Where(x => x.Id == Instance.Id)
.Select(x => x.DreamDaemonSettings)
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
if (settings == default)
return this.Gone();
}
@@ -80,8 +80,7 @@ namespace Tgstation.Server.Host.Controllers
.DreamMakerSettings
.AsQueryable()
.Where(x => x.InstanceId == Instance.Id)
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
return Json(dreamMakerSettings.ToApi());
}
@@ -153,8 +152,7 @@ namespace Tgstation.Server.Host.Controllers
job,
(core, databaseContextFactory, paramJob, progressReporter, jobCancellationToken)
=> core.DreamMaker.DeploymentProcess(paramJob, databaseContextFactory, progressReporter, jobCancellationToken),
cancellationToken)
;
cancellationToken);
return Accepted(job.ToApi());
}
@@ -189,8 +187,7 @@ namespace Tgstation.Server.Host.Controllers
.DreamMakerSettings
.AsQueryable()
.Where(x => x.InstanceId == Instance.Id)
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
if (hostModel == null)
return this.Gone();
@@ -215,8 +212,7 @@ namespace Tgstation.Server.Host.Controllers
.GetAvailablePort(
model.ApiValidationPort.Value,
true,
cancellationToken)
;
cancellationToken);
if (verifiedPort != model.ApiValidationPort)
return Conflict(new ErrorMessageResponse(ErrorCode.PortNotAvailable));
@@ -242,7 +242,7 @@ namespace Tgstation.Server.Host.Controllers
}
catch (NotImplementedException ex)
{
Logger.LogTrace(ex, "System identities not implemented!");
RequiresPosixSystemIdentity(ex);
}
using (systemIdentity)
@@ -298,9 +298,9 @@ namespace Tgstation.Server.Host.Controllers
PasswordHash = x.PasswordHash,
Enabled = x.Enabled,
Name = x.Name,
SystemIdentifier = x.SystemIdentifier,
})
.ToListAsync(cancellationToken)
;
.ToListAsync(cancellationToken);
// Pick the DB user first
var user = users
@@ -317,13 +317,13 @@ namespace Tgstation.Server.Host.Controllers
// FALLBACK TO THE DB USER HERE, DO NOT REVEAL A SYSTEM LOGIN!!!
// This of course, allows system users to discover TGS users in this (HIGHLY IMPROBABLE) case but that is not our fault
var originalHash = user.PasswordHash;
var isDbUser = originalHash != null;
bool usingSystemIdentity = systemIdentity != null && !isDbUser;
var isLikelyDbUser = originalHash != null;
bool usingSystemIdentity = systemIdentity != null && !isLikelyDbUser;
if (!oAuthLogin)
if (!usingSystemIdentity)
{
// DB User password check and update
if (!cryptographySuite.CheckUserPassword(user, ApiHeaders.Password))
if (!isLikelyDbUser || !cryptographySuite.CheckUserPassword(user, ApiHeaders.Password))
return Unauthorized();
if (user.PasswordHash != originalHash)
{
@@ -337,14 +337,30 @@ namespace Tgstation.Server.Host.Controllers
await DatabaseContext.Save(cancellationToken);
}
}
else if (systemIdentity.Username != user.Name)
else
{
// System identity username change update
Logger.LogDebug("User ID {userId}'s system identity needs a refresh, updating database.", user.Id);
DatabaseContext.Users.Attach(user);
user.Name = systemIdentity.Username;
user.CanonicalName = Models.User.CanonicalizeName(user.Name);
await DatabaseContext.Save(cancellationToken);
var usernameMismatch = systemIdentity.Username != user.Name;
if (isLikelyDbUser || usernameMismatch)
{
DatabaseContext.Users.Attach(user);
if (isLikelyDbUser)
{
// cleanup from https://github.com/tgstation/tgstation-server/issues/1528
Logger.LogDebug("System user ID {userId}'s PasswordHash is polluted, updating database.", user.Id);
user.PasswordHash = null;
user.LastPasswordUpdate = DateTimeOffset.UtcNow;
}
if (usernameMismatch)
{
// System identity username change update
Logger.LogDebug("User ID {userId}'s system identity needs a refresh, updating database.", user.Id);
user.Name = systemIdentity.Username;
user.CanonicalName = Models.User.CanonicalizeName(user.Name);
}
await DatabaseContext.Save(cancellationToken);
}
}
// Now that the bookeeping is done, tell them to fuck off if necessary
@@ -183,8 +183,7 @@ namespace Tgstation.Server.Host.Controllers
if (earlyOut != null && !newCancellationToken.IsCancellationRequested)
cts.Cancel();
},
newCancellationToken)
;
newCancellationToken);
}
catch (OperationCanceledException)
{
@@ -420,8 +419,7 @@ namespace Tgstation.Server.Host.Controllers
.ChatBots
.AsQueryable()
.Where(x => x.InstanceId == originalModel.Id)
.CountAsync(cancellationToken)
;
.CountAsync(cancellationToken);
if (countOfExistingChatBots > model.ChatBotLimit.Value)
return Conflict(new ErrorMessageResponse(ErrorCode.ChatBotMax));
@@ -487,8 +485,7 @@ namespace Tgstation.Server.Host.Controllers
job,
(core, databaseContextFactory, paramJob, progressHandler, ct) // core will be null here since the instance is offline
=> InstanceOperations.MoveInstance(originalModel, originalModelPath, ct),
cancellationToken)
;
cancellationToken);
api.MoveJob = job.ToApi();
}
@@ -549,8 +546,7 @@ namespace Tgstation.Server.Host.Controllers
.Where(x => !x.StoppedAt.HasValue && x.Description.StartsWith(MoveInstanceJobPrefix))
.Include(x => x.StartedBy).ThenInclude(x => x.CreatedBy)
.Include(x => x.Instance)
.ToListAsync(cancellationToken)
;
.ToListAsync(cancellationToken);
var needsUpdate = false;
var result = await Paginated<Models.Instance, InstanceResponse>(
@@ -566,8 +562,7 @@ namespace Tgstation.Server.Host.Controllers
},
page,
pageSize,
cancellationToken)
;
cancellationToken);
if (needsUpdate)
await DatabaseContext.Save(cancellationToken);
@@ -626,8 +621,7 @@ namespace Tgstation.Server.Host.Controllers
.SelectMany(x => x.Jobs)
.Where(x => !x.StoppedAt.HasValue && x.Description.StartsWith(MoveInstanceJobPrefix))
.Include(x => x.StartedBy).ThenInclude(x => x.CreatedBy)
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
api.MoveJob = moveJob?.ToApi();
await CheckAccessible(api, cancellationToken);
return Json(api);
@@ -655,14 +649,12 @@ namespace Tgstation.Server.Host.Controllers
var usersInstancePermissionSet = await BaseQuery()
.SelectMany(x => x.InstancePermissionSets)
.Where(x => x.PermissionSetId == AuthenticationContext.PermissionSet.Id.Value)
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
if (usersInstancePermissionSet == default)
{
// does the instance actually exist?
var instanceExists = await BaseQuery()
.AnyAsync(cancellationToken)
;
.AnyAsync(cancellationToken);
if (!instanceExists)
return this.Gone();
@@ -701,8 +693,7 @@ namespace Tgstation.Server.Host.Controllers
.GetAvailablePort(
Math.Min((ushort)(ddPort.Value + 1), DefaultApiValidationPort),
false,
cancellationToken)
;
cancellationToken);
if (!dmPort.HasValue)
return null;
@@ -74,8 +74,7 @@ namespace Tgstation.Server.Host.Controllers
{
UserId = x.UserId,
})
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
if (existingPermissionSet == default)
return this.Gone();
@@ -87,8 +86,7 @@ namespace Tgstation.Server.Host.Controllers
.AsQueryable()
.Where(x => x.Id == existingPermissionSet.UserId.Value)
.Select(x => x.CanonicalName)
.FirstAsync(cancellationToken)
;
.FirstAsync(cancellationToken);
if (userCanonicalName == Models.User.CanonicalizeName(Models.User.TgsSystemUserName))
return Forbid();
@@ -138,8 +136,7 @@ namespace Tgstation.Server.Host.Controllers
.Where(x => x.Id == Instance.Id)
.SelectMany(x => x.InstancePermissionSets)
.Where(x => x.PermissionSetId == model.PermissionSetId)
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
if (originalPermissionSet == null)
return this.Gone();
@@ -220,8 +217,7 @@ namespace Tgstation.Server.Host.Controllers
.Where(x => x.Id == Instance.Id)
.SelectMany(x => x.InstancePermissionSets)
.Where(x => x.PermissionSetId == id)
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
if (permissionSet == default)
return this.Gone();
return Json(permissionSet.ToApi());
@@ -247,8 +243,8 @@ namespace Tgstation.Server.Host.Controllers
.Where(x => x.Id == Instance.Id)
.SelectMany(x => x.InstancePermissionSets)
.Where(x => x.PermissionSetId == id)
.DeleteAsync(cancellationToken)
;
.DeleteAsync(cancellationToken);
return numDeleted > 0 ? NoContent() : this.Gone();
}
}
@@ -128,8 +128,7 @@ namespace Tgstation.Server.Host.Controllers
.AsQueryable()
.Include(x => x.StartedBy)
.Where(x => x.Id == id && x.Instance.Id == Instance.Id)
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
if (job == default)
return NotFound();
@@ -163,8 +162,7 @@ namespace Tgstation.Server.Host.Controllers
.Where(x => x.Id == id && x.Instance.Id == Instance.Id)
.Include(x => x.StartedBy)
.Include(x => x.CancelledBy)
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
if (job == default)
return NotFound();
var api = job.ToApi();
@@ -104,8 +104,7 @@ namespace Tgstation.Server.Host.Controllers
.RepositorySettings
.AsQueryable()
.Where(x => x.InstanceId == Instance.Id)
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
if (currentModel == default)
return this.Gone();
@@ -179,19 +178,16 @@ namespace Tgstation.Server.Host.Controllers
databaseContext.Instances.Attach(instance);
if (await PopulateApi(api, repos, databaseContext, instance, ct))
await databaseContext.Save(ct);
})
;
});
},
cancellationToken)
;
cancellationToken);
api.Origin = model.Origin;
api.Reference = model.Reference;
api.ActiveJob = job.ToApi();
return Created(api);
})
;
});
}
/// <summary>
@@ -211,8 +207,7 @@ namespace Tgstation.Server.Host.Controllers
.RepositorySettings
.AsQueryable()
.Where(x => x.InstanceId == Instance.Id)
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
if (currentModel == default)
return this.Gone();
@@ -234,8 +229,7 @@ namespace Tgstation.Server.Host.Controllers
await jobManager.RegisterOperation(
job,
(core, databaseContextFactory, paramJob, progressReporter, ct) => core.RepositoryManager.DeleteRepository(ct),
cancellationToken)
;
cancellationToken);
api.ActiveJob = job.ToApi();
return Accepted(api);
}
@@ -259,8 +253,7 @@ namespace Tgstation.Server.Host.Controllers
.RepositorySettings
.AsQueryable()
.Where(x => x.InstanceId == Instance.Id)
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
if (currentModel == default)
return this.Gone();
@@ -287,8 +280,7 @@ namespace Tgstation.Server.Host.Controllers
}
return Json(api);
})
;
});
}
/// <summary>
@@ -348,8 +340,7 @@ namespace Tgstation.Server.Host.Controllers
.RepositorySettings
.AsQueryable()
.Where(x => x.InstanceId == Instance.Id)
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
if (currentModel == default)
return this.Gone();
@@ -413,8 +404,7 @@ namespace Tgstation.Server.Host.Controllers
await PopulateApi(api, repo, DatabaseContext, Instance, cancellationToken);
return null;
})
;
});
if (earlyOut != null)
return earlyOut;
@@ -128,9 +128,9 @@ namespace Tgstation.Server.Host.Controllers
dbUser.Name = sysIdentity.Username;
dbUser.SystemIdentifier = sysIdentity.Uid;
}
catch (NotImplementedException)
catch (NotImplementedException ex)
{
return RequiresPosixSystemIdentity();
return RequiresPosixSystemIdentity(ex);
}
else if (!(model.Password?.Length == 0 && model.OAuthConnections?.Any() == true))
{
@@ -145,7 +145,7 @@ namespace Tgstation.Server.Host.Controllers
await DatabaseContext.Save(cancellationToken);
Logger.LogInformation("Created new user {0} ({1})", dbUser.Name, dbUser.Id);
Logger.LogInformation("Created new user {name} ({id})", dbUser.Name, dbUser.Id);
return Created(dbUser.ToApi());
}
@@ -196,8 +196,7 @@ namespace Tgstation.Server.Host.Controllers
.Include(x => x.Group)
.ThenInclude(x => x.PermissionSet)
.Include(x => x.PermissionSet)
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
if (originalUser == default)
return NotFound();
@@ -216,12 +215,21 @@ namespace Tgstation.Server.Host.Controllers
|| (!oAuthEdit && model.OAuthConnections != null))
return Forbid();
var originalUserHasSid = originalUser.SystemIdentifier != null;
if (originalUserHasSid && originalUser.PasswordHash != null)
{
// cleanup from https://github.com/tgstation/tgstation-server/issues/1528
Logger.LogDebug("System user ID {userId}'s PasswordHash is polluted, updating database.", originalUser.Id);
originalUser.PasswordHash = null;
originalUser.LastPasswordUpdate = DateTimeOffset.UtcNow;
}
if (model.SystemIdentifier != null && model.SystemIdentifier != originalUser.SystemIdentifier)
return BadRequest(new ErrorMessageResponse(ErrorCode.UserSidChange));
if (model.Password != null)
{
if (model.SystemIdentifier != null)
if (originalUserHasSid)
return BadRequest(new ErrorMessageResponse(ErrorCode.UserMismatchPasswordSid));
var result = TrySetPassword(originalUser, model.Password, false);
@@ -266,8 +274,7 @@ namespace Tgstation.Server.Host.Controllers
.AsQueryable()
.Where(x => x.Id == model.Group.Id)
.Include(x => x.PermissionSet)
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
if (originalUser.Group == default)
return this.Gone();
@@ -409,8 +416,7 @@ namespace Tgstation.Server.Host.Controllers
.AsQueryable()
.Where(x => x.Id == model.Group.Id)
.Include(x => x.PermissionSet)
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
else
permissionSet = new Models.PermissionSet
{
@@ -76,8 +76,7 @@ namespace Tgstation.Server.Host.Controllers
var totalGroups = await DatabaseContext
.Groups
.AsQueryable()
.CountAsync(cancellationToken)
;
.CountAsync(cancellationToken);
if (totalGroups >= generalConfiguration.UserGroupLimit)
return Conflict(new ErrorMessageResponse(ErrorCode.UserGroupLimitReached));
@@ -122,8 +121,7 @@ namespace Tgstation.Server.Host.Controllers
.Where(x => x.Id == model.Id)
.Include(x => x.PermissionSet)
.Include(x => x.Users)
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
if (currentGroup == default)
return this.Gone();
@@ -168,8 +166,7 @@ namespace Tgstation.Server.Host.Controllers
.Where(x => x.Id == id)
.Include(x => x.Users)
.Include(x => x.PermissionSet)
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
if (group == default)
return this.Gone();
return Json(group.ToApi(true));
@@ -221,8 +218,7 @@ namespace Tgstation.Server.Host.Controllers
.Groups
.AsQueryable()
.Where(x => x.Id == id && x.Users.Count == 0)
.DeleteAsync(cancellationToken)
;
.DeleteAsync(cancellationToken);
if (numDeleted > 0)
return NoContent();
@@ -232,8 +228,7 @@ namespace Tgstation.Server.Host.Controllers
.Groups
.AsQueryable()
.Where(x => x.Id == id)
.AnyAsync(cancellationToken)
;
.AnyAsync(cancellationToken);
return groupExists
? Conflict(new ErrorMessageResponse(ErrorCode.UserGroupNotEmpty))
@@ -215,8 +215,7 @@ namespace Tgstation.Server.Host.Database
.Users
.AsQueryable()
.Where(x => x.CanonicalName == User.CanonicalizeName(User.TgsSystemUserName))
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
if (tgsUser != null)
logger.LogError(
@@ -233,8 +232,7 @@ namespace Tgstation.Server.Host.Database
var allInstances = await databaseContext
.Instances
.AsQueryable()
.ToListAsync(cancellationToken)
;
.ToListAsync(cancellationToken);
foreach (var instance in allInstances)
instance.Path = instance.Path.Replace('\\', '/');
}
@@ -246,8 +244,7 @@ namespace Tgstation.Server.Host.Database
.AsQueryable()
.Where(x => x.TopicRequestTimeout == 0)
.Select(x => x.Id)
.ToListAsync(cancellationToken)
;
.ToListAsync(cancellationToken);
var rowsUpdated = ids.Count;
foreach (var id in ids)
@@ -318,8 +315,7 @@ namespace Tgstation.Server.Host.Database
.Include(x => x.CreatedBy)
.Include(x => x.PermissionSet)
.Include(x => x.Group)
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
if (admin == default)
SeedAdminUser(databaseContext);
@@ -14,6 +14,11 @@ namespace Tgstation.Server.Host.Extensions
/// </summary>
static readonly TaskCompletionSource InfiniteTaskCompletionSource = new ();
/// <summary>
/// Gets a <see cref="Task"/> that never completes.
/// </summary>
public static Task InfiniteTask => InfiniteTaskCompletionSource.Task;
/// <summary>
/// Create a <see cref="Task"/> that can be awaited while respecting a given <paramref name="cancellationToken"/>.
/// </summary>
@@ -53,11 +58,5 @@ namespace Tgstation.Server.Host.Extensions
return await task;
}
/// <summary>
/// Creates a <see cref="Task"/> that never completes.
/// </summary>
/// <returns>A never ending <see cref="Task"/>.</returns>
public static Task InfiniteTask() => InfiniteTaskCompletionSource.Task;
}
}
+15 -10
View File
@@ -11,6 +11,21 @@ namespace Tgstation.Server.Host.Jobs
/// </summary>
sealed class JobHandler : IDisposable
{
/// <summary>
/// If the job has started.
/// </summary>
public bool Started => task != null;
/// <summary>
/// The progress of the job.
/// </summary>
public int? Progress { get; set; }
/// <summary>
/// The stage of the job.
/// </summary>
public string Stage { get; set; }
/// <summary>
/// The <see cref="CancellationTokenSource"/> for <see cref="task"/>.
/// </summary>
@@ -39,16 +54,6 @@ namespace Tgstation.Server.Host.Jobs
/// <inheritdoc />
public void Dispose() => cancellationTokenSource.Dispose();
/// <summary>
/// The progress of the job.
/// </summary>
public int? Progress { get; set; }
/// <summary>
/// The stage of the job.
/// </summary>
public string Stage { get; set; }
/// <summary>
/// Wait for <see cref="task"/> to complete.
/// </summary>
+11 -2
View File
@@ -108,8 +108,7 @@ namespace Tgstation.Server.Host.Jobs
if (job.StartedBy == null)
job.StartedBy = await databaseContext
.Users
.GetTgsUser(cancellationToken)
;
.GetTgsUser(cancellationToken);
else
job.StartedBy = new User
{
@@ -256,13 +255,23 @@ namespace Tgstation.Server.Host.Jobs
{
if (job == null)
throw new ArgumentNullException(nameof(job));
if (!cancellationToken.CanBeCanceled)
throw new ArgumentException("A cancellable CancellationToken should be provided!", nameof(cancellationToken));
JobHandler handler;
bool noMoreJobsShouldStart;
lock (synchronizationLock)
{
if (!jobs.TryGetValue(job.Id.Value, out handler))
return;
noMoreJobsShouldStart = this.noMoreJobsShouldStart;
}
if (noMoreJobsShouldStart && !handler.Started)
await Extensions.TaskExtensions.InfiniteTask.WithToken(cancellationToken);
Task cancelTask = null;
using (jobCancellationToken.Register(() => cancelTask = CancelJob(job, canceller, true, cancellationToken)))
await handler.Wait(cancellationToken);
@@ -76,8 +76,7 @@ namespace Tgstation.Server.Host.Security
.Include(x => x.Group)
.ThenInclude(x => x.PermissionSet)
.Include(x => x.OAuthConnections)
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
if (user == default)
{
logger.LogWarning("Unable to find user with ID {0}!", userId);
@@ -110,8 +109,7 @@ namespace Tgstation.Server.Host.Security
.AsQueryable()
.Where(x => x.PermissionSetId == userPermissionSet.Id && x.InstanceId == instanceId && x.Instance.SwarmIdentifer == swarmConfiguration.Identifier)
.Include(x => x.Instance)
.FirstOrDefaultAsync(cancellationToken)
;
.FirstOrDefaultAsync(cancellationToken);
if (instancePermissionSet == null)
logger.LogDebug("User {0} does not have permissions on instance {1}!", userId, instanceId.Value);
@@ -68,8 +68,7 @@ namespace Tgstation.Server.Host.Security
userId,
apiHeaders.InstanceId,
tokenValidatedContext.SecurityToken.ValidFrom,
cancellationToken)
;
cancellationToken);
var authenticationContext = authenticationContextFactory.CurrentAuthenticationContext;
@@ -54,6 +54,11 @@ namespace Tgstation.Server.Host.Security
/// <inheritdoc />
public bool CheckUserPassword(User user, string password)
{
if (user == null)
throw new ArgumentNullException(nameof(user));
if (password == null)
throw new ArgumentNullException(nameof(password));
var result = passwordHasher.VerifyHashedPassword(user, user.PasswordHash, password);
switch (result)
{
@@ -63,11 +63,15 @@ namespace Tgstation.Server.Host.Security
PrincipalContext pc = null;
UserPrincipal principal = null;
GetUserAndDomainName(user.SystemIdentifier, out _, out var domainName);
bool TryGetPrincipalFromContextType(ContextType contextType)
{
try
{
pc = new PrincipalContext(contextType);
pc = domainName != null
? new PrincipalContext(contextType, domainName)
: new PrincipalContext(contextType);
cancellationToken.ThrowIfCancellationRequested();
principal = UserPrincipal.FindByIdentity(pc, user.SystemIdentifier);
}
@@ -75,9 +79,13 @@ namespace Tgstation.Server.Host.Security
{
throw;
}
catch (Exception e)
catch (Exception ex)
{
logger.LogWarning(e, "Error loading user for context type {0}!", contextType);
logger.LogDebug(
ex,
"Error loading user for context type {contextType} and principal \"{domainName}\"!",
contextType,
domainName);
}
finally
{
+8 -16
View File
@@ -176,8 +176,7 @@ namespace Tgstation.Server.Host.Setup
await console.WriteAsync(
$"API Port (leave blank for default of {GeneralConfiguration.DefaultApiPort}): ",
false,
cancellationToken)
;
cancellationToken);
var portString = await console.ReadLineAsync(false, cancellationToken);
if (String.IsNullOrWhiteSpace(portString))
return null;
@@ -319,8 +318,7 @@ namespace Tgstation.Server.Host.Setup
await console.WriteAsync(resolvedPath, true, cancellationToken);
bool writeResolved = await PromptYesNo(
"Would you like to save the relative path in the configuration? If not, the full path will be saved. (y/n): ",
cancellationToken)
;
cancellationToken);
if (writeResolved)
databaseName = resolvedPath;
@@ -344,23 +342,19 @@ namespace Tgstation.Server.Host.Setup
await console.WriteAsync(
"NOTE: It is HIGHLY reccommended that TGS runs on a complete relational database, specfically *NOT* Sqlite.",
true,
cancellationToken)
;
cancellationToken);
await console.WriteAsync(
"Sqlite, by nature cannot perform several DDL operations. Because of this future compatiblility cannot be guaranteed.",
true,
cancellationToken)
;
cancellationToken);
await console.WriteAsync(
"This means that you may not be able to update to the next minor version of TGS without a clean re-installation!",
true,
cancellationToken)
;
cancellationToken);
await console.WriteAsync(
"Please consider taking the time to set up a relational database if this is meant to be a long-standing server.",
true,
cancellationToken)
;
cancellationToken);
await console.WriteAsync(String.Empty, true, cancellationToken);
await asyncDelayer.Delay(TimeSpan.FromSeconds(3), cancellationToken);
@@ -379,8 +373,7 @@ namespace Tgstation.Server.Host.Setup
DatabaseType.SqlServer,
DatabaseType.Sqlite),
false,
cancellationToken)
;
cancellationToken);
var databaseTypeString = await console.ReadLineAsync(false, cancellationToken);
if (Enum.TryParse<DatabaseType>(databaseTypeString, out var databaseType))
return databaseType;
@@ -1007,8 +1000,7 @@ namespace Tgstation.Server.Host.Setup
elasticSearchConfiguration,
controlPanelConfiguration,
swarmConfiguration,
cancellationToken)
;
cancellationToken);
}
/// <summary>
@@ -380,7 +380,7 @@ namespace Tgstation.Server.Host.Swarm
? asyncDelayer.Delay(
TimeSpan.FromMinutes(UpdateCommitTimeoutMinutes),
cancellationToken)
: Extensions.TaskExtensions.InfiniteTask().WithToken(cancellationToken);
: Extensions.TaskExtensions.InfiniteTask.WithToken(cancellationToken);
var commitTask = Task.WhenAny(commitTcsTask, timeoutTask);
@@ -965,8 +965,7 @@ namespace Tgstation.Server.Host.Swarm
await Task.WhenAll(
currentSwarmServers
.Where(x => !x.Controller)
.Select(HealthRequestForServer))
;
.Select(HealthRequestForServer));
lock (swarmServers)
if (swarmServers.Count != currentSwarmServers.Count)
@@ -67,16 +67,14 @@ namespace Tgstation.Server.Host.Utils
.AsQueryable()
.Where(x => x.Instance.SwarmIdentifer == swarmConfiguration.Identifier)
.Select(x => x.Port)
.ToListAsync(cancellationToken)
;
.ToListAsync(cancellationToken);
var dmPorts = await databaseContext
.DreamMakerSettings
.AsQueryable()
.Where(x => x.Instance.SwarmIdentifer == swarmConfiguration.Identifier)
.Select(x => x.ApiValidationPort)
.ToListAsync(cancellationToken)
;
.ToListAsync(cancellationToken);
var exceptions = new List<Exception>();
ushort port = 0;
@@ -59,8 +59,8 @@ namespace Tgstation.Server.Tests.Live.Instance
if (!targetActiveJob.Progress.HasValue)
{
// give it 15 more seconds
targetActiveJob = await WaitForJobProgress(targetActiveJob, 15, cancellationToken);
// give it a few more seconds
targetActiveJob = await WaitForJobProgress(targetActiveJob, 30, cancellationToken);
allJobs = await JobsClient.List(null, cancellationToken);
}
@@ -224,29 +224,47 @@ namespace Tgstation.Server.Tests.Live.Instance
Assert.AreEqual(1, dumpFiles.Length);
File.Delete(dumpFiles.Single());
KillDD(true);
var jobTcs = new TaskCompletionSource();
var killTaskStarted = new TaskCompletionSource();
var killTask = Task.Run(() =>
{
killTaskStarted.SetResult();
while (!jobTcs.Task.IsCompleted)
KillDD(false);
}, cancellationToken);
JobResponse job;
try
while (true)
{
await killTaskStarted.Task;
var dumpTask = instanceClient.DreamDaemon.CreateDump(cancellationToken);
job = await WaitForJob(await dumpTask, 20, true, null, cancellationToken);
}
finally
{
jobTcs.SetResult();
await killTask;
KillDD(true);
var jobTcs = new TaskCompletionSource();
var killTaskStarted = new TaskCompletionSource();
var killTask = Task.Run(() =>
{
killTaskStarted.SetResult();
while (!jobTcs.Task.IsCompleted)
KillDD(false);
}, cancellationToken);
try
{
await killTaskStarted.Task;
var dumpTask = instanceClient.DreamDaemon.CreateDump(cancellationToken);
job = await WaitForJob(await dumpTask, 20, true, null, cancellationToken);
}
finally
{
jobTcs.SetResult();
await killTask;
}
// these can also happen
if (!(new PlatformIdentifier().IsWindows
&& (job.ExceptionDetails.Contains("BetterWin32Errors.Win32Exception: E_ACCESSDENIED: Access is denied.")
|| job.ExceptionDetails.Contains("BetterWin32Errors.Win32Exception: E_HANDLE: The handle is invalid.")
|| job.ExceptionDetails.Contains("BetterWin32Errors.Win32Exception: 3489660936: Unknown error (0xd0000008)")
|| job.ExceptionDetails.Contains("System.InvalidOperationException: No process is associated with this object.")
|| job.ExceptionDetails.Contains("BetterWin32Errors.Win32Exception: 2147942424: The program issued a command but the command length is incorrect."))))
break;
var restartJob = await instanceClient.DreamDaemon.Restart(cancellationToken);
await WaitForJob(restartJob, 20, false, null, cancellationToken);
}
Assert.IsTrue(job.ErrorCode == ErrorCode.DreamDaemonOffline || job.ErrorCode == ErrorCode.GCoreFailure, $"{job.ErrorCode}: {job.ExceptionDetails}");
await Task.Delay(TimeSpan.FromSeconds(20), cancellationToken);
var ddStatus = await instanceClient.DreamDaemon.Read(cancellationToken);
@@ -543,7 +561,7 @@ namespace Tgstation.Server.Tests.Live.Instance
TopicResponse topicRequestResult = null;
try
{
System.Console.WriteLine($"Topic limit test S:{payloadSize}...");
System.Console.WriteLine($"Topic send limit test S:{currentSize}...");
topicRequestResult = await TopicClientNoLogger.SendTopic(
IPAddress.Loopback,
$"tgs_integration_test_tactics3={TopicClient.SanitizeString(JsonConvert.SerializeObject(topic, DMApiConstants.SerializerSettings))}",
@@ -575,6 +593,8 @@ namespace Tgstation.Server.Tests.Live.Instance
Assert.AreEqual(DMApiConstants.MaximumTopicRequestLength, (uint)lastSize);
System.Console.WriteLine("TEST: Receiving Topic tests topics...");
// Receive
baseSize = 1;
nextPow = 0;
@@ -582,6 +602,7 @@ namespace Tgstation.Server.Tests.Live.Instance
while (!cancellationToken.IsCancellationRequested)
{
var currentSize = baseSize + (int)Math.Pow(2, nextPow);
System.Console.WriteLine($"Topic recieve limit test S:{currentSize}...");
var topicRequestResult = await TopicClientNoLogger.SendTopic(
IPAddress.Loopback,
$"tgs_integration_test_tactics4={TopicClient.SanitizeString(currentSize.ToString())}",