mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-07-16 18:43:43 +01:00
Merge branch 'dev' of https://github.com/tgstation/tgstation-server into 1458-AddMapThreadsParam
This commit is contained in:
@@ -4,8 +4,7 @@
|
||||
name: "Auto-Approve Dominion's PRs"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
pull_request_target:
|
||||
branches:
|
||||
- dev
|
||||
- master
|
||||
@@ -22,4 +21,5 @@ jobs:
|
||||
--url https://api.github.com/repos/${{github.repository}}/pulls/${{github.event.number}}/reviews \
|
||||
--header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \
|
||||
--header 'content-type: application/json' \
|
||||
-d '{"event":"APPROVE"}'
|
||||
-d '{"event":"APPROVE"}' \
|
||||
--fail
|
||||
|
||||
+421
-256
@@ -9,6 +9,11 @@ on:
|
||||
branches:
|
||||
- dev
|
||||
- master
|
||||
pull_request_target:
|
||||
types: [opened, reopened, labeled, synchronize]
|
||||
branches:
|
||||
- dev
|
||||
- master
|
||||
|
||||
env:
|
||||
TGS_DOTNET_VERSION: 6.0.x
|
||||
@@ -16,12 +21,48 @@ env:
|
||||
TGS_TEST_DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
TGS_TEST_IRC_CHANNEL: ${{ secrets.IRC_CHANNEL }}
|
||||
TGS_TEST_IRC_CONNECTION_STRING: ${{ secrets.IRC_CONNECTION_STRING }}
|
||||
TGS_TEST_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TGS_TEST_GITHUB_TOKEN: ${{ secrets.LIVE_TESTS_TOKEN }}
|
||||
TGS_RELEASE_NOTES_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
|
||||
|
||||
concurrency:
|
||||
group: "ci-${{ github.event.number || github.head_ref }}-${{ github.event_name }}"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
security-checkpoint:
|
||||
name: Check CI Clearance
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.id != github.event.pull_request.base.repo.id && github.event.pull_request.state == 'open'
|
||||
steps:
|
||||
- name: Comment on new Fork PR
|
||||
if: github.event.action == 'opened' && !contains(github.event.pull_request.labels.*.name, 'CI Cleared')
|
||||
uses: thollander/actions-comment-pull-request@v2
|
||||
with:
|
||||
message: Thank you for contributing to tgstation-server! As this pull request is from a fork, we can't allow the CI actions which require repository secrets to run on it without approval. After a brief review to make sure you're not misusing those secrets, a maintainer will add the `CI Cleared` label to allow the CI suite to run. Maintainers, please note that any changes to workflow files will not be reflected in the CI run.
|
||||
|
||||
- name: "Remove Stale 'CI Cleared' Label"
|
||||
if: github.event.action == 'synchronize' || github.event.action == 'reopened'
|
||||
uses: actions-ecosystem/action-remove-labels@v1
|
||||
with:
|
||||
labels: CI Cleared
|
||||
|
||||
- name: Fail Clearance Check if PR has Unlabeled new Commits from Fork
|
||||
if: (github.event.action == 'synchronize' || github.event.action == 'reopened') || ((github.event.action == 'opened' || github.event.action == 'labeled') && !contains(github.event.pull_request.labels.*.name, 'CI Cleared'))
|
||||
run: exit 1
|
||||
|
||||
start-ci-run-gate:
|
||||
name: Start CI Run Gate
|
||||
needs: security-checkpoint
|
||||
runs-on: ubuntu-latest
|
||||
if: "!(cancelled() || failure()) && (needs.security-checkpoint.result == 'success' || (needs.security-checkpoint.result == 'skipped' && (github.event_name == 'push' || (github.event.pull_request.head.repo.id == github.event.pull_request.base.repo.id && github.event_name != 'pull_request_target'))))"
|
||||
steps:
|
||||
- name: GitHub Requires at Least One Step for a Job
|
||||
run: exit 0
|
||||
|
||||
analyze:
|
||||
name: Code Scanning
|
||||
needs: start-ci-run-gate
|
||||
if: "!(cancelled() || failure()) && needs.start-ci-run-gate.result == 'success'"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Node 12.X
|
||||
@@ -32,11 +73,19 @@ jobs:
|
||||
- name: Upgrade NPM
|
||||
run: npm install -g npm
|
||||
|
||||
- name: Checkout repository
|
||||
- name: Checkout (Branch Push)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name == 'push'
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Checkout (PR Merge)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name != 'push'
|
||||
with:
|
||||
fetch-depth: 2
|
||||
ref: "refs/pull/${{ github.event.number }}/merge"
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
@@ -50,6 +99,8 @@ jobs:
|
||||
|
||||
dmapi-build:
|
||||
name: Build DMAPI
|
||||
needs: start-ci-run-gate
|
||||
if: "!(cancelled() || failure()) && needs.start-ci-run-gate.result == 'success'"
|
||||
env:
|
||||
BYOND_MAJOR: 515
|
||||
BYOND_MINOR: 1592
|
||||
@@ -73,8 +124,15 @@ jobs:
|
||||
make here
|
||||
exit 0
|
||||
|
||||
- name: Checkout
|
||||
- name: Checkout (Branch Push)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name == 'push'
|
||||
|
||||
- name: Checkout (PR Merge)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name != 'push'
|
||||
with:
|
||||
ref: "refs/pull/${{ github.event.number }}/merge"
|
||||
|
||||
- name: Build DMAPI Test Project
|
||||
run: |
|
||||
@@ -99,59 +157,76 @@ jobs:
|
||||
dox-build:
|
||||
name: Build Doxygen Site
|
||||
runs-on: ubuntu-latest
|
||||
needs: start-ci-run-gate
|
||||
if: "!(cancelled() || failure()) && needs.start-ci-run-gate.result == 'success'"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Checkout (Branch Push)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name == 'push'
|
||||
|
||||
- name: Patch Doxyfile
|
||||
run: |
|
||||
VERSION=$(cat "build/Version.props" | grep -oPm1 "(?<=<TgsCoreVersion>)[^<]+")
|
||||
echo -e "\nPROJECT_NUMBER = $VERSION\nINPUT = .\nOUTPUT_DIRECTORY = ./doxout\nPROJECT_LOGO = ./build/tgs.ico\nHAVE_DOT=YES" >> "docs/Doxyfile"
|
||||
- name: Checkout (PR Merge)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name != 'push'
|
||||
with:
|
||||
ref: "refs/pull/${{ github.event.number }}/merge"
|
||||
|
||||
- name: Doxygen Build
|
||||
uses: mattnotmitt/doxygen-action@v1
|
||||
with:
|
||||
doxyfile-path: 'docs/Doxyfile'
|
||||
- name: Patch Doxyfile
|
||||
run: |
|
||||
VERSION=$(cat "build/Version.props" | grep -oPm1 "(?<=<TgsCoreVersion>)[^<]+")
|
||||
echo -e "\nPROJECT_NUMBER = $VERSION\nINPUT = .\nOUTPUT_DIRECTORY = ./doxout\nPROJECT_LOGO = ./build/tgs.ico\nHAVE_DOT=YES" >> "docs/Doxyfile"
|
||||
|
||||
- name: gh-pages push
|
||||
if: github.event_name == 'push' && github.event.ref == 'refs/heads/dev'
|
||||
run: |
|
||||
git clone -b gh-pages --single-branch "https://git@github.com/tgstation/tgstation-server" $HOME/tgsdox
|
||||
pushd $HOME/tgsdox
|
||||
rm -r *
|
||||
popd
|
||||
echo ./doxout/* | xargs -n 10 sudo mv -t $HOME/tgsdox
|
||||
cd $HOME/tgsdox
|
||||
git config --global push.default simple
|
||||
git config user.name "tgstation-server"
|
||||
git config user.email "tgstation-server@tgstation13.org"
|
||||
echo '# THIS BRANCH IS AUTO GENERATED BY GITHUB ACTIONS' > README.md
|
||||
- name: Doxygen Build
|
||||
uses: mattnotmitt/doxygen-action@v1
|
||||
with:
|
||||
doxyfile-path: 'docs/Doxyfile'
|
||||
|
||||
# Need to create a .nojekyll file to allow filenames starting with an underscore
|
||||
# to be seen on the gh-pages site. Therefore creating an empty .nojekyll file.
|
||||
echo "" > .nojekyll
|
||||
echo "Adding files..."
|
||||
git add --all
|
||||
echo "Committing..."
|
||||
git diff-index --quiet HEAD || git commit -m "Deploy code docs to GitHub Pages for workflow run ${{ github.run_number }}" -m "Commit: ${{ github.event.head_commit.id }}"
|
||||
echo "Pushing..."
|
||||
git push -f "https://${{ secrets.DEV_PUSH_TOKEN }}@github.com/tgstation/tgstation-server" 2>&1
|
||||
- name: gh-pages push
|
||||
if: github.event_name == 'push' && github.event.ref == 'refs/heads/dev'
|
||||
run: |
|
||||
git clone -b gh-pages --single-branch "https://git@github.com/tgstation/tgstation-server" $HOME/tgsdox
|
||||
pushd $HOME/tgsdox
|
||||
rm -r *
|
||||
popd
|
||||
echo ./doxout/* | xargs -n 10 sudo mv -t $HOME/tgsdox
|
||||
cd $HOME/tgsdox
|
||||
git config --global push.default simple
|
||||
git config user.name "tgstation-server"
|
||||
git config user.email "tgstation-server@tgstation13.org"
|
||||
echo '# THIS BRANCH IS AUTO GENERATED BY GITHUB ACTIONS' > README.md
|
||||
|
||||
# Need to create a .nojekyll file to allow filenames starting with an underscore
|
||||
# to be seen on the gh-pages site. Therefore creating an empty .nojekyll file.
|
||||
echo "" > .nojekyll
|
||||
echo "Adding files..."
|
||||
git add --all
|
||||
echo "Committing..."
|
||||
git diff-index --quiet HEAD || git commit -m "Deploy code docs to GitHub Pages for workflow run ${{ github.run_number }}" -m "Commit: ${{ github.event.head_commit.id }}"
|
||||
echo "Pushing..."
|
||||
git push -f "https://${{ secrets.DEV_PUSH_TOKEN }}@github.com/tgstation/tgstation-server" 2>&1
|
||||
|
||||
docker-build:
|
||||
name: Build Docker Image
|
||||
runs-on: ubuntu-latest
|
||||
needs: start-ci-run-gate
|
||||
if: "!(cancelled() || failure()) && needs.start-ci-run-gate.result == 'success'"
|
||||
steps:
|
||||
- name: Checkout
|
||||
- name: Checkout (Branch Push)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name == 'push'
|
||||
|
||||
- name: Upgrade NPM
|
||||
run: sudo npm install -g npm
|
||||
- name: Checkout (PR Merge)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name != 'push'
|
||||
with:
|
||||
ref: "refs/pull/${{ github.event.number }}/merge"
|
||||
|
||||
- name: Build Docker Image
|
||||
run: docker build . -f build/Dockerfile
|
||||
|
||||
linux-unit-tests:
|
||||
name: Linux Tests
|
||||
needs: start-ci-run-gate
|
||||
if: "!(cancelled() || failure()) && needs.start-ci-run-gate.result == 'success'"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -163,8 +238,15 @@ jobs:
|
||||
with:
|
||||
dotnet-version: ${{ env.TGS_DOTNET_VERSION }}
|
||||
|
||||
- name: Checkout
|
||||
- name: Checkout (Branch Push)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name == 'push'
|
||||
|
||||
- name: Checkout (PR Merge)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name != 'push'
|
||||
with:
|
||||
ref: "refs/pull/${{ github.event.number }}/merge"
|
||||
|
||||
- name: Upgrade NPM
|
||||
run: sudo npm install -g npm
|
||||
@@ -183,6 +265,8 @@ jobs:
|
||||
|
||||
windows-unit-tests:
|
||||
name: Windows Tests
|
||||
needs: start-ci-run-gate
|
||||
if: "!(cancelled() || failure()) && needs.start-ci-run-gate.result == 'success'"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -194,8 +278,15 @@ jobs:
|
||||
with:
|
||||
dotnet-version: ${{ env.TGS_DOTNET_VERSION }}
|
||||
|
||||
- name: Checkout
|
||||
- name: Checkout (Branch Push)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name == 'push'
|
||||
|
||||
- name: Checkout (PR Merge)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name != 'push'
|
||||
with:
|
||||
ref: "refs/pull/${{ github.event.number }}/merge"
|
||||
|
||||
- name: Upgrade NPM
|
||||
run: npm install -g npm
|
||||
@@ -215,6 +306,7 @@ jobs:
|
||||
windows-integration-test:
|
||||
name: Windows Live Tests
|
||||
needs: dmapi-build
|
||||
if: "!(cancelled() || failure()) && needs.dmapi-build.result == 'success'"
|
||||
env:
|
||||
TGS_TEST_DATABASE_TYPE: SqlServer
|
||||
TGS_TEST_DUMP_API_SPEC: yes
|
||||
@@ -243,17 +335,24 @@ jobs:
|
||||
TGS_CONNSTRING_VALUE="Server=(localdb)\MSSQLLocalDB;Integrated Security=true;Initial Catalog=TGS_${{ matrix.watchdog-type }}_${{ matrix.configuration }};Application Name=tgstation-server"
|
||||
echo "TGS_TEST_CONNECTION_STRING=$(echo $TGS_CONNSTRING_VALUE)" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout
|
||||
- name: Checkout (Branch Push)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name == 'push'
|
||||
|
||||
- name: Checkout (PR Merge)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name != 'push'
|
||||
with:
|
||||
ref: "refs/pull/${{ github.event.number }}/merge"
|
||||
|
||||
- name: Build
|
||||
run: dotnet build -c ${{ matrix.configuration }} tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj
|
||||
|
||||
- name: Run Integration Test
|
||||
- name: Run Live Tests
|
||||
run: |
|
||||
cd tests/Tgstation.Server.Tests
|
||||
Start-Sleep -Seconds 10
|
||||
dotnet test -c ${{ matrix.configuration }} --no-build --logger GitHubActions --collect:"XPlat Code Coverage" --settings ../../build/coverlet.runsettings --results-directory ../../TestResults
|
||||
dotnet test -c ${{ matrix.configuration }} --no-build --filter FullyQualifiedName~TestLiveServer --logger GitHubActions --collect:"XPlat Code Coverage" --settings ../../build/coverlet.runsettings --results-directory ../../TestResults
|
||||
|
||||
- name: Store Code Coverage
|
||||
uses: actions/upload-artifact@v3
|
||||
@@ -287,6 +386,7 @@ jobs:
|
||||
linux-integration-tests:
|
||||
name: Linux Live Tests
|
||||
needs: dmapi-build
|
||||
if: "!(cancelled() || failure()) && needs.dmapi-build.result == 'success'"
|
||||
services: # We start all dbs here so we can just code the stuff once
|
||||
postgres:
|
||||
image: cyberboss/postgres-max-connections # Fork of _/postgres:latest with max_connections=500 becuase GitHub actions service containers have no way to set command lines. Rebuilds with updates.
|
||||
@@ -380,17 +480,24 @@ jobs:
|
||||
if: ${{ matrix.watchdog-type == 'Basic' }}
|
||||
run: echo "General__UseBasicWatchdog=true" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout
|
||||
- name: Checkout (Branch Push)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name == 'push'
|
||||
|
||||
- name: Checkout (PR Merge)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name != 'push'
|
||||
with:
|
||||
ref: "refs/pull/${{ github.event.number }}/merge"
|
||||
|
||||
- name: Build
|
||||
run: dotnet build -c ${{ matrix.configuration }}NoService tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj
|
||||
|
||||
- name: Run Integration Test
|
||||
- name: Run Live Tests
|
||||
run: |
|
||||
cd tests/Tgstation.Server.Tests
|
||||
sleep 10
|
||||
dotnet test -c ${{ matrix.configuration }}NoService --logger GitHubActions --no-build --collect:"XPlat Code Coverage" --settings ../../build/coverlet.runsettings --results-directory ../../TestResults
|
||||
dotnet test -c ${{ matrix.configuration }}NoService --filter FullyQualifiedName~TestLiveServer --logger GitHubActions --no-build --collect:"XPlat Code Coverage" --settings ../../build/coverlet.runsettings --results-directory ../../TestResults
|
||||
|
||||
- name: Store Code Coverage
|
||||
uses: actions/upload-artifact@v3
|
||||
@@ -431,6 +538,7 @@ jobs:
|
||||
validate-openapi-spec:
|
||||
name: OpenAPI Spec Validation
|
||||
needs: windows-integration-test
|
||||
if: "!(cancelled() || failure()) && needs.windows-integration-test.result == 'success'"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Node 12.X
|
||||
@@ -441,8 +549,15 @@ jobs:
|
||||
- name: Install IBM OpenAPI Validator
|
||||
run: npm i -g ibm-openapi-validator@0.51.3
|
||||
|
||||
- name: Checkout
|
||||
- name: Checkout (Branch Push)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name == 'push'
|
||||
|
||||
- name: Checkout (PR Merge)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name != 'push'
|
||||
with:
|
||||
ref: "refs/pull/${{ github.event.number }}/merge"
|
||||
|
||||
- name: Retrieve OpenAPI Spec
|
||||
uses: actions/download-artifact@v3
|
||||
@@ -456,10 +571,18 @@ jobs:
|
||||
upload-code-coverage:
|
||||
name: Upload Code Coverage
|
||||
needs: [linux-unit-tests, linux-integration-tests, windows-unit-tests, windows-integration-test]
|
||||
if: "!(cancelled() || failure()) && needs.linux-unit-tests.result == 'success' && needs.linux-integration-tests.result == 'success' && needs.windows-unit-tests.result == 'success' && needs.windows-integration-test.result == 'success'"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout for .codecov.yml
|
||||
- name: Checkout (Branch Push)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name == 'push'
|
||||
|
||||
- name: Checkout (PR Merge)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name != 'push'
|
||||
with:
|
||||
ref: "refs/pull/${{ github.event.number }}/merge"
|
||||
|
||||
- name: Retrieve Linux Unit Test Coverage (Debug)
|
||||
uses: actions/download-artifact@v3
|
||||
@@ -538,271 +661,313 @@ jobs:
|
||||
name: Deploy HTTP API
|
||||
needs: [upload-code-coverage, validate-openapi-spec]
|
||||
runs-on: windows-latest
|
||||
if: github.event_name == 'push' && contains(github.event.head_commit.message, '[APIDeploy]')
|
||||
if: "!(cancelled() || failure()) && needs.upload-code-coverage.result == 'success' && needs.validate-openapi-spec.result == 'success' && github.event_name == 'push' && contains(github.event.head_commit.message, '[APIDeploy]')"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Checkout (Branch Push)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name == 'push'
|
||||
|
||||
- name: Parse API version
|
||||
shell: powershell
|
||||
run: |
|
||||
[XML]$versionXML = Get-Content build/Version.props
|
||||
$apiVersion = $versionXML.Project.PropertyGroup.TgsApiVersion
|
||||
echo "TGS_API_VERSION=$apiVersion" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
|
||||
- name: Checkout (PR Merge)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name != 'push'
|
||||
with:
|
||||
ref: "refs/pull/${{ github.event.number }}/merge"
|
||||
|
||||
- name: Retrieve OpenAPI Spec
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: openapi-spec
|
||||
path: swagger
|
||||
- name: Parse API version
|
||||
shell: powershell
|
||||
run: |
|
||||
[XML]$versionXML = Get-Content build/Version.props
|
||||
$apiVersion = $versionXML.Project.PropertyGroup.TgsApiVersion
|
||||
echo "TGS_API_VERSION=$apiVersion" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: juitnow/github-action-create-release@v1
|
||||
id: create_release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: api-v${{ env.TGS_API_VERSION }}
|
||||
release_name: tgstation-server API v${{ env.TGS_API_VERSION }}
|
||||
body: The TGS HTTP API
|
||||
commitish: ${{ github.event.head_commit.id }}
|
||||
- name: Retrieve OpenAPI Spec
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: openapi-spec
|
||||
path: swagger
|
||||
|
||||
- name: Upload OpenApi Spec
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./swagger/swagger.json
|
||||
asset_name: swagger.json
|
||||
asset_content_type: application/json
|
||||
- name: Create GitHub Release
|
||||
uses: juitnow/github-action-create-release@v1
|
||||
id: create_release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
|
||||
with:
|
||||
tag_name: api-v${{ env.TGS_API_VERSION }}
|
||||
release_name: tgstation-server API v${{ env.TGS_API_VERSION }}
|
||||
body: The TGS HTTP API
|
||||
commitish: ${{ github.event.head_commit.id }}
|
||||
|
||||
- name: Upload OpenApi Spec
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./swagger/swagger.json
|
||||
asset_name: swagger.json
|
||||
asset_content_type: application/json
|
||||
|
||||
deploy-dm:
|
||||
name: Deploy DreamMaker API
|
||||
needs: [upload-code-coverage, validate-openapi-spec]
|
||||
runs-on: windows-latest
|
||||
if: github.event_name == 'push' && contains(github.event.head_commit.message, '[DMDeploy]')
|
||||
if: "!(cancelled() || failure()) && needs.upload-code-coverage.result == 'success' && needs.validate-openapi-spec.result == 'success' && github.event_name == 'push' && contains(github.event.head_commit.message, '[DMDeploy]')"
|
||||
steps:
|
||||
- name: Checkout (Branch Push)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name == 'push'
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Checkout (PR Merge)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name != 'push'
|
||||
with:
|
||||
ref: "refs/pull/${{ github.event.number }}/merge"
|
||||
|
||||
- name: Parse DMAPI version
|
||||
shell: powershell
|
||||
run: |
|
||||
[XML]$versionXML = Get-Content build/Version.props
|
||||
$dmVersion = $versionXML.Project.PropertyGroup.TgsDmapiVersion
|
||||
echo "TGS_DM_VERSION=$dmVersion" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
|
||||
- name: Parse DMAPI version
|
||||
shell: powershell
|
||||
run: |
|
||||
[XML]$versionXML = Get-Content build/Version.props
|
||||
$dmVersion = $versionXML.Project.PropertyGroup.TgsDmapiVersion
|
||||
echo "TGS_DM_VERSION=$dmVersion" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
|
||||
|
||||
- name: Zip DMAPI
|
||||
shell: powershell
|
||||
run: |
|
||||
&"C:/Program Files/7-Zip/7z.exe" a DMAPI.zip ./src/DMAPI/* -tzip
|
||||
- name: Zip DMAPI
|
||||
shell: powershell
|
||||
run: |
|
||||
&"C:/Program Files/7-Zip/7z.exe" a DMAPI.zip ./src/DMAPI/* -tzip
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: juitnow/github-action-create-release@v1
|
||||
id: create_release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: dmapi-v${{ env.TGS_DM_VERSION }}
|
||||
release_name: tgstation-server DMAPI v${{ env.TGS_DM_VERSION }}
|
||||
body: The TGS DMAPI \#tgs-dmapi-release
|
||||
commitish: ${{ github.event.head_commit.id }}
|
||||
- name: Create GitHub Release
|
||||
uses: juitnow/github-action-create-release@v1
|
||||
id: create_release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
|
||||
with:
|
||||
tag_name: dmapi-v${{ env.TGS_DM_VERSION }}
|
||||
release_name: tgstation-server DMAPI v${{ env.TGS_DM_VERSION }}
|
||||
body: The TGS DMAPI \#tgs-dmapi-release
|
||||
commitish: ${{ github.event.head_commit.id }}
|
||||
|
||||
- name: Upload DMAPI Artifact
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./DMAPI.zip
|
||||
asset_name: DMAPI.zip
|
||||
asset_content_type: application/zip
|
||||
- name: Upload DMAPI Artifact
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./DMAPI.zip
|
||||
asset_name: DMAPI.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
deploy-client:
|
||||
name: Deploy Nuget Packages
|
||||
needs: [upload-code-coverage, validate-openapi-spec]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' && contains(github.event.head_commit.message, '[NugetDeploy]')
|
||||
if: "!(cancelled() || failure()) && needs.upload-code-coverage.result == 'success' && needs.validate-openapi-spec.result == 'success' && github.event_name == 'push' && contains(github.event.head_commit.message, '[NugetDeploy]')"
|
||||
steps:
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: ${{ env.TGS_DOTNET_VERSION }}
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: ${{ env.TGS_DOTNET_VERSION }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Checkout (Branch Push)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name == 'push'
|
||||
|
||||
- name: Publish API to NuGet
|
||||
uses: alirezanet/publish-nuget@v3.0.0
|
||||
with:
|
||||
PROJECT_FILE_PATH: src/Tgstation.Server.Api/Tgstation.Server.Api.csproj
|
||||
TAG_COMMIT: false
|
||||
INCLUDE_SYMBOLS: true
|
||||
NUGET_KEY: ${{ secrets.NUGET_API_KEY }}
|
||||
- name: Checkout (PR Merge)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name != 'push'
|
||||
with:
|
||||
ref: "refs/pull/${{ github.event.number }}/merge"
|
||||
|
||||
- name: Publish Client to NuGet
|
||||
uses: alirezanet/publish-nuget@v3.0.0
|
||||
with:
|
||||
PROJECT_FILE_PATH: src/Tgstation.Server.Client/Tgstation.Server.Client.csproj
|
||||
TAG_COMMIT: false
|
||||
INCLUDE_SYMBOLS: true
|
||||
NUGET_KEY: ${{ secrets.NUGET_API_KEY }}
|
||||
- name: Publish API to NuGet
|
||||
uses: alirezanet/publish-nuget@v3.0.0
|
||||
with:
|
||||
PROJECT_FILE_PATH: src/Tgstation.Server.Api/Tgstation.Server.Api.csproj
|
||||
TAG_COMMIT: false
|
||||
INCLUDE_SYMBOLS: true
|
||||
NUGET_KEY: ${{ secrets.NUGET_API_KEY }}
|
||||
|
||||
- name: Publish Client to NuGet
|
||||
uses: alirezanet/publish-nuget@v3.0.0
|
||||
with:
|
||||
PROJECT_FILE_PATH: src/Tgstation.Server.Client/Tgstation.Server.Client.csproj
|
||||
TAG_COMMIT: false
|
||||
INCLUDE_SYMBOLS: true
|
||||
NUGET_KEY: ${{ secrets.NUGET_API_KEY }}
|
||||
|
||||
ensure-release:
|
||||
name: Ensure TGS Release is Latest GitHub Release
|
||||
needs: [deploy-dm, deploy-http]
|
||||
runs-on: ubuntu-latest
|
||||
if: always() && github.event_name == 'push' && !contains(github.event.head_commit.message, '[TGSDeploy]') && (contains(github.event.head_commit.message, '[APIDeploy]') || contains(github.event.head_commit.message, '[DMDeploy]'))
|
||||
if: "!(cancelled() || failure()) && (needs.deploy-dm.result == 'success' || needs.deploy-http.result == 'success') && !contains(github.event.head_commit.message, '[TGSDeploy]')"
|
||||
steps:
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: ${{ env.TGS_DOTNET_VERSION }}
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: ${{ env.TGS_DOTNET_VERSION }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Checkout (Branch Push)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name == 'push'
|
||||
|
||||
- name: Run ReleaseNotes with --ensure-release
|
||||
run: dotnet run -c Release --project tools/ReleaseNotes --ensure-release
|
||||
- name: Checkout (PR Merge)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name != 'push'
|
||||
with:
|
||||
ref: "refs/pull/${{ github.event.number }}/merge"
|
||||
|
||||
- name: Run ReleaseNotes with --ensure-release
|
||||
run: dotnet run -c Release --project tools/ReleaseNotes --ensure-release
|
||||
|
||||
deploy-tgs:
|
||||
name: Deploy TGS
|
||||
needs: [upload-code-coverage, validate-openapi-spec]
|
||||
needs: [deploy-dm, deploy-http, upload-code-coverage, validate-openapi-spec]
|
||||
runs-on: windows-latest
|
||||
if: github.event_name == 'push' && github.event.ref == 'refs/heads/master' && contains(github.event.head_commit.message, '[TGSDeploy]')
|
||||
if: "!(cancelled() || failure()) && (needs.upload-code-coverage.result == 'success' || needs.validate-openapi-spec.result == 'success') && github.event_name == 'push' && github.event.ref == 'refs/heads/master' && contains(github.event.head_commit.message, '[TGSDeploy]')"
|
||||
steps:
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: ${{ env.TGS_DOTNET_VERSION }}
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: ${{ env.TGS_DOTNET_VERSION }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Checkout (Branch Push)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name == 'push'
|
||||
|
||||
- name: Parse TGS version
|
||||
shell: powershell
|
||||
run: |
|
||||
[XML]$versionXML = Get-Content build/Version.props
|
||||
$tgsVersion = $versionXML.Project.PropertyGroup.TgsCoreVersion
|
||||
echo "TGS_VERSION=$tgsVersion" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
|
||||
- name: Checkout (PR Merge)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name != 'push'
|
||||
with:
|
||||
ref: "refs/pull/${{ github.event.number }}/merge"
|
||||
|
||||
- name: Retrieve Server Service
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: ServerService
|
||||
path: ServerService
|
||||
- name: Parse TGS version
|
||||
shell: powershell
|
||||
run: |
|
||||
[XML]$versionXML = Get-Content build/Version.props
|
||||
$tgsVersion = $versionXML.Project.PropertyGroup.TgsCoreVersion
|
||||
echo "TGS_VERSION=$tgsVersion" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
|
||||
|
||||
- name: Retrieve Server Console
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: ServerConsole
|
||||
path: ServerConsole
|
||||
- name: Retrieve Server Service
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: ServerService
|
||||
path: ServerService
|
||||
|
||||
- name: Retrieve Server Update Package
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: ServerUpdatePackage
|
||||
path: ServerUpdatePackage
|
||||
- name: Retrieve Server Console
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: ServerConsole
|
||||
path: ServerConsole
|
||||
|
||||
- name: Retrieve OpenAPI Spec
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: openapi-spec
|
||||
path: swagger
|
||||
- name: Retrieve Server Update Package
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: ServerUpdatePackage
|
||||
path: ServerUpdatePackage
|
||||
|
||||
- name: Zip Artifacts
|
||||
shell: powershell
|
||||
run: |
|
||||
&"C:/Program Files/7-Zip/7z.exe" a DMAPI.zip ./src/DMAPI/* -tzip
|
||||
&"C:/Program Files/7-Zip/7z.exe" a ServerService.zip ./ServerService/* -tzip
|
||||
&"C:/Program Files/7-Zip/7z.exe" a ServerConsole.zip ./ServerConsole/* -tzip
|
||||
&"C:/Program Files/7-Zip/7z.exe" a ServerUpdatePackage.zip ./ServerUpdatePackage/* -tzip
|
||||
- name: Retrieve OpenAPI Spec
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: openapi-spec
|
||||
path: swagger
|
||||
|
||||
- name: Generate Release Notes
|
||||
run: dotnet run -c Release --project tools/ReleaseNotes ${{ env.TGS_VERSION }}
|
||||
- name: Zip Artifacts
|
||||
shell: powershell
|
||||
run: |
|
||||
&"C:/Program Files/7-Zip/7z.exe" a DMAPI.zip ./src/DMAPI/* -tzip
|
||||
&"C:/Program Files/7-Zip/7z.exe" a ServerService.zip ./ServerService/* -tzip
|
||||
&"C:/Program Files/7-Zip/7z.exe" a ServerConsole.zip ./ServerConsole/* -tzip
|
||||
&"C:/Program Files/7-Zip/7z.exe" a ServerUpdatePackage.zip ./ServerUpdatePackage/* -tzip
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: actions/create-release@v1
|
||||
id: create_release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
|
||||
with:
|
||||
tag_name: tgstation-server-v${{ env.TGS_VERSION }}
|
||||
release_name: tgstation-server-v${{ env.TGS_VERSION }}
|
||||
body_path: release_notes.md
|
||||
commitish: ${{ github.event.head_commit.id }}
|
||||
- name: Generate Release Notes
|
||||
run: dotnet run -c Release --project tools/ReleaseNotes ${{ env.TGS_VERSION }}
|
||||
|
||||
- name: Upload Server Console Artifact
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./ServerConsole.zip
|
||||
asset_name: ServerConsole.zip
|
||||
asset_content_type: application/zip
|
||||
- name: Create GitHub Release
|
||||
uses: actions/create-release@v1
|
||||
id: create_release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
|
||||
with:
|
||||
tag_name: tgstation-server-v${{ env.TGS_VERSION }}
|
||||
release_name: tgstation-server-v${{ env.TGS_VERSION }}
|
||||
body_path: release_notes.md
|
||||
commitish: ${{ github.event.head_commit.id }}
|
||||
|
||||
- name: Upload Server Service Artifact
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./ServerService.zip
|
||||
asset_name: ServerService.zip
|
||||
asset_content_type: application/zip
|
||||
- name: Upload Server Console Artifact
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./ServerConsole.zip
|
||||
asset_name: ServerConsole.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Upload DMAPI Artifact
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./DMAPI.zip
|
||||
asset_name: DMAPI.zip
|
||||
asset_content_type: application/zip
|
||||
- name: Upload Server Service Artifact
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./ServerService.zip
|
||||
asset_name: ServerService.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Upload OpenApi Spec Artifact
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./swagger/swagger.json
|
||||
asset_name: swagger.json
|
||||
asset_content_type: application/json
|
||||
- name: Upload DMAPI Artifact
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./DMAPI.zip
|
||||
asset_name: DMAPI.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Upload Server Update Package Artifact
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./ServerUpdatePackage.zip
|
||||
asset_name: ServerUpdatePackage.zip
|
||||
asset_content_type: application/zip
|
||||
- name: Upload OpenApi Spec Artifact
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./swagger/swagger.json
|
||||
asset_name: swagger.json
|
||||
asset_content_type: application/json
|
||||
|
||||
- name: Upload Server Update Package Artifact
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./ServerUpdatePackage.zip
|
||||
asset_name: ServerUpdatePackage.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
deploy-docker:
|
||||
name: Deploy TGS (Docker)
|
||||
needs: [deploy-tgs]
|
||||
if: "!(cancelled() || failure()) && needs.deploy-tgs.result == 'success'"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Checkout (Branch Push)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name == 'push'
|
||||
|
||||
- name: Parse TGS version
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y xmlstarlet
|
||||
echo "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)" >> $GITHUB_ENV
|
||||
- name: Checkout (PR Merge)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name != 'push'
|
||||
with:
|
||||
ref: "refs/pull/${{ github.event.number }}/merge"
|
||||
|
||||
- name: Docker Build and Push
|
||||
uses: elgohr/Publish-Docker-Github-Action@v5
|
||||
with:
|
||||
name: tgstation/server
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
dockerfile: build/Dockerfile
|
||||
tags: "latest,v${{ env.TGS_VERSION }}"
|
||||
- name: Parse TGS version
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y xmlstarlet
|
||||
echo "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)" >> $GITHUB_ENV
|
||||
|
||||
- name: Docker Build and Push
|
||||
uses: elgohr/Publish-Docker-Github-Action@v5
|
||||
with:
|
||||
name: tgstation/server
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
dockerfile: build/Dockerfile
|
||||
tags: "latest,v${{ env.TGS_VERSION }}"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
name: 'Master Merge'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
master-merge:
|
||||
|
||||
@@ -25,7 +25,7 @@ using Tgstation.Server.Common;
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class ApiClient : IApiClient
|
||||
class ApiClient : IApiClient
|
||||
{
|
||||
/// <summary>
|
||||
/// PATCH <see cref="HttpMethod"/>.
|
||||
@@ -248,7 +248,9 @@ namespace Tgstation.Server.Client
|
||||
|
||||
using (memoryStream)
|
||||
{
|
||||
#pragma warning disable CA2000 // Dispose objects before losing scope
|
||||
var streamContent = new StreamContent(uploadStream ?? memoryStream);
|
||||
#pragma warning restore CA2000 // Dispose objects before losing scope
|
||||
try
|
||||
{
|
||||
await RunRequest<object>(
|
||||
@@ -259,7 +261,7 @@ namespace Tgstation.Server.Client
|
||||
false,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
streamContent = null; // CA2000
|
||||
streamContent = null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -268,75 +270,6 @@ namespace Tgstation.Server.Client
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to refresh the bearer token in the <see cref="headers"/>.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="true"/> if the refresh was successful, <see langword="false"/> otherwise.</returns>
|
||||
async Task<bool> RefreshToken(CancellationToken cancellationToken)
|
||||
{
|
||||
if (tokenRefreshHeaders == null)
|
||||
return false;
|
||||
|
||||
var startingToken = headers.Token;
|
||||
await semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (startingToken != headers.Token)
|
||||
return true;
|
||||
|
||||
var token = await RunRequest<object, TokenResponse>(Routes.Root, new object(), HttpMethod.Post, null, true, cancellationToken);
|
||||
headers = new ApiHeaders(headers.UserAgent!, token.Bearer!);
|
||||
}
|
||||
catch (ClientException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphoreSlim.Release();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main request method.
|
||||
/// </summary>
|
||||
/// <typeparam name="TBody">The body <see cref="Type"/>.</typeparam>
|
||||
/// <typeparam name="TResult">The resulting POCO type.</typeparam>
|
||||
/// <param name="route">The route to run.</param>
|
||||
/// <param name="body">The body of the request.</param>
|
||||
/// <param name="method">The method of the request.</param>
|
||||
/// <param name="instanceId">The optional instance <see cref="EntityId.Id"/> for the request.</param>
|
||||
/// <param name="tokenRefresh">If this is a token refresh operation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response on success.</returns>
|
||||
Task<TResult> RunRequest<TBody, TResult>(
|
||||
string route,
|
||||
TBody? body,
|
||||
HttpMethod method,
|
||||
long? instanceId,
|
||||
bool tokenRefresh,
|
||||
CancellationToken cancellationToken)
|
||||
where TBody : class
|
||||
{
|
||||
HttpContent? content = null;
|
||||
if (body != null)
|
||||
content = new StringContent(
|
||||
JsonConvert.SerializeObject(body, typeof(TBody), Formatting.None, GetSerializerSettings()),
|
||||
Encoding.UTF8,
|
||||
ApiHeaders.ApplicationJsonMime);
|
||||
|
||||
return RunRequest<TResult>(
|
||||
route,
|
||||
content,
|
||||
method,
|
||||
instanceId,
|
||||
tokenRefresh,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main request method.
|
||||
/// </summary>
|
||||
@@ -348,7 +281,7 @@ namespace Tgstation.Server.Client
|
||||
/// <param name="tokenRefresh">If this is a token refresh operation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response on success.</returns>
|
||||
async Task<TResult> RunRequest<TResult>(
|
||||
protected virtual async Task<TResult> RunRequest<TResult>(
|
||||
string route,
|
||||
HttpContent? content,
|
||||
HttpMethod method,
|
||||
@@ -427,5 +360,76 @@ namespace Tgstation.Server.Client
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to refresh the bearer token in the <see cref="headers"/>.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="true"/> if the refresh was successful, <see langword="false"/> otherwise.</returns>
|
||||
async Task<bool> RefreshToken(CancellationToken cancellationToken)
|
||||
{
|
||||
if (tokenRefreshHeaders == null)
|
||||
return false;
|
||||
|
||||
var startingToken = headers.Token;
|
||||
await semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (startingToken != headers.Token)
|
||||
return true;
|
||||
|
||||
var token = await RunRequest<object, TokenResponse>(Routes.Root, new object(), HttpMethod.Post, null, true, cancellationToken).ConfigureAwait(false);
|
||||
headers = new ApiHeaders(headers.UserAgent!, token.Bearer!);
|
||||
}
|
||||
catch (ClientException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphoreSlim.Release();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main request method.
|
||||
/// </summary>
|
||||
/// <typeparam name="TBody">The body <see cref="Type"/>.</typeparam>
|
||||
/// <typeparam name="TResult">The resulting POCO type.</typeparam>
|
||||
/// <param name="route">The route to run.</param>
|
||||
/// <param name="body">The body of the request.</param>
|
||||
/// <param name="method">The method of the request.</param>
|
||||
/// <param name="instanceId">The optional instance <see cref="EntityId.Id"/> for the request.</param>
|
||||
/// <param name="tokenRefresh">If this is a token refresh operation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response on success.</returns>
|
||||
async Task<TResult> RunRequest<TBody, TResult>(
|
||||
string route,
|
||||
TBody? body,
|
||||
HttpMethod method,
|
||||
long? instanceId,
|
||||
bool tokenRefresh,
|
||||
CancellationToken cancellationToken)
|
||||
where TBody : class
|
||||
{
|
||||
HttpContent? content = null;
|
||||
if (body != null)
|
||||
content = new StringContent(
|
||||
JsonConvert.SerializeObject(body, typeof(TBody), Formatting.None, GetSerializerSettings()),
|
||||
Encoding.UTF8,
|
||||
ApiHeaders.ApplicationJsonMime);
|
||||
|
||||
using (content)
|
||||
return await RunRequest<TResult>(
|
||||
route,
|
||||
content,
|
||||
method,
|
||||
instanceId,
|
||||
tokenRefresh,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,13 +17,21 @@ namespace Tgstation.Server.Client
|
||||
/// <summary>
|
||||
/// The <see cref="IApiClientFactory"/> for the <see cref="ServerClientFactory"/>.
|
||||
/// </summary>
|
||||
static readonly IApiClientFactory ApiClientFactory = new ApiClientFactory();
|
||||
internal static IApiClientFactory ApiClientFactory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ProductHeaderValue"/> for the <see cref="ServerClientFactory"/>.
|
||||
/// </summary>
|
||||
readonly ProductHeaderValue productHeaderValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes static members of the <see cref="ServerClientFactory"/> class.
|
||||
/// </summary>
|
||||
static ServerClientFactory()
|
||||
{
|
||||
ApiClientFactory = new ApiClientFactory();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServerClientFactory"/> class.
|
||||
/// </summary>
|
||||
|
||||
@@ -257,7 +257,8 @@ namespace Tgstation.Server.Host.Controllers
|
||||
throw new ArgumentNullException(nameof(rateLimitException));
|
||||
|
||||
Logger.LogWarning(rateLimitException, "Exceeded GitHub rate limit!");
|
||||
var secondsString = Math.Ceiling((rateLimitException.Reset - DateTimeOffset.UtcNow).TotalSeconds).ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
var secondsString = Math.Ceiling(rateLimitException.GetRetryAfterTimeSpan().TotalSeconds).ToString(CultureInfo.InvariantCulture);
|
||||
Response.Headers.Add(HeaderNames.RetryAfter, secondsString);
|
||||
return StatusCode(HttpStatusCode.TooManyRequests, new ErrorMessageResponse(ErrorCode.GitHubApiRateLimit));
|
||||
}
|
||||
|
||||
@@ -261,8 +261,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
return BadRequest(new ErrorMessageResponse(ErrorCode.OAuthProviderDisabled));
|
||||
|
||||
externalUserId = await validator
|
||||
.ValidateResponseCode(ApiHeaders.Token, cancellationToken)
|
||||
;
|
||||
.ValidateResponseCode(ApiHeaders.Token, cancellationToken);
|
||||
|
||||
Logger.LogTrace("External {oAuthProvider} UID: {externalUserId}", oAuthProvider, externalUserId);
|
||||
}
|
||||
|
||||
@@ -53,24 +53,10 @@ namespace Tgstation.Server.Tests.Live
|
||||
|
||||
async Task TestRead(CancellationToken cancellationToken)
|
||||
{
|
||||
AdministrationResponse model;
|
||||
try
|
||||
{
|
||||
model = await client.Read(cancellationToken);
|
||||
}
|
||||
catch (RateLimitException)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("TGS_TEST_GITHUB_TOKEN")))
|
||||
{
|
||||
Assert.Inconclusive("GitHub rate limit hit while testing administration endpoint. Set environment variable TGS_TEST_GITHUB_TOKEN to fix this!");
|
||||
}
|
||||
var model = await client.Read(cancellationToken);
|
||||
|
||||
// CI fails all the time b/c of this, ignore it
|
||||
return;
|
||||
}
|
||||
|
||||
//we've released a few 4.x versions now, check the release checker is at least somewhat functional
|
||||
Assert.IsTrue(4 <= model.LatestVersion.Major);
|
||||
//we've released a few 5.x versions now, check the release checker is at least somewhat functional
|
||||
Assert.IsTrue(4 < model.LatestVersion.Major);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Client;
|
||||
using Tgstation.Server.Common;
|
||||
|
||||
namespace Tgstation.Server.Tests.Live
|
||||
{
|
||||
sealed class RateLimitRetryingApiClient : ApiClient
|
||||
{
|
||||
public RateLimitRetryingApiClient(IHttpClient httpClient, Uri url, ApiHeaders apiHeaders, ApiHeaders tokenRefreshHeaders, bool authless)
|
||||
: base(httpClient, url, apiHeaders, tokenRefreshHeaders, authless)
|
||||
{
|
||||
}
|
||||
|
||||
protected override async Task<TResult> RunRequest<TResult>(string route, HttpContent content, HttpMethod method, long? instanceId, bool tokenRefresh, CancellationToken cancellationToken)
|
||||
{
|
||||
var hasGitHubToken = !String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("TGS_TEST_GITHUB_TOKEN"));
|
||||
while (true)
|
||||
try
|
||||
{
|
||||
return await base.RunRequest<TResult>(route, content, method, instanceId, tokenRefresh, cancellationToken);
|
||||
}
|
||||
catch (RateLimitException ex) when (hasGitHubToken && ex.RetryAfter.HasValue)
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
Console.WriteLine($"TEST ERROR RATE LIMITED: {ex}");
|
||||
|
||||
var sleepTime = ex.RetryAfter.Value - now;
|
||||
Console.WriteLine($"Sleeping for {sleepTime.TotalMinutes} minutes and retrying...");
|
||||
await Task.Delay(sleepTime, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Client;
|
||||
using Tgstation.Server.Common;
|
||||
|
||||
namespace Tgstation.Server.Tests.Live
|
||||
{
|
||||
sealed class RateLimitRetryingApiClientFactory : IApiClientFactory
|
||||
{
|
||||
public IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders, ApiHeaders tokenRefreshHeaders, bool authless)
|
||||
=> new RateLimitRetryingApiClient(
|
||||
new HttpClient(),
|
||||
url,
|
||||
apiHeaders,
|
||||
tokenRefreshHeaders,
|
||||
authless);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -12,9 +11,6 @@ using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
@@ -29,8 +25,6 @@ using Tgstation.Server.Client;
|
||||
using Tgstation.Server.Client.Components;
|
||||
using Tgstation.Server.Host.Components;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Database.Migrations;
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
using Tgstation.Server.Host.System;
|
||||
using Tgstation.Server.Tests.Live.Instance;
|
||||
@@ -77,6 +71,12 @@ namespace Tgstation.Server.Tests.Live
|
||||
}
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
ServerClientFactory.ApiClientFactory = new RateLimitRetryingApiClientFactory();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestUpdateProtocolAndDisabledOAuth()
|
||||
{
|
||||
@@ -128,13 +128,6 @@ namespace Tgstation.Server.Tests.Live
|
||||
var updatedAssemblyVersion = FileVersionInfo.GetVersionInfo(updatedAssemblyPath);
|
||||
Assert.AreEqual(testUpdateVersion, Version.Parse(updatedAssemblyVersion.FileVersion).Semver());
|
||||
}
|
||||
catch (RateLimitException ex)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("TGS_TEST_GITHUB_TOKEN")))
|
||||
throw;
|
||||
|
||||
Assert.Inconclusive("GitHub rate limit hit: {0}", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
serverCts.Cancel();
|
||||
@@ -170,7 +163,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
}, false, 5011))
|
||||
{
|
||||
using var serverCts = new CancellationTokenSource();
|
||||
serverCts.CancelAfter(TimeSpan.FromMinutes(3));
|
||||
serverCts.CancelAfter(TimeSpan.FromHours(3));
|
||||
var cancellationToken = serverCts.Token;
|
||||
var serverTask = controller.Run(cancellationToken);
|
||||
|
||||
@@ -217,13 +210,6 @@ namespace Tgstation.Server.Tests.Live
|
||||
|
||||
CheckServerUpdated(controller);
|
||||
}
|
||||
catch (RateLimitException ex)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("TGS_TEST_GITHUB_TOKEN")))
|
||||
throw;
|
||||
|
||||
Assert.Inconclusive("GitHub rate limit hit: {0}", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
serverCts.Cancel();
|
||||
@@ -452,13 +438,6 @@ namespace Tgstation.Server.Tests.Live
|
||||
CheckServerUpdated(node1);
|
||||
CheckServerUpdated(node2);
|
||||
}
|
||||
catch (RateLimitException ex)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("TGS_TEST_GITHUB_TOKEN")))
|
||||
throw;
|
||||
|
||||
Assert.Inconclusive("GitHub rate limit hit: {0}", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
serverCts.Cancel();
|
||||
@@ -636,13 +615,6 @@ namespace Tgstation.Server.Tests.Live
|
||||
Assert.AreEqual(2, node2Info.SwarmServers.Count);
|
||||
Assert.IsNotNull(node2Info.SwarmServers.SingleOrDefault(x => x.Identifier == "controller"));
|
||||
}
|
||||
catch (RateLimitException ex)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("TGS_TEST_GITHUB_TOKEN")))
|
||||
throw;
|
||||
|
||||
Assert.Inconclusive("GitHub rate limit hit: {0}", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
serverCts.Cancel();
|
||||
@@ -653,158 +625,6 @@ namespace Tgstation.Server.Tests.Live
|
||||
new LiveTestingServer(null, false).Dispose();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestDownMigrations()
|
||||
{
|
||||
var connectionString = Environment.GetEnvironmentVariable("TGS_TEST_CONNECTION_STRING");
|
||||
|
||||
if (string.IsNullOrEmpty(connectionString))
|
||||
Assert.Inconclusive("No connection string configured in env var TGS_TEST_CONNECTION_STRING!");
|
||||
|
||||
var databaseTypeString = Environment.GetEnvironmentVariable("TGS_TEST_DATABASE_TYPE");
|
||||
if (!Enum.TryParse<DatabaseType>(databaseTypeString, out var databaseType))
|
||||
Assert.Inconclusive("No/invalid database type configured in env var TGS_TEST_DATABASE_TYPE!");
|
||||
|
||||
string migrationName = null;
|
||||
DatabaseContext CreateContext()
|
||||
{
|
||||
string serverVersion = Environment.GetEnvironmentVariable($"{DatabaseConfiguration.Section}__{nameof(DatabaseConfiguration.ServerVersion)}");
|
||||
if (string.IsNullOrWhiteSpace(serverVersion))
|
||||
serverVersion = null;
|
||||
switch (databaseType)
|
||||
{
|
||||
case DatabaseType.MySql:
|
||||
case DatabaseType.MariaDB:
|
||||
migrationName = nameof(MYInitialCreate);
|
||||
return new MySqlDatabaseContext(
|
||||
Host.Database.Design.DesignTimeDbContextFactoryHelpers.CreateDatabaseContextOptions<MySqlDatabaseContext>(
|
||||
databaseType,
|
||||
connectionString,
|
||||
serverVersion));
|
||||
case DatabaseType.PostgresSql:
|
||||
migrationName = nameof(PGCreate);
|
||||
return new PostgresSqlDatabaseContext(
|
||||
Host.Database.Design.DesignTimeDbContextFactoryHelpers.CreateDatabaseContextOptions<PostgresSqlDatabaseContext>(
|
||||
databaseType,
|
||||
connectionString,
|
||||
serverVersion));
|
||||
case DatabaseType.SqlServer:
|
||||
migrationName = nameof(MSInitialCreate);
|
||||
return new SqlServerDatabaseContext(
|
||||
Host.Database.Design.DesignTimeDbContextFactoryHelpers.CreateDatabaseContextOptions<SqlServerDatabaseContext>(
|
||||
databaseType,
|
||||
connectionString,
|
||||
serverVersion));
|
||||
case DatabaseType.Sqlite:
|
||||
migrationName = nameof(SLRebuild);
|
||||
return new SqliteDatabaseContext(
|
||||
Host.Database.Design.DesignTimeDbContextFactoryHelpers.CreateDatabaseContextOptions<SqliteDatabaseContext>(
|
||||
databaseType,
|
||||
connectionString,
|
||||
serverVersion));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
using var context = CreateContext();
|
||||
await context.Database.EnsureDeletedAsync();
|
||||
await context.Database.MigrateAsync(default);
|
||||
|
||||
// add usergroups and dummy instances for testing purposes
|
||||
var group = new Host.Models.UserGroup
|
||||
{
|
||||
PermissionSet = new Host.Models.PermissionSet
|
||||
{
|
||||
AdministrationRights = AdministrationRights.ChangeVersion,
|
||||
InstanceManagerRights = InstanceManagerRights.GrantPermissions
|
||||
},
|
||||
Name = "TestGroup",
|
||||
};
|
||||
|
||||
const string TestUserName = "TestUser42";
|
||||
var user = new Host.Models.User
|
||||
{
|
||||
Name = TestUserName,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
OAuthConnections = new List<Host.Models.OAuthConnection>(),
|
||||
CanonicalName = Host.Models.User.CanonicalizeName(TestUserName),
|
||||
Enabled = false,
|
||||
Group = group,
|
||||
PasswordHash = "_",
|
||||
};
|
||||
|
||||
var instance = new Host.Models.Instance
|
||||
{
|
||||
AutoUpdateInterval = 0,
|
||||
ChatBotLimit = 1,
|
||||
ChatSettings = new List<Host.Models.ChatBot>(),
|
||||
ConfigurationType = ConfigurationType.HostWrite,
|
||||
DreamDaemonSettings = new Host.Models.DreamDaemonSettings
|
||||
{
|
||||
AllowWebClient = false,
|
||||
AutoStart = false,
|
||||
HeartbeatSeconds = 0,
|
||||
DumpOnHeartbeatRestart = false,
|
||||
Port = 1447,
|
||||
SecurityLevel = DreamDaemonSecurity.Safe,
|
||||
Visibility = DreamDaemonVisibility.Public,
|
||||
StartupTimeout = 1000,
|
||||
TopicRequestTimeout = 1000,
|
||||
AdditionalParameters = string.Empty,
|
||||
StartProfiler = false,
|
||||
LogOutput = true,
|
||||
MapThreads = 69,
|
||||
},
|
||||
DreamMakerSettings = new Host.Models.DreamMakerSettings
|
||||
{
|
||||
ApiValidationPort = 1557,
|
||||
ApiValidationSecurityLevel = DreamDaemonSecurity.Trusted,
|
||||
RequireDMApiValidation = false,
|
||||
Timeout = TimeSpan.FromSeconds(13),
|
||||
},
|
||||
InstancePermissionSets = new List<Host.Models.InstancePermissionSet>
|
||||
{
|
||||
new Host.Models.InstancePermissionSet
|
||||
{
|
||||
ByondRights = ByondRights.InstallCustomVersion,
|
||||
ChatBotRights = ChatBotRights.None,
|
||||
ConfigurationRights = ConfigurationRights.Read,
|
||||
DreamDaemonRights = DreamDaemonRights.ReadRevision,
|
||||
DreamMakerRights = DreamMakerRights.SetApiValidationPort,
|
||||
InstancePermissionSetRights = InstancePermissionSetRights.Write,
|
||||
PermissionSet = group.PermissionSet,
|
||||
RepositoryRights = RepositoryRights.SetReference
|
||||
}
|
||||
},
|
||||
Name = "sfdsadfsa",
|
||||
Online = false,
|
||||
Path = "/a/b/c/d",
|
||||
RepositorySettings = new Host.Models.RepositorySettings
|
||||
{
|
||||
AutoUpdatesKeepTestMerges = false,
|
||||
AutoUpdatesSynchronize = false,
|
||||
CommitterEmail = "email@eample.com",
|
||||
CommitterName = "blubluh",
|
||||
CreateGitHubDeployments = false,
|
||||
PostTestMergeComment = false,
|
||||
PushTestMergeCommits = false,
|
||||
ShowTestMergeCommitters = false,
|
||||
UpdateSubmodules = false,
|
||||
},
|
||||
};
|
||||
|
||||
context.Users.Add(user);
|
||||
context.Groups.Add(group);
|
||||
context.Instances.Add(instance);
|
||||
await context.Save(default);
|
||||
|
||||
var dbServiceProvider = ((IInfrastructure<IServiceProvider>)context.Database).Instance;
|
||||
var migrator = dbServiceProvider.GetRequiredService<IMigrator>();
|
||||
await migrator.MigrateAsync(migrationName, default);
|
||||
await context.Database.EnsureDeletedAsync();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestStandardTgsOperation()
|
||||
{
|
||||
@@ -820,7 +640,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
|
||||
using var server = new LiveTestingServer(null, true);
|
||||
|
||||
const int MaximumTestMinutes = 20;
|
||||
const int MaximumTestMinutes = 180;
|
||||
using var hardTimeoutCancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(MaximumTestMinutes));
|
||||
var hardCancellationToken = hardTimeoutCancellationTokenSource.Token;
|
||||
using var serverCts = CancellationTokenSource.CreateLinkedTokenSource(hardCancellationToken);
|
||||
@@ -1069,12 +889,12 @@ namespace Tgstation.Server.Tests.Live
|
||||
}
|
||||
catch (ApiException ex)
|
||||
{
|
||||
System.Console.WriteLine($"[{DateTimeOffset.UtcNow}] TEST ERROR: {ex.ErrorCode}: {ex.Message}\n{ex.AdditionalServerData}");
|
||||
Console.WriteLine($"[{DateTimeOffset.UtcNow}] TEST ERROR: {ex.ErrorCode}: {ex.Message}\n{ex.AdditionalServerData}");
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Console.WriteLine($"[{DateTimeOffset.UtcNow}] TEST ERROR: {ex}");
|
||||
Console.WriteLine($"[{DateTimeOffset.UtcNow}] TEST ERROR: {ex}");
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Database.Migrations;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Tgstation.Server.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class TestDatabase
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task TestDownMigrations()
|
||||
{
|
||||
var connectionString = Environment.GetEnvironmentVariable("TGS_TEST_CONNECTION_STRING");
|
||||
|
||||
if (string.IsNullOrEmpty(connectionString))
|
||||
Assert.Inconclusive("No connection string configured in env var TGS_TEST_CONNECTION_STRING!");
|
||||
|
||||
var databaseTypeString = Environment.GetEnvironmentVariable("TGS_TEST_DATABASE_TYPE");
|
||||
if (!Enum.TryParse<DatabaseType>(databaseTypeString, out var databaseType))
|
||||
Assert.Inconclusive("No/invalid database type configured in env var TGS_TEST_DATABASE_TYPE!");
|
||||
|
||||
string migrationName = null;
|
||||
DatabaseContext CreateContext()
|
||||
{
|
||||
string serverVersion = Environment.GetEnvironmentVariable($"{DatabaseConfiguration.Section}__{nameof(DatabaseConfiguration.ServerVersion)}");
|
||||
if (string.IsNullOrWhiteSpace(serverVersion))
|
||||
serverVersion = null;
|
||||
switch (databaseType)
|
||||
{
|
||||
case DatabaseType.MySql:
|
||||
case DatabaseType.MariaDB:
|
||||
migrationName = nameof(MYInitialCreate);
|
||||
return new MySqlDatabaseContext(
|
||||
Host.Database.Design.DesignTimeDbContextFactoryHelpers.CreateDatabaseContextOptions<MySqlDatabaseContext>(
|
||||
databaseType,
|
||||
connectionString,
|
||||
serverVersion));
|
||||
case DatabaseType.PostgresSql:
|
||||
migrationName = nameof(PGCreate);
|
||||
return new PostgresSqlDatabaseContext(
|
||||
Host.Database.Design.DesignTimeDbContextFactoryHelpers.CreateDatabaseContextOptions<PostgresSqlDatabaseContext>(
|
||||
databaseType,
|
||||
connectionString,
|
||||
serverVersion));
|
||||
case DatabaseType.SqlServer:
|
||||
migrationName = nameof(MSInitialCreate);
|
||||
return new SqlServerDatabaseContext(
|
||||
Host.Database.Design.DesignTimeDbContextFactoryHelpers.CreateDatabaseContextOptions<SqlServerDatabaseContext>(
|
||||
databaseType,
|
||||
connectionString,
|
||||
serverVersion));
|
||||
case DatabaseType.Sqlite:
|
||||
migrationName = nameof(SLRebuild);
|
||||
return new SqliteDatabaseContext(
|
||||
Host.Database.Design.DesignTimeDbContextFactoryHelpers.CreateDatabaseContextOptions<SqliteDatabaseContext>(
|
||||
databaseType,
|
||||
connectionString,
|
||||
serverVersion));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
using var context = CreateContext();
|
||||
await context.Database.EnsureDeletedAsync();
|
||||
await context.Database.MigrateAsync(default);
|
||||
|
||||
// add usergroups and dummy instances for testing purposes
|
||||
var group = new Host.Models.UserGroup
|
||||
{
|
||||
PermissionSet = new Host.Models.PermissionSet
|
||||
{
|
||||
AdministrationRights = AdministrationRights.ChangeVersion,
|
||||
InstanceManagerRights = InstanceManagerRights.GrantPermissions
|
||||
},
|
||||
Name = "TestGroup",
|
||||
};
|
||||
|
||||
const string TestUserName = "TestUser42";
|
||||
var user = new Host.Models.User
|
||||
{
|
||||
Name = TestUserName,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
OAuthConnections = new List<Host.Models.OAuthConnection>(),
|
||||
CanonicalName = Host.Models.User.CanonicalizeName(TestUserName),
|
||||
Enabled = false,
|
||||
Group = group,
|
||||
PasswordHash = "_",
|
||||
};
|
||||
|
||||
var instance = new Host.Models.Instance
|
||||
{
|
||||
AutoUpdateInterval = 0,
|
||||
ChatBotLimit = 1,
|
||||
ChatSettings = new List<Host.Models.ChatBot>(),
|
||||
ConfigurationType = ConfigurationType.HostWrite,
|
||||
DreamDaemonSettings = new Host.Models.DreamDaemonSettings
|
||||
{
|
||||
AllowWebClient = false,
|
||||
AutoStart = false,
|
||||
HeartbeatSeconds = 0,
|
||||
DumpOnHeartbeatRestart = false,
|
||||
Port = 1447,
|
||||
SecurityLevel = DreamDaemonSecurity.Safe,
|
||||
Visibility = DreamDaemonVisibility.Public,
|
||||
StartupTimeout = 1000,
|
||||
TopicRequestTimeout = 1000,
|
||||
AdditionalParameters = string.Empty,
|
||||
StartProfiler = false,
|
||||
LogOutput = true,
|
||||
MapThreads = 69,
|
||||
},
|
||||
DreamMakerSettings = new Host.Models.DreamMakerSettings
|
||||
{
|
||||
ApiValidationPort = 1557,
|
||||
ApiValidationSecurityLevel = DreamDaemonSecurity.Trusted,
|
||||
RequireDMApiValidation = false,
|
||||
Timeout = TimeSpan.FromSeconds(13),
|
||||
},
|
||||
InstancePermissionSets = new List<Host.Models.InstancePermissionSet>
|
||||
{
|
||||
new Host.Models.InstancePermissionSet
|
||||
{
|
||||
ByondRights = ByondRights.InstallCustomVersion,
|
||||
ChatBotRights = ChatBotRights.None,
|
||||
ConfigurationRights = ConfigurationRights.Read,
|
||||
DreamDaemonRights = DreamDaemonRights.ReadRevision,
|
||||
DreamMakerRights = DreamMakerRights.SetApiValidationPort,
|
||||
InstancePermissionSetRights = InstancePermissionSetRights.Write,
|
||||
PermissionSet = group.PermissionSet,
|
||||
RepositoryRights = RepositoryRights.SetReference
|
||||
}
|
||||
},
|
||||
Name = "sfdsadfsa",
|
||||
Online = false,
|
||||
Path = "/a/b/c/d",
|
||||
RepositorySettings = new Host.Models.RepositorySettings
|
||||
{
|
||||
AutoUpdatesKeepTestMerges = false,
|
||||
AutoUpdatesSynchronize = false,
|
||||
CommitterEmail = "email@eample.com",
|
||||
CommitterName = "blubluh",
|
||||
CreateGitHubDeployments = false,
|
||||
PostTestMergeComment = false,
|
||||
PushTestMergeCommits = false,
|
||||
ShowTestMergeCommitters = false,
|
||||
UpdateSubmodules = false,
|
||||
},
|
||||
};
|
||||
|
||||
context.Users.Add(user);
|
||||
context.Groups.Add(group);
|
||||
context.Instances.Add(instance);
|
||||
await context.Save(default);
|
||||
|
||||
var dbServiceProvider = ((IInfrastructure<IServiceProvider>)context.Database).Instance;
|
||||
var migrator = dbServiceProvider.GetRequiredService<IMigrator>();
|
||||
await migrator.MigrateAsync(migrationName, default);
|
||||
await context.Database.EnsureDeletedAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user