mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-24 05:27:30 +01:00
Merge pull request #2310 from tgstation/Omega [TGSDeploy]
v6.18.0: Merging way too many things at once
This commit is contained in:
@@ -50,6 +50,9 @@ You must also have the following environment variables set. To run them more acc
|
||||
- `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.
|
||||
- (Optional) `TGS_TEST_GITHUB_TOKEN`: A GitHub personal access token with no scopes used to bypass rate limits.
|
||||
- (Optional) `TGS_TEST_BYOND_ZIP_DOWNLOAD_TEMPLATE`: Template URL for downloading BYOND zip files from a non-official mirror.
|
||||
- (Optional) `TGS_TEST_BYOND_MIRROR_VERSION_TXT`: version.txt for a BYOND zip mirror. Requires `TGS_TEST_BYOND_ZIP_DOWNLOAD_TEMPLATE`.
|
||||
- (Optional) `TGS_TEST_BYOND_ZIPS_BASE_PATH`: Directory on disk to cache BYOND zip files.
|
||||
- (Optional) The following variables are all interdependent, so if one is set they all must be.
|
||||
- `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.
|
||||
|
||||
@@ -42,6 +42,9 @@ env:
|
||||
TGS_NODE_VERSION: 20.x
|
||||
TGS_TEST_GITHUB_TOKEN: ${{ secrets.LIVE_TESTS_TOKEN }}
|
||||
PACKAGING_PRIVATE_KEY_PASSPHRASE: ${{ secrets.PACKAGING_PRIVATE_KEY_PASSPHRASE }}
|
||||
TGS_TEST_BYOND_ZIP_DOWNLOAD_TEMPLATE: https://mocha.affectedarc07.co.uk/tgs_byond_mirrors/${Major}.${Minor}_byond${Linux:_linux}.zip
|
||||
TGS_TEST_BYOND_MIRROR_VERSION_TXT: https://mocha.affectedarc07.co.uk/tgs_byond_mirrors/version.txt
|
||||
DMAPI_BYOND_BUILD_MIRROR_DOWNLOAD_TEMPLATE: https://mocha.affectedarc07.co.uk/tgs_byond_mirrors/${FULL_VERSION}_byond_linux.zip # Available vars are $BYOND_MAJOR (i.e. 516) and $FULL_VERSION (i.e. 516.1666)
|
||||
|
||||
concurrency:
|
||||
group: "ci-${{ (github.event_name != 'push' && github.event_name != 'schedule' && github.event.inputs.pull_request_number) || github.run_id }}-${{ github.event_name }}"
|
||||
@@ -90,6 +93,56 @@ jobs:
|
||||
name: release_notes_bins
|
||||
path: ./release_notes_bins/
|
||||
|
||||
validate-nix-flake:
|
||||
name: Validate Nix Flake
|
||||
needs: start-gate
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TEST_TGS_VERSION: "6.17.0" # Version we use here doesn't matter as it won't be executed. Just used to download a zip and calc hash
|
||||
steps:
|
||||
- name: Install Native Packages # Name checked in rerunFlakyTests.js
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y xmlstarlet
|
||||
|
||||
- name: Setup Nix
|
||||
uses: cachix/install-nix-action@v31
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
|
||||
- name: Checkout (Branch)
|
||||
uses: actions/checkout@v4
|
||||
if: github.event_name == 'push' || github.event_name == 'schedule'
|
||||
|
||||
- name: Checkout (PR Merge)
|
||||
uses: actions/checkout@v4
|
||||
if: github.event_name != 'push' && github.event_name != 'schedule'
|
||||
with:
|
||||
ref: "refs/pull/${{ inputs.pull_request_number }}/merge"
|
||||
|
||||
- name: Replace current TGS version with test version
|
||||
run: |
|
||||
CURRENT_TGS_VERSION="$(xmlstarlet sel -N X="http://schemas.microsoft.com/developer/msbuild/2003" --template --value-of /X:Project/X:PropertyGroup/X:TgsCoreVersion build/Version.props)"
|
||||
sed -i -e "s/<TgsCoreVersion>${CURRENT_TGS_VERSION}/<TgsCoreVersion>${{ env.TEST_TGS_VERSION }}/g" build/Version.props
|
||||
|
||||
- name: Retrieve ServerConsole.zip Artifact
|
||||
run: |
|
||||
mkdir release
|
||||
curl -L https://github.com/tgstation/tgstation-server/releases/download/tgstation-server-v${{ env.TEST_TGS_VERSION }}/ServerConsole.zip -f -o release/ServerConsole.zip
|
||||
|
||||
- name: Regenerate Nix Hash
|
||||
run: |
|
||||
nix hash path ./release > build/package/nix/ServerConsole.sha256
|
||||
cat build/package/nix/ServerConsole.sha256
|
||||
|
||||
- name: Cleanup Download
|
||||
run: rm -rf ./release
|
||||
|
||||
- name: Check Flake
|
||||
run: |
|
||||
cd build/package/nix
|
||||
nix flake check
|
||||
|
||||
code-scanning:
|
||||
name: Run CodeQL
|
||||
needs: start-gate
|
||||
@@ -164,21 +217,14 @@ jobs:
|
||||
run: |
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y -o APT::Immediate-Configure=0 libc6-i386 libstdc++6:i386 libgcc-s1:i386
|
||||
sudo apt-get install -y -o APT::Immediate-Configure=0 libc6-i386 libstdc++6:i386 libgcc-s1:i386 libcurl4:i386
|
||||
|
||||
- name: Cache BYOND .zips
|
||||
uses: actions/cache@v4
|
||||
id: cache-byond
|
||||
with:
|
||||
path: ~/byond-zips-cache
|
||||
key: byond-zips
|
||||
|
||||
- name: Setup BYOND Cache if Necessary and Install
|
||||
- name: Evaluate BYOND version
|
||||
id: version_evaluation
|
||||
run: |
|
||||
echo "Setting up BYOND."
|
||||
FULL_VERSION=${{ matrix.byond }}
|
||||
if [[ "$FULL_VERSION" = "EDGE" ]] ; then
|
||||
VERSIONS=$(curl https://www.byond.com/download/version.txt)
|
||||
VERSIONS=$(curl https://spacestation13.github.io/byond-builds/version.txt)
|
||||
FULL_VERSION=$(echo "$VERSIONS" | tail -n1)
|
||||
echo "EDGE version evaluated to $FULL_VERSION"
|
||||
|
||||
@@ -190,14 +236,31 @@ jobs:
|
||||
FULL_VERSION=${bad_linux_releases[$FULL_VERSION]}
|
||||
fi
|
||||
fi
|
||||
if [[ ! -f $HOME/byond-zips-cache/linux/$FULL_VERSION.zip ]] ; then
|
||||
echo "EVALUATED_VERSION=$FULL_VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache BYOND .zips
|
||||
uses: actions/cache@v4
|
||||
id: cache-byond
|
||||
with:
|
||||
path: ~/byond-zips-cache/linux/${{ steps.version_evaluation.outputs.EVALUATED_VERSION }}
|
||||
key: byond-zips-linux-${{ steps.version_evaluation.outputs.EVALUATED_VERSION }}
|
||||
|
||||
- name: Setup BYOND Cache if Necessary and Install
|
||||
run: |
|
||||
echo "Setting up BYOND."
|
||||
FULL_VERSION=${{ steps.version_evaluation.outputs.EVALUATED_VERSION }}
|
||||
if [[ ! -f $HOME/byond-zips-cache/linux/$FULL_VERSION/$FULL_VERSION.zip ]] ; then
|
||||
BYOND_MAJOR=${FULL_VERSION%.*}
|
||||
mkdir -p $HOME/byond-zips-cache/linux
|
||||
curl "https://www.byond.com/download/build/$BYOND_MAJOR/${FULL_VERSION}_byond_linux.zip" -o $HOME/byond-zips-cache/linux/$FULL_VERSION.zip
|
||||
mkdir -p $HOME/byond-zips-cache/linux/$FULL_VERSION
|
||||
DOWNLOAD_URL="${{ env.DMAPI_BYOND_BUILD_MIRROR_DOWNLOAD_TEMPLATE }}"
|
||||
if [[ "${{ matrix.byond }}" = "EDGE" ]] ; then
|
||||
DOWNLOAD_URL="https://spacestation13.github.io/byond-builds/$BYOND_MAJOR/${FULL_VERSION}_byond_linux.zip"
|
||||
fi
|
||||
curl "$DOWNLOAD_URL" -o $HOME/byond-zips-cache/linux/$FULL_VERSION/$FULL_VERSION.zip
|
||||
fi
|
||||
mkdir -p "$HOME/BYOND"
|
||||
cd "$HOME/BYOND"
|
||||
cp $HOME/byond-zips-cache/linux/$FULL_VERSION.zip byond.zip
|
||||
cp $HOME/byond-zips-cache/linux/$FULL_VERSION/$FULL_VERSION.zip byond.zip
|
||||
unzip byond.zip
|
||||
cd byond
|
||||
make here
|
||||
@@ -509,13 +572,6 @@ jobs:
|
||||
if: always()
|
||||
run: rm -f ${{ env.TGS_TELEMETRY_KEY_FILE }}
|
||||
|
||||
- name: Cache BYOND .zips
|
||||
uses: actions/cache@v4
|
||||
id: cache-byond
|
||||
with:
|
||||
path: ~/byond-zips-cache
|
||||
key: byond-zips
|
||||
|
||||
- name: Run Unit Tests
|
||||
run: sudo dotnet test --no-build --logger "GitHubActions;summary.includePassedTests=true;summary.includeSkippedTests=true" --filter TestCategory!=RequiresDatabase -c ${{ matrix.configuration }}NoWindows --collect:"XPlat Code Coverage" --settings build/ci.runsettings --results-directory ./TestResults tgstation-server.sln
|
||||
env:
|
||||
@@ -578,13 +634,6 @@ jobs:
|
||||
if: always()
|
||||
run: rm -f ${{ env.TGS_TELEMETRY_KEY_FILE }}
|
||||
|
||||
- name: Cache BYOND .zips
|
||||
uses: actions/cache@v4
|
||||
id: cache-byond
|
||||
with:
|
||||
path: ~/byond-zips-cache
|
||||
key: byond-zips
|
||||
|
||||
- name: Run Unit Tests
|
||||
run: dotnet test --no-build --logger "GitHubActions;summary.includePassedTests=true;summary.includeSkippedTests=true" --filter TestCategory!=RequiresDatabase -c ${{ matrix.configuration }}NoWix --collect:"XPlat Code Coverage" --settings build/ci.runsettings --results-directory ./TestResults tgstation-server.sln
|
||||
env:
|
||||
@@ -598,9 +647,50 @@ jobs:
|
||||
name: windows-unit-test-coverage-${{ matrix.configuration }}
|
||||
path: ./TestResults/
|
||||
|
||||
prep-edge-versions:
|
||||
name: Prepare Live Tests Cache of EDGE Versions
|
||||
needs: start-gate
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Cache BYOND .zips (Linux)
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/byond-zips-cache/live/linux
|
||||
key: byond-zips-linux-live
|
||||
|
||||
- name: Cache BYOND .zips (Windows)
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/byond-zips-cache/live/windows
|
||||
key: byond-zips-windows-live
|
||||
|
||||
- name: Evaluate BYOND version
|
||||
id: version_evaluation
|
||||
run: |
|
||||
VERSIONS=$(curl https://spacestation13.github.io/byond-builds/version.txt)
|
||||
FULL_VERSION=$(echo "$VERSIONS" | tail -n1)
|
||||
echo "EDGE version evaluated to $FULL_VERSION"
|
||||
echo "EVALUATED_VERSION=$FULL_VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Setup BYOND Cache if Necessary and Install
|
||||
run: |
|
||||
echo "Downloading BYOND."
|
||||
FULL_VERSION=${{ steps.version_evaluation.outputs.EVALUATED_VERSION }}
|
||||
BYOND_MAJOR=${FULL_VERSION%.*}
|
||||
if [[ ! -f $HOME/byond-zips-cache/live/linux/$FULL_VERSION/$FULL_VERSION.zip ]] ; then
|
||||
mkdir -p $HOME/byond-zips-cache/live/linux/$FULL_VERSION
|
||||
DOWNLOAD_URL="https://spacestation13.github.io/byond-builds/$BYOND_MAJOR/${FULL_VERSION}_byond_linux.zip"
|
||||
curl "$DOWNLOAD_URL" -o $HOME/byond-zips-cache/live/linux/$FULL_VERSION/$FULL_VERSION.zip
|
||||
fi
|
||||
if [[ ! -f $HOME/byond-zips-cache/live/windows/$FULL_VERSION/$FULL_VERSION.zip ]] ; then
|
||||
mkdir -p $HOME/byond-zips-cache/live/windows/$FULL_VERSION
|
||||
DOWNLOAD_URL="https://spacestation13.github.io/byond-builds/$BYOND_MAJOR/${FULL_VERSION}_byond.zip"
|
||||
curl "$DOWNLOAD_URL" -o $HOME/byond-zips-cache/live/windows/$FULL_VERSION/$FULL_VERSION.zip
|
||||
fi
|
||||
|
||||
windows-integration-tests:
|
||||
name: Windows Live Tests
|
||||
needs: [dmapi-build, opendream-build]
|
||||
needs: [dmapi-build, opendream-build, prep-edge-versions]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -633,6 +723,12 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ env.TGS_NODE_VERSION }}
|
||||
|
||||
- name: Cache BYOND .zips
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/byond-zips-cache/live/windows
|
||||
key: byond-zips-windows-live
|
||||
|
||||
- name: Set TGS_TEST_DUMP_API_SPEC
|
||||
if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'Advanced' && matrix.database-type == 'SqlServer' }}
|
||||
run: echo "TGS_TEST_DUMP_API_SPEC=yes" >> $Env:GITHUB_ENV
|
||||
@@ -717,13 +813,6 @@ jobs:
|
||||
if: always()
|
||||
run: rm -f ${{ env.TGS_TELEMETRY_KEY_FILE }}
|
||||
|
||||
- name: Cache BYOND .zips
|
||||
uses: actions/cache@v4
|
||||
id: cache-byond
|
||||
with:
|
||||
path: ~/byond-zips-cache
|
||||
key: byond-zips
|
||||
|
||||
- name: Run Live Tests # Logging here is weird because printing massive amounts of text on Windows runners is SLOW AS SHIT!!!
|
||||
id: live-tests
|
||||
shell: bash
|
||||
@@ -816,7 +905,7 @@ jobs:
|
||||
|
||||
linux-integration-tests:
|
||||
name: Linux Live Tests
|
||||
needs: [dmapi-build, opendream-build]
|
||||
needs: [dmapi-build, opendream-build, prep-edge-versions]
|
||||
services: # We start all dbs here so we can just code the stuff once
|
||||
mssql:
|
||||
image: ${{ (matrix.database-type == 'SqlServer') && 'mcr.microsoft.com/mssql/server:2019-latest' || '' }}
|
||||
@@ -884,13 +973,19 @@ jobs:
|
||||
run: |
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y -o APT::Immediate-Configure=0 libc6-i386 libstdc++6:i386 gdb libgcc-s1:i386 libgdiplus
|
||||
sudo apt-get install -y -o APT::Immediate-Configure=0 libc6-i386 libstdc++6:i386 gdb libgcc-s1:i386 libgdiplus libcurl4:i386
|
||||
|
||||
- name: Setup Node.JS
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.TGS_NODE_VERSION }}
|
||||
|
||||
- name: Cache BYOND .zips
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/byond-zips-cache/live/linux
|
||||
key: byond-zips-linux-live
|
||||
|
||||
- name: Set Sqlite Connection Info
|
||||
if: ${{ matrix.database-type == 'Sqlite' }}
|
||||
run: |
|
||||
@@ -945,13 +1040,6 @@ jobs:
|
||||
if: always()
|
||||
run: rm -f ${{ env.TGS_TELEMETRY_KEY_FILE }}
|
||||
|
||||
- name: Cache BYOND .zips
|
||||
uses: actions/cache@v4
|
||||
id: cache-byond
|
||||
with:
|
||||
path: ~/byond-zips-cache
|
||||
key: byond-zips
|
||||
|
||||
- name: Run Live Tests
|
||||
run: |
|
||||
cd tests/Tgstation.Server.Tests
|
||||
@@ -1657,6 +1745,7 @@ jobs:
|
||||
check-winget-pr-template,
|
||||
efcore-version-match,
|
||||
code-scanning,
|
||||
validate-nix-flake,
|
||||
]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
sudo apt-get install -y xmlstarlet
|
||||
|
||||
- name: Setup Nix
|
||||
uses: cachix/install-nix-action@v30
|
||||
uses: cachix/install-nix-action@v31
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
|
||||
|
||||
+3
-1
@@ -59,11 +59,13 @@ RUN export TGS_TELEMETRY_KEY_FILE="../../${TGS_TELEMETRY_KEY_FILE}" \
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0-bookworm-slim
|
||||
|
||||
#needed for byond, curl for healthchecks
|
||||
RUN apt-get update \
|
||||
RUN dpkg --add-architecture i386 \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y \
|
||||
gcc-multilib \
|
||||
gdb \
|
||||
curl \
|
||||
libcurl4:i386 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<!-- Usage: Hard to say what exactly this is for, but not including it removes the test icon and breaks vstest.console.exe for some reason -->
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" Condition="'$(TgsTestNoSdk)' != 'true'" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" Condition="'$(TgsTestNoSdk)' != 'true'" />
|
||||
<!-- Usage: Dependency mocking for tests -->
|
||||
<!-- Pinned: Be VERY careful about updating https://github.com/moq/moq/issues/1372 -->
|
||||
<PackageReference Include="Moq" Version="4.20.72" />
|
||||
<!-- Usage: MSTest execution -->
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.8.3" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.9.3" />
|
||||
<!-- Usage: MSTest asserts etc... -->
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.8.3" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.9.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
+2
-2
@@ -3,8 +3,8 @@
|
||||
<!-- Integration tests will ensure they match across the board -->
|
||||
<Import Project="WebpanelVersion.props" />
|
||||
<PropertyGroup>
|
||||
<TgsCoreVersion>6.17.0</TgsCoreVersion>
|
||||
<TgsConfigVersion>5.7.0</TgsConfigVersion>
|
||||
<TgsCoreVersion>6.18.0</TgsCoreVersion>
|
||||
<TgsConfigVersion>5.8.0</TgsConfigVersion>
|
||||
<TgsRestVersion>10.13.0</TgsRestVersion>
|
||||
<TgsGraphQLVersion>0.6.0</TgsGraphQLVersion>
|
||||
<TgsCommonLibraryVersion>7.0.0</TgsCommonLibraryVersion>
|
||||
|
||||
@@ -22,11 +22,6 @@ apt-get install -y \
|
||||
xmlstarlet \
|
||||
libgdiplus
|
||||
|
||||
declare repo_version=$(if command -v lsb_release &> /dev/null; then lsb_release -r -s; else grep -oP '(?<=^VERSION_ID=).+' /etc/os-release | tr -d '"'; fi)
|
||||
curl -L https://packages.microsoft.com/config/ubuntu/$repo_version/packages-microsoft-prod.deb -o packages-microsoft-prod.deb
|
||||
dpkg -i ./packages-microsoft-prod.deb
|
||||
rm packages-microsoft-prod.deb
|
||||
|
||||
# https://github.com/nodesource/distributions
|
||||
mkdir -p /etc/apt/keyrings
|
||||
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
||||
|
||||
@@ -21,6 +21,7 @@ Depends:
|
||||
libstdc++6:i386 [amd64],
|
||||
libstdc++6 [i386],
|
||||
gcc-multilib [amd64],
|
||||
libcurl4 [i386],
|
||||
Recommends:
|
||||
libsystemd0,
|
||||
gdb,
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
|
||||
inputs = {};
|
||||
|
||||
outputs = { ... }: {
|
||||
outputs = { nixpkgs, ... }: {
|
||||
nixosModules = {
|
||||
default = { ... }: {
|
||||
imports = [ ./tgstation-server.nix ];
|
||||
};
|
||||
};
|
||||
checks.x86_64-linux.package-build = nixpkgs.legacyPackages.x86_64-linux.callPackage ./package.nix { };
|
||||
};
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ stdenv.mkDerivation {
|
||||
gcc_multi
|
||||
glibc
|
||||
bash
|
||||
curl
|
||||
];
|
||||
nativeBuildInputs = with pkgs; [
|
||||
makeWrapper
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<!-- Usage: HTTP constants reference -->
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.3.0" />
|
||||
<!-- Usage: Decoding the 'nbf' property of JWTs -->
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.8.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.13.0" />
|
||||
<!-- Usage: Data model annotating -->
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<!-- GraphQL connector and code generator -->
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.4" />
|
||||
<PackageReference Include="StrawberryShake.Server" Version="15.1.3" />
|
||||
<PackageReference Include="StrawberryShake.Server" Version="15.1.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Usage: Connecting to SignalR hubs in API -->
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.7" />
|
||||
<!-- Usage: Using target JSON serializer for API -->
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="9.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Usage: Identifying if we're running under SystemD -->
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="9.0.7" />
|
||||
<!-- Usage: Console logging plugin -->
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -21,21 +21,21 @@
|
||||
<!-- Usage: Command line argument support -->
|
||||
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
|
||||
<!-- Usage: Identifies when we are running in the context of the Windows SCM -->
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.7" />
|
||||
<!-- Usage: Windows event log logging plugin -->
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.EventLog" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.EventLog" Version="9.0.7" />
|
||||
<!-- Usage: Console logging plugin -->
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.7" />
|
||||
<!-- Usage: Updated transitive dependency of Core.System.ServiceProcess -->
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<!-- Usage: Updated transitive dependency of Core.System.ServiceProcess -->
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.4" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.7" />
|
||||
<!-- Usage: Updated transitive dependency, unable to tell what of -->
|
||||
<PackageReference Include="System.Private.Uri" Version="4.3.2" />
|
||||
<!-- Usage: OS identification -->
|
||||
<PackageReference Include="System.Runtime.InteropServices" Version="4.3.0" />
|
||||
<!-- Usage: Windows Service Manager intergration -->
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="9.0.4" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="9.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
+2
-1
@@ -38,7 +38,8 @@
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="StrawberryShake.Server" Version="15.1.3" />
|
||||
<!-- GraphQL code generator -->
|
||||
<PackageReference Include="StrawberryShake.Server" Version="15.1.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1855,14 +1855,15 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"form-data@npm:^4.0.0":
|
||||
version: 4.0.2
|
||||
resolution: "form-data@npm:4.0.2"
|
||||
version: 4.0.4
|
||||
resolution: "form-data@npm:4.0.4"
|
||||
dependencies:
|
||||
asynckit: "npm:^0.4.0"
|
||||
combined-stream: "npm:^1.0.8"
|
||||
es-set-tostringtag: "npm:^2.1.0"
|
||||
hasown: "npm:^2.0.2"
|
||||
mime-types: "npm:^2.1.12"
|
||||
checksum: 10/82c65b426af4a40090e517a1bc9057f76970b4c6043e37aa49859c447d88553e77d4cc5626395079a53d2b0889ba5f2a49f3900db3ad3f3f1bf76613532572fb
|
||||
checksum: 10/a4b62e21932f48702bc468cc26fb276d186e6b07b557e3dd7cc455872bdbb82db7db066844a64ad3cf40eaf3a753c830538183570462d3649fdfd705601cbcfb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Usage: Logging abstractions -->
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.7" />
|
||||
<!-- Usage: POSIX support for signals -->
|
||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-ef": {
|
||||
"version": "9.0.4",
|
||||
"version": "9.0.7",
|
||||
"commands": [
|
||||
"dotnet-ef"
|
||||
]
|
||||
|
||||
@@ -351,28 +351,11 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
|
||||
/// <inheritdoc />
|
||||
public void QueueWatchdogMessage(string message)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(message);
|
||||
=> QueueMessageGeneric(mapping => mapping.IsWatchdogChannel, message, "WD");
|
||||
|
||||
message = String.Format(CultureInfo.InvariantCulture, "WD: {0}", message);
|
||||
|
||||
if (!initialProviderConnectionsTask!.IsCompleted)
|
||||
logger.LogTrace("Waiting for initial provider connections before sending watchdog message...");
|
||||
|
||||
// Reimplementing QueueMessage
|
||||
QueueMessageInternal(
|
||||
new MessageContent
|
||||
{
|
||||
Text = message,
|
||||
},
|
||||
() =>
|
||||
{
|
||||
// so it doesn't change while we're using it
|
||||
lock (mappedChannels)
|
||||
return mappedChannels.Where(x => x.Value.IsWatchdogChannel).Select(x => x.Key).ToList();
|
||||
},
|
||||
true);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public void QueueRawDeploymentMessage(string message)
|
||||
=> QueueMessageGeneric(mapping => mapping.IsUpdatesChannel, message, null);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Func<string?, string, Action<bool>> QueueDeploymentMessage(
|
||||
@@ -1111,5 +1094,38 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
|
||||
AddMessageTask(SendMessageTask());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queues a message to a selected set of <see cref="ChannelMapping"/>s.
|
||||
/// </summary>
|
||||
/// <param name="channelSelector">A <see cref="Predicate{T}"/> for selecting the <see cref="ChannelMapping"/>s to send to.</param>
|
||||
/// <param name="message">The message to send.</param>
|
||||
/// <param name="prefix">The optional prefix to the message to be sent.</param>
|
||||
void QueueMessageGeneric(Predicate<ChannelMapping> channelSelector, string message, string? prefix)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(message);
|
||||
|
||||
if (prefix != null)
|
||||
{
|
||||
message = $"{prefix}: {message}";
|
||||
}
|
||||
|
||||
if (!initialProviderConnectionsTask!.IsCompleted)
|
||||
logger.LogTrace("Waiting for initial provider connections before sending chat message...");
|
||||
|
||||
// Reimplementing QueueMessage
|
||||
QueueMessageInternal(
|
||||
new MessageContent
|
||||
{
|
||||
Text = message,
|
||||
},
|
||||
() =>
|
||||
{
|
||||
// so it doesn't change while we're using it
|
||||
lock (mappedChannels)
|
||||
return mappedChannels.Where(x => channelSelector(x.Value)).Select(x => x.Key).ToList();
|
||||
},
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,12 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// <param name="message">The message being sent.</param>
|
||||
void QueueWatchdogMessage(string message);
|
||||
|
||||
/// <summary>
|
||||
/// Queue a chat <paramref name="message"/> to configured deployment channels.
|
||||
/// </summary>
|
||||
/// <param name="message">The message being sent.</param>
|
||||
void QueueRawDeploymentMessage(string message);
|
||||
|
||||
/// <summary>
|
||||
/// Send the message for a deployment to configured deployment channels.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
@@ -431,7 +430,7 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
// Don't dispose it
|
||||
logger.LogDebug("Creating legacy two folder .dmb provider targeting {aDirName} directory...", LegacyADirectoryName);
|
||||
#pragma warning disable CA2000 // Dispose objects before losing scope (false positive)
|
||||
newProvider = new DmbProvider(compileJob, engineVersion, ioManager, new DisposeInvoker(CleanupAction), Path.DirectorySeparatorChar + LegacyADirectoryName);
|
||||
newProvider = new DmbProvider(compileJob, engineVersion, ioManager, new DisposeInvoker(CleanupAction), LegacyADirectoryName);
|
||||
#pragma warning restore CA2000 // Dispose objects before losing scope
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,18 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
sealed class DmbProvider : DmbProviderBase, IDmbProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string Directory => ioManager.ResolvePath(CompileJob.DirectoryName!.Value.ToString() + directoryAppend);
|
||||
public override string Directory
|
||||
{
|
||||
get
|
||||
{
|
||||
var stringifiedCompileJobDirectory = CompileJob.DirectoryName!.Value.ToString();
|
||||
|
||||
if (directoryAppend != null)
|
||||
stringifiedCompileJobDirectory = ioManager.ConcatPath(stringifiedCompileJobDirectory, directoryAppend);
|
||||
|
||||
return ioManager.ResolvePath(stringifiedCompileJobDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Models.CompileJob CompileJob { get; }
|
||||
@@ -28,7 +39,7 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
/// <summary>
|
||||
/// Extra path to add to the end of <see cref="CompileJob.DirectoryName"/>.
|
||||
/// </summary>
|
||||
readonly string directoryAppend;
|
||||
readonly string? directoryAppend;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Action"/> to run when <see cref="DisposeAsync"/> is called.
|
||||
@@ -49,7 +60,7 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
EngineVersion = engineVersion ?? throw new ArgumentNullException(nameof(engineVersion));
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose));
|
||||
this.directoryAppend = directoryAppend ?? String.Empty;
|
||||
this.directoryAppend = directoryAppend;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -187,8 +188,10 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
dest = IOManager.ResolvePath(mirrorGuid.ToString());
|
||||
|
||||
using var semaphore = taskThrottle.HasValue ? new SemaphoreSlim(taskThrottle.Value) : null;
|
||||
|
||||
var dir = await IOManager.DirectoryInfo(src, cancellationToken);
|
||||
await Task.WhenAll(MirrorDirectoryImpl(
|
||||
src,
|
||||
dir,
|
||||
dest,
|
||||
semaphore,
|
||||
securityLevel,
|
||||
@@ -230,21 +233,20 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
/// <summary>
|
||||
/// Recursively create tasks to create a hard link directory mirror of <paramref name="src"/> to <paramref name="dest"/>.
|
||||
/// </summary>
|
||||
/// <param name="src">The source directory path.</param>
|
||||
/// <param name="src">The source <see cref="IDirectoryInfo"/>.</param>
|
||||
/// <param name="dest">The destination directory path.</param>
|
||||
/// <param name="semaphore">Optional <see cref="SemaphoreSlim"/> used to limit degree of parallelism.</param>
|
||||
/// <param name="securityLevel">The launch <see cref="DreamDaemonSecurity"/> level.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="IEnumerable{T}"/> of <see cref="Task"/>s representing the running operations. The first <see cref="Task"/> returned is always the necessary call to <see cref="IIOManager.CreateDirectory(string, CancellationToken)"/>.</returns>
|
||||
/// <returns>An <see cref="IEnumerable{T}"/> of <see cref="Task"/>s representing the running operations. The first <see cref="Task"/> returned is always the necessary call to <see cref="IIOManager.CreateDirectory(string, CancellationToken)"/>.</returns>
|
||||
/// <remarks>I genuinely don't know how this will work with symlinked files. Waiting for the issue report I guess.</remarks>
|
||||
IEnumerable<Task> MirrorDirectoryImpl(string src, string dest, SemaphoreSlim? semaphore, DreamDaemonSecurity securityLevel, CancellationToken cancellationToken)
|
||||
IEnumerable<Task> MirrorDirectoryImpl(IDirectoryInfo src, string dest, SemaphoreSlim? semaphore, DreamDaemonSecurity securityLevel, CancellationToken cancellationToken)
|
||||
{
|
||||
var dir = new DirectoryInfo(src);
|
||||
Task? subdirCreationTask = null;
|
||||
var dreamDaemonWillAcceptOutOfDirectorySymlinks = securityLevel == DreamDaemonSecurity.Trusted;
|
||||
foreach (var subDirectory in dir.EnumerateDirectories())
|
||||
foreach (var subDirectory in src.EnumerateDirectories())
|
||||
{
|
||||
var mirroredName = Path.Combine(dest, subDirectory.Name);
|
||||
var mirroredName = IOManager.ConcatPath(dest, subDirectory.Name);
|
||||
|
||||
// check if we are a symbolic link
|
||||
if (subDirectory.Attributes.HasFlag(FileAttributes.ReparsePoint))
|
||||
@@ -275,7 +277,7 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
logger.LogDebug("Recreating symlinked directory {name} as hard links...", subDirectory.Name);
|
||||
|
||||
var checkingSubdirCreationTask = true;
|
||||
foreach (var copyTask in MirrorDirectoryImpl(subDirectory.FullName, mirroredName, semaphore, securityLevel, cancellationToken))
|
||||
foreach (var copyTask in MirrorDirectoryImpl(subDirectory, mirroredName, semaphore, securityLevel, cancellationToken))
|
||||
{
|
||||
if (subdirCreationTask == null)
|
||||
{
|
||||
@@ -289,7 +291,7 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var fileInfo in dir.EnumerateFiles())
|
||||
foreach (var fileInfo in src.EnumerateFiles())
|
||||
{
|
||||
if (subdirCreationTask == null)
|
||||
{
|
||||
|
||||
@@ -6,8 +6,10 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
|
||||
@@ -41,6 +43,11 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
/// <inheritdoc />
|
||||
protected override EngineType TargetEngineType => EngineType.Byond;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="GeneralConfiguration"/> <see cref="IOptionsMonitor{TOptions}"/> for the <see cref="ByondInstallerBase"/>.
|
||||
/// </summary>
|
||||
protected IOptionsMonitor<GeneralConfiguration> GeneralConfigurationOptions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Path to the system user's local BYOND folder.
|
||||
/// </summary>
|
||||
@@ -52,25 +59,85 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
protected abstract string DreamMakerName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URL formatter string for downloading a byond version of {0:Major} {1:Minor}.
|
||||
/// Template to do ${Marker:xxx} replacements in <see cref="GeneralConfiguration.ByondZipDownloadTemplate"/>.
|
||||
/// </summary>
|
||||
protected abstract string ByondRevisionsUrlTemplate { get; }
|
||||
protected abstract string OSMarkerTemplate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IFileDownloader"/> for the <see cref="ByondInstallerBase"/>.
|
||||
/// </summary>
|
||||
readonly IFileDownloader fileDownloader;
|
||||
|
||||
/// <summary>
|
||||
/// Format a given <paramref name="byondZipDownloadTemplate"/>.
|
||||
/// </summary>
|
||||
/// <param name="semver">The BYOND version to download.</param>
|
||||
/// <param name="byondZipDownloadTemplate">The template.</param>
|
||||
/// <param name="osMarkerTemplate">The <see cref="OSMarkerTemplate"/>.</param>
|
||||
/// <returns>The formatted byond download <see cref="Uri"/>.</returns>
|
||||
/// <remarks>Exposed only for testability.</remarks>
|
||||
internal static Uri GetDownloadZipUrl(Version semver, string byondZipDownloadTemplate, string osMarkerTemplate)
|
||||
{
|
||||
// god forbid
|
||||
var guardGuid = Guid.NewGuid();
|
||||
|
||||
var url = byondZipDownloadTemplate
|
||||
.Replace("$$", guardGuid.ToString(), StringComparison.Ordinal)
|
||||
.Replace("${Major}", semver.Major.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal)
|
||||
.Replace("${Minor}", semver.Minor.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal);
|
||||
|
||||
var osMarkerPrefix = $"${{{osMarkerTemplate}:";
|
||||
var osMarkerIndex = url.IndexOf(osMarkerPrefix);
|
||||
while (osMarkerIndex != -1)
|
||||
{
|
||||
var start = osMarkerIndex + osMarkerPrefix.Length;
|
||||
var end = url.IndexOf('}', start);
|
||||
if (end == -1)
|
||||
break;
|
||||
|
||||
var substitution = url.Substring(start, end - start);
|
||||
url = url.Replace($"{osMarkerPrefix}{substitution}}}", substitution, StringComparison.Ordinal);
|
||||
|
||||
osMarkerIndex = url.IndexOf(osMarkerPrefix);
|
||||
}
|
||||
|
||||
// at this point, any other substitution attempts should be removed
|
||||
var otherMarkerPrefix = "${";
|
||||
var otherMarkerIndex = url.IndexOf(otherMarkerPrefix);
|
||||
while (otherMarkerIndex != -1)
|
||||
{
|
||||
var start = otherMarkerIndex + otherMarkerPrefix.Length;
|
||||
var end = url.IndexOf('}', start);
|
||||
if (end == -1)
|
||||
break;
|
||||
|
||||
var substitution = url.Substring(start, end - start);
|
||||
url = url.Replace($"{otherMarkerPrefix}{substitution}}}", String.Empty, StringComparison.Ordinal);
|
||||
|
||||
otherMarkerIndex = url.IndexOf(otherMarkerPrefix);
|
||||
}
|
||||
|
||||
url = url.Replace(guardGuid.ToString(), "$", StringComparison.Ordinal);
|
||||
|
||||
return new Uri(url);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ByondInstallerBase"/> class.
|
||||
/// </summary>
|
||||
/// <param name="ioManager">The <see cref="IIOManager"/> for the <see cref="EngineInstallerBase"/>.</param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="EngineInstallerBase"/>.</param>
|
||||
/// <param name="fileDownloader">The value of <see cref="fileDownloader"/>.</param>
|
||||
protected ByondInstallerBase(IIOManager ioManager, ILogger<ByondInstallerBase> logger, IFileDownloader fileDownloader)
|
||||
/// <param name="generalConfigurationOptions">The value of <see cref="GeneralConfigurationOptions"/>.</param>
|
||||
protected ByondInstallerBase(
|
||||
IIOManager ioManager,
|
||||
ILogger<ByondInstallerBase> logger,
|
||||
IFileDownloader fileDownloader,
|
||||
IOptionsMonitor<GeneralConfiguration> generalConfigurationOptions)
|
||||
: base(ioManager, logger)
|
||||
{
|
||||
this.fileDownloader = fileDownloader ?? throw new ArgumentNullException(nameof(fileDownloader));
|
||||
GeneralConfigurationOptions = generalConfigurationOptions ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -78,7 +145,7 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
{
|
||||
CheckVersionValidity(version);
|
||||
|
||||
var installationIOManager = new ResolvingIOManager(IOManager, path);
|
||||
var installationIOManager = IOManager.CreateResolverForSubdirectory(path);
|
||||
var supportsMapThreads = version.Version >= MapThreadsVersion;
|
||||
|
||||
return ValueTask.FromResult<IEngineInstallation>(
|
||||
@@ -195,8 +262,12 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
Uri GetDownloadZipUrl(EngineVersion version)
|
||||
{
|
||||
CheckVersionValidity(version);
|
||||
var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsUrlTemplate, version.Version!.Major, version.Version.Minor);
|
||||
return new Uri(url);
|
||||
|
||||
var guardGuid = Guid.NewGuid();
|
||||
|
||||
var semver = version.Version!;
|
||||
var template = GeneralConfigurationOptions.CurrentValue.ByondZipDownloadTemplate;
|
||||
return GetDownloadZipUrl(semver, template, OSMarkerTemplate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
var dotnetPath = (await DotnetHelper.GetDotnetPath(platformIdentifier, IOManager, cancellationToken))
|
||||
?? throw new JobException("Failed to find dotnet path!");
|
||||
return new OpenDreamInstallation(
|
||||
new ResolvingIOManager(IOManager, path),
|
||||
IOManager.CreateResolverForSubdirectory(path),
|
||||
asyncDelayer,
|
||||
httpClientFactory,
|
||||
dotnetPath,
|
||||
|
||||
@@ -5,9 +5,11 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Common.Extensions;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.IO;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Engine
|
||||
@@ -39,7 +41,7 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
protected override string DreamMakerName => DreamMakerExecutableName + ShellScriptExtension;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string ByondRevisionsUrlTemplate => "https://www.byond.com/download/build/{0}/{0}.{1}_byond_linux.zip";
|
||||
protected override string OSMarkerTemplate => "Linux";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IPostWriteHandler"/> for the <see cref="PosixByondInstaller"/>.
|
||||
@@ -52,13 +54,15 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
/// <param name="postWriteHandler">The value of <see cref="postWriteHandler"/>.</param>
|
||||
/// <param name="ioManager">The <see cref="IIOManager"/> for the <see cref="ByondInstallerBase"/>.</param>
|
||||
/// <param name="fileDownloader">The <see cref="IFileDownloader"/> for the <see cref="ByondInstallerBase"/>.</param>
|
||||
/// <param name="generalConfigurationOptions">The <see cref="GeneralConfiguration"/> <see cref="IOptionsMonitor{TOptions}"/>.</param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="ByondInstallerBase"/>.</param>
|
||||
public PosixByondInstaller(
|
||||
IPostWriteHandler postWriteHandler,
|
||||
IIOManager ioManager,
|
||||
IFileDownloader fileDownloader,
|
||||
IOptionsMonitor<GeneralConfiguration> generalConfigurationOptions,
|
||||
ILogger<PosixByondInstaller> logger)
|
||||
: base(ioManager, logger, fileDownloader)
|
||||
: base(ioManager, logger, fileDownloader, generalConfigurationOptions)
|
||||
{
|
||||
this.postWriteHandler = postWriteHandler ?? throw new ArgumentNullException(nameof(postWriteHandler));
|
||||
|
||||
|
||||
@@ -69,18 +69,13 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
protected override string PathToUserFolder { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string ByondRevisionsUrlTemplate => "https://www.byond.com/download/build/{0}/{0}.{1}_byond.zip";
|
||||
protected override string OSMarkerTemplate => "Windows";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IProcessExecutor"/> for the <see cref="WindowsByondInstaller"/>.
|
||||
/// </summary>
|
||||
readonly IProcessExecutor processExecutor;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="GeneralConfiguration"/> for the <see cref="WindowsByondInstaller"/>.
|
||||
/// </summary>
|
||||
readonly GeneralConfiguration generalConfiguration;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SessionConfiguration"/> for the <see cref="WindowsByondInstaller"/>.
|
||||
/// </summary>
|
||||
@@ -100,7 +95,7 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
/// Initializes a new instance of the <see cref="WindowsByondInstaller"/> class.
|
||||
/// </summary>
|
||||
/// <param name="processExecutor">The value of <see cref="processExecutor"/>.</param>
|
||||
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="generalConfiguration"/>.</param>
|
||||
/// <param name="generalConfigurationOptions">The <see cref="IOptionsMonitor{TOptions}"/> containing the <see cref="GeneralConfiguration"/>.</param>
|
||||
/// <param name="sessionConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="sessionConfiguration"/>.</param>
|
||||
/// <param name="ioManager">The <see cref="IIOManager"/> for the <see cref="ByondInstallerBase"/>.</param>
|
||||
/// <param name="fileDownloader">The <see cref="IFileDownloader"/> for the <see cref="ByondInstallerBase"/>.</param>
|
||||
@@ -109,13 +104,12 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
IProcessExecutor processExecutor,
|
||||
IIOManager ioManager,
|
||||
IFileDownloader fileDownloader,
|
||||
IOptions<GeneralConfiguration> generalConfigurationOptions,
|
||||
IOptionsMonitor<GeneralConfiguration> generalConfigurationOptions,
|
||||
IOptions<SessionConfiguration> sessionConfigurationOptions,
|
||||
ILogger<WindowsByondInstaller> logger)
|
||||
: base(ioManager, logger, fileDownloader)
|
||||
: base(ioManager, logger, fileDownloader, generalConfigurationOptions)
|
||||
{
|
||||
this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
|
||||
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
|
||||
sessionConfiguration = sessionConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(sessionConfigurationOptions));
|
||||
|
||||
var useServiceSpecialTactics = Environment.Is64BitProcess && Environment.UserName == $"{Environment.MachineName}$";
|
||||
@@ -150,7 +144,7 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
installDirectXTask,
|
||||
};
|
||||
|
||||
if (!generalConfiguration.SkipAddingByondFirewallException)
|
||||
if (!GeneralConfigurationOptions.CurrentValue.SkipAddingByondFirewallException)
|
||||
{
|
||||
var firewallTask = AddDreamDaemonToFirewall(version, path, deploymentPipelineProcesses, cancellationToken);
|
||||
tasks.Add(firewallTask);
|
||||
@@ -165,7 +159,7 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
CheckVersionValidity(version);
|
||||
ArgumentNullException.ThrowIfNull(path);
|
||||
|
||||
if (generalConfiguration.SkipAddingByondFirewallException)
|
||||
if (GeneralConfigurationOptions.CurrentValue.SkipAddingByondFirewallException)
|
||||
return;
|
||||
|
||||
if (version.Version < DDExeVersion)
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
readonly IIOManager ioManager;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="MemoryStream"/> containing the zip data of the engine.
|
||||
/// The <see cref="Stream"/> containing the zip data of the engine.
|
||||
/// </summary>
|
||||
readonly Stream zipStream;
|
||||
|
||||
|
||||
@@ -531,7 +531,10 @@ namespace Tgstation.Server.Host.Components
|
||||
}
|
||||
}
|
||||
else if (preserveTestMerges)
|
||||
{
|
||||
Chat.QueueRawDeploymentMessage("Automatic update has failed due to a conflicting testmerge!");
|
||||
throw new JobException(Api.Models.ErrorCode.InstanceUpdateTestMergeConflict);
|
||||
}
|
||||
|
||||
if (!preserveTestMerges)
|
||||
{
|
||||
|
||||
@@ -164,7 +164,7 @@ namespace Tgstation.Server.Host.Components
|
||||
/// </summary>
|
||||
/// <param name="instanceIOManager">The instance's <see cref="IIOManager"/>.</param>
|
||||
/// <returns>The <see cref="IIOManager"/> for the instance's "Game" directory.</returns>
|
||||
static ResolvingIOManager CreateGameIOManager(IIOManager instanceIOManager) => new(instanceIOManager, "Game");
|
||||
static IIOManager CreateGameIOManager(IIOManager instanceIOManager) => instanceIOManager.CreateResolverForSubdirectory("Game");
|
||||
|
||||
#pragma warning disable CA1502 // TODO: Decomplexify
|
||||
/// <summary>
|
||||
@@ -270,11 +270,11 @@ namespace Tgstation.Server.Host.Components
|
||||
var instanceIoManager = CreateInstanceIOManager(metadata);
|
||||
|
||||
// various other ioManagers
|
||||
var repoIoManager = new ResolvingIOManager(instanceIoManager, "Repository");
|
||||
var byondIOManager = new ResolvingIOManager(instanceIoManager, "Byond");
|
||||
var repoIoManager = instanceIoManager.CreateResolverForSubdirectory("Repository");
|
||||
var byondIOManager = instanceIoManager.CreateResolverForSubdirectory("Byond");
|
||||
var gameIoManager = CreateGameIOManager(instanceIoManager);
|
||||
var diagnosticsIOManager = new ResolvingIOManager(instanceIoManager, "Diagnostics");
|
||||
var configurationIoManager = new ResolvingIOManager(instanceIoManager, "Configuration");
|
||||
var diagnosticsIOManager = instanceIoManager.CreateResolverForSubdirectory("Diagnostics");
|
||||
var configurationIoManager = instanceIoManager.CreateResolverForSubdirectory("Configuration");
|
||||
|
||||
var metricFactory = this.metricFactory.WithLabels(
|
||||
new Dictionary<string, string>
|
||||
@@ -441,6 +441,6 @@ namespace Tgstation.Server.Host.Components
|
||||
/// </summary>
|
||||
/// <param name="metadata">The <see cref="Models.Instance"/>.</param>
|
||||
/// <returns>The <see cref="IIOManager"/> for the <paramref name="metadata"/>.</returns>
|
||||
ResolvingIOManager CreateInstanceIOManager(Models.Instance metadata) => new(ioManager, metadata.Path!);
|
||||
IIOManager CreateInstanceIOManager(Models.Instance metadata) => ioManager.CreateResolverForSubdirectory(metadata.Path!);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -702,7 +702,7 @@ namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
logger.LogDebug("Running as user: {username}", Environment.UserName);
|
||||
|
||||
generalConfiguration.CheckCompatibility(logger);
|
||||
generalConfiguration.CheckCompatibility(logger, ioManager);
|
||||
|
||||
using (var systemIdentity = systemIdentityFactory.GetCurrent())
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -1099,8 +1098,8 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
=> ioManager.GetDirectoryName(libGitRepo
|
||||
.Info
|
||||
.Path
|
||||
.TrimEnd(Path.DirectorySeparatorChar)
|
||||
.TrimEnd(Path.AltDirectorySeparatorChar));
|
||||
.TrimEnd(ioManager.DirectorySeparatorChar)
|
||||
.TrimEnd(ioManager.AltDirectorySeparatorChar));
|
||||
|
||||
/// <summary>
|
||||
/// Recusively update all <see cref="Submodule"/>s in the <see cref="libGitRepo"/>.
|
||||
|
||||
@@ -333,10 +333,10 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
},
|
||||
async cancellationToken =>
|
||||
{
|
||||
FileStream? result = null;
|
||||
Stream? result = null;
|
||||
void GetFileStream()
|
||||
{
|
||||
result = ioManager.GetFileStream(path, false);
|
||||
result = synchronousIOManager.GetFileStream(path);
|
||||
}
|
||||
|
||||
if (systemIdentity == null)
|
||||
@@ -771,7 +771,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
var nullOrEmptyCheck = String.IsNullOrEmpty(configurationRelativePath);
|
||||
if (nullOrEmptyCheck)
|
||||
configurationRelativePath = DefaultIOManager.CurrentDirectory;
|
||||
if (configurationRelativePath![0] == Path.DirectorySeparatorChar || configurationRelativePath[0] == Path.AltDirectorySeparatorChar)
|
||||
if (configurationRelativePath![0] == ioManager.DirectorySeparatorChar || configurationRelativePath[0] == ioManager.AltDirectorySeparatorChar)
|
||||
configurationRelativePath = DefaultIOManager.CurrentDirectory + configurationRelativePath;
|
||||
var resolved = ioManager.ResolvePath(configurationRelativePath);
|
||||
var local = !nullOrEmptyCheck ? ioManager.ResolvePath() : null;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -9,6 +8,7 @@ using Newtonsoft.Json.Converters;
|
||||
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Properties;
|
||||
using Tgstation.Server.Host.Setup;
|
||||
|
||||
@@ -31,6 +31,11 @@ namespace Tgstation.Server.Host.Configuration
|
||||
/// </summary>
|
||||
public const ushort DefaultApiPort = 5000;
|
||||
|
||||
/// <summary>
|
||||
/// Default vale for <see cref="ByondZipDownloadTemplate"/>.
|
||||
/// </summary>
|
||||
public const string DefaultByondZipDownloadTemplate = "https://www.byond.com/download/build/${Major}/${Major}.${Minor}_byond${Linux:_linux}.zip";
|
||||
|
||||
/// <summary>
|
||||
/// The default value for <see cref="ServerInformationBase.MinimumPasswordLength"/>.
|
||||
/// </summary>
|
||||
@@ -148,6 +153,17 @@ namespace Tgstation.Server.Host.Configuration
|
||||
[YamlMember(SerializeAs = typeof(string))]
|
||||
public Uri OpenDreamGitUrl { get; set; } = new Uri(DefaultOpenDreamGitUrl);
|
||||
|
||||
/// <summary>
|
||||
/// The formatter used to download official byond zip files for a given version
|
||||
/// - ${Major} is substituted with the major version number
|
||||
/// - ${Minor} is substituted with the minor version number
|
||||
/// - ${Linux:xxx}, where xxx is any string, will be substituted with xxx if running under Linux.
|
||||
/// - ${Windows:xxx}, where xxx is any string, will be substituted with xxx if running under Windows.
|
||||
/// - $$ will evaluate to a literal $ and not be used for substitutions.
|
||||
/// - Any inapplicable ${xxx} string will be removed.
|
||||
/// </summary>
|
||||
public string ByondZipDownloadTemplate { get; set; } = DefaultByondZipDownloadTemplate;
|
||||
|
||||
/// <summary>
|
||||
/// The prefix to the OpenDream semver as tags appear in the git repository.
|
||||
/// </summary>
|
||||
@@ -178,7 +194,8 @@ namespace Tgstation.Server.Host.Configuration
|
||||
/// Validates the current <see cref="ConfigVersion"/>'s compatibility and provides migration instructions.
|
||||
/// </summary>
|
||||
/// <param name="logger">The <see cref="ILogger"/> to use.</param>
|
||||
public void CheckCompatibility(ILogger logger)
|
||||
/// <param name="ioManager">The <see cref="IIOManager"/> to use.</param>
|
||||
public void CheckCompatibility(ILogger logger, IIOManager ioManager)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
@@ -204,7 +221,7 @@ namespace Tgstation.Server.Host.Configuration
|
||||
if (ByondTopicTimeout <= 1000)
|
||||
logger.LogWarning("The timeout for sending BYOND topics is very low ({ms}ms). Topic calls may fail to complete at all!", ByondTopicTimeout);
|
||||
|
||||
if (AdditionalEventScriptsDirectories?.Any(path => !Path.IsPathRooted(path)) == true)
|
||||
if (AdditionalEventScriptsDirectories?.Any(path => !ioManager.IsPathRooted(path)) == true)
|
||||
logger.LogWarning($"Config option \"{nameof(AdditionalEventScriptsDirectories)}\" contains non-rooted paths. These will be evaluated relative to each instances \"Configuration\" directory!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO.Abstractions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
|
||||
@@ -105,10 +106,12 @@ namespace Tgstation.Server.Host.Core
|
||||
public static IServerFactory CreateDefaultServerFactory()
|
||||
{
|
||||
var assemblyInformationProvider = new AssemblyInformationProvider();
|
||||
var ioManager = new DefaultIOManager();
|
||||
var fileSystem = new FileSystem();
|
||||
var ioManager = new DefaultIOManager(fileSystem);
|
||||
return new ServerFactory(
|
||||
assemblyInformationProvider,
|
||||
ioManager);
|
||||
ioManager,
|
||||
fileSystem);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -154,11 +157,13 @@ namespace Tgstation.Server.Host.Core
|
||||
/// <param name="assemblyInformationProvider">The <see cref="IAssemblyInformationProvider"/> needed for configuration.</param>
|
||||
/// <param name="ioManager">The <see cref="IIOManager"/> needed for configuration.</param>
|
||||
/// <param name="postSetupServices">The <see cref="IPostSetupServices"/> needed for configuration.</param>
|
||||
/// <param name="fileSystem">The <see cref="IFileSystem"/> needed for configuration.</param>
|
||||
public void ConfigureServices(
|
||||
IServiceCollection services,
|
||||
IAssemblyInformationProvider assemblyInformationProvider,
|
||||
IIOManager ioManager,
|
||||
IPostSetupServices postSetupServices)
|
||||
IPostSetupServices postSetupServices,
|
||||
IFileSystem fileSystem)
|
||||
{
|
||||
ConfigureServices(services, assemblyInformationProvider, ioManager);
|
||||
|
||||
@@ -485,8 +490,7 @@ namespace Tgstation.Server.Host.Core
|
||||
services => services
|
||||
.GetRequiredService<IRepositoryManagerFactory>()
|
||||
.CreateRepositoryManager(
|
||||
new ResolvingIOManager(
|
||||
services.GetRequiredService<IIOManager>(),
|
||||
services.GetRequiredService<IIOManager>().CreateResolverForSubdirectory(
|
||||
openDreamRepositoryDirectory),
|
||||
new NoopEventConsumer()));
|
||||
|
||||
@@ -542,6 +546,7 @@ namespace Tgstation.Server.Host.Core
|
||||
services.AddSingleton<ISynchronousIOManager, SynchronousIOManager>();
|
||||
services.AddSingleton<IServerPortProvider, ServerPortProivder>();
|
||||
services.AddSingleton<ITopicClientFactory, TopicClientFactory>();
|
||||
services.AddSingleton(fileSystem);
|
||||
services.AddHostedService<CommandPipeManager>();
|
||||
services.AddHostedService<VersionReportingService>();
|
||||
|
||||
@@ -940,10 +945,7 @@ namespace Tgstation.Server.Host.Core
|
||||
options.Scope.Add(OpenIdConnectScope.OpenId);
|
||||
options.Scope.Add(OpenIdConnectScope.OfflineAccess);
|
||||
|
||||
#if DEBUG
|
||||
options.RequireHttpsMetadata = false;
|
||||
#endif
|
||||
|
||||
options.SaveTokens = true;
|
||||
options.ResponseType = OpenIdConnectResponseType.Code;
|
||||
options.MapInboundClaims = false;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.IO.Abstractions;
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
@@ -26,24 +27,27 @@ namespace Tgstation.Server.Host.Extensions
|
||||
/// <param name="builder">The <see cref="IWebHostBuilder"/> to configure.</param>
|
||||
/// <param name="assemblyInformationProvider">The <see cref="IAssemblyInformationProvider"/> to use.</param>
|
||||
/// <param name="ioManager">The <see cref="IIOManager"/> to use.</param>
|
||||
/// <returns>The configured <paramref name="builder"/>.</returns>
|
||||
/// <param name="postSetupServices">The <see cref="IPostSetupServices"/> to use.</param>
|
||||
/// <param name="fileSystem">The <see cref="IFileSystem"/> to use.</param>
|
||||
/// <returns>The configured <paramref name="builder"/>.</returns>
|
||||
public static IWebHostBuilder UseApplication(
|
||||
this IWebHostBuilder builder,
|
||||
IAssemblyInformationProvider assemblyInformationProvider,
|
||||
IIOManager ioManager,
|
||||
IPostSetupServices postSetupServices)
|
||||
IPostSetupServices postSetupServices,
|
||||
IFileSystem fileSystem)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(builder);
|
||||
ArgumentNullException.ThrowIfNull(assemblyInformationProvider);
|
||||
ArgumentNullException.ThrowIfNull(ioManager);
|
||||
ArgumentNullException.ThrowIfNull(postSetupServices);
|
||||
ArgumentNullException.ThrowIfNull(fileSystem);
|
||||
|
||||
return builder.ConfigureServices(
|
||||
(context, services) =>
|
||||
{
|
||||
var application = new Application(context.Configuration, context.HostingEnvironment);
|
||||
application.ConfigureServices(services, assemblyInformationProvider, ioManager, postSetupServices);
|
||||
application.ConfigureServices(services, assemblyInformationProvider, ioManager, postSetupServices, fileSystem);
|
||||
services.AddSingleton(application);
|
||||
})
|
||||
.Configure(ConfigureApplication);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@@ -30,12 +31,32 @@ namespace Tgstation.Server.Host.IO
|
||||
/// </summary>
|
||||
public const TaskCreationOptions BlockingTaskCreationOptions = TaskCreationOptions.None;
|
||||
|
||||
/// <inheritdoc />
|
||||
public char DirectorySeparatorChar => fileSystem.Path.DirectorySeparatorChar;
|
||||
|
||||
/// <inheritdoc />
|
||||
public char AltDirectorySeparatorChar => fileSystem.Path.AltDirectorySeparatorChar;
|
||||
|
||||
/// <summary>
|
||||
/// The backing <see cref="IFileSystem"/>.
|
||||
/// </summary>
|
||||
readonly IFileSystem fileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DefaultIOManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="fileSystem">The value of <see cref="fileSystem"/>.</param>
|
||||
public DefaultIOManager(IFileSystem fileSystem)
|
||||
{
|
||||
this.fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively empty a directory.
|
||||
/// </summary>
|
||||
/// <param name="dir"><see cref="DirectoryInfo"/> of the directory to empty.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
static void NormalizeAndDelete(DirectoryInfo dir, CancellationToken cancellationToken)
|
||||
static void NormalizeAndDelete(IDirectoryInfo dir, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -111,7 +132,7 @@ namespace Tgstation.Server.Host.IO
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ConcatPath(params string[] paths) => Path.Combine(paths);
|
||||
public string ConcatPath(params string[] paths) => fileSystem.Path.Combine(paths);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask CopyFile(string src, string dest, CancellationToken cancellationToken)
|
||||
@@ -120,7 +141,7 @@ namespace Tgstation.Server.Host.IO
|
||||
ArgumentNullException.ThrowIfNull(dest);
|
||||
|
||||
// tested to hell and back, these are the optimal buffer sizes
|
||||
await using var srcStream = new FileStream(
|
||||
await using var srcStream = fileSystem.FileStream.New(
|
||||
ResolvePath(src),
|
||||
FileMode.Open,
|
||||
FileAccess.Read,
|
||||
@@ -134,41 +155,40 @@ namespace Tgstation.Server.Host.IO
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task CreateDirectory(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => Directory.CreateDirectory(ResolvePath(path)), cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current);
|
||||
public Task CreateDirectory(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => fileSystem.Directory.CreateDirectory(ResolvePath(path)), cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task DeleteDirectory(string path, CancellationToken cancellationToken)
|
||||
{
|
||||
path = ResolvePath(path);
|
||||
var di = new DirectoryInfo(path);
|
||||
if (!di.Exists)
|
||||
return Task.CompletedTask;
|
||||
|
||||
return Task.Factory.StartNew(
|
||||
() => NormalizeAndDelete(di, cancellationToken),
|
||||
=> Task.Factory.StartNew(
|
||||
() =>
|
||||
{
|
||||
var di = fileSystem.DirectoryInfo.New(
|
||||
ResolvePath(path));
|
||||
if (di.Exists)
|
||||
NormalizeAndDelete(di, cancellationToken);
|
||||
},
|
||||
cancellationToken,
|
||||
BlockingTaskCreationOptions,
|
||||
TaskScheduler.Current);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task DeleteFile(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => File.Delete(ResolvePath(path)), cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current);
|
||||
public Task DeleteFile(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => fileSystem.File.Delete(ResolvePath(path)), cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<bool> FileExists(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => File.Exists(ResolvePath(path)), cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current);
|
||||
public Task<bool> FileExists(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => fileSystem.File.Exists(ResolvePath(path)), cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<bool> DirectoryExists(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => Directory.Exists(ResolvePath(path)), cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current);
|
||||
public Task<bool> DirectoryExists(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => fileSystem.Directory.Exists(ResolvePath(path)), cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current);
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetDirectoryName(string path) => Path.GetDirectoryName(path ?? throw new ArgumentNullException(nameof(path)))
|
||||
public string GetDirectoryName(string path) => fileSystem.Path.GetDirectoryName(path ?? throw new ArgumentNullException(nameof(path)))
|
||||
?? throw new InvalidOperationException($"Null was returned. Path ({path}) must be rooted. This is not supported!");
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetFileName(string path) => Path.GetFileName(path ?? throw new ArgumentNullException(nameof(path)));
|
||||
public string GetFileName(string path) => fileSystem.Path.GetFileName(path ?? throw new ArgumentNullException(nameof(path)));
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetFileNameWithoutExtension(string path) => Path.GetFileNameWithoutExtension(path ?? throw new ArgumentNullException(nameof(path)));
|
||||
public string GetFileNameWithoutExtension(string path) => fileSystem.Path.GetFileNameWithoutExtension(path ?? throw new ArgumentNullException(nameof(path)));
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<List<string>> GetFilesWithExtension(string path, string extension, bool recursive, CancellationToken cancellationToken) => Task.Factory.StartNew(
|
||||
@@ -177,7 +197,7 @@ namespace Tgstation.Server.Host.IO
|
||||
path = ResolvePath(path);
|
||||
ArgumentNullException.ThrowIfNull(extension);
|
||||
var results = new List<string>();
|
||||
foreach (var fileName in Directory.EnumerateFiles(
|
||||
foreach (var fileName in fileSystem.Directory.EnumerateFiles(
|
||||
path,
|
||||
$"*.{extension}",
|
||||
recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly))
|
||||
@@ -199,7 +219,7 @@ namespace Tgstation.Server.Host.IO
|
||||
ArgumentNullException.ThrowIfNull(destination);
|
||||
source = ResolvePath(source ?? throw new ArgumentNullException(nameof(source)));
|
||||
destination = ResolvePath(destination);
|
||||
File.Move(source, destination);
|
||||
fileSystem.File.Move(source, destination);
|
||||
},
|
||||
cancellationToken,
|
||||
BlockingTaskCreationOptions,
|
||||
@@ -212,7 +232,7 @@ namespace Tgstation.Server.Host.IO
|
||||
ArgumentNullException.ThrowIfNull(destination);
|
||||
source = ResolvePath(source ?? throw new ArgumentNullException(nameof(source)));
|
||||
destination = ResolvePath(destination);
|
||||
Directory.Move(source, destination);
|
||||
fileSystem.Directory.Move(source, destination);
|
||||
},
|
||||
cancellationToken,
|
||||
BlockingTaskCreationOptions,
|
||||
@@ -221,7 +241,7 @@ namespace Tgstation.Server.Host.IO
|
||||
/// <inheritdoc />
|
||||
public async ValueTask<byte[]> ReadAllBytes(string path, CancellationToken cancellationToken)
|
||||
{
|
||||
await using var file = CreateAsyncSequentialReadStream(path);
|
||||
await using var file = CreateAsyncReadStream(path, true, true);
|
||||
byte[] buf;
|
||||
buf = new byte[file.Length];
|
||||
await file.ReadAsync(buf, cancellationToken);
|
||||
@@ -229,10 +249,12 @@ namespace Tgstation.Server.Host.IO
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ResolvePath() => ResolvePath(CurrentDirectory);
|
||||
public string ResolvePath()
|
||||
=> ResolvePath(CurrentDirectory);
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual string ResolvePath(string path) => Path.GetFullPath(path ?? throw new ArgumentNullException(nameof(path)));
|
||||
public virtual string ResolvePath(string path)
|
||||
=> fileSystem.Path.GetFullPath(path ?? throw new ArgumentNullException(nameof(path)));
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask WriteAllBytes(string path, byte[] contents, CancellationToken cancellationToken)
|
||||
@@ -242,10 +264,10 @@ namespace Tgstation.Server.Host.IO
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public FileStream CreateAsyncSequentialWriteStream(string path)
|
||||
public Stream CreateAsyncSequentialWriteStream(string path)
|
||||
{
|
||||
path = ResolvePath(path);
|
||||
return new FileStream(
|
||||
return fileSystem.FileStream.New(
|
||||
path,
|
||||
FileMode.Create,
|
||||
FileAccess.Write,
|
||||
@@ -255,16 +277,18 @@ namespace Tgstation.Server.Host.IO
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public FileStream CreateAsyncSequentialReadStream(string path)
|
||||
public Stream CreateAsyncReadStream(string path, bool sequental, bool shareWrite)
|
||||
{
|
||||
path = ResolvePath(path);
|
||||
return new FileStream(
|
||||
return fileSystem.FileStream.New(
|
||||
path,
|
||||
FileMode.Open,
|
||||
FileAccess.Read,
|
||||
FileShare.ReadWrite | FileShare.Delete,
|
||||
FileShare.ReadWrite | FileShare.Delete | (shareWrite ? FileShare.Write : FileShare.None),
|
||||
DefaultBufferSize,
|
||||
FileOptions.Asynchronous | FileOptions.SequentialScan);
|
||||
sequental
|
||||
? FileOptions.Asynchronous | FileOptions.SequentialScan
|
||||
: FileOptions.Asynchronous);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -274,7 +298,7 @@ namespace Tgstation.Server.Host.IO
|
||||
path = ResolvePath(path);
|
||||
var results = new List<string>();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
foreach (var directoryName in Directory.EnumerateDirectories(path))
|
||||
foreach (var directoryName in fileSystem.Directory.EnumerateDirectories(path))
|
||||
{
|
||||
results.Add(directoryName);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -293,7 +317,7 @@ namespace Tgstation.Server.Host.IO
|
||||
path = ResolvePath(path);
|
||||
var results = new List<string>();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
foreach (var fileName in Directory.EnumerateFiles(path))
|
||||
foreach (var fileName in fileSystem.Directory.EnumerateFiles(path))
|
||||
{
|
||||
results.Add(fileName);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -331,8 +355,8 @@ namespace Tgstation.Server.Host.IO
|
||||
public bool PathContainsParentAccess(string path) => path
|
||||
?.Split(
|
||||
[
|
||||
Path.DirectorySeparatorChar,
|
||||
Path.AltDirectorySeparatorChar,
|
||||
fileSystem.Path.DirectorySeparatorChar,
|
||||
fileSystem.Path.AltDirectorySeparatorChar,
|
||||
])
|
||||
.Any(x => x == "..")
|
||||
?? throw new ArgumentNullException(nameof(path));
|
||||
@@ -342,22 +366,13 @@ namespace Tgstation.Server.Host.IO
|
||||
() =>
|
||||
{
|
||||
path = ResolvePath(path ?? throw new ArgumentNullException(nameof(path)));
|
||||
var fileInfo = new FileInfo(path);
|
||||
var fileInfo = fileSystem.FileInfo.New(path);
|
||||
return new DateTimeOffset(fileInfo.LastWriteTimeUtc);
|
||||
},
|
||||
cancellationToken,
|
||||
BlockingTaskCreationOptions,
|
||||
TaskScheduler.Current);
|
||||
|
||||
/// <inheritdoc />
|
||||
public FileStream GetFileStream(string path, bool shareWrite) => new(
|
||||
ResolvePath(path),
|
||||
FileMode.Open,
|
||||
FileAccess.Read,
|
||||
FileShare.Read | FileShare.Delete | (shareWrite ? FileShare.Write : FileShare.None),
|
||||
DefaultBufferSize,
|
||||
true);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<bool> PathIsChildOf(string parentPath, string childPath, CancellationToken cancellationToken) => Task.Factory.StartNew(
|
||||
() =>
|
||||
@@ -369,8 +384,8 @@ namespace Tgstation.Server.Host.IO
|
||||
return true;
|
||||
|
||||
// https://stackoverflow.com/questions/5617320/given-full-path-check-if-path-is-subdirectory-of-some-other-path-or-otherwise?lq=1
|
||||
var di1 = new DirectoryInfo(parentPath);
|
||||
var di2 = new DirectoryInfo(childPath);
|
||||
var di1 = fileSystem.DirectoryInfo.New(parentPath);
|
||||
var di2 = fileSystem.DirectoryInfo.New(childPath);
|
||||
while (di2.Parent != null)
|
||||
{
|
||||
if (di2.Parent.FullName == di1.FullName)
|
||||
@@ -385,6 +400,33 @@ namespace Tgstation.Server.Host.IO
|
||||
BlockingTaskCreationOptions,
|
||||
TaskScheduler.Current);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IDirectoryInfo> DirectoryInfo(string path, CancellationToken cancellationToken)
|
||||
=> Task.Factory.StartNew(
|
||||
() => fileSystem.DirectoryInfo.New(ResolvePath(path)),
|
||||
cancellationToken,
|
||||
BlockingTaskCreationOptions,
|
||||
TaskScheduler.Current);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsPathRooted(string path)
|
||||
=> fileSystem.Path.IsPathRooted(path);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IIOManager CreateResolverForSubdirectory(string subdirectoryPath)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(subdirectoryPath);
|
||||
|
||||
if (!Path.IsPathRooted(subdirectoryPath))
|
||||
subdirectoryPath = ConcatPath(
|
||||
ResolvePath(),
|
||||
subdirectoryPath);
|
||||
|
||||
return new ResolvingIOManager(
|
||||
fileSystem,
|
||||
subdirectoryPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a directory from <paramref name="src"/> to <paramref name="dest"/>.
|
||||
/// </summary>
|
||||
@@ -403,7 +445,7 @@ namespace Tgstation.Server.Host.IO
|
||||
SemaphoreSlim? semaphore,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var dir = new DirectoryInfo(src);
|
||||
var dir = fileSystem.DirectoryInfo.New(src);
|
||||
Task? subdirCreationTask = null;
|
||||
foreach (var subDirectory in dir.EnumerateDirectories())
|
||||
{
|
||||
@@ -411,7 +453,7 @@ namespace Tgstation.Server.Host.IO
|
||||
continue;
|
||||
|
||||
var checkingSubdirCreationTask = true;
|
||||
foreach (var copyTask in CopyDirectoryImpl(subDirectory.FullName, Path.Combine(dest, subDirectory.Name), null, postCopyCallback, semaphore, cancellationToken))
|
||||
foreach (var copyTask in CopyDirectoryImpl(subDirectory.FullName, fileSystem.Path.Combine(dest, subDirectory.Name), null, postCopyCallback, semaphore, cancellationToken))
|
||||
{
|
||||
if (subdirCreationTask == null)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -11,6 +12,23 @@ namespace Tgstation.Server.Host.IO
|
||||
/// </summary>
|
||||
public interface IIOManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the primary directory separator character.
|
||||
/// </summary>
|
||||
char DirectorySeparatorChar { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the alternative directory separator character.
|
||||
/// </summary>
|
||||
char AltDirectorySeparatorChar { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="IIOManager"/> that resolves paths to the specified <paramref name="subdirectoryPath"/>.
|
||||
/// </summary>
|
||||
/// <param name="subdirectoryPath">A relative or absolute path that the new <see cref="IIOManager"/> will resolve as its current directory.</param>
|
||||
/// <returns>A new <see cref="IIOManager"/>.</returns>
|
||||
IIOManager CreateResolverForSubdirectory(string subdirectoryPath);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the full path of the current working directory.
|
||||
/// </summary>
|
||||
@@ -117,15 +135,17 @@ namespace Tgstation.Server.Host.IO
|
||||
/// Creates an asynchronous <see cref="FileStream"/> for sequential writing.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file to write, will be truncated.</param>
|
||||
/// <returns>The open <see cref="FileStream"/>.</returns>
|
||||
FileStream CreateAsyncSequentialWriteStream(string path);
|
||||
/// <returns>The open <see cref="Stream"/>.</returns>
|
||||
Stream CreateAsyncSequentialWriteStream(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an asynchronous <see cref="FileStream"/> for sequential reading.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file to write, will be truncated.</param>
|
||||
/// <returns>The open <see cref="FileStream"/>.</returns>
|
||||
FileStream CreateAsyncSequentialReadStream(string path);
|
||||
/// <param name="sequential">If the sequential read flag should be added.</param>
|
||||
/// <param name="shareWrite">If <see cref="FileShare.Write"/> should be used.</param>
|
||||
/// <returns>The open <see cref="Stream"/>.</returns>
|
||||
Stream CreateAsyncReadStream(string path, bool sequential, bool shareWrite);
|
||||
|
||||
/// <summary>
|
||||
/// Writes some <paramref name="contents"/> to a file at <paramref name="path"/> overwriting previous content.
|
||||
@@ -230,12 +250,18 @@ namespace Tgstation.Server.Host.IO
|
||||
Task<DateTimeOffset> GetLastModified(string path, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Stream"/> for a given file <paramref name="path"/>.
|
||||
/// Gets a <see cref="IDirectoryInfo"/> for the given <paramref name="path"/>.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file.</param>
|
||||
/// <param name="shareWrite">If <see cref="FileShare.Write"/> should be used.</param>
|
||||
/// <returns>The <see cref="FileStream"/> of the file.</returns>
|
||||
/// <remarks>This function is sychronous.</remarks>
|
||||
FileStream GetFileStream(string path, bool shareWrite);
|
||||
/// <param name="path">The path to get <see cref="IDirectoryInfo"/> for.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IDirectoryInfo"/> of the <paramref name="path"/>.</returns>
|
||||
Task<IDirectoryInfo> DirectoryInfo(string path, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Check if a given <paramref name="path"/> is at the root level of the filesystem.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to check.</param>
|
||||
/// <returns><see langword="true"/> if the path is rooted, <see langword="false"/> otherwise.</returns>
|
||||
bool IsPathRooted(string path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,5 +63,12 @@ namespace Tgstation.Server.Host.IO
|
||||
/// <param name="path">The path to check.</param>
|
||||
/// <returns><see langword="true"/> if <paramref name="path"/> is a directory, <see langword="false"/> otherwise.</returns>
|
||||
bool IsDirectory(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Stream"/> for a given file <paramref name="path"/> without write share.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file.</param>
|
||||
/// <returns>The <see cref="Stream"/> of the file.</returns>
|
||||
Stream GetFileStream(string path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -15,6 +15,20 @@ namespace Tgstation.Server.Host.IO
|
||||
/// <inheritdoc />
|
||||
public bool SymlinkedDirectoriesAreDeletedAsFiles => true;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IFileSystem"/> to use.
|
||||
/// </summary>
|
||||
readonly IFileSystem fileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PosixFilesystemLinkFactory"/> class.
|
||||
/// </summary>
|
||||
/// <param name="fileSystem">The value of <see cref="fileSystem"/>.</param>
|
||||
public PosixFilesystemLinkFactory(IFileSystem fileSystem)
|
||||
{
|
||||
this.fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task CreateHardLink(string targetPath, string linkPath, CancellationToken cancellationToken) => Task.Factory.StartNew(
|
||||
() =>
|
||||
@@ -39,7 +53,7 @@ namespace Tgstation.Server.Host.IO
|
||||
ArgumentNullException.ThrowIfNull(linkPath);
|
||||
|
||||
UnixFileSystemInfo fsInfo;
|
||||
var isFile = File.Exists(targetPath);
|
||||
var isFile = fileSystem.File.Exists(targetPath);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (isFile)
|
||||
fsInfo = new UnixFileInfo(targetPath);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
|
||||
namespace Tgstation.Server.Host.IO
|
||||
{
|
||||
@@ -16,20 +16,20 @@ namespace Tgstation.Server.Host.IO
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ResolvingIOManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parent">The <see cref="IIOManager"/> that resolves to the directory to work out of.</param>
|
||||
/// <param name="fileSystem">The <see cref="IFileSystem"/> for the <see cref="DefaultIOManager"/>.</param>
|
||||
/// <param name="subdirectory">The value of <see cref="subdirectory"/>.</param>
|
||||
public ResolvingIOManager(IIOManager parent, string subdirectory)
|
||||
public ResolvingIOManager(
|
||||
IFileSystem fileSystem,
|
||||
string subdirectory)
|
||||
: base(fileSystem)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(parent);
|
||||
ArgumentNullException.ThrowIfNull(subdirectory);
|
||||
|
||||
this.subdirectory = ConcatPath(parent.ResolvePath(), subdirectory);
|
||||
this.subdirectory = subdirectory ?? throw new ArgumentNullException(nameof(subdirectory));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ResolvePath(string path)
|
||||
{
|
||||
if (!Path.IsPathRooted(path))
|
||||
if (!IsPathRooted(path))
|
||||
return base.ResolvePath(ConcatPath(subdirectory, path));
|
||||
return path;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
@@ -13,6 +14,11 @@ namespace Tgstation.Server.Host.IO
|
||||
/// <inheritdoc />
|
||||
sealed class SynchronousIOManager : ISynchronousIOManager
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IFileSystem"/> to use.
|
||||
/// </summary>
|
||||
readonly IFileSystem fileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="SynchronousIOManager"/>.
|
||||
/// </summary>
|
||||
@@ -21,9 +27,11 @@ namespace Tgstation.Server.Host.IO
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SynchronousIOManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="fileSystem">The value of <see cref="fileSystem"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="logger"/>.</param>
|
||||
public SynchronousIOManager(ILogger<SynchronousIOManager> logger)
|
||||
public SynchronousIOManager(IFileSystem fileSystem, ILogger<SynchronousIOManager> logger)
|
||||
{
|
||||
this.fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
@@ -33,32 +41,32 @@ namespace Tgstation.Server.Host.IO
|
||||
if (IsDirectory(path))
|
||||
return true;
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
Directory.CreateDirectory(path);
|
||||
fileSystem.Directory.CreateDirectory(path);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool DeleteDirectory(string path)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
if (fileSystem.File.Exists(path))
|
||||
return false;
|
||||
|
||||
if (!Directory.Exists(path))
|
||||
if (!fileSystem.Directory.Exists(path))
|
||||
return true;
|
||||
|
||||
if (Directory.EnumerateFileSystemEntries(path).Any())
|
||||
if (fileSystem.Directory.EnumerateFileSystemEntries(path).Any())
|
||||
return false;
|
||||
|
||||
Directory.Delete(path);
|
||||
fileSystem.Directory.Delete(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<string> GetDirectories(string path, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var directoryName in Directory.EnumerateDirectories(path))
|
||||
foreach (var directoryName in fileSystem.Directory.EnumerateDirectories(path))
|
||||
{
|
||||
yield return Path.GetFileName(directoryName);
|
||||
yield return fileSystem.Path.GetFileName(directoryName);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
}
|
||||
@@ -66,9 +74,9 @@ namespace Tgstation.Server.Host.IO
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<string> GetFiles(string path, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var fileName in Directory.EnumerateFiles(path))
|
||||
foreach (var fileName in fileSystem.Directory.EnumerateFiles(path))
|
||||
{
|
||||
yield return Path.GetFileName(fileName);
|
||||
yield return fileSystem.Path.GetFileName(fileName);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
}
|
||||
@@ -77,14 +85,14 @@ namespace Tgstation.Server.Host.IO
|
||||
public bool IsDirectory(string path)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(path);
|
||||
return Directory.Exists(path);
|
||||
return fileSystem.Directory.Exists(path);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public byte[] ReadFile(string path)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(path);
|
||||
return File.ReadAllBytes(path);
|
||||
return fileSystem.File.ReadAllBytes(path);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -94,16 +102,16 @@ namespace Tgstation.Server.Host.IO
|
||||
ArgumentNullException.ThrowIfNull(data);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var directory = Path.GetDirectoryName(path) ?? throw new ArgumentException("path cannot be rooted!", nameof(path));
|
||||
Directory.CreateDirectory(directory);
|
||||
var directory = fileSystem.Path.GetDirectoryName(path) ?? throw new ArgumentException("path cannot be rooted!", nameof(path));
|
||||
fileSystem.Directory.CreateDirectory(directory);
|
||||
|
||||
var newFile = !File.Exists(path);
|
||||
var newFile = !fileSystem.File.Exists(path);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
logger.LogTrace("Starting checked write to {path} ({fileType} file)", path, newFile ? "New" : "Pre-existing");
|
||||
|
||||
using (var file = File.Open(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
|
||||
using (var file = fileSystem.File.Open(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -160,10 +168,20 @@ namespace Tgstation.Server.Host.IO
|
||||
if (data.Length == 0)
|
||||
{
|
||||
logger.LogDebug("Stream is empty, deleting file");
|
||||
File.Delete(path);
|
||||
fileSystem.File.Delete(path);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Stream GetFileStream(string path)
|
||||
=> fileSystem.FileStream.New(
|
||||
path,
|
||||
FileMode.Open,
|
||||
FileAccess.Read,
|
||||
FileShare.Read | FileShare.Delete,
|
||||
DefaultIOManager.DefaultBufferSize,
|
||||
true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -16,6 +16,20 @@ namespace Tgstation.Server.Host.IO
|
||||
/// <inheritdoc />
|
||||
public bool SymlinkedDirectoriesAreDeletedAsFiles => false;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IFileSystem"/> to use.
|
||||
/// </summary>
|
||||
readonly IFileSystem fileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WindowsFilesystemLinkFactory"/> class.
|
||||
/// </summary>
|
||||
/// <param name="fileSystem">The value of <see cref="fileSystem"/>.</param>
|
||||
public WindowsFilesystemLinkFactory(IFileSystem fileSystem)
|
||||
{
|
||||
this.fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task CreateHardLink(string targetPath, string linkPath, CancellationToken cancellationToken)
|
||||
=> throw new NotSupportedException();
|
||||
@@ -28,7 +42,7 @@ namespace Tgstation.Server.Host.IO
|
||||
ArgumentNullException.ThrowIfNull(linkPath);
|
||||
|
||||
// check if its not a file
|
||||
var flags = File.Exists(targetPath) ? NativeMethods.CreateSymbolicLinkFlags.None : NativeMethods.CreateSymbolicLinkFlags.Directory;
|
||||
var flags = fileSystem.File.Exists(targetPath) ? NativeMethods.CreateSymbolicLinkFlags.None : NativeMethods.CreateSymbolicLinkFlags.Directory;
|
||||
|
||||
/*
|
||||
* no don't fucking use this
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -29,23 +30,33 @@ namespace Tgstation.Server.Host
|
||||
/// </summary>
|
||||
public const string AppSettings = "appsettings";
|
||||
|
||||
/// <inheritdoc />
|
||||
public IIOManager IOManager { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IAssemblyInformationProvider"/> for the <see cref="ServerFactory"/>.
|
||||
/// </summary>
|
||||
readonly IAssemblyInformationProvider assemblyInformationProvider;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IIOManager IOManager { get; }
|
||||
/// <summary>
|
||||
/// The <see cref="IFileSystem"/> for the <see cref="ServerFactory"/>.
|
||||
/// </summary>
|
||||
readonly IFileSystem fileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServerFactory"/> class.
|
||||
/// </summary>
|
||||
/// <param name="assemblyInformationProvider">The value of <see cref="assemblyInformationProvider"/>.</param>
|
||||
/// <param name="ioManager">The value of <see cref="IOManager"/>.</param>
|
||||
internal ServerFactory(IAssemblyInformationProvider assemblyInformationProvider, IIOManager ioManager)
|
||||
/// <param name="fileSystem">The value of <see cref="fileSystem"/>.</param>
|
||||
internal ServerFactory(
|
||||
IAssemblyInformationProvider assemblyInformationProvider,
|
||||
IIOManager ioManager,
|
||||
IFileSystem fileSystem)
|
||||
{
|
||||
this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
|
||||
IOManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -160,7 +171,7 @@ namespace Tgstation.Server.Host
|
||||
})
|
||||
.UseIIS()
|
||||
.UseIISIntegration()
|
||||
.UseApplication(assemblyInformationProvider, IOManager, postSetupServices)
|
||||
.UseApplication(assemblyInformationProvider, IOManager, postSetupServices, fileSystem)
|
||||
.SuppressStatusMessages(true)
|
||||
.UseShutdownTimeout(
|
||||
TimeSpan.FromMinutes(
|
||||
|
||||
@@ -304,7 +304,7 @@ namespace Tgstation.Server.Host.Setup
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the SQLite database path to store in the configuration.</returns>
|
||||
async ValueTask<string?> ValidateNonExistantSqliteDBName(string databaseName, CancellationToken cancellationToken)
|
||||
{
|
||||
var dbPathIsRooted = Path.IsPathRooted(databaseName);
|
||||
var dbPathIsRooted = ioManager.IsPathRooted(databaseName);
|
||||
var resolvedPath = ioManager.ResolvePath(
|
||||
dbPathIsRooted
|
||||
? databaseName
|
||||
|
||||
@@ -64,39 +64,6 @@ namespace Tgstation.Server.Host.System
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets potential paths to the gcore executable.
|
||||
/// </summary>
|
||||
/// <returns>The potential paths to the gcore executable.</returns>
|
||||
static IEnumerable<string> GetPotentialGCorePaths()
|
||||
{
|
||||
var enviromentPath = Environment.GetEnvironmentVariable("PATH");
|
||||
IEnumerable<string> enumerator;
|
||||
if (enviromentPath == null)
|
||||
enumerator = Enumerable.Empty<string>();
|
||||
else
|
||||
{
|
||||
var paths = enviromentPath.Split(';');
|
||||
enumerator = paths
|
||||
.Select(x => x.Split(':'))
|
||||
.SelectMany(x => x);
|
||||
}
|
||||
|
||||
var exeName = "gcore";
|
||||
|
||||
enumerator = enumerator
|
||||
.Concat(new List<string>(2)
|
||||
{
|
||||
"/usr/bin",
|
||||
"/usr/share/bin",
|
||||
"/bin",
|
||||
});
|
||||
|
||||
enumerator = enumerator.Select(x => Path.Combine(x, exeName));
|
||||
|
||||
return enumerator;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ResumeProcess(global::System.Diagnostics.Process process)
|
||||
{
|
||||
@@ -201,8 +168,8 @@ namespace Tgstation.Server.Host.System
|
||||
{
|
||||
// can't use ReadAllBytes here, /proc files have 0 length so the buffer is initialized to empty
|
||||
// https://stackoverflow.com/questions/12237712/how-can-i-show-the-size-of-files-in-proc-it-should-not-be-size-zero
|
||||
await using var fileStream = ioManager.CreateAsyncSequentialReadStream(
|
||||
"/proc/self/oom_score_adj");
|
||||
await using var fileStream = ioManager.CreateAsyncReadStream(
|
||||
"/proc/self/oom_score_adj", true, true);
|
||||
using var reader = new StreamReader(fileStream, Encoding.UTF8, leaveOpen: true);
|
||||
originalString = await reader.ReadToEndAsync(cancellationToken);
|
||||
}
|
||||
@@ -255,5 +222,38 @@ namespace Tgstation.Server.Host.System
|
||||
Encoding.UTF8.GetBytes(adjustedValue.ToString(CultureInfo.InvariantCulture)),
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets potential paths to the gcore executable.
|
||||
/// </summary>
|
||||
/// <returns>The potential paths to the gcore executable.</returns>
|
||||
IEnumerable<string> GetPotentialGCorePaths()
|
||||
{
|
||||
var enviromentPath = Environment.GetEnvironmentVariable("PATH");
|
||||
IEnumerable<string> enumerator;
|
||||
if (enviromentPath == null)
|
||||
enumerator = Enumerable.Empty<string>();
|
||||
else
|
||||
{
|
||||
var paths = enviromentPath.Split(';');
|
||||
enumerator = paths
|
||||
.Select(x => x.Split(':'))
|
||||
.SelectMany(x => x);
|
||||
}
|
||||
|
||||
var exeName = "gcore";
|
||||
|
||||
enumerator = enumerator
|
||||
.Concat(new List<string>(2)
|
||||
{
|
||||
"/usr/bin",
|
||||
"/usr/share/bin",
|
||||
"/bin",
|
||||
});
|
||||
|
||||
enumerator = enumerator.Select(x => ioManager.ConcatPath(x, exeName));
|
||||
|
||||
return enumerator;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,6 +145,7 @@ namespace Tgstation.Server.Host.System
|
||||
throw new JobException(ErrorCode.GameServerOffline, ex);
|
||||
}
|
||||
|
||||
// Windows API so has to be a real FS
|
||||
await using var fileStream = new FileStream(outputFile, FileMode.CreateNew);
|
||||
|
||||
await Task.Factory.StartNew(
|
||||
|
||||
@@ -96,41 +96,41 @@
|
||||
<!-- Usage: .env file parsing -->
|
||||
<PackageReference Include="DotEnv.Core" Version="3.1.0" />
|
||||
<!-- Usage: Text formatter for Elasticsearch logging plugin -->
|
||||
<PackageReference Include="Elastic.CommonSchema.Serilog" Version="8.12.3" />
|
||||
<PackageReference Include="Elastic.CommonSchema.Serilog" Version="8.18.2" />
|
||||
<!-- Usage: GraphQL API Engine -->
|
||||
<PackageReference Include="HotChocolate.AspNetCore" Version="15.1.3" />
|
||||
<PackageReference Include="HotChocolate.AspNetCore" Version="15.1.8" />
|
||||
<!-- Usage: GraphQL Authorization Plugin -->
|
||||
<PackageReference Include="HotChocolate.AspNetCore.Authorization" Version="15.1.3" />
|
||||
<PackageReference Include="HotChocolate.AspNetCore.Authorization" Version="15.1.8" />
|
||||
<!-- Usage: GraphQL IDatabaseContext support -->
|
||||
<PackageReference Include="HotChocolate.Data.EntityFramework" Version="15.1.3" />
|
||||
<PackageReference Include="HotChocolate.Data.EntityFramework" Version="15.1.8" />
|
||||
<!-- Usage: DataLoader source generation -->
|
||||
<PackageReference Include="HotChocolate.Types.Analyzers" Version="15.1.3" />
|
||||
<PackageReference Include="HotChocolate.Types.Analyzers" Version="15.1.8" />
|
||||
<!-- Usage: GraphQL additional scalar type definitions -->
|
||||
<PackageReference Include="HotChocolate.Types.Scalars" Version="15.1.3" />
|
||||
<PackageReference Include="HotChocolate.Types.Scalars" Version="15.1.8" />
|
||||
<!-- Usage: git interop -->
|
||||
<PackageReference Include="LibGit2Sharp" Version="0.31.0" />
|
||||
<!-- Usage: OpenID Connect support -->
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.15" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.18" />
|
||||
<!-- Usage: Support ""legacy"" Newotonsoft.Json in HTTP pipeline. The rest of our codebase uses Newtonsoft. -->
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.15" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.18" />
|
||||
<!-- Usage: Using target JSON serializer for API -->
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="9.0.7" />
|
||||
<!-- Usage: Generating dumps of dotnet engine processes -->
|
||||
<PackageReference Include="Microsoft.Diagnostics.NETCore.Client" Version="0.2.621003" />
|
||||
<!-- Usage: Database ORM -->
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.7" />
|
||||
<!-- Usage: Automatic migration generation using command line -->
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.4">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<!-- Usage: Sqlite ORM plugin -->
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.7" />
|
||||
<!-- Usage: MSSQL ORM plugin -->
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.7" />
|
||||
<!-- Usage: Database connectivity health check -->
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.15" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.18" />
|
||||
<!-- Usage: OIDC support prerequisite. See https://stackoverflow.com/a/78650835/3976486 -->
|
||||
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.8.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.13.0" />
|
||||
<!-- Usage: POSIX support for syscalls, signals, and symlinks -->
|
||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
||||
<!-- Usage: Cron string parsing -->
|
||||
@@ -144,9 +144,9 @@
|
||||
<!-- Usage: Publishing Prometheus metrics -->
|
||||
<PackageReference Include="prometheus-net.AspNetCore.HealthChecks" Version="8.2.1" />
|
||||
<!-- Usage: Discord interop -->
|
||||
<PackageReference Include="Remora.Discord" Version="2025.1.0" />
|
||||
<PackageReference Include="Remora.Discord" Version="2025.2.0" />
|
||||
<!-- Usage: Rich logger builder -->
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.1" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />
|
||||
<!-- Usage: Async logging plugin -->
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="2.1.0" />
|
||||
<!-- Usage: Console logging plugin -->
|
||||
@@ -154,15 +154,17 @@
|
||||
<!-- Usage: Elasticsearch logging plugin -->
|
||||
<PackageReference Include="Serilog.Sinks.Elasticsearch" Version="10.0.0" />
|
||||
<!-- Usage: File logging plugin -->
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<!-- Usage: OpenAPI spec generator -->
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3" />
|
||||
<!-- Usage: Newtonsoft.Json plugin for OpenAPI spec generator -->
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="8.1.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="9.0.3" />
|
||||
<!-- Usage: Windows authentication plugin allowing searching for users by name -->
|
||||
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="9.0.4" />
|
||||
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="9.0.7" />
|
||||
<!-- Usage: Filesystem abstraction -->
|
||||
<PackageReference Include="System.IO.Abstractions" Version="22.0.15" />
|
||||
<!-- Usage: Identifying owning user of Windows Process objects -->
|
||||
<PackageReference Include="System.Management" Version="9.0.4" />
|
||||
<PackageReference Include="System.Management" Version="9.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -212,7 +212,7 @@ namespace Tgstation.Server.Host.Transfer
|
||||
if (downloadProvider.StreamProvider != null)
|
||||
stream = await downloadProvider.StreamProvider(cancellationToken);
|
||||
else
|
||||
stream = ioManager.GetFileStream(downloadProvider.FilePath, downloadProvider.ShareWrite);
|
||||
stream = ioManager.CreateAsyncReadStream(downloadProvider.FilePath, false, downloadProvider.ShareWrite);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
|
||||
@@ -17,6 +17,7 @@ General:
|
||||
HostApiDocumentation: false # Make HTTP API documentation available at /api/doc/tgs_api.json and /api/graphql
|
||||
SkipAddingByondFirewallException: false # Windows Only: Prevent running netsh.exe to add a firewall exception for installed engine binaries
|
||||
DeploymentDirectoryCopyTasksPerCore: 100 # Maximum number of concurrent file copy operations PER available CPU core
|
||||
ByondZipDownloadTemplate: https://www.byond.com/download/build/${Major}/${Major}.${Minor}_byond${Linux:_linux}.zip # Template for downloading official byond versions
|
||||
OpenDreamGitUrl: https://github.com/OpenDreamProject/OpenDream # The repository to retrieve OpenDream from
|
||||
OpenDreamGitTagPrefix: v # The prefix to the OpenDream semver as tags appear in the git repository
|
||||
OpenDreamSuppressInstallOutput: false # Suppress the dotnet output of creating an OpenDream installation. Known to cause hangs in CI.
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Usage: JWT injection into HTTP pipeline -->
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.15" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.18" />
|
||||
<!-- Usage: GitHub.com interop -->
|
||||
<PackageReference Include="Octokit" Version="14.0.0" />
|
||||
<!-- Usage: YAML conversion of Version objects -->
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Engine.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class TestByondInstallerBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void TestUrlTemplateFormatting()
|
||||
{
|
||||
const string OSMarker = "TempleOS";
|
||||
|
||||
Assert.AreEqual(
|
||||
new Uri("https://example.com/$515.1111_Hello Worl$d.zip"),
|
||||
ByondInstallerBase.GetDownloadZipUrl(
|
||||
new Version(515, 1111),
|
||||
"https://example.com/$$${Major}.${Minor}_${TempleOS:Hello Worl$$d}.zip${Linux:Not this}${Or This}",
|
||||
OSMarker));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using System;
|
||||
@@ -9,6 +10,7 @@ using System.Threading.Tasks;
|
||||
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.IO;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Engine.Tests
|
||||
@@ -19,16 +21,18 @@ namespace Tgstation.Server.Host.Components.Engine.Tests
|
||||
[TestMethod]
|
||||
public void TestConstruction()
|
||||
{
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(null, null, null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(null, null, null, null, null));
|
||||
var mockPostWriteHandler = new Mock<IPostWriteHandler>();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(mockPostWriteHandler.Object, null, null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(mockPostWriteHandler.Object, null, null, null, null));
|
||||
var mockIOManager = new Mock<IIOManager>();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, null, null, null));
|
||||
var mockFileDownloader = Mock.Of<IFileDownloader>();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, null, null));
|
||||
var mockOptions = Mock.Of<IOptionsMonitor<GeneralConfiguration>>();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, mockOptions, null));
|
||||
|
||||
var mockLogger = new Mock<ILogger<PosixByondInstaller>>();
|
||||
_ = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, mockLogger.Object);
|
||||
_ = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, mockOptions, mockLogger.Object);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -38,7 +42,8 @@ namespace Tgstation.Server.Host.Components.Engine.Tests
|
||||
var mockIOManager = new Mock<IIOManager>();
|
||||
var mockLogger = new Mock<ILogger<PosixByondInstaller>>();
|
||||
var mockFileDownloader = Mock.Of<IFileDownloader>();
|
||||
var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, mockLogger.Object);
|
||||
var mockOptions = Mock.Of<IOptionsMonitor<GeneralConfiguration>>();
|
||||
var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, mockOptions, mockLogger.Object);
|
||||
await installer.CleanCache(default);
|
||||
}
|
||||
|
||||
@@ -50,7 +55,14 @@ namespace Tgstation.Server.Host.Components.Engine.Tests
|
||||
var mockPostWriteHandler = new Mock<IPostWriteHandler>();
|
||||
var mockLogger = new Mock<ILogger<PosixByondInstaller>>();
|
||||
var mockFileDownloader = new Mock<IFileDownloader>();
|
||||
var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader.Object, mockLogger.Object);
|
||||
var mockOptions = new Mock<IOptionsMonitor<GeneralConfiguration>>();
|
||||
const string TestUrl = "https://chumb.is";
|
||||
mockOptions.SetupGet(x => x.CurrentValue).Returns(new GeneralConfiguration
|
||||
{
|
||||
ByondZipDownloadTemplate = TestUrl,
|
||||
});
|
||||
|
||||
var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader.Object, mockOptions.Object, mockLogger.Object);
|
||||
|
||||
await Assert.ThrowsExceptionAsync<ArgumentNullException>(() => installer.DownloadVersion(null, null, default).AsTask());
|
||||
|
||||
@@ -58,7 +70,7 @@ namespace Tgstation.Server.Host.Components.Engine.Tests
|
||||
mockFileDownloader
|
||||
.Setup(
|
||||
x => x.DownloadFile(
|
||||
It.Is<Uri>(uri => uri == new Uri("https://www.byond.com/download/build/511/511.1385_byond_linux.zip")),
|
||||
It.Is<Uri>(uri => uri == new Uri(TestUrl)),
|
||||
null))
|
||||
.Returns(
|
||||
new BufferedFileStreamProvider(
|
||||
@@ -87,7 +99,8 @@ namespace Tgstation.Server.Host.Components.Engine.Tests
|
||||
var mockPostWriteHandler = new Mock<IPostWriteHandler>();
|
||||
var mockLogger = new Mock<ILogger<PosixByondInstaller>>();
|
||||
var mockFileDownloader = Mock.Of<IFileDownloader>();
|
||||
var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, mockLogger.Object);
|
||||
var mockOptions = Mock.Of<IOptionsMonitor<GeneralConfiguration>>();
|
||||
var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, mockOptions, mockLogger.Object);
|
||||
|
||||
const string FakePath = "fake";
|
||||
await Assert.ThrowsExceptionAsync<ArgumentNullException>(() => installer.Install(null, null, false, default).AsTask());
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -31,9 +32,10 @@ namespace Tgstation.Server.Host.Components.StaticFiles.Tests
|
||||
builder.SetMinimumLevel(LogLevel.Trace);
|
||||
});
|
||||
|
||||
var mockFs = new MockFileSystem();
|
||||
var tempPath = Path.GetTempFileName();
|
||||
File.Delete(tempPath);
|
||||
var ioManager = new ResolvingIOManager(new DefaultIOManager(), tempPath);
|
||||
var ioManager = new DefaultIOManager(mockFs).CreateResolverForSubdirectory(tempPath);
|
||||
await ioManager.CreateDirectory(".", CancellationToken.None);
|
||||
try
|
||||
{
|
||||
@@ -46,7 +48,9 @@ namespace Tgstation.Server.Host.Components.StaticFiles.Tests
|
||||
|
||||
var configuration = new Configuration(
|
||||
ioManager,
|
||||
new SynchronousIOManager(loggerFactory.CreateLogger<SynchronousIOManager>()),
|
||||
new SynchronousIOManager(
|
||||
mockFs,
|
||||
loggerFactory.CreateLogger<SynchronousIOManager>()),
|
||||
Mock.Of<IFilesystemLinkFactory>(),
|
||||
Mock.Of<IProcessExecutor>(),
|
||||
Mock.Of<IPostWriteHandler>(),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Principal;
|
||||
using System.Threading;
|
||||
@@ -17,10 +18,11 @@ namespace Tgstation.Server.Host.IO.Tests
|
||||
[ClassInitialize]
|
||||
public static void SelectFactory(TestContext _)
|
||||
{
|
||||
var fileSystem = new FileSystem();
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
linkFactory = new WindowsFilesystemLinkFactory();
|
||||
linkFactory = new WindowsFilesystemLinkFactory(fileSystem);
|
||||
else
|
||||
linkFactory = new PosixFilesystemLinkFactory();
|
||||
linkFactory = new PosixFilesystemLinkFactory(fileSystem);
|
||||
}
|
||||
|
||||
public static bool HasPermissionToMakeSymlinks()
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
@@ -14,27 +16,34 @@ namespace Tgstation.Server.Host.IO.Tests
|
||||
[TestClass]
|
||||
public sealed class TestIOManager
|
||||
{
|
||||
readonly IIOManager ioManager = new DefaultIOManager();
|
||||
readonly IFileSystem fileSystem;
|
||||
readonly IIOManager ioManager;
|
||||
|
||||
public TestIOManager()
|
||||
{
|
||||
fileSystem = new MockFileSystem();
|
||||
ioManager = new DefaultIOManager(fileSystem);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestDeleteDirectory()
|
||||
{
|
||||
var tempPath = Path.GetTempFileName();
|
||||
File.Delete(tempPath);
|
||||
Directory.CreateDirectory(tempPath);
|
||||
var tempPath = fileSystem.Path.Combine(fileSystem.Path.GetTempPath(), fileSystem.Path.GetRandomFileName());
|
||||
fileSystem.File.Delete(tempPath);
|
||||
fileSystem.Directory.CreateDirectory(tempPath);
|
||||
try
|
||||
{
|
||||
await File.WriteAllTextAsync(Path.Combine(tempPath, "file.txt"), "asdf");
|
||||
var subDir = Path.Combine(tempPath, "subdir");
|
||||
Directory.CreateDirectory(subDir);
|
||||
await File.WriteAllTextAsync(Path.Combine(subDir, "file2.txt"), "fdsa");
|
||||
await fileSystem.File.WriteAllTextAsync(Path.Combine(tempPath, "file.txt"), "asdf");
|
||||
var subDir = fileSystem.Path.Combine(tempPath, "subdir");
|
||||
fileSystem.Directory.CreateDirectory(subDir);
|
||||
await fileSystem.File.WriteAllTextAsync(Path.Combine(subDir, "file2.txt"), "fdsa");
|
||||
await ioManager.DeleteDirectory(tempPath, default);
|
||||
|
||||
Assert.IsFalse(Directory.Exists(tempPath));
|
||||
Assert.IsFalse(fileSystem.Directory.Exists(tempPath));
|
||||
}
|
||||
catch
|
||||
{
|
||||
Directory.Delete(tempPath, true);
|
||||
fileSystem.Directory.Delete(tempPath, true);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -42,13 +51,17 @@ namespace Tgstation.Server.Host.IO.Tests
|
||||
[TestMethod]
|
||||
public async Task TestDeleteDirectoryWithSymlinkInsideDoesntRecurse()
|
||||
{
|
||||
var linkFactory = (IFilesystemLinkFactory)(new PlatformIdentifier().IsWindows
|
||||
? new WindowsFilesystemLinkFactory()
|
||||
: new PosixFilesystemLinkFactory());
|
||||
// need a real FS here
|
||||
var fileSystem = new FileSystem();
|
||||
var ioManager = new DefaultIOManager(fileSystem);
|
||||
|
||||
var tempPath = Path.GetTempFileName();
|
||||
File.Delete(tempPath);
|
||||
Directory.CreateDirectory(tempPath);
|
||||
var linkFactory = (IFilesystemLinkFactory)(new PlatformIdentifier().IsWindows
|
||||
? new WindowsFilesystemLinkFactory(fileSystem)
|
||||
: new PosixFilesystemLinkFactory(fileSystem));
|
||||
|
||||
var tempPath = fileSystem.Path.Combine(fileSystem.Path.GetTempPath(), fileSystem.Path.GetRandomFileName());
|
||||
fileSystem.File.Delete(tempPath);
|
||||
fileSystem.Directory.CreateDirectory(tempPath);
|
||||
try
|
||||
{
|
||||
var targetDir = ioManager.ConcatPath(tempPath, "targetdir");
|
||||
@@ -81,7 +94,7 @@ namespace Tgstation.Server.Host.IO.Tests
|
||||
}
|
||||
catch
|
||||
{
|
||||
Directory.Delete(tempPath, true);
|
||||
fileSystem.Directory.Delete(tempPath, true);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -89,14 +102,15 @@ namespace Tgstation.Server.Host.IO.Tests
|
||||
[TestMethod]
|
||||
public async Task TestFileExists()
|
||||
{
|
||||
var tempPath = Path.GetTempFileName();
|
||||
var tempPath = fileSystem.Path.Combine(fileSystem.Path.GetTempPath(), fileSystem.Path.GetRandomFileName());
|
||||
await fileSystem.File.WriteAllBytesAsync(tempPath, Array.Empty<byte>());
|
||||
try
|
||||
{
|
||||
Assert.IsTrue(await ioManager.FileExists(tempPath, default));
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(tempPath);
|
||||
fileSystem.File.Delete(tempPath);
|
||||
}
|
||||
|
||||
Assert.IsFalse(await ioManager.FileExists(tempPath, default));
|
||||
@@ -105,12 +119,12 @@ namespace Tgstation.Server.Host.IO.Tests
|
||||
[TestMethod]
|
||||
public async Task TestDirectoryExists()
|
||||
{
|
||||
var tempPath = Path.GetTempFileName();
|
||||
File.Delete(tempPath);
|
||||
var tempPath = fileSystem.Path.Combine(fileSystem.Path.GetTempPath(), fileSystem.Path.GetRandomFileName());
|
||||
fileSystem.File.Delete(tempPath);
|
||||
|
||||
Assert.IsFalse(await ioManager.DirectoryExists(tempPath, default));
|
||||
|
||||
Directory.CreateDirectory(tempPath);
|
||||
fileSystem.Directory.CreateDirectory(tempPath);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -118,7 +132,7 @@ namespace Tgstation.Server.Host.IO.Tests
|
||||
}
|
||||
catch
|
||||
{
|
||||
Directory.Delete(tempPath);
|
||||
fileSystem.Directory.Delete(tempPath);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -180,18 +194,18 @@ namespace Tgstation.Server.Host.IO.Tests
|
||||
|
||||
async Task TestCopyDirectory(int? throttle)
|
||||
{
|
||||
var tempPath = Path.GetTempFileName();
|
||||
File.Delete(tempPath);
|
||||
Directory.CreateDirectory(tempPath);
|
||||
var tempPath = fileSystem.Path.Combine(fileSystem.Path.GetTempPath(), fileSystem.Path.GetRandomFileName());
|
||||
fileSystem.File.Delete(tempPath);
|
||||
fileSystem.Directory.CreateDirectory(tempPath);
|
||||
try
|
||||
{
|
||||
var tempPath2 = Path.GetTempFileName();
|
||||
File.Delete(tempPath2);
|
||||
var tempPath2 = fileSystem.Path.Combine(fileSystem.Path.GetTempPath(), fileSystem.Path.GetRandomFileName());
|
||||
fileSystem.File.Delete(tempPath2);
|
||||
|
||||
await File.WriteAllTextAsync(Path.Combine(tempPath, "file.txt"), "asdf");
|
||||
var subDir = Path.Combine(tempPath, "subdir");
|
||||
Directory.CreateDirectory(subDir);
|
||||
await File.WriteAllTextAsync(Path.Combine(subDir, "file2.txt"), "fdsa");
|
||||
await fileSystem.File.WriteAllTextAsync(fileSystem.Path.Combine(tempPath, "file.txt"), "asdf");
|
||||
var subDir = fileSystem.Path.Combine(tempPath, "subdir");
|
||||
fileSystem.Directory.CreateDirectory(subDir);
|
||||
await fileSystem.File.WriteAllTextAsync(fileSystem.Path.Combine(subDir, "file2.txt"), "fdsa");
|
||||
|
||||
try
|
||||
{
|
||||
@@ -203,26 +217,26 @@ namespace Tgstation.Server.Host.IO.Tests
|
||||
throttle,
|
||||
default);
|
||||
|
||||
Assert.IsTrue(Directory.Exists(tempPath2));
|
||||
var newFilePath = Path.Combine(tempPath2, "file.txt");
|
||||
Assert.IsTrue(File.Exists(newFilePath));
|
||||
var newFileText = await File.ReadAllTextAsync(newFilePath);
|
||||
Assert.IsTrue(fileSystem.Directory.Exists(tempPath2));
|
||||
var newFilePath = fileSystem.Path.Combine(tempPath2, "file.txt");
|
||||
Assert.IsTrue(fileSystem.File.Exists(newFilePath));
|
||||
var newFileText = await fileSystem.File.ReadAllTextAsync(newFilePath);
|
||||
Assert.AreEqual("asdf", newFileText);
|
||||
var newDirPath = Path.Combine(tempPath2, "subdir");
|
||||
Assert.IsTrue(Directory.Exists(newDirPath));
|
||||
var newFile2Path = Path.Combine(newDirPath, "file2.txt");
|
||||
Assert.IsTrue(File.Exists(newFile2Path));
|
||||
var newFile2Text = await File.ReadAllTextAsync(newFile2Path);
|
||||
var newDirPath = fileSystem.Path.Combine(tempPath2, "subdir");
|
||||
Assert.IsTrue(fileSystem.Directory.Exists(newDirPath));
|
||||
var newFile2Path = fileSystem.Path.Combine(newDirPath, "file2.txt");
|
||||
Assert.IsTrue(fileSystem.File.Exists(newFile2Path));
|
||||
var newFile2Text = await fileSystem.File.ReadAllTextAsync(newFile2Path);
|
||||
Assert.AreEqual("fdsa", newFile2Text);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempPath2, true);
|
||||
fileSystem.Directory.Delete(tempPath2, true);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempPath, true);
|
||||
fileSystem.Directory.Delete(tempPath, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -58,7 +59,8 @@ namespace Tgstation.Server.Host.System.Tests
|
||||
processExecutor = new ProcessExecutor(
|
||||
new PosixProcessFeatures(
|
||||
new Lazy<IProcessExecutor>(() => processExecutor),
|
||||
new DefaultIOManager(),
|
||||
new DefaultIOManager(
|
||||
new FileSystem()),
|
||||
loggerFactory.CreateLogger<PosixProcessFeatures>()),
|
||||
Mock.Of<IIOManager>(),
|
||||
loggerFactory.CreateLogger<ProcessExecutor>(),
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using System;
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Tgstation.Server.Host.IO;
|
||||
@@ -21,7 +22,7 @@ namespace Tgstation.Server.Host.System.Tests
|
||||
{
|
||||
features = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||
? (IProcessFeatures)new WindowsProcessFeatures(Mock.Of<ILogger<WindowsProcessFeatures>>())
|
||||
: new PosixProcessFeatures(new Lazy<IProcessExecutor>(() => null), new DefaultIOManager(), Mock.Of<ILogger<PosixProcessFeatures>>());
|
||||
: new PosixProcessFeatures(new Lazy<IProcessExecutor>(() => null), new DefaultIOManager(new MockFileSystem()), Mock.Of<ILogger<PosixProcessFeatures>>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -13,8 +14,10 @@ namespace Tgstation.Server.Host.System.Tests
|
||||
public sealed class TestSymlinkFactory
|
||||
{
|
||||
readonly IFilesystemLinkFactory factory = new PlatformIdentifier().IsWindows
|
||||
? new WindowsFilesystemLinkFactory()
|
||||
: new PosixFilesystemLinkFactory();
|
||||
? new WindowsFilesystemLinkFactory(
|
||||
new FileSystem())
|
||||
: new PosixFilesystemLinkFactory(
|
||||
new FileSystem());
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestSymlinks()
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Moq;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -80,28 +81,29 @@ namespace Tgstation.Server.Host.Tests
|
||||
public async Task TestStandardRunWithExceptionAndWatchdog()
|
||||
{
|
||||
var mockServer = new Mock<IServer>();
|
||||
var mockFs = new MockFileSystem();
|
||||
var exception = new DivideByZeroException();
|
||||
mockServer.Setup(x => x.Run(It.IsAny<CancellationToken>())).Throws(exception);
|
||||
mockServer.SetupGet(x => x.RestartRequested).Returns(true);
|
||||
var mockServerFactory = new Mock<IServerFactory>();
|
||||
mockServerFactory.SetupGet(x => x.IOManager).Returns(new DefaultIOManager());
|
||||
mockServerFactory.SetupGet(x => x.IOManager).Returns(new DefaultIOManager(mockFs));
|
||||
mockServerFactory.Setup(x => x.CreateServer(It.IsNotNull<string[]>(), It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(mockServer.Object);
|
||||
var program = new Program
|
||||
{
|
||||
ServerFactory = mockServerFactory.Object
|
||||
};
|
||||
|
||||
var tempFileName = Path.GetTempFileName();
|
||||
File.Delete(tempFileName);
|
||||
var tempFileName = mockFs.Path.Combine(mockFs.Path.GetTempPath(), mockFs.Path.GetRandomFileName());
|
||||
mockFs.File.Delete(tempFileName);
|
||||
try
|
||||
{
|
||||
var result = await program.Main(Array.Empty<string>(), tempFileName);
|
||||
Assert.AreEqual(HostExitCode.Error, result);
|
||||
Assert.AreEqual(exception.ToString(), File.ReadAllText(tempFileName));
|
||||
Assert.AreEqual(exception.ToString(), mockFs.File.ReadAllText(tempFileName));
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(tempFileName);
|
||||
mockFs.File.Delete(tempFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using System;
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Tgstation.Server.Host.Core;
|
||||
@@ -20,11 +21,13 @@ namespace Tgstation.Server.Host.Tests
|
||||
[TestMethod]
|
||||
public void TestConstructor()
|
||||
{
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new ServerFactory(null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new ServerFactory(null, null, null));
|
||||
IAssemblyInformationProvider assemblyInformationProvider = Mock.Of<IAssemblyInformationProvider>();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new ServerFactory(assemblyInformationProvider, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new ServerFactory(assemblyInformationProvider, null, null));
|
||||
IIOManager ioManager = Mock.Of<IIOManager>();
|
||||
_ = new ServerFactory(assemblyInformationProvider, ioManager);
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new ServerFactory(assemblyInformationProvider, ioManager, null));
|
||||
var mockFileSystem = new MockFileSystem();
|
||||
_ = new ServerFactory(assemblyInformationProvider, ioManager, mockFileSystem);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Usage: Creating mock database implementations -->
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.7" />
|
||||
<!-- Usage: Creating mock filesystem implementations -->
|
||||
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="22.0.15" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -12,6 +13,8 @@ using Moq;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Common.Http;
|
||||
using Tgstation.Server.Host.Components.Engine;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.System;
|
||||
@@ -44,9 +47,9 @@ namespace Tgstation.Server.Tests
|
||||
var logger = loggerFactory.CreateLogger("CachingFileDownloader");
|
||||
|
||||
var cfd = new CachingFileDownloader(loggerFactory.CreateLogger<CachingFileDownloader>());
|
||||
var edgeVersion = await EngineTest.GetEdgeVersion(Api.Models.EngineType.Byond, cfd, cancellationToken);
|
||||
|
||||
await InitializeByondVersion(logger, edgeVersion.Version, new PlatformIdentifier().IsWindows, cancellationToken);
|
||||
// this also will inject the edge version
|
||||
var edgeVersion = await EngineTest.GetEdgeVersion(Api.Models.EngineType.Byond, logger, cfd, cancellationToken);
|
||||
|
||||
// predownload the target github release update asset
|
||||
var gitHubToken = Environment.GetEnvironmentVariable("TGS_TEST_GITHUB_TOKEN");
|
||||
@@ -78,7 +81,7 @@ namespace Tgstation.Server.Tests
|
||||
ServiceCollectionExtensions.UseFileDownloader<CachingFileDownloader>();
|
||||
}
|
||||
|
||||
public static async ValueTask InitializeByondVersion(ILogger logger, Version byondVersion, bool windows, CancellationToken cancellationToken)
|
||||
public static async ValueTask InitializeByondVersion(ILogger logger, Version byondVersion, bool windows, CancellationToken cancellationToken, string urlCacheOverrideTemplate = null)
|
||||
{
|
||||
var version = new EngineVersion
|
||||
{
|
||||
@@ -86,25 +89,50 @@ namespace Tgstation.Server.Tests
|
||||
Version = byondVersion,
|
||||
};
|
||||
|
||||
var url = new Uri(
|
||||
$"https://www.byond.com/download/build/{version.Version.Major}/{version.Version.Major}.{version.Version.Minor}_byond{(!windows ? "_linux" : string.Empty)}.zip");
|
||||
string path = null;
|
||||
if (TestingUtils.RunningInGitHubActions)
|
||||
{
|
||||
// actions is supposed to cache BYOND for us
|
||||
var urlTemplate = TestingUtils.ByondZipDownloadTemplate;
|
||||
|
||||
var dir = Path.Combine(
|
||||
var url = ByondInstallerBase.GetDownloadZipUrl(byondVersion, urlTemplate, new PlatformIdentifier().IsWindows ? "Windows" : "Linux");
|
||||
string path = null;
|
||||
string basePath = Environment.GetEnvironmentVariable("TGS_TEST_BYOND_ZIPS_BASE_PATH");
|
||||
if (basePath == null && TestingUtils.RunningInGitHubActions)
|
||||
{
|
||||
// actions is supposed to cache BYOND for us here
|
||||
basePath = Path.Combine(
|
||||
Environment.GetFolderPath(
|
||||
Environment.SpecialFolder.UserProfile,
|
||||
Environment.SpecialFolderOption.DoNotVerify),
|
||||
"byond-zips-cache",
|
||||
"byond-zips-cache");
|
||||
}
|
||||
|
||||
if (basePath != null)
|
||||
{
|
||||
var dir = Path.Combine(
|
||||
basePath,
|
||||
"live",
|
||||
windows ? "windows" : "linux");
|
||||
path = Path.Combine(
|
||||
dir,
|
||||
$"{version.Version.Major}.{version.Version.Minor}",
|
||||
$"{version.Version.Major}.{version.Version.Minor}.zip");
|
||||
}
|
||||
|
||||
await (await CacheFile(logger, url, null, path, cancellationToken)).DisposeAsync();
|
||||
Uri overrideUrl = null;
|
||||
if (urlCacheOverrideTemplate != null)
|
||||
{
|
||||
overrideUrl = url;
|
||||
url = ByondInstallerBase.GetDownloadZipUrl(byondVersion, urlCacheOverrideTemplate, new PlatformIdentifier().IsWindows ? "Windows" : "Linux");
|
||||
}
|
||||
|
||||
await (await CacheFile(
|
||||
logger,
|
||||
url,
|
||||
null,
|
||||
path,
|
||||
cancellationToken))
|
||||
.DisposeAsync();
|
||||
|
||||
if (overrideUrl != null)
|
||||
cachedPaths[overrideUrl.ToString()] = cachedPaths[url.ToString()];
|
||||
}
|
||||
|
||||
public static void Cleanup()
|
||||
@@ -195,7 +223,7 @@ namespace Tgstation.Server.Tests
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
await using var fs = new DefaultIOManager().CreateAsyncSequentialWriteStream(path);
|
||||
await using var fs = new DefaultIOManager(new FileSystem()).CreateAsyncSequentialWriteStream(path);
|
||||
await ms.CopyToAsync(fs, cancellationToken);
|
||||
|
||||
cachedPaths.Add(url.ToString(), Tuple.Create(path, temporal));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
@@ -90,7 +91,8 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
public ValueTask SetupDMApiTests(bool includingRoot, CancellationToken cancellationToken)
|
||||
{
|
||||
// just use an I/O manager here
|
||||
var ioManager = new DefaultIOManager();
|
||||
var ioManager = new DefaultIOManager(
|
||||
new FileSystem());
|
||||
|
||||
async ValueTask TestStaticFileAndDir()
|
||||
{
|
||||
@@ -127,7 +129,7 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
Path = $"/EventScripts/{scriptName}"
|
||||
};
|
||||
|
||||
await using var readStream = ioManager.GetFileStream($"../../../../DMAPI/{(basic ? "BasicOperation" : "LongRunning")}/{scriptName}", false);
|
||||
await using var readStream = ioManager.CreateAsyncReadStream($"../../../../DMAPI/{(basic ? "BasicOperation" : "LongRunning")}/{scriptName}", true, false);
|
||||
await configurationClient.Write(
|
||||
resourcingScript,
|
||||
readStream,
|
||||
|
||||
@@ -41,13 +41,13 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
EngineVersion testVersion;
|
||||
readonly EngineType testEngine = engineType;
|
||||
|
||||
public Task Run(CancellationToken cancellationToken, out Task firstInstall)
|
||||
public Task Run(ILogger logger, CancellationToken cancellationToken, out Task firstInstall)
|
||||
{
|
||||
firstInstall = RunPartOne(cancellationToken);
|
||||
firstInstall = RunPartOne(logger, cancellationToken);
|
||||
return RunContinued(firstInstall, cancellationToken);
|
||||
}
|
||||
|
||||
public static async ValueTask<EngineVersion> GetEdgeVersion(EngineType engineType, IFileDownloader fileDownloader, CancellationToken cancellationToken)
|
||||
public static async ValueTask<EngineVersion> GetEdgeVersion(EngineType engineType, ILogger logger, IFileDownloader fileDownloader, CancellationToken cancellationToken)
|
||||
{
|
||||
var edgeVersion = edgeVersions[engineType];
|
||||
|
||||
@@ -57,26 +57,7 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
EngineVersion engineVersion;
|
||||
if (engineType == EngineType.Byond)
|
||||
{
|
||||
await using var provider = fileDownloader.DownloadFile(new Uri("https://www.byond.com/download/version.txt"), null);
|
||||
var stream = await provider.GetResult(cancellationToken);
|
||||
using var reader = new StreamReader(stream, Encoding.UTF8, false, -1, true);
|
||||
var text = await reader.ReadToEndAsync(cancellationToken);
|
||||
var splits = text.Split('\n', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
var targetVersion = splits.Last();
|
||||
|
||||
var badVersionMap = new PlatformIdentifier().IsWindows
|
||||
? []
|
||||
// linux map also needs updating in CI
|
||||
: new Dictionary<string, string>()
|
||||
{
|
||||
{ "515.1612", "515.1611" }
|
||||
};
|
||||
|
||||
badVersionMap.Add("515.1617", "515.1616");
|
||||
|
||||
if (badVersionMap.TryGetValue(targetVersion, out var remappedVersion))
|
||||
targetVersion = remappedVersion;
|
||||
var targetVersion = await TestingUtils.GetByondEdgeVersion(logger, fileDownloader, cancellationToken);
|
||||
|
||||
Assert.IsTrue(EngineVersion.TryParse(targetVersion, out engineVersion), $"Bad version: {targetVersion}");
|
||||
}
|
||||
@@ -112,9 +93,9 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
return edgeVersions[engineType] = engineVersion;
|
||||
}
|
||||
|
||||
async Task RunPartOne(CancellationToken cancellationToken)
|
||||
async Task RunPartOne(ILogger logger, CancellationToken cancellationToken)
|
||||
{
|
||||
testVersion = await GetEdgeVersion(testEngine, fileDownloader, cancellationToken);
|
||||
testVersion = await GetEdgeVersion(testEngine, logger, fileDownloader, cancellationToken);
|
||||
await TestNoVersion(cancellationToken);
|
||||
await TestInstallNullVersion(cancellationToken);
|
||||
await TestInstallStable(cancellationToken);
|
||||
@@ -284,8 +265,11 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
|
||||
async Task TestCustomInstalls(CancellationToken cancellationToken)
|
||||
{
|
||||
var generalConfigOptionsMock = new Mock<IOptions<GeneralConfiguration>>();
|
||||
generalConfigOptionsMock.SetupGet(x => x.Value).Returns(new GeneralConfiguration());
|
||||
var generalConfigOptionsMock = new Mock<IOptionsMonitor<GeneralConfiguration>>();
|
||||
generalConfigOptionsMock.SetupGet(x => x.CurrentValue).Returns(new GeneralConfiguration
|
||||
{
|
||||
ByondZipDownloadTemplate = TestingUtils.ByondZipDownloadTemplate,
|
||||
});
|
||||
var sessionConfigOptionsMock = new Mock<IOptions<SessionConfiguration>>();
|
||||
sessionConfigOptionsMock.SetupGet(x => x.Value).Returns(new SessionConfiguration());
|
||||
|
||||
@@ -303,6 +287,7 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
Mock.Of<IPostWriteHandler>(),
|
||||
Mock.Of<IIOManager>(),
|
||||
fileDownloader,
|
||||
generalConfigOptionsMock.Object,
|
||||
Mock.Of<ILogger<PosixByondInstaller>>());
|
||||
|
||||
using var windowsByondInstaller = byondInstaller as WindowsByondInstaller;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -36,6 +37,7 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
readonly ushort serverPort = serverPort;
|
||||
|
||||
public async Task RunTests(
|
||||
ILogger logger,
|
||||
IInstanceClient instanceClient,
|
||||
ushort dmPort,
|
||||
ushort ddPort,
|
||||
@@ -44,14 +46,14 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
bool usingBasicWatchdog,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var testVersion = await EngineTest.GetEdgeVersion(EngineType.Byond, fileDownloader, cancellationToken);
|
||||
var testVersion = await EngineTest.GetEdgeVersion(EngineType.Byond, logger, fileDownloader, cancellationToken);
|
||||
await using var engineTest = new EngineTest(instanceClient.Engine, instanceClient.Jobs, fileDownloader, instanceClient.Metadata, testVersion.Engine.Value);
|
||||
await using var chatTest = new ChatTest(instanceClient.ChatBots, instanceManagerClient, instanceClient.Jobs, instanceClient.Metadata);
|
||||
var configTest = new ConfigurationTest(instanceClient.Configuration, instanceClient.Metadata);
|
||||
await using var repoTest = new RepositoryTest(instanceClient, instanceClient.Repository, instanceClient.Jobs);
|
||||
await using var dmTest = new DeploymentTest(instanceClient, instanceClient.Jobs, dmPort, ddPort, lowPrioDeployment, testVersion);
|
||||
|
||||
var byondTask = engineTest.Run(cancellationToken, out var firstInstall);
|
||||
var byondTask = engineTest.Run(logger, cancellationToken, out var firstInstall);
|
||||
var chatTask = chatTest.RunPreWatchdog(cancellationToken);
|
||||
|
||||
var repoLongJob = await repoTest.RunLongClone(cancellationToken);
|
||||
@@ -89,25 +91,26 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
Uri openDreamUrl,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var ioManager = new DefaultIOManager();
|
||||
var ioManager = new DefaultIOManager(new FileSystem());
|
||||
var odRepoDir = ioManager.ConcatPath(
|
||||
Environment.GetFolderPath(
|
||||
Environment.SpecialFolder.LocalApplicationData,
|
||||
Environment.SpecialFolderOption.DoNotVerify),
|
||||
new AssemblyInformationProvider().VersionPrefix,
|
||||
"OpenDreamRepository");
|
||||
var odRepoIoManager = new ResolvingIOManager(ioManager, odRepoDir);
|
||||
var odRepoIoManager = ioManager.CreateResolverForSubdirectory(odRepoDir);
|
||||
|
||||
var mockOptions = new Mock<IOptions<GeneralConfiguration>>();
|
||||
var mockOptionsMonitor = new Mock<IOptionsMonitor<GeneralConfiguration>>();
|
||||
var genConfig = new GeneralConfiguration
|
||||
{
|
||||
OpenDreamGitUrl = openDreamUrl,
|
||||
ByondZipDownloadTemplate = TestingUtils.ByondZipDownloadTemplate,
|
||||
};
|
||||
mockOptions.SetupGet(x => x.Value).Returns(genConfig);
|
||||
mockOptionsMonitor.SetupGet(x => x.CurrentValue).Returns(genConfig);
|
||||
IEngineInstaller byondInstaller =
|
||||
compatVersion.Engine == EngineType.OpenDream
|
||||
? new OpenDreamInstaller(
|
||||
new DefaultIOManager(),
|
||||
ioManager,
|
||||
Mock.Of<ILogger<OpenDreamInstaller>>(),
|
||||
new PlatformIdentifier(),
|
||||
Mock.Of<IProcessExecutor>(),
|
||||
@@ -124,20 +127,21 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
genConfig),
|
||||
Mock.Of<IAsyncDelayer>(),
|
||||
Mock.Of<IAbstractHttpClientFactory>(),
|
||||
mockOptions.Object,
|
||||
Options.Create(genConfig),
|
||||
Options.Create(new SessionConfiguration()))
|
||||
: new PlatformIdentifier().IsWindows
|
||||
? new WindowsByondInstaller(
|
||||
Mock.Of<IProcessExecutor>(),
|
||||
Mock.Of<IIOManager>(),
|
||||
fileDownloader,
|
||||
Options.Create(genConfig),
|
||||
mockOptionsMonitor.Object,
|
||||
Options.Create(new SessionConfiguration()),
|
||||
Mock.Of<ILogger<WindowsByondInstaller>>())
|
||||
: new PosixByondInstaller(
|
||||
Mock.Of<IPostWriteHandler>(),
|
||||
Mock.Of<IIOManager>(),
|
||||
fileDownloader,
|
||||
mockOptionsMonitor.Object,
|
||||
Mock.Of<ILogger<PosixByondInstaller>>());
|
||||
|
||||
using var windowsByondInstaller = byondInstaller as WindowsByondInstaller;
|
||||
|
||||
@@ -14,6 +14,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
@@ -809,7 +810,7 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
|
||||
var features = new PosixProcessFeatures(
|
||||
new Lazy<IProcessExecutor>(Mock.Of<IProcessExecutor>()),
|
||||
new DefaultIOManager(),
|
||||
new DefaultIOManager(new FileSystem()),
|
||||
Mock.Of<ILogger<PosixProcessFeatures>>());
|
||||
|
||||
features.SuspendProcess(proc);
|
||||
@@ -876,7 +877,7 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
executor = new ProcessExecutor(
|
||||
RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||
? new WindowsProcessFeatures(Mock.Of<ILogger<WindowsProcessFeatures>>())
|
||||
: new PosixProcessFeatures(new Lazy<IProcessExecutor>(() => executor), new DefaultIOManager(), Mock.Of<ILogger<PosixProcessFeatures>>()),
|
||||
: new PosixProcessFeatures(new Lazy<IProcessExecutor>(() => executor), new DefaultIOManager(new FileSystem()), Mock.Of<ILogger<PosixProcessFeatures>>()),
|
||||
Mock.Of<IIOManager>(),
|
||||
Mock.Of<ILogger<ProcessExecutor>>(),
|
||||
LoggerFactory.Create(x => { }));
|
||||
|
||||
@@ -6,6 +6,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -43,7 +44,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
for (int i = 0; i < 5; ++i)
|
||||
try
|
||||
{
|
||||
new DefaultIOManager().DeleteDirectory(directory, default).GetAwaiter().GetResult();
|
||||
new DefaultIOManager(new FileSystem()).DeleteDirectory(directory, default).GetAwaiter().GetResult();
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -156,7 +157,8 @@ namespace Tgstation.Server.Tests.Live
|
||||
$"Security:TokenExpiryMinutes=120", // timeouts are useless for us
|
||||
$"General:OpenDreamSuppressInstallOutput={TestingUtils.RunningInGitHubActions}",
|
||||
"Telemetry:DisableVersionReporting=true",
|
||||
$"General:PrometheusPort={port}"
|
||||
$"General:PrometheusPort={port}",
|
||||
$"General:ByondZipDownloadTemplate={TestingUtils.ByondZipDownloadTemplate}"
|
||||
};
|
||||
|
||||
if (MultiServerClient.UseGraphQL)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
using System.Net;
|
||||
@@ -415,7 +416,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
var gitHubToken = Environment.GetEnvironmentVariable("TGS_TEST_GITHUB_TOKEN");
|
||||
if (String.IsNullOrWhiteSpace(gitHubToken))
|
||||
gitHubToken = null;
|
||||
await new Host.IO.DefaultIOManager().DeleteDirectory(server.UpdatePath, cancellationToken);
|
||||
await new Host.IO.DefaultIOManager(new FileSystem()).DeleteDirectory(server.UpdatePath, cancellationToken);
|
||||
serverTask = server.Run(cancellationToken).AsTask();
|
||||
|
||||
await using (var adminClient = await CreateAdminClient(server.ApiUrl, cancellationToken))
|
||||
@@ -1135,7 +1136,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
ApiValidationSecurityLevel = DreamDaemonSecurity.Trusted,
|
||||
}, cancellationToken);
|
||||
|
||||
var ioManager = new Host.IO.DefaultIOManager();
|
||||
var ioManager = new Host.IO.DefaultIOManager(new FileSystem());
|
||||
var repoPath = ioManager.ConcatPath(instance.Path, "Repository");
|
||||
await using var jobsTest = new JobsRequiredTest(instanceClient.Jobs);
|
||||
var postWriteHandler = (Host.IO.IPostWriteHandler)(new PlatformIdentifier().IsWindows
|
||||
@@ -1421,6 +1422,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
await Task.Yield();
|
||||
|
||||
InstanceManager GetInstanceManager() => ((Host.Server)server.RealServer).Host.Services.GetRequiredService<InstanceManager>();
|
||||
ILogger GetLogger() => ((Host.Server)server.RealServer).Host.Services.GetRequiredService<ILogger<TestLiveServer>>();
|
||||
|
||||
// main run
|
||||
var serverTask = server.Run(cancellationToken).AsTask();
|
||||
@@ -1590,7 +1592,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
var testSerialized = TestingUtils.RunningInGitHubActions; // they only have 2 cores, can't handle intense parallelization
|
||||
async Task ODCompatTests()
|
||||
{
|
||||
var edgeODVersionTask = EngineTest.GetEdgeVersion(EngineType.OpenDream, fileDownloader, cancellationToken);
|
||||
var edgeODVersionTask = EngineTest.GetEdgeVersion(EngineType.OpenDream, GetLogger(), fileDownloader, cancellationToken);
|
||||
|
||||
var ex = await Assert.ThrowsExceptionAsync<JobException>(
|
||||
() => InstanceTest.DownloadEngineVersion(
|
||||
@@ -1625,6 +1627,16 @@ namespace Tgstation.Server.Tests.Live
|
||||
if (openDreamOnly)
|
||||
return;
|
||||
|
||||
var windowsMinCompat = new Version(510, 1346);
|
||||
var linuxMinCompat = new Version(512, 1451); // http://www.byond.com/forum/?forum=5&command=search&scope=local&text=resolved%3a512.1451
|
||||
await CachingFileDownloader.InitializeByondVersion(
|
||||
GetLogger(),
|
||||
new PlatformIdentifier().IsWindows
|
||||
? windowsMinCompat
|
||||
: linuxMinCompat,
|
||||
new PlatformIdentifier().IsWindows,
|
||||
cancellationToken);
|
||||
|
||||
var compatTests = FailFast(
|
||||
instanceTest
|
||||
.RunCompatTests(
|
||||
@@ -1632,8 +1644,8 @@ namespace Tgstation.Server.Tests.Live
|
||||
{
|
||||
Engine = EngineType.Byond,
|
||||
Version = new PlatformIdentifier().IsWindows
|
||||
? new Version(510, 1346)
|
||||
: new Version(512, 1451) // http://www.byond.com/forum/?forum=5&command=search&scope=local&text=resolved%3a512.1451
|
||||
? windowsMinCompat
|
||||
: linuxMinCompat,
|
||||
},
|
||||
server.OpenDreamUrl,
|
||||
firstAdminRestClient.Instances.CreateClient(compatInstance),
|
||||
@@ -1649,6 +1661,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
await FailFast(
|
||||
instanceTest
|
||||
.RunTests(
|
||||
GetLogger(),
|
||||
instanceClient,
|
||||
mainDMPort.Value,
|
||||
mainDDPort.Value,
|
||||
@@ -1881,7 +1894,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
preStartupTime = DateTimeOffset.UtcNow;
|
||||
serverTask = server.Run(cancellationToken).AsTask();
|
||||
long expectedCompileJobId, expectedStaged;
|
||||
var edgeVersion = await EngineTest.GetEdgeVersion(EngineType.Byond, fileDownloader, cancellationToken);
|
||||
var edgeVersion = await EngineTest.GetEdgeVersion(EngineType.Byond, GetLogger(), fileDownloader, cancellationToken);
|
||||
await using (var adminClient = await CreateAdminClient(server.ApiUrl, cancellationToken))
|
||||
{
|
||||
var restAdminClient = adminClient.RestClient;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -58,7 +59,7 @@ namespace Tgstation.Server.Tests
|
||||
}
|
||||
finally
|
||||
{
|
||||
await new DefaultIOManager().DeleteDirectory(
|
||||
await new DefaultIOManager(new FileSystem()).DeleteDirectory(
|
||||
Path.GetDirectoryName(tempPath),
|
||||
CancellationToken.None);
|
||||
}
|
||||
@@ -75,8 +76,7 @@ namespace Tgstation.Server.Tests
|
||||
using var manager = new RepositoryManager(
|
||||
repoFac,
|
||||
commands,
|
||||
new ResolvingIOManager(
|
||||
new DefaultIOManager(),
|
||||
new DefaultIOManager(new FileSystem()).CreateResolverForSubdirectory(
|
||||
tempPath),
|
||||
Mock.Of<IEventConsumer>(),
|
||||
new WindowsPostWriteHandler(),
|
||||
@@ -131,7 +131,7 @@ namespace Tgstation.Server.Tests
|
||||
}
|
||||
finally
|
||||
{
|
||||
await new DefaultIOManager().DeleteDirectory(
|
||||
await new DefaultIOManager(new FileSystem()).DeleteDirectory(
|
||||
Path.GetDirectoryName(tempPath),
|
||||
CancellationToken.None);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -24,7 +25,7 @@ namespace Tgstation.Server.Tests
|
||||
var platformIdentifier = new PlatformIdentifier();
|
||||
var processExecutor = new ProcessExecutor(
|
||||
Mock.Of<IProcessFeatures>(),
|
||||
new DefaultIOManager(),
|
||||
new DefaultIOManager(new FileSystem()),
|
||||
Mock.Of<ILogger<ProcessExecutor>>(),
|
||||
loggerFactory);
|
||||
|
||||
@@ -52,7 +53,7 @@ namespace Tgstation.Server.Tests
|
||||
var platformIdentifier = new PlatformIdentifier();
|
||||
var processExecutor = new ProcessExecutor(
|
||||
Mock.Of<IProcessFeatures>(),
|
||||
new DefaultIOManager(),
|
||||
new DefaultIOManager(new FileSystem()),
|
||||
loggerFactory.CreateLogger<ProcessExecutor>(),
|
||||
loggerFactory);
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ using Tgstation.Server.Host.System;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Tests.Live;
|
||||
using Tgstation.Server.Host.Properties;
|
||||
using System.IO.Abstractions;
|
||||
|
||||
namespace Tgstation.Server.Tests
|
||||
{
|
||||
@@ -103,10 +104,14 @@ namespace Tgstation.Server.Tests
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestCategory("RequiresDatabase")]
|
||||
public async Task TestDDExeByondVersion()
|
||||
{
|
||||
var mockGeneralConfigurationOptions = new Mock<IOptions<GeneralConfiguration>>();
|
||||
mockGeneralConfigurationOptions.SetupGet(x => x.Value).Returns(new GeneralConfiguration());
|
||||
var mockGeneralConfigurationOptions = new Mock<IOptionsMonitor<GeneralConfiguration>>();
|
||||
mockGeneralConfigurationOptions.SetupGet(x => x.CurrentValue).Returns(new GeneralConfiguration
|
||||
{
|
||||
ByondZipDownloadTemplate = TestingUtils.ByondZipDownloadTemplate,
|
||||
});
|
||||
var mockSessionConfigurationOptions = new Mock<IOptions<SessionConfiguration>>();
|
||||
mockSessionConfigurationOptions.SetupGet(x => x.Value).Returns(new SessionConfiguration());
|
||||
|
||||
@@ -165,12 +170,14 @@ namespace Tgstation.Server.Tests
|
||||
static Version MapThreadsVersion() => (Version)typeof(ByondInstallerBase).GetField("MapThreadsVersion", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null) ?? throw new InvalidOperationException("Couldn't find MapThreadsVersion");
|
||||
|
||||
[TestMethod]
|
||||
[TestCategory("RequiresDatabase")]
|
||||
public async Task TestMapThreadsByondVersion()
|
||||
{
|
||||
var mockGeneralConfigurationOptions = new Mock<IOptions<GeneralConfiguration>>();
|
||||
mockGeneralConfigurationOptions.SetupGet(x => x.Value).Returns(new GeneralConfiguration
|
||||
var mockGeneralConfigurationOptions = new Mock<IOptionsMonitor<GeneralConfiguration>>();
|
||||
mockGeneralConfigurationOptions.SetupGet(x => x.CurrentValue).Returns(new GeneralConfiguration
|
||||
{
|
||||
SkipAddingByondFirewallException = true,
|
||||
ByondZipDownloadTemplate = TestingUtils.ByondZipDownloadTemplate,
|
||||
});
|
||||
var mockSessionConfigurationOptions = new Mock<IOptions<SessionConfiguration>>();
|
||||
mockSessionConfigurationOptions.SetupGet(x => x.Value).Returns(new SessionConfiguration());
|
||||
@@ -207,8 +214,9 @@ namespace Tgstation.Server.Tests
|
||||
loggerFactory.CreateLogger<WindowsByondInstaller>())
|
||||
: new PosixByondInstaller(
|
||||
new PosixPostWriteHandler(loggerFactory.CreateLogger<PosixPostWriteHandler>()),
|
||||
new DefaultIOManager(),
|
||||
new DefaultIOManager(new FileSystem()),
|
||||
fileDownloader,
|
||||
mockGeneralConfigurationOptions.Object,
|
||||
loggerFactory.CreateLogger<PosixByondInstaller>());
|
||||
using var disposable = byondInstaller as IDisposable;
|
||||
|
||||
@@ -217,13 +225,13 @@ namespace Tgstation.Server.Tests
|
||||
? new WindowsProcessFeatures(Mock.Of<ILogger<WindowsProcessFeatures>>())
|
||||
: new PosixProcessFeatures(
|
||||
new Lazy<IProcessExecutor>(() => null),
|
||||
new DefaultIOManager(),
|
||||
new DefaultIOManager(new FileSystem()),
|
||||
loggerFactory.CreateLogger<PosixProcessFeatures>()),
|
||||
Mock.Of<IIOManager>(),
|
||||
loggerFactory.CreateLogger<ProcessExecutor>(),
|
||||
loggerFactory);
|
||||
|
||||
var ioManager = new DefaultIOManager();
|
||||
var ioManager = new DefaultIOManager(new FileSystem());
|
||||
var tempPath = ioManager.ConcatPath(LiveTestingServer.BaseDirectory, "mapthreads");
|
||||
await ioManager.CreateDirectory(tempPath, default);
|
||||
try
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using Moq;
|
||||
|
||||
using Tgstation.Server.Host.Components.Engine;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Tests
|
||||
{
|
||||
@@ -67,8 +75,91 @@ namespace Tgstation.Server.Tests
|
||||
}
|
||||
finally
|
||||
{
|
||||
await new DefaultIOManager().DeleteDirectory(tempFolder, cancellationToken);
|
||||
await new DefaultIOManager(new FileSystem()).DeleteDirectory(tempFolder, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
static string byondZipDownloadTemplate;
|
||||
|
||||
public static string ByondZipDownloadTemplate
|
||||
{
|
||||
get
|
||||
{
|
||||
if (byondZipDownloadTemplate == null)
|
||||
{
|
||||
var envvar = Environment.GetEnvironmentVariable("TGS_TEST_BYOND_ZIP_DOWNLOAD_TEMPLATE");
|
||||
if (envvar != null)
|
||||
byondZipDownloadTemplate = envvar;
|
||||
else
|
||||
byondZipDownloadTemplate = GeneralConfiguration.DefaultByondZipDownloadTemplate;
|
||||
}
|
||||
|
||||
return byondZipDownloadTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
static string edgeVersion = null;
|
||||
public static async ValueTask<string> GetByondEdgeVersion(ILogger logger, IFileDownloader fileDownloader, CancellationToken cancellationToken)
|
||||
{
|
||||
if (edgeVersion != null)
|
||||
return edgeVersion;
|
||||
|
||||
async ValueTask<string> GetVersionFromResponse(string versionTxt)
|
||||
{
|
||||
await using var provider = fileDownloader.DownloadFile(new Uri(versionTxt), null);
|
||||
var stream = await provider.GetResult(cancellationToken);
|
||||
using var reader = new StreamReader(stream, Encoding.UTF8, false, -1, true);
|
||||
var text = await reader.ReadToEndAsync(cancellationToken);
|
||||
var splits = text.Split('\n', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
var targetVersion = splits.Last();
|
||||
|
||||
var badVersionMap = new PlatformIdentifier().IsWindows
|
||||
? []
|
||||
// linux map also needs updating in CI
|
||||
: new Dictionary<string, string>()
|
||||
{
|
||||
{ "515.1612", "515.1611" }
|
||||
};
|
||||
|
||||
badVersionMap.Add("515.1617", "515.1616");
|
||||
|
||||
if (badVersionMap.TryGetValue(targetVersion, out var remappedVersion))
|
||||
targetVersion = remappedVersion;
|
||||
|
||||
return targetVersion;
|
||||
}
|
||||
|
||||
var mirroredVersionTxt = Environment.GetEnvironmentVariable("TGS_TEST_BYOND_MIRROR_VERSION_TXT");
|
||||
try
|
||||
{
|
||||
// always check byond.com first for latest up-to-date, mirror should ALWAYS have stable versions
|
||||
// except byond hates all CI runners now
|
||||
const string DefaultMirror = "https://spacestation13.github.io/byond-builds/version.txt";
|
||||
edgeVersion = await GetVersionFromResponse(DefaultMirror);
|
||||
|
||||
logger.LogInformation("Downloading edge version from SS13 mirror {edge}", edgeVersion);
|
||||
|
||||
// if we got the result from byond.com, make sure the cache grabs the zip from there as well
|
||||
await CachingFileDownloader.InitializeByondVersion(
|
||||
logger,
|
||||
Version.Parse(edgeVersion),
|
||||
new PlatformIdentifier().IsWindows,
|
||||
cancellationToken,
|
||||
"https://spacestation13.github.io/byond-builds/${Major}/${Major}.${Minor}_byond${Linux:_linux}.zip");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning(ex, "Cannot download zip from byond.com!");
|
||||
if (ByondZipDownloadTemplate == GeneralConfiguration.DefaultByondZipDownloadTemplate || mirroredVersionTxt == null)
|
||||
throw;
|
||||
|
||||
// fall back to the mirrored version.txt
|
||||
await using var provider = fileDownloader.DownloadFile(new Uri(mirroredVersionTxt), null);
|
||||
edgeVersion = await GetVersionFromResponse(mirroredVersionTxt);
|
||||
}
|
||||
|
||||
return edgeVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
<TargetFramework>$(TgsFrameworkVersion)</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Tgstation.Server.Client.GraphQL\Tgstation.Server.Client.GraphQL.csproj" />
|
||||
<ProjectReference Include="..\..\src\Tgstation.Server.Host.Watchdog\Tgstation.Server.Host.Watchdog.csproj" />
|
||||
|
||||
Reference in New Issue
Block a user