Merge pull request #1334 from tgstation/dev [TGSDeploy][APIDeploy][NugetDeploy]

v4.15.0
This commit is contained in:
Jordan Brown
2021-09-25 23:30:28 -04:00
committed by GitHub
42 changed files with 403 additions and 287 deletions
+10 -10
View File
@@ -39,14 +39,14 @@ You need the Dotnet 3.1 SDK and npm>=v5.7 (in your PATH) to compile the server.
The recommended IDE is Visual Studio 2019 which has installation options for both of these.
In order to run the integration tests you must have the following environment variables set:
- `TGS4_TEST_DATABASE_TYPE`: `MySql`, `MariaDB`, `PostgresSql`, or `SqlServer`.
- `TGS4_TEST_CONNECTION_STRING`: To a valid database connection string. You can use the setup wizard to create one.
- `TSG4_TEST_DISCORD_TOKEN`: To a valid discord bot token.
- `TGS4_TEST_DISCORD_CHANNEL`: To a valid discord channel ID that the above bot can access.
- `TGS4_TEST_IRC_CONNECTION_STRING`: To a valid TGS4 IRC connection string. See the code for [IrcConnectionStringBuilder](../src/Tgstation.Server.Api/Models/IrcConnectionStringBuilder.cs) for details.
- `TGS4_TEST_IRC_CHANNEL`: To a valid IRC channel accessible with the above connection.
- `TGS4_TEST_BRANCH`: Should be either `dev` or `master` depending on what you are working off of. Used for repository tests.
- (Optional) `TGS4_TEST_GITHUB_TOKEN`: A GitHub personal access token with no scopes used to bypass rate limits.
- `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.
- `TSG_TEST_DISCORD_TOKEN`: To a valid discord bot token.
- `TGS_TEST_DISCORD_CHANNEL`: To a valid discord channel ID that the above bot can access.
- `TGS_TEST_IRC_CONNECTION_STRING`: To a valid IRC connection string. See the code for [IrcConnectionStringBuilder](../src/Tgstation.Server.Api/Models/IrcConnectionStringBuilder.cs) for details.
- `TGS_TEST_IRC_CHANNEL`: To a valid IRC channel accessible with the above connection.
- `TGS_TEST_BRANCH`: Should be either `dev` or `master` depending on what you are working off of. Used for repository tests.
- (Optional) `TGS_TEST_GITHUB_TOKEN`: A GitHub personal access token with no scopes used to bypass rate limits.
### Know your Code
@@ -240,9 +240,9 @@ The NuGet package Tgstation.Server.Client is another part of the suite which sho
_This section mainly applies to people with write access to the repository. Anyone is free to propose their work and maintainers will triage it appropriately._
When issues affecting the server come in, they should be lebeled appropriately and either put into the `V4 Backlog` milestone or current patch milestone depending on if it's a feature request or bug.
When issues affecting the server come in, they should be lebeled appropriately and either put into the `Backlog` milestone or current patch milestone depending on if it's a feature request or bug.
After a minor release, the team should decide at that time what will go into it and setup the milestone accordingly. At this point the `Backlog` label should be removed and replaced with `Ready` and the milestone changed from `V4 Backlog` to `v4.X.0` with X being the minor release version.
After a minor release, the team should decide at that time what will go into it and setup the milestone accordingly. At this point the `Backlog` label should be removed and replaced with `Ready` and the milestone changed from `Backlog` to `vX.Y.0` with X/Y being the major/minor release versions respectively.
Assign work before beginning on it. When work is started, replace the `Ready` label with the `Work In Progress` label.
+38 -38
View File
@@ -1,4 +1,4 @@
name: 'CI'
name: 'CI'
on:
push:
@@ -11,13 +11,13 @@ on:
- master
env:
TGS4_DOTNET_VERSION: 3.1.x
TGS4_TEST_DISCORD_CHANNEL: ${{ secrets.DISCORD_CHANNEL_ID }}
TGS4_TEST_DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
TGS4_TEST_IRC_CHANNEL: ${{ secrets.IRC_CHANNEL }}
TGS4_TEST_IRC_CONNECTION_STRING: ${{ secrets.IRC_CONNECTION_STRING }}
TGS4_TEST_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TGS4_RELEASE_NOTES_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
TGS_DOTNET_VERSION: 3.1.x
TGS_TEST_DISCORD_CHANNEL: ${{ secrets.DISCORD_CHANNEL_ID }}
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_RELEASE_NOTES_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
jobs:
dmapi-build:
@@ -134,7 +134,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ env.TGS4_DOTNET_VERSION }}
dotnet-version: ${{ env.TGS_DOTNET_VERSION }}
- name: Checkout
uses: actions/checkout@v1
@@ -161,7 +161,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ env.TGS4_DOTNET_VERSION }}
dotnet-version: ${{ env.TGS_DOTNET_VERSION }}
- name: Checkout
uses: actions/checkout@v1
@@ -185,8 +185,8 @@ jobs:
name: Windows Integration Test
needs: dmapi-build
env:
TGS4_TEST_DATABASE_TYPE: SqlServer
TGS4_TEST_DUMP_API_SPEC: yes
TGS_TEST_DATABASE_TYPE: SqlServer
TGS_TEST_DUMP_API_SPEC: yes
concurrency: integration-windows-${{ github.head_ref }}
strategy:
max-parallel: 2
@@ -198,35 +198,35 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ env.TGS4_DOTNET_VERSION }}
dotnet-version: ${{ env.TGS_DOTNET_VERSION }}
- name: Set General__UseBasicWatchdog
if: ${{ matrix.watchdog-type == 'Basic' }}
run: echo "General__UseBasicWatchdog=true" >> $Env:GITHUB_ENV
- name: Set TGS4_TEST_CONNECTION_STRING
- name: Set TGS_TEST_CONNECTION_STRING
shell: bash
run: |
TGS4_CONNSTRING_VALUE="Server=(localdb)\MSSQLLocalDB;Integrated Security=true;Initial Catalog=TGS_${{ matrix.watchdog-type }}_${{ matrix.configuration }};Application Name=tgstation-server"
echo "TGS4_TEST_CONNECTION_STRING=$(echo $TGS4_CONNSTRING_VALUE)" >> $GITHUB_ENV
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
uses: actions/checkout@v1
- name: Set TGS4_TEST_PULL_REQUEST_NUMBER
- name: Set TGS_TEST_PULL_REQUEST_NUMBER
if: ${{ github.event_name == 'pull_request' }}
run: echo "TGS4_TEST_PULL_REQUEST_NUMBER=${{ github.event.number }}" >> $Env:GITHUB_ENV
run: echo "TGS_TEST_PULL_REQUEST_NUMBER=${{ github.event.number }}" >> $Env:GITHUB_ENV
- name: Set TGS4_GITHUB_REF for PR
- name: Set TGS_GITHUB_REF for PR
if: ${{ github.event_name == 'pull_request' }}
run: echo "TGS4_GITHUB_REF=${{ github.base_ref }}" >> $Env:GITHUB_ENV
run: echo "TGS_GITHUB_REF=${{ github.base_ref }}" >> $Env:GITHUB_ENV
- name: Set TGS4_GITHUB_REF for push
- name: Set TGS_GITHUB_REF for push
if: ${{ github.event_name == 'push' }}
shell: bash
run: |
TEMP_GITHUB_REF="${{ github.event.ref }}"
echo "TGS4_GITHUB_REF=${TEMP_GITHUB_REF##*/}" >> $GITHUB_ENV
echo "TGS_GITHUB_REF=${TEMP_GITHUB_REF##*/}" >> $GITHUB_ENV
- name: Clean package cache as a temporary workaround for actions/setup-dotnet#155
run: dotnet clean && dotnet nuget locals all --clear
@@ -329,31 +329,31 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ env.TGS4_DOTNET_VERSION }}
dotnet-version: ${{ env.TGS_DOTNET_VERSION }}
- name: Set Sqlite Connection Info
if: ${{ matrix.database-type == 'Sqlite' }}
run: |
echo "TGS4_TEST_DATABASE_TYPE=Sqlite" >> $GITHUB_ENV
echo "TGS4_TEST_CONNECTION_STRING=Data Source=TGS_${{ matrix.watchdog-type }}_${{ matrix.configuration }}.sqlite3;Mode=ReadWriteCreate" >> $GITHUB_ENV
echo "TGS_TEST_DATABASE_TYPE=Sqlite" >> $GITHUB_ENV
echo "TGS_TEST_CONNECTION_STRING=Data Source=TGS_${{ matrix.watchdog-type }}_${{ matrix.configuration }}.sqlite3;Mode=ReadWriteCreate" >> $GITHUB_ENV
- name: Set PostgresSql Connection Info
if: ${{ matrix.database-type == 'PostgresSql' }}
run: |
echo "TGS4_TEST_DATABASE_TYPE=PostgresSql" >> $GITHUB_ENV
echo "TGS4_TEST_CONNECTION_STRING=Application Name=tgstation-server;Host=127.0.0.1;Username=postgres;Password=postgres;Database=TGS__${{ matrix.watchdog-type }}_${{ matrix.configuration }}" >> $GITHUB_ENV
echo "TGS_TEST_DATABASE_TYPE=PostgresSql" >> $GITHUB_ENV
echo "TGS_TEST_CONNECTION_STRING=Application Name=tgstation-server;Host=127.0.0.1;Username=postgres;Password=postgres;Database=TGS__${{ matrix.watchdog-type }}_${{ matrix.configuration }}" >> $GITHUB_ENV
- name: Set MariaDB Connection Info
if: ${{ matrix.database-type == 'MariaDB' }}
run: |
echo "TGS4_TEST_DATABASE_TYPE=MariaDB" >> $GITHUB_ENV
echo "TGS4_TEST_CONNECTION_STRING=Server=127.0.0.1;uid=root;pwd=mariadb;database=tgs__${{ matrix.watchdog-type }}_${{ matrix.configuration }}" >> $GITHUB_ENV
echo "TGS_TEST_DATABASE_TYPE=MariaDB" >> $GITHUB_ENV
echo "TGS_TEST_CONNECTION_STRING=Server=127.0.0.1;uid=root;pwd=mariadb;database=tgs__${{ matrix.watchdog-type }}_${{ matrix.configuration }}" >> $GITHUB_ENV
- name: Set MySQL Connection Info
if: ${{ matrix.database-type == 'MySql' }}
run: |
echo "TGS4_TEST_DATABASE_TYPE=MySql" >> $GITHUB_ENV
echo "TGS4_TEST_CONNECTION_STRING=Server=127.0.0.1;Port=3307;uid=root;pwd=mysql;database=tgs__${{ matrix.watchdog-type }}_${{ matrix.configuration }}" >> $GITHUB_ENV
echo "TGS_TEST_DATABASE_TYPE=MySql" >> $GITHUB_ENV
echo "TGS_TEST_CONNECTION_STRING=Server=127.0.0.1;Port=3307;uid=root;pwd=mysql;database=tgs__${{ matrix.watchdog-type }}_${{ matrix.configuration }}" >> $GITHUB_ENV
echo "Database__ServerVersion=5.7.31" >> $GITHUB_ENV
- name: Set General__UseBasicWatchdog
@@ -363,20 +363,20 @@ jobs:
- name: Checkout
uses: actions/checkout@v1
- name: Set TGS4_TEST_PULL_REQUEST_NUMBER
- name: Set TGS_TEST_PULL_REQUEST_NUMBER
if: ${{ github.event_name == 'pull_request' }}
run: echo "TGS4_TEST_PULL_REQUEST_NUMBER=${{ github.event.number }}" >> $GITHUB_ENV
run: echo "TGS_TEST_PULL_REQUEST_NUMBER=${{ github.event.number }}" >> $GITHUB_ENV
- name: Set TGS4_GITHUB_REF for PR
- name: Set TGS_GITHUB_REF for PR
if: ${{ github.event_name == 'pull_request' }}
run: echo "TGS4_GITHUB_REF=${{ github.base_ref }}" >> $GITHUB_ENV
run: echo "TGS_GITHUB_REF=${{ github.base_ref }}" >> $GITHUB_ENV
- name: Set TGS4_GITHUB_REF for push
- name: Set TGS_GITHUB_REF for push
if: ${{ github.event_name == 'push' }}
shell: bash
run: |
TEMP_GITHUB_REF="${{ github.event.ref }}"
echo "TGS4_GITHUB_REF=${TEMP_GITHUB_REF##*/}" >> $GITHUB_ENV
echo "TGS_GITHUB_REF=${TEMP_GITHUB_REF##*/}" >> $GITHUB_ENV
- name: Run Integration Test
run: |
@@ -658,7 +658,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ env.TGS4_DOTNET_VERSION }}
dotnet-version: ${{ env.TGS_DOTNET_VERSION }}
- name: Checkout
uses: actions/checkout@v1
+28
View File
@@ -0,0 +1,28 @@
name: 'V5 Merge'
on:
push:
branches:
- dev
jobs:
master-merge:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Merge dev into V5
uses: robotology/gh-action-nightly-merge@v1.2.0
with:
stable_branch: 'dev'
development_branch: 'V5'
allow_ff: true
user_name: tgstation-server
user_email: tgstation-server@users.noreply.github.com
push_token: DEV_PUSH_TOKEN
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DEV_PUSH_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
+13 -11
View File
@@ -1,4 +1,4 @@
# tgstation-server v4:
# tgstation-server:
![CI](https://github.com/tgstation/tgstation-server/workflows/CI/badge.svg) [![codecov](https://codecov.io/gh/tgstation/tgstation-server/branch/master/graph/badge.svg)](https://codecov.io/gh/tgstation/tgstation-server)
@@ -12,7 +12,7 @@ This is a toolset to manage production BYOND servers. It includes the ability to
### Legacy Servers
Older server versions can be found in the V# branches of this repository. Note that V4 is nearly fully incompatible with existing installations. Only some static files may be copied over: https://github.com/tgstation/tgstation-server#static-files
Older server versions can be found in the V# branches of this repository. Note that the current server fully incompatible with installations before version 4. Only some static files may be copied over: https://github.com/tgstation/tgstation-server#static-files
## Setup
@@ -23,7 +23,7 @@ Older server versions can be found in the V# branches of this repository. Note t
### Installation
1. [Download the latest V4 release .zip](https://github.com/tgstation/tgstation-server/releases/latest). The `ServerService` package will only work on Windows. Choose `ServerConsole` if that is not your target OS or you prefer not to use the Windows service.
1. [Download the latest release .zip](https://github.com/tgstation/tgstation-server/releases/latest). The `ServerService` package will only work on Windows. Choose `ServerConsole` if that is not your target OS or you prefer not to use the Windows service.
2. Extract the .zip file to where you want the server to run from. Note the account running the server must have write and delete access to the `lib` subdirectory.
#### Windows
@@ -67,7 +67,7 @@ docker run \
-p 5000:5000 \ # Port bridge for accessing TGS, you can change this if you need
-p 0.0.0.0:<public game port>:<public game port> \ # Port bridge for accessing DreamDaemon
-v /path/to/your/configfile/directory:/config_data \ # Recommended, create a volume mapping for server configuration
-v /path/to/store/instances:/tgs4_instances \ # Recommended, create a volume mapping for server instances
-v /path/to/store/instances:/tgs_instances \ # Recommended, create a volume mapping for server instances
-v /path/to/your/log/folder:/tgs_logs \ # Recommended, create a volume mapping for server logs
tgstation/server[:<release version>]
```
@@ -79,7 +79,7 @@ Important note about port exposure: The internal port used by DreamDaemon _**MUS
Note although `/app/lib` is specified as a volume mount point in the `Dockerfile`, unless you REALLY know what you're doing. Do not mount any volumes over this for fear of breaking your container.
The configuration option `General:ValidInstancePaths` will be preconfigured to point to `/tgs4_instances`. It is recommended you don't change this.
The configuration option `General:ValidInstancePaths` will be preconfigured to point to `/tgs_instances`. It is recommended you don't change this.
Note that this container is meant to be long running. Updates are handled internally as opposed to at the container level.
@@ -89,7 +89,7 @@ If using manual configuration, before starting your container make sure the afor
### Configuring
The first time you run TGS4 you should be prompted with a configuration wizard which will guide you through setting up your `appsettings.Production.yml`
The first time you run TGS you should be prompted with a configuration wizard which will guide you through setting up your `appsettings.Production.yml`
This wizard will, generally, run whenever the server is launched without detecting the config yml. Follow the instructions below to perform this process manually.
@@ -107,7 +107,7 @@ The latter two are not recommended as they cannot be dynamically changed at runt
Create an `appsettings.Production.yml` file next to `appsettings.yml`. This will override the default settings in `appsettings.yml` with your production settings. There are a few keys meant to be changed by hosts. Modifying any config files while the server is running will trigger a safe restart (Keeps DreamDaemon instances running). Note these are all case-sensitive:
- `General:ConfigVersion`: Suppresses warnings about out of date config versions. You should change this after updating TGS to one with a new config version. The current version can be found on the releases page for your server version (This field did not exist before v4.4.0).
- `General:ConfigVersion`: Suppresses warnings about out of date config versions. You should change this after updating TGS to one with a new config version. The current version can be found on the releases page for your server version.
- `General:MinimumPasswordLength`: Minimum password length requirement for database users
@@ -137,7 +137,7 @@ Create an `appsettings.Production.yml` file next to `appsettings.yml`. This will
- `ControlPanel:AllowedOrigins`: Set the Access-Control-Allow-Origin headers to this list of origins for all responses (also enables all headers and methods). This is overridden by `ControlPanel:AllowAnyOrigin`
- `Elasticsearch`: tgstation-server-v4 also supports automatically ingesting its logs to ElasticSearch. You can set this up in the setup wizard, or with the following configuration:
- `Elasticsearch`: tgstation-server also supports automatically ingesting its logs to ElasticSearch. You can set this up in the setup wizard, or with the following configuration:
```yml
Elasticsearch:
Enable: true
@@ -356,11 +356,13 @@ Instances can be either part of a swarm or not. Once in the database they cannot
## Usage
tgstation-server v4 is controlled via a RESTful HTTP json API. Documentation on this API can be found [here](https://tgstation.github.io/tgstation-server/api.html). This section serves to document the concepts of the server. The API is versioned separately from the release version. A specification for it can be found in the api-vX.X.X git releases/tags.
tgstation-server is controlled via a RESTful HTTP json API. Documentation on this API can be found [here](https://tgstation.github.io/tgstation-server/api.html). This section serves to document the concepts of the server. The API is versioned separately from the release version. A specification for it can be found in the api-vX.X.X git releases/tags.
### Updating
TGS 4 can self update without stopping your DreamDaemon servers. Any V4 release made to this repository is bound by a contract that allows changes of the runtime assemblies without stopping your servers. Database migrations are automatically applied as well. Because of this REVERTING TO LOWER VERSIONS IS NOT OFFICIALLY SUPPORTED, do so at your own risk (check changes made to `/src/Tgstation.Server.Host/Models/Migrations`).
TGS can self update without stopping your DreamDaemon servers. Releases made to this repository are bound by a contract that allows changes of the runtime assemblies without stopping your servers. Database migrations are automatically applied as well. Because of this REVERTING TO LOWER VERSIONS IS NOT OFFICIALLY SUPPORTED, do so at your own risk (check changes made to `/src/Tgstation.Server.Host/Models/Migrations`).
Major version updates may require additional action on the part of the user (apart from the configuration changes).
#### Notifications
@@ -446,7 +448,7 @@ Any files and folders contained in this root level of this folder will be symbol
### Clients
Here are tools for interacting with the TGS 4 web API
Here are tools for interacting with the TGS web API
- [tgstation-server-webpanel](https://github.com/tgstation/tgstation-server-webpanel): Official client and included with the server (WIP). A react web app for using tgstation-server.
- [Tgstation.Server.ControlPanel](https://github.com/tgstation/Tgstation.Server.ControlPanel): Official client. A cross platform GUI for using tgstation-server. Feature complete but lacks OAuth login options.
+1 -1
View File
@@ -71,7 +71,7 @@ RUN apt-get update \
EXPOSE 5000
ENV General__ValidInstancePaths__0 /tgs4_instances
ENV General__ValidInstancePaths__0 /tgs_instances
ENV FileLogging__Directory /tgs_logs
WORKDIR /app
+4 -4
View File
@@ -3,11 +3,11 @@
<!-- Integration tests will ensure they match across the board -->
<Import Project="ControlPanelVersion.props" />
<PropertyGroup>
<TgsCoreVersion>4.14.3</TgsCoreVersion>
<TgsCoreVersion>4.15.0</TgsCoreVersion>
<TgsConfigVersion>4.0.0</TgsConfigVersion>
<TgsApiVersion>9.2.0</TgsApiVersion>
<TgsApiLibraryVersion>9.2.0</TgsApiLibraryVersion>
<TgsClientVersion>10.2.0</TgsClientVersion>
<TgsApiVersion>9.3.0</TgsApiVersion>
<TgsApiLibraryVersion>9.3.0</TgsApiLibraryVersion>
<TgsClientVersion>10.3.0</TgsClientVersion>
<TgsDmapiVersion>6.0.4</TgsDmapiVersion>
<TgsInteropVersion>5.3.0</TgsInteropVersion>
<TgsHostWatchdogVersion>1.1.1</TgsHostWatchdogVersion>
+5 -5
View File
@@ -5,7 +5,7 @@
@section api_swag OpenAPI Spec
TGS4 has a, from code, generated OpenAPI 3.0 specification. It is much more authorative than these documents.
TGS has a, from code, generated OpenAPI 3.0 specification. It is much more authorative than these documents.
The most up to date version should be found in the most recent appveyor build artifacts. It is also included in release artifacts.
@@ -13,7 +13,7 @@ You can use the API explorer SwaggerUI to interact with it: https://petstore.swa
@section api_intro Introduction
The TGS4 API is designed to be a fully realized RESTful service. Once hosted, follow the specified protocol for developing new clients or one off requests that provide full control over the server
The TGS API is designed to be a fully realized RESTful service. Once hosted, follow the specified protocol for developing new clients or one off requests that provide full control over the server
Routes and their usages are defined as follows
@@ -21,7 +21,7 @@ Routes and their usages are defined as follows
@section api_lib Official Libraries
The TGS4 API's canonical definitions are provided as a .NET Standard library in the form of a nuget package located here: https://www.nuget.org/packages/Tgstation.Server.Api
The TGS API's canonical definitions are provided as a .NET Standard library in the form of a nuget package located here: https://www.nuget.org/packages/Tgstation.Server.Api
An all inclusive TAP interface for using the API is also provided in this package: https://www.nuget.org/packages/Tgstation.Server.Client
@@ -38,7 +38,7 @@ This document will reference the canonical C# models in the @ref Tgstation.Serve
@section api_header Headers
TGS4 expects this set of headers. Failure to provide them will result in 400 error responses
TGS expects this set of headers. Failure to provide them will result in 400 error responses
- User-Agent: The user agent product header value of the calling program
- Api: Another product header value representing the version of the API to use. Currently this must be: Tgstation.Server.Api/4.0.0.0
@@ -124,7 +124,7 @@ Continue to use this token until you begin to recieve 401 responses from the API
@subsection api_auth_o OAuth 2.0
TGS4 supports OAuth 2.0 with select providers for authentication.
TGS supports OAuth 2.0 with select providers for authentication.
The flow for this is as follows:
+8 -8
View File
@@ -7,7 +7,7 @@
@section arch_intro Introduction
This is meant to be a brief overview of the TGS4 architecture to give new coders direction on where to code and the curious some insight to their questions. Given that this document is seperate from the authorative code it may fall out of date. For clairity, please contact project maintainers.
This is meant to be a brief overview of the TGS architecture to give new coders direction on where to code and the curious some insight to their questions. Given that this document is seperate from the authorative code it may fall out of date. For clairity, please contact project maintainers.
@section arch_hwatchdog Host Watchdog
@@ -29,7 +29,7 @@ The @ref Tgstation.Server.Host.Core.Application class has two methods called by
- Respond with 503 if the application is still starting or shutting down
- Authenticate the JWT in Authentication header if present and run @ref Tgstation.Server.Host.Controllers.ApiController on success
- Catch database exceptions and convert to 409 responses with the exception's @ref Tgstation.Server.Api.Models.ErrorMessage
- Check @ref Tgstation.Server.Host.Controllers for correct controller and run the action and use it's response.
- Check @ref Tgstation.Server.Host.Controllers for correct controller and run the action and use it's response.
- If not properly authenticated beforehand and action has a @ref Tgstation.Server.Host.Controllers.TgsAuthorizeAttribute return 401
- If not properly authorized beforehand according to the parameters of the action's @ref Tgstation.Server.Host.Controllers.TgsAuthorizeAttribute (if present) return 403
- If requested action does not exist return 404
@@ -98,9 +98,9 @@ The compilation process is a distinct series of steps:
14. Symlink all `GameStaticFiles` to both the A and B directories
15. Commit the @ref Tgstation.Server.Host.Models.CompileJob to the database
If any of the above steps fail, the target directory is deleted and the deployment is considered a bust. If all went well, after the @ref Tgstation.Server.Host.Models.Job completes the new CompileJob is loaded into the instance's @ref Tgstation.Server.Host.Components.Deployment.IDmbFactory .
If any of the above steps fail, the target directory is deleted and the deployment is considered a bust. If all went well, after the @ref Tgstation.Server.Host.Models.Job completes the new CompileJob is loaded into the instance's @ref Tgstation.Server.Host.Components.Deployment.IDmbFactory .
The DmbFactory is where the @ref arch_watchdog gets the @ref Tgstation.Server.Host.Components.Deployment.IDmbProvider instances to run. Each CompileJob loaded into it is given a lock count. The latest CompileJob holds 1 lock and every DreamDaemon instance running that CompileJob holds another. Loading a new CompileJob releases the initial lock, and when all other locks are released the CompileJob's directory is deleted. Any directories in the `Game` folder not in use are also deleted when the Instance starts.
The DmbFactory is where the @ref arch_watchdog gets the @ref Tgstation.Server.Host.Components.Deployment.IDmbProvider instances to run. Each CompileJob loaded into it is given a lock count. The latest CompileJob holds 1 lock and every DreamDaemon instance running that CompileJob holds another. Loading a new CompileJob releases the initial lock, and when all other locks are released the CompileJob's directory is deleted. Any directories in the `Game` folder not in use are also deleted when the Instance starts.
@section arch_chat Chat Bot System
@@ -118,7 +118,7 @@ The @ref Tgstation.Server.Host.Components.StaticFiles.IConfiguration object is a
@section arch_watchdog Watchdog
This is the core of tgstation-server, the component that starts, monitors, and updates DreamDaemon.
This is the core of tgstation-server, the component that starts, monitors, and updates DreamDaemon.
At it's core, the watchdog operates using a hot/cold server setup. At any given moment there are two DreamDaemon instances running, only one of which players can see. If anything bad happens to that server, it is killed and the inactive server has its port changed to catch all the connections. If any changes need to be made to the configuration (port, security, compile job), the inactive server is killed and immediately relaunched with the new configuration. Whenever the active server reboots, the two servers change ports so as to minimize load times.
@@ -128,9 +128,9 @@ That's a high level view of things, now let's get to the nitty gritty.
@subsection arch_wd_launch Launch
First the most recent @ref Tgstation.Server.Host.Components.Deployment.IDmbProvider is retrieved from the @ref Tgstation.Server.Host.Components.Deployment.IDmbFactory twice, adding 2 locks.
First the most recent @ref Tgstation.Server.Host.Components.Deployment.IDmbProvider is retrieved from the @ref Tgstation.Server.Host.Components.Deployment.IDmbFactory twice, adding 2 locks.
This is used to launch a @ref Tgstation.Server.Host.Components.Watchdog.ISessionController via the watchdog's @ref Tgstation.Server.Host.Components.Watchdog.ISessionControllerFactory in the `A` directory of dmb providers @ref Tgstation.Server.Host.Models.CompileJob . This will be designated the `Alpha` server.
This is used to launch a @ref Tgstation.Server.Host.Components.Watchdog.ISessionController via the watchdog's @ref Tgstation.Server.Host.Components.Watchdog.ISessionControllerFactory in the `A` directory of dmb providers @ref Tgstation.Server.Host.Models.CompileJob . This will be designated the `Alpha` server.
Whenever DreamDaemon is launched by any part of the watchdog, we try to elevate its process priority to the equivalent of Windows' `Above Normal`
@@ -140,7 +140,7 @@ If the watchdog ever enters a failure state it can't recover from, it kills both
@subsection arch_wd_monitor The Monitor
The monitor is responsible for handling every @ref Tgstation.Server.Host.Components.Watchdog.MonitorActivationReason . It sleeps until one of these things happen. If multiple things happen at once, they are processed in their order of declaration.
The monitor is responsible for handling every @ref Tgstation.Server.Host.Components.Watchdog.MonitorActivationReason . It sleeps until one of these things happen. If multiple things happen at once, they are processed in their order of declaration.
The monitor maintains a @ref Tgstation.Server.Host.Components.Watchdog.MonitorState which helps it make descisions on how to handle activation reasons. The @ref Tgstation.Server.Host.Components.Watchdog.MonitorState.NextAction determines how multiple simultaneous events are handled in succession.
+6 -5
View File
@@ -3,7 +3,7 @@
@tableofcontents
@section new_features New in V4
@section new_features New since TGS3 (Rewrite)
- Agnostic HTTP API: The replaces the WCF service calls used in TGS3. This helps avoid Windows vendor lock-in and get away from the SOAP API that literally no one understood (not even me). With it, it's much easier to expose TGS to the internet, all you need is a HTTPS reverse proxy in front of it. A rundown of the new API exists here: https://tgstation.github.io/tgstation-server/api.html.
- Granular Access Controls: Windows users are no longer (required to be) the basis for authentication to the server. We now have database-backed users as a login option. These use a combined Basic/JWT authentication scheme with industry standard password hashing and salting. Users are fully customizable and can be given granular access to every bit of the server via the new permissions system. From changing the BYOND version, to test merging a PR, to restarting the server, every action may now be granted or revoked on a per user basis.
@@ -11,8 +11,8 @@
- Proper Long Running Operation Support: Server actions take a long time, from a git pull to a DreamMaker compile. TGS now internally allows for them to be run in parallel with each other and provides an audit record via the database. This is an improvement over the old system where connections had to be held open for the duration of operations.
- Database Backend: TGS requires an SQL database to operate. This allows for much better concurrency and is just overall much cleaner than the old single json file storage blob per instance.
- Limitation: There is a one-to-one relationship with a TGS server and a database. **DO NOT SHARE TGS DATABASES**.
- Linux/Docker Support: TGS4 is Linux and docker compatible. (Note this does not mean that rust-g and BSQL work out of the box, they must be compiled using event scripts like PreCompile.sh).
- Limitation: TGS4 has a dependency on the native library libgit2 which is known to cause issues on Linux. The binaries distributed with TGS are kept up to date with the upstream repository, but out of the box Linux support can't be assured in every environment. Docker is guaranteed to always work, however. See the repository for the distributed binary here: https://github.com/libgit2/libgit2sharp.nativebinaries.
- Linux/Docker Support: TGS is Linux and docker compatible. (Note this does not mean that rust-g and BSQL work out of the box, they must be compiled using event scripts like PreCompile.sh).
- Limitation: TGS has a dependency on the native library libgit2 which is known to cause issues on Linux. The binaries distributed with TGS are kept up to date with the upstream repository, but out of the box Linux support can't be assured in every environment. Docker is guaranteed to always work, however. See the repository for the distributed binary here: https://github.com/libgit2/libgit2sharp.nativebinaries.
- Limitation: System based logins are not supported on Linux. https://github.com/tgstation/tgstation-server/issues/709
- Incredibly Detailed Logging: Various log levels exist now (Trace/Debug/Info/Warning/Error/Critical) and are sanely output to a rolling file on the host. Significant improvement over having to use the Windows event viewer with TGS3. Until such a point where bugs stop copping up I'd recommend Trace logging for the main log level.
- Historical Deployment Data: Every time code is compiled the following data is logged and stored.
@@ -28,8 +28,9 @@
- Watchdog Heartbeats: An interval in seconds can now be set at which TGS will send /world/Topic() packets to DreamDaemon. If four of these are missed, the server will be rebooted. No more endless @Key Holder pings in discord (and I can finally unmute the /tg/ guild)! This feature can be disabled.
- Better DMAPI: No longer requires injecting a .NET runtime .dll into the DreamDaemon process. DD -> TGS communication is now handled securely via BYOND's native /world/Export() API ("But Cyberboss, BYOND only supports GET requests." Who said anything about respecting HTTP standards when dealing with BYOND?).
- Safe/Ultrasafe Security Support: Thanks to the new DMAPI, the ultrasafe and safe security levels may be used without running into BYOND's limitations. But no one really cares...
- Self Upgrading: To upgrade TGS3 you needed to download and run the installer. This was pretty seamless, but it's now even better in V4 as the command to upgrade can be given straight to the API. At that point the server will handle downloading the update, detaching running DreamDaemon instances, restarting with the new version, and reattaching to them. Easier than ever patch delivery.
- *Gasp* TESTING: TGS4 currently has over 60% code coverage in automated unit and full stack integration tests. I aim to have that number ever increasing to prevent trivial mistakes. Big improvement over V3 which had... literally none...
- Private/Invisible visibility Support: Stored per instance.
- Self Upgrading: To upgrade TGS3 you needed to download and run the installer. This was pretty seamless, but it's now even better in versions >=4 as the command to upgrade can be given straight to the API. At that point the server will handle downloading the update, detaching running DreamDaemon instances, restarting with the new version, and reattaching to them. Easier than ever patch delivery.
- *Gasp* TESTING: TGS currently has over 60% code coverage in automated unit and full stack integration tests. I aim to have that number ever increasing to prevent trivial mistakes. Big improvement over V3 which had... literally none...
Along with these features, nearly every single V3 feature has been included and possibly improved in some fashion. This includes stuff like Windows accounts for logins, and using ACLs for static file handling. The following exceptions exist but are planned for future updates:
- Process memory/CPU diagnostic data is not generated: https://github.com/tgstation/tgstation-server/issues/611
+1 -1
View File
@@ -7,7 +7,7 @@ This folder should be placed on it's own inside a codebase that wishes to use th
- The other versioned folders contain code for the different DMAPI versions.
- [v3210](./v3210) contains the final TGS3 API.
- [v4](./v4) is the legacy DMAPI 4 (Used in TGS 4.0.X versions).
- [v5](./v5) is the current DMAPI version used by TGS4 >=4.1.
- [v5](./v5) is the current DMAPI version used by TGS >=4.1.
- [LICENSE](./LICENSE) is the MIT license for the DMAPI.
APIs communicate with TGS in two ways. All versions implement TGS -> DM communication using /world/Topic. DM -> TGS communication, called the bridge method, is different for each version.
+1 -1
View File
@@ -269,7 +269,7 @@
if(!result)
return
//okay so the standard TGS4 proceedure is: right before rebooting change the port to whatever was sent to us in the above json's data parameter
//okay so the standard TGS proceedure is: right before rebooting change the port to whatever was sent to us in the above json's data parameter
var/port = result[DMAPI5_BRIDGE_RESPONSE_NEW_PORT]
if(!isnum(port))
+4 -4
View File
@@ -48,7 +48,7 @@ namespace Tgstation.Server.Api.Models
/// <summary>
/// Generic database integrity failure.
/// </summary>
[Description("The operation could not be performed as it would violate database integrity. Please retry the request, making sure to not duplicate field names with existing entities.")]
[Description("The operation could not be performed as it would violate database integrity. Please retry the request, making sure to not duplicate field names with existing entities!")]
DatabaseIntegrityConflict,
/// <summary>
@@ -58,9 +58,9 @@ namespace Tgstation.Server.Api.Models
MissingHostWatchdog,
/// <summary>
/// Attempted to change to a suite other than TGS4.
/// Attempted to change to a major version less than 4.
/// </summary>
[Description("Cannot update to a different tgstation-server suite version.")]
[Description("Cannot downgrade to tgstation-server major version <4!")]
CannotChangeServerSuite,
/// <summary>
@@ -72,7 +72,7 @@ namespace Tgstation.Server.Api.Models
/// <summary>
/// A server update was requested while another was in progress.
/// </summary>
[Description("A server update was requested while another was in progress")]
[Description("A server update was requested while another was in progress!")]
ServerUpdateInProgress,
/// <summary>
@@ -21,5 +21,11 @@
/// </summary>
[ResponseOptions]
public int? Progress { get; set; }
/// <summary>
/// Optional description of the job's current .
/// </summary>
[ResponseOptions]
public string? Stage { get; set; }
}
}
+2 -2
View File
@@ -124,8 +124,8 @@ namespace Tgstation.Server.Host.Service
processInstaller.Account = ServiceAccount.LocalSystem;
installer.Context = new InstallContext("tgs-4-install.log", new string[] { String.Format(CultureInfo.InvariantCulture, "/assemblypath={0}", Assembly.GetEntryAssembly().Location) });
installer.Description = "/tg/station 13 server v4 running as a windows service";
installer.DisplayName = "/tg/station server 4";
installer.Description = "/tg/station 13 server running as a windows service";
installer.DisplayName = "/tg/station server";
installer.DelayedAutoStart = true;
installer.StartType = ServiceStartMode.Automatic;
installer.ServicesDependedOn = new string[] { "Tcpip", "Dhcp", "Dnscache" };
@@ -113,6 +113,11 @@ namespace Tgstation.Server.Host.Components.Deployment
/// </summary>
string currentDreamMakerOutput;
/// <summary>
/// Current stage to report on the job.
/// </summary>
string currentStage;
/// <summary>
/// If a compile job is running.
/// </summary>
@@ -178,7 +183,7 @@ namespace Tgstation.Server.Host.Components.Deployment
public async Task DeploymentProcess(
Models.Job job,
IDatabaseContextFactory databaseContextFactory,
Action<int> progressReporter,
JobProgressReporter progressReporter,
CancellationToken cancellationToken)
{
if (job == null)
@@ -450,7 +455,7 @@ namespace Tgstation.Server.Host.Components.Deployment
/// <param name="apiValidateTimeout">The API validation timeout.</param>
/// <param name="repository">The <see cref="IRepository"/>.</param>
/// <param name="remoteDeploymentManager">The <see cref="IRemoteDeploymentManager"/>.</param>
/// <param name="progressReporter">The progress reporting <see cref="Action{T}"/>.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
/// <param name="estimatedDuration">The optional estimated <see cref="TimeSpan"/> of the compilation.</param>
/// <param name="localCommitExistsOnRemote">Whether or not the <paramref name="repository"/>'s current commit exists on the remote repository.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
@@ -461,7 +466,7 @@ namespace Tgstation.Server.Host.Components.Deployment
uint apiValidateTimeout,
IRepository repository,
IRemoteDeploymentManager remoteDeploymentManager,
Action<int> progressReporter,
JobProgressReporter progressReporter,
TimeSpan? estimatedDuration,
bool localCommitExistsOnRemote,
CancellationToken cancellationToken)
@@ -469,7 +474,9 @@ namespace Tgstation.Server.Host.Components.Deployment
logger.LogTrace("Begin Compile");
using var progressCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
var progressTask = estimatedDuration.HasValue ? ProgressTask(progressReporter, estimatedDuration.Value, progressCts.Token) : Task.CompletedTask;
currentStage = "Reserving BYOND version";
var progressTask = ProgressTask(progressReporter, estimatedDuration, progressCts.Token);
try
{
using var byondLock = await byond.UseExecutables(null, cancellationToken).ConfigureAwait(false);
@@ -490,6 +497,7 @@ namespace Tgstation.Server.Host.Components.Deployment
RepositoryOrigin = repository.Origin.ToString(),
};
currentStage = "Creating remote deployment notification";
await remoteDeploymentManager.StartDeployment(
repository,
job,
@@ -525,6 +533,7 @@ namespace Tgstation.Server.Host.Components.Deployment
catch (OperationCanceledException)
{
// DCT: Cancellation token is for job, delaying here is fine
currentStage = "Running CompileCancelled event";
await eventConsumer.HandleEvent(EventType.CompileCancelled, Enumerable.Empty<string>(), default).ConfigureAwait(false);
throw;
}
@@ -561,7 +570,8 @@ namespace Tgstation.Server.Host.Components.Deployment
try
{
// copy the repository
logger.LogTrace("Copying repository to game directory...");
logger.LogTrace("Copying repository to game directory");
currentStage = "Copying repository";
var resolvedOutputDirectory = ioManager.ResolvePath(outputDirectory);
var repoOrigin = repository.Origin;
using (repository)
@@ -570,6 +580,7 @@ namespace Tgstation.Server.Host.Components.Deployment
// repository closed now
// run precompile scripts
currentStage = "Running PreCompile event";
await eventConsumer.HandleEvent(
EventType.CompileStart,
new List<string>
@@ -582,9 +593,10 @@ namespace Tgstation.Server.Host.Components.Deployment
.ConfigureAwait(false);
// determine the dme
currentStage = "Determining .dme";
if (job.DmeName == null)
{
logger.LogTrace("Searching for available .dmes...");
logger.LogTrace("Searching for available .dmes");
var foundPaths = await ioManager.GetFilesWithExtension(resolvedOutputDirectory, DmeExtension, true, cancellationToken).ConfigureAwait(false);
var foundPath = foundPaths.FirstOrDefault();
if (foundPath == default)
@@ -603,9 +615,11 @@ namespace Tgstation.Server.Host.Components.Deployment
logger.LogDebug("Selected {0}.dme for compilation!", job.DmeName);
currentStage = "Modifying .dme";
await ModifyDme(job, cancellationToken).ConfigureAwait(false);
// run precompile scripts
currentStage = "Running PreDreamMaker event";
await eventConsumer.HandleEvent(
EventType.PreDreamMaker,
new List<string>
@@ -618,6 +632,7 @@ namespace Tgstation.Server.Host.Components.Deployment
.ConfigureAwait(false);
// run compiler
currentStage = "Running DreamMaker";
var exitCode = await RunDreamMaker(byondLock.DreamMakerPath, job, cancellationToken).ConfigureAwait(false);
// verify api
@@ -628,6 +643,7 @@ namespace Tgstation.Server.Host.Components.Deployment
ErrorCode.DreamMakerExitCode,
new JobException($"Exit code: {exitCode}{Environment.NewLine}{Environment.NewLine}{job.Output}"));
currentStage = "Validating DMAPI";
await VerifyApi(
apiValidateTimeout,
dreamMakerSettings.ApiValidationSecurityLevel.Value,
@@ -641,6 +657,7 @@ namespace Tgstation.Server.Host.Components.Deployment
catch (JobException)
{
// DD never validated or compile failed
currentStage = "Running CompileFailure event";
await eventConsumer.HandleEvent(
EventType.CompileFailure,
new List<string>
@@ -654,6 +671,7 @@ namespace Tgstation.Server.Host.Components.Deployment
throw;
}
currentStage = "Running CompileComplete event";
await eventConsumer.HandleEvent(
EventType.CompileComplete,
new List<string>
@@ -665,6 +683,7 @@ namespace Tgstation.Server.Host.Components.Deployment
.ConfigureAwait(false);
logger.LogTrace("Applying static game file symlinks...");
currentStage = "Symlinking GameStaticFiles";
// symlink in the static data
await configuration.SymlinkStaticFilesTo(resolvedOutputDirectory, cancellationToken).ConfigureAwait(false);
@@ -673,6 +692,7 @@ namespace Tgstation.Server.Host.Components.Deployment
}
catch (Exception ex)
{
currentStage = "Cleaning output directory";
await CleanupFailedCompile(job, remoteDeploymentManager, ex).ConfigureAwait(false);
throw;
}
@@ -681,22 +701,22 @@ namespace Tgstation.Server.Host.Components.Deployment
/// <summary>
/// Gradually triggers a given <paramref name="progressReporter"/> over a given <paramref name="estimatedDuration"/>.
/// </summary>
/// <param name="progressReporter">The <see cref="Action{T1}"/> to report progress.</param>
/// <param name="estimatedDuration">A <see cref="TimeSpan"/> representing the duration to give progress over.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
/// <param name="estimatedDuration">A <see cref="TimeSpan"/> representing the duration to give progress over if any.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
async Task ProgressTask(Action<int> progressReporter, TimeSpan estimatedDuration, CancellationToken cancellationToken)
async Task ProgressTask(JobProgressReporter progressReporter, TimeSpan? estimatedDuration, CancellationToken cancellationToken)
{
progressReporter(0);
var sleepInterval = estimatedDuration / 100;
progressReporter(currentStage, estimatedDuration.HasValue ? (int?)0 : null);
var sleepInterval = estimatedDuration.HasValue ? estimatedDuration.Value / 100 : TimeSpan.FromMilliseconds(250);
logger.LogDebug("Compile is expected to take: {0}", estimatedDuration);
try
{
for (var iteration = 0; iteration < 99; ++iteration)
for (var iteration = 0; iteration < (estimatedDuration.HasValue ? 99 : Int32.MaxValue); ++iteration)
{
await Task.Delay(sleepInterval, cancellationToken).ConfigureAwait(false);
progressReporter(iteration + 1);
progressReporter(currentStage, estimatedDuration.HasValue ? (int?)(iteration + 1) : null);
}
}
catch (OperationCanceledException)
@@ -1,8 +1,8 @@
using System;
using System.Threading;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Jobs;
using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Components.Deployment
@@ -13,17 +13,17 @@ namespace Tgstation.Server.Host.Components.Deployment
public interface IDreamMaker
{
/// <summary>
/// Create and a compile job and insert it into the database. Meant to be called by a <see cref="Jobs.IJobManager"/>.
/// Create and a compile job and insert it into the database. Meant to be called by a <see cref="IJobManager"/>.
/// </summary>
/// <param name="job">The running <see cref="Job"/>.</param>
/// <param name="databaseContextFactory">The <see cref="IDatabaseContextFactory"/> for the operation.</param>
/// <param name="progressReporter">The <see cref="Action{T1}"/> to report compilation progress.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report compilation progress.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
Task DeploymentProcess(
Job job,
IDatabaseContextFactory databaseContextFactory,
Action<int> progressReporter,
JobProgressReporter progressReporter,
CancellationToken cancellationToken);
}
}
@@ -256,7 +256,7 @@ namespace Tgstation.Server.Host.Components
IInstanceCore core,
IDatabaseContextFactory databaseContextFactory,
Job job,
Action<int> progressReporter,
JobProgressReporter progressReporter,
CancellationToken cancellationToken)
=> databaseContextFactory.UseContext(
async databaseContext =>
@@ -277,11 +277,11 @@ namespace Tgstation.Server.Host.Components
const int NumSteps = 3;
var doneSteps = 0;
Action<int> NextProgressReporter()
JobProgressReporter NextProgressReporter()
{
var tmpDoneSteps = doneSteps;
++doneSteps;
return progress => progressReporter((progress + (100 * tmpDoneSteps)) / NumSteps);
return (status, progress) => progressReporter(status, (progress + (100 * tmpDoneSteps)) / NumSteps);
}
using var repo = await RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false);
@@ -466,7 +466,7 @@ namespace Tgstation.Server.Host.Components
throw;
}
progressReporter(5 * ProgressStep);
progressReporter(null, 5 * ProgressStep);
});
#pragma warning restore CA1502 // Cyclomatic complexity
@@ -3,6 +3,7 @@ using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Jobs;
namespace Tgstation.Server.Host.Components.Repository
{
@@ -46,7 +47,7 @@ namespace Tgstation.Server.Host.Components.Repository
/// <param name="username">The username used for fetching from submodule repositories.</param>
/// <param name="password">The password used for fetching from submodule repositories.</param>
/// <param name="updateSubmodules">If a submodule update should be attempted after the merge.</param>
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
Task CheckoutObject(
@@ -54,7 +55,7 @@ namespace Tgstation.Server.Host.Components.Repository
string username,
string password,
bool updateSubmodules,
Action<int> progressReporter,
JobProgressReporter progressReporter,
CancellationToken cancellationToken);
/// <summary>
@@ -66,7 +67,7 @@ namespace Tgstation.Server.Host.Components.Repository
/// <param name="username">The username used to fetch from the origin and submodule repositories.</param>
/// <param name="password">The password used to fetch from the origin and submodule repositories.</param>
/// <param name="updateSubmodules">If a submodule update should be attempted after the merge.</param>
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="Nullable{T}"/> <see cref="bool"/> representing the merge result that is <see langword="true"/> after a fast forward or up to date, <see langword="false"/> on a non-fast-forward, <see langword="null"/> on a conflict.</returns>
Task<bool?> AddTestMerge(
@@ -76,7 +77,7 @@ namespace Tgstation.Server.Host.Components.Repository
string username,
string password,
bool updateSubmodules,
Action<int> progressReporter,
JobProgressReporter progressReporter,
CancellationToken cancellationToken);
/// <summary>
@@ -84,10 +85,14 @@ namespace Tgstation.Server.Host.Components.Repository
/// </summary>
/// <param name="username">The username to fetch from the origin repository.</param>
/// <param name="password">The password to fetch from the origin repository.</param>
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
Task FetchOrigin(string username, string password, Action<int> progressReporter, CancellationToken cancellationToken);
Task FetchOrigin(
string username,
string password,
JobProgressReporter progressReporter,
CancellationToken cancellationToken);
/// <summary>
/// Requires the current HEAD to be a tracked reference. Hard resets the reference to what it tracks on the origin repository.
@@ -95,34 +100,34 @@ namespace Tgstation.Server.Host.Components.Repository
/// <param name="username">The username used for fetching from submodule repositories.</param>
/// <param name="password">The password used for fetching from submodule repositories.</param>
/// <param name="updateSubmodules">If a submodule update should be attempted after the merge.</param>
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the SHA of the new HEAD.</returns>
Task ResetToOrigin(
string username,
string password,
bool updateSubmodules,
Action<int> progressReporter,
JobProgressReporter progressReporter,
CancellationToken cancellationToken);
/// <summary>
/// Requires the current HEAD to be a reference. Hard resets the reference to the given sha.
/// </summary>
/// <param name="sha">The sha hash to reset to.</param>
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the SHA of the new HEAD.</returns>
Task ResetToSha(string sha, Action<int> progressReporter, CancellationToken cancellationToken);
Task ResetToSha(string sha, JobProgressReporter progressReporter, CancellationToken cancellationToken);
/// <summary>
/// Requires the current HEAD to be a tracked reference. Merges the reference to what it tracks on the origin repository.
/// </summary>
/// <param name="committerName">The name of the merge committer.</param>
/// <param name="committerEmail">The e-mail of the merge committer.</param>
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="Nullable{T}"/> <see cref="bool"/> representing the merge result that is <see langword="true"/> after a fast forward, <see langword="false"/> on a merge or up to date, <see langword="null"/> on a conflict.</returns>
Task<bool?> MergeOrigin(string committerName, string committerEmail, Action<int> progressReporter, CancellationToken cancellationToken);
Task<bool?> MergeOrigin(string committerName, string committerEmail, JobProgressReporter progressReporter, CancellationToken cancellationToken);
/// <summary>
/// Runs the synchronize event script and attempts to push any changes made to the <see cref="IRepository"/> if on a tracked branch.
@@ -131,11 +136,18 @@ namespace Tgstation.Server.Host.Components.Repository
/// <param name="password">The password to fetch from the origin repository.</param>
/// <param name="committerName">The name of the potential committer.</param>
/// <param name="committerEmail">The e-mail of the potential committer.</param>
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
/// <param name="synchronizeTrackedBranch">If the synchronizations should be made to the tracked reference as opposed to a temporary branch.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="true"/> if commits were pushed to the tracked origin reference, <see langword="false"/> otherwise.</returns>
Task<bool> Sychronize(string username, string password, string committerName, string committerEmail, Action<int> progressReporter, bool synchronizeTrackedBranch, CancellationToken cancellationToken);
Task<bool> Sychronize(
string username,
string password,
string committerName,
string committerEmail,
JobProgressReporter progressReporter,
bool synchronizeTrackedBranch,
CancellationToken cancellationToken);
/// <summary>
/// Copies the current working directory to a given <paramref name="path"/>.
@@ -2,6 +2,8 @@
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Host.Jobs;
namespace Tgstation.Server.Host.Components.Repository
{
/// <summary>
@@ -15,7 +17,7 @@ namespace Tgstation.Server.Host.Components.Repository
bool InUse { get; }
/// <summary>
/// If a <see cref="CloneRepository(Uri, string, string, string, Action{int}, bool, CancellationToken)"/> operation is in progress.
/// If a <see cref="CloneRepository(Uri, string, string, string, JobProgressReporter, bool, CancellationToken)"/> operation is in progress.
/// </summary>
bool CloneInProgress { get; }
@@ -33,7 +35,7 @@ namespace Tgstation.Server.Host.Components.Repository
/// <param name="initialBranch">The branch to clone.</param>
/// <param name="username">The username to clone from <paramref name="url"/>.</param>
/// <param name="password">The password to clone from <paramref name="url"/>.</param>
/// <param name="progressReporter">A function to report 0-100 progress of the clone.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> for progress of the clone.</param>
/// <param name="recurseSubmodules">If submodules should be recusively cloned and initialized.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>The newly cloned <see cref="IRepository"/>, <see langword="null"/> if one already exists.</returns>
@@ -42,7 +44,7 @@ namespace Tgstation.Server.Host.Components.Repository
string initialBranch,
string username,
string password,
Action<int> progressReporter,
JobProgressReporter progressReporter,
bool recurseSubmodules,
CancellationToken cancellationToken);
@@ -114,20 +114,22 @@ namespace Tgstation.Server.Host.Components.Repository
/// <summary>
/// Converts a given <paramref name="progressReporter"/> to a <see cref="LibGit2Sharp.Handlers.CheckoutProgressHandler"/>.
/// </summary>
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> of the operation.</param>
/// <param name="stage">The stage argument for <paramref name="progressReporter"/>.</param>
/// <returns>A <see cref="LibGit2Sharp.Handlers.CheckoutProgressHandler"/> based on <paramref name="progressReporter"/>.</returns>
static CheckoutProgressHandler CheckoutProgressHandler(Action<int> progressReporter) => (a, completedSteps, totalSteps) => progressReporter((int)(((float)completedSteps) / totalSteps * 100));
static CheckoutProgressHandler CheckoutProgressHandler(JobProgressReporter progressReporter, string stage) => (a, completedSteps, totalSteps) => progressReporter(stage, (int)(((float)completedSteps) / totalSteps * 100));
/// <summary>
/// Generate a <see cref="LibGit2Sharp.Handlers.TransferProgressHandler"/> from a given <paramref name="progressReporter"/> and <paramref name="cancellationToken"/>.
/// </summary>
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> of the operation.</param>
/// <param name="stage">The stage argument for <paramref name="progressReporter"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A new <see cref="LibGit2Sharp.Handlers.TransferProgressHandler"/> based on <paramref name="progressReporter"/>.</returns>
static TransferProgressHandler TransferProgressHandler(Action<int> progressReporter, CancellationToken cancellationToken) => (transferProgress) =>
static TransferProgressHandler TransferProgressHandler(JobProgressReporter progressReporter, string stage, CancellationToken cancellationToken) => (transferProgress) =>
{
var percentage = 100 * (((float)transferProgress.IndexedObjects + transferProgress.ReceivedObjects) / (transferProgress.TotalObjects * 2));
progressReporter((int)percentage);
progressReporter(stage, (int)percentage);
return !cancellationToken.IsCancellationRequested;
};
@@ -204,7 +206,7 @@ namespace Tgstation.Server.Host.Components.Repository
string username,
string password,
bool updateSubmodules,
Action<int> progressReporter,
JobProgressReporter progressReporter,
CancellationToken cancellationToken)
{
if (testMergeParameters == null)
@@ -258,7 +260,8 @@ namespace Tgstation.Server.Host.Components.Repository
logger.LogTrace("Fetching refspec {0}...", refSpec);
var remote = libGitRepo.Network.Remotes.First();
progressReporter(0);
var stage = $"Fetch {refSpec}";
progressReporter(stage, 0);
commands.Fetch(
libGitRepo,
refSpecList,
@@ -267,7 +270,10 @@ namespace Tgstation.Server.Host.Components.Repository
{
Prune = true,
OnProgress = (a) => !cancellationToken.IsCancellationRequested,
OnTransferProgress = TransferProgressHandler(percentage => progressReporter(percentage / 2), cancellationToken),
OnTransferProgress = TransferProgressHandler(
(lambdaStage, progress) => progressReporter(lambdaStage, progress / 2),
stage,
cancellationToken),
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password),
},
@@ -304,7 +310,9 @@ namespace Tgstation.Server.Host.Components.Repository
FailOnConflict = true,
FastForwardStrategy = FastForwardStrategy.NoFastForward,
SkipReuc = true,
OnCheckoutProgress = CheckoutProgressHandler(percentage => progressReporter(50 + (percentage / 2))),
OnCheckoutProgress = CheckoutProgressHandler(
(lambdaStage, progress) => progressReporter(lambdaStage, 50 + (progress / 2)),
$"Merge {testMergeParameters.TargetCommitSha}"),
});
}
finally
@@ -359,7 +367,11 @@ namespace Tgstation.Server.Host.Components.Repository
.ConfigureAwait(false);
if (updateSubmodules)
await UpdateSubmodules(percentage => progressReporter(66 + (percentage / 3)), username, password, cancellationToken).ConfigureAwait(false);
await UpdateSubmodules(
(stage, progress) => progressReporter(stage, 66 + (progress.Value / 3)),
username,
password,
cancellationToken).ConfigureAwait(false);
}
await eventConsumer.HandleEvent(
@@ -383,7 +395,7 @@ namespace Tgstation.Server.Host.Components.Repository
string username,
string password,
bool updateSubmodules,
Action<int> progressReporter,
JobProgressReporter progressReporter,
CancellationToken cancellationToken)
{
if (committish == null)
@@ -396,7 +408,10 @@ namespace Tgstation.Server.Host.Components.Repository
() =>
{
libGitRepo.RemoveUntrackedFiles();
RawCheckout(committish, percentage => progressReporter(percentage * (updateSubmodules ? 2 : 3) / 3), cancellationToken);
RawCheckout(
committish,
(stage, progress) => progressReporter(stage, progress * (updateSubmodules ? 2 : 3) / 3),
cancellationToken);
},
cancellationToken,
DefaultIOManager.BlockingTaskCreationOptions,
@@ -404,11 +419,15 @@ namespace Tgstation.Server.Host.Components.Repository
.ConfigureAwait(false);
if (updateSubmodules)
await UpdateSubmodules(percentage => progressReporter(66 + (percentage / 3)), username, password, cancellationToken).ConfigureAwait(false);
await UpdateSubmodules(
(stage, progress) => progressReporter(stage, 66 + (progress / 3)),
username,
password,
cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc />
public async Task FetchOrigin(string username, string password, Action<int> progressReporter, CancellationToken cancellationToken)
public async Task FetchOrigin(string username, string password, JobProgressReporter progressReporter, CancellationToken cancellationToken)
{
if (progressReporter == null)
throw new ArgumentNullException(nameof(progressReporter));
@@ -430,7 +449,7 @@ namespace Tgstation.Server.Host.Components.Repository
{
Prune = true,
OnProgress = (a) => !cancellationToken.IsCancellationRequested,
OnTransferProgress = TransferProgressHandler(progressReporter, cancellationToken),
OnTransferProgress = TransferProgressHandler(progressReporter, "Fetch Origin", cancellationToken),
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password),
},
@@ -452,7 +471,12 @@ namespace Tgstation.Server.Host.Components.Repository
}
/// <inheritdoc />
public async Task ResetToOrigin(string username, string password, bool updateSubmodules, Action<int> progressReporter, CancellationToken cancellationToken)
public async Task ResetToOrigin(
string username,
string password,
bool updateSubmodules,
JobProgressReporter progressReporter,
CancellationToken cancellationToken)
{
if (progressReporter == null)
throw new ArgumentNullException(nameof(progressReporter));
@@ -463,16 +487,16 @@ namespace Tgstation.Server.Host.Components.Repository
await eventConsumer.HandleEvent(EventType.RepoResetOrigin, new List<string> { trackedBranch.FriendlyName, trackedBranch.Tip.Sha }, cancellationToken).ConfigureAwait(false);
await ResetToSha(
trackedBranch.Tip.Sha,
percentage => progressReporter(percentage / (updateSubmodules ? 2 : 1)),
(stage, progress) => progressReporter(stage, progress / (updateSubmodules ? 2 : 1)),
cancellationToken)
.ConfigureAwait(false);
if (updateSubmodules)
await UpdateSubmodules(percentage => progressReporter(50 + (percentage / 2)), username, password, cancellationToken).ConfigureAwait(false);
await UpdateSubmodules((stage, progress) => progressReporter(stage, 50 + (progress / 2)), username, password, cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc />
public Task ResetToSha(string sha, Action<int> progressReporter, CancellationToken cancellationToken) => Task.Factory.StartNew(
public Task ResetToSha(string sha, JobProgressReporter progressReporter, CancellationToken cancellationToken) => Task.Factory.StartNew(
() =>
{
if (sha == null)
@@ -493,7 +517,7 @@ namespace Tgstation.Server.Host.Components.Repository
libGitRepo.Reset(ResetMode.Hard, gitObject.Peel<Commit>(), new CheckoutOptions
{
OnCheckoutProgress = CheckoutProgressHandler(progressReporter),
OnCheckoutProgress = CheckoutProgressHandler(progressReporter, $"Reset to {gitObject.Sha}"),
});
},
cancellationToken,
@@ -525,7 +549,11 @@ namespace Tgstation.Server.Host.Components.Repository
TaskScheduler.Current);
/// <inheritdoc />
public async Task<bool?> MergeOrigin(string committerName, string committerEmail, Action<int> progressReporter, CancellationToken cancellationToken)
public async Task<bool?> MergeOrigin(
string committerName,
string committerEmail,
JobProgressReporter progressReporter,
CancellationToken cancellationToken)
{
if (progressReporter == null)
throw new ArgumentNullException(nameof(progressReporter));
@@ -558,7 +586,7 @@ namespace Tgstation.Server.Host.Components.Repository
FailOnConflict = true,
FastForwardStrategy = FastForwardStrategy.Default,
SkipReuc = true,
OnCheckoutProgress = CheckoutProgressHandler(progressReporter),
OnCheckoutProgress = CheckoutProgressHandler(progressReporter, "Merge Origin"),
});
cancellationToken.ThrowIfCancellationRequested();
@@ -568,7 +596,7 @@ namespace Tgstation.Server.Host.Components.Repository
logger.LogDebug("Merge conflict, aborting and reverting to {0}", oldHead.FriendlyName);
libGitRepo.Reset(ResetMode.Hard, oldTip, new CheckoutOptions
{
OnCheckoutProgress = CheckoutProgressHandler(progressReporter),
OnCheckoutProgress = CheckoutProgressHandler(progressReporter, $"Hard Reset to {oldHead.FriendlyName}"),
});
cancellationToken.ThrowIfCancellationRequested();
}
@@ -595,7 +623,7 @@ namespace Tgstation.Server.Host.Components.Repository
string password,
string committerName,
string committerEmail,
Action<int> progressReporter,
JobProgressReporter progressReporter,
bool synchronizeTrackedBranch,
CancellationToken cancellationToken)
{
@@ -653,7 +681,7 @@ namespace Tgstation.Server.Host.Components.Repository
{
libGitRepo.Reset(ResetMode.Hard, libGitRepo.Head.Tip, new CheckoutOptions
{
OnCheckoutProgress = CheckoutProgressHandler(progress => progressReporter(progress / 10)),
OnCheckoutProgress = CheckoutProgressHandler((stage, progress) => progressReporter(stage, progress / 10), "Hard reset and remove untracked files"),
});
cancellationToken.ThrowIfCancellationRequested();
libGitRepo.RemoveUntrackedFiles();
@@ -664,7 +692,7 @@ namespace Tgstation.Server.Host.Components.Repository
.ConfigureAwait(false);
}
void FinalReporter(int progress) => progressReporter((int)(((float)progress) / 100 * 90));
void FinalReporter(string stage, int? progress) => progressReporter(stage, (int)(((float)progress) / 100 * 90));
if (!synchronizeTrackedBranch)
{
@@ -808,19 +836,20 @@ namespace Tgstation.Server.Host.Components.Repository
/// Runs a blocking force checkout to <paramref name="committish"/>.
/// </summary>
/// <param name="committish">The committish to checkout.</param>
/// <param name="progressReporter">Progress reporter <see cref="Action{T}"/>.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> for the operation.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
void RawCheckout(string committish, Action<int> progressReporter, CancellationToken cancellationToken)
void RawCheckout(string committish, JobProgressReporter progressReporter, CancellationToken cancellationToken)
{
logger.LogTrace("Checkout: {0}", committish);
progressReporter(0);
var stage = $"Checkout {committish}";
progressReporter(stage, 0);
cancellationToken.ThrowIfCancellationRequested();
var checkoutOptions = new CheckoutOptions
{
CheckoutModifiers = CheckoutModifiers.Force,
OnCheckoutProgress = CheckoutProgressHandler(progressReporter),
OnCheckoutProgress = CheckoutProgressHandler(progressReporter, stage),
};
void RunCheckout() => commands.Checkout(
@@ -860,10 +889,10 @@ namespace Tgstation.Server.Host.Components.Repository
/// </summary>
/// <param name="username">The username to fetch from the origin repository.</param>
/// <param name="password">The password to fetch from the origin repository.</param>
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation.</param>
/// <param name="progressReporter"><see cref="JobProgressReporter"/> of the operation.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
Task PushHeadToTemporaryBranch(string username, string password, Action<int> progressReporter, CancellationToken cancellationToken) => Task.Factory.StartNew(
Task PushHeadToTemporaryBranch(string username, string password, JobProgressReporter progressReporter, CancellationToken cancellationToken) => Task.Factory.StartNew(
() =>
{
logger.LogInformation("Pushing changes to temporary remote branch...");
@@ -875,9 +904,9 @@ namespace Tgstation.Server.Host.Components.Repository
try
{
var forcePushString = String.Format(CultureInfo.InvariantCulture, "+{0}:{0}", branch.CanonicalName);
libGitRepo.Network.Push(remote, forcePushString, GeneratePushOptions(progress => progressReporter((int)(0.9f * progress)), username, password, cancellationToken));
libGitRepo.Network.Push(remote, forcePushString, GeneratePushOptions((stage, progress) => progressReporter(stage, (int)(0.9f * progress)), username, password, cancellationToken));
var removalString = String.Format(CultureInfo.InvariantCulture, ":{0}", branch.CanonicalName);
libGitRepo.Network.Push(remote, removalString, GeneratePushOptions(progress => progressReporter(90 + (int)(0.1f * progress)), username, password, cancellationToken));
libGitRepo.Network.Push(remote, removalString, GeneratePushOptions((stage, progress) => progressReporter(stage, 90 + (int)(0.1f * progress)), username, password, cancellationToken));
}
catch (UserCancelledException)
{
@@ -900,23 +929,23 @@ namespace Tgstation.Server.Host.Components.Repository
/// <summary>
/// Generate a standard set of <see cref="PushOptions"/>.
/// </summary>
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation.</param>
/// <param name="progressReporter"><see cref="JobProgressReporter"/> of the operation.</param>
/// <param name="username">The username for the <see cref="credentialsProvider"/>.</param>
/// <param name="password">The password for the <see cref="credentialsProvider"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A new set of <see cref="PushOptions"/>.</returns>
PushOptions GeneratePushOptions(Action<int> progressReporter, string username, string password, CancellationToken cancellationToken) => new PushOptions
PushOptions GeneratePushOptions(JobProgressReporter progressReporter, string username, string password, CancellationToken cancellationToken) => new PushOptions
{
OnPackBuilderProgress = (stage, current, total) =>
{
var baseProgress = stage == PackBuilderStage.Counting ? 0 : 25;
progressReporter(baseProgress + ((int)(25 * ((float)current) / total)));
progressReporter("Push", baseProgress + ((int)(25 * ((float)current) / total)));
return !cancellationToken.IsCancellationRequested;
},
OnNegotiationCompletedBeforePush = (a) => !cancellationToken.IsCancellationRequested,
OnPushTransferProgress = (a, sentBytes, totalBytes) =>
{
progressReporter(50 + ((int)(50 * ((float)sentBytes) / totalBytes)));
progressReporter("Push", 50 + ((int)(50 * ((float)sentBytes) / totalBytes)));
return !cancellationToken.IsCancellationRequested;
},
CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password),
@@ -925,12 +954,12 @@ namespace Tgstation.Server.Host.Components.Repository
/// <summary>
/// Recusively update all <see cref="Submodule"/>s in the <see cref="libGitRepo"/>.
/// </summary>
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation.</param>
/// <param name="progressReporter"><see cref="JobProgressReporter"/> of the operation.</param>
/// <param name="username">The username for the <see cref="credentialsProvider"/>.</param>
/// <param name="password">The password for the <see cref="credentialsProvider"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
async Task UpdateSubmodules(Action<int> progressReporter, string username, string password, CancellationToken cancellationToken)
async Task UpdateSubmodules(JobProgressReporter progressReporter, string username, string password, CancellationToken cancellationToken)
{
var submoduleCount = libGitRepo.Submodules.Count();
if (submoduleCount == 0)
@@ -945,15 +974,20 @@ namespace Tgstation.Server.Host.Components.Repository
var factor = 100 / submoduleCount;
foreach (var submodule in libGitRepo.Submodules)
{
void LocalProgressReporter(int percentage) => progressReporter((iteration * factor) + (percentage / submoduleCount));
void LocalProgressReporter(string stage, int percentage) => progressReporter(stage, (iteration * factor) + (percentage / submoduleCount));
var submoduleUpdateOptions = new SubmoduleUpdateOptions
{
Init = true,
OnTransferProgress = TransferProgressHandler(percentage => LocalProgressReporter(percentage / 2), cancellationToken),
OnTransferProgress = TransferProgressHandler(
(stage, progress) => LocalProgressReporter(stage, progress.Value / 2),
$"Fetch submodule {submodule.Name}",
cancellationToken),
OnProgress = output => !cancellationToken.IsCancellationRequested,
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password),
OnCheckoutProgress = CheckoutProgressHandler(percentage => LocalProgressReporter(50 + (percentage / 2))),
OnCheckoutProgress = CheckoutProgressHandler(
(stage, progress) => LocalProgressReporter(stage, 50 + (progress.Value / 2)),
$"Checkout submodule {submodule.Name}"),
};
logger.LogDebug("Updating submodule {0}...", submodule.Name);
@@ -104,7 +104,7 @@ namespace Tgstation.Server.Host.Components.Repository
string initialBranch,
string username,
string password,
Action<int> progressReporter,
JobProgressReporter progressReporter,
bool recurseSubmodules,
CancellationToken cancellationToken)
{
@@ -136,7 +136,7 @@ namespace Tgstation.Server.Host.Components.Repository
OnTransferProgress = (a) =>
{
var percentage = 100 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2));
progressReporter((int)percentage);
progressReporter("Cloning", (int)percentage);
return !cancellationToken.IsCancellationRequested;
},
RecurseSubmodules = recurseSubmodules,
@@ -166,7 +166,7 @@ namespace Tgstation.Server.Host.Controllers
foreach (var release in releases)
if (Version.TryParse(release.TagName.Replace(updatesConfiguration.GitTagPrefix, String.Empty, StringComparison.Ordinal), out var version)
&& version.Major == assemblyInformationProvider.Version.Major
&& version.Major > 3 // Forward/backward compatible but not before TGS4
&& (greatestVersion == null || version > greatestVersion))
greatestVersion = version;
repoUrl = new Uri((await repositoryTask.ConfigureAwait(false)).HtmlUrl);
@@ -225,7 +225,7 @@ namespace Tgstation.Server.Host.Controllers
AdditionalData = "newVersion is required!",
});
if (model.NewVersion.Major != assemblyInformationProvider.Version.Major)
if (model.NewVersion.Major < 3)
return BadRequest(new ErrorMessageResponse(ErrorCode.CannotChangeServerSuite));
if (!serverControl.WatchdogPresent)
@@ -218,7 +218,7 @@ namespace Tgstation.Server.Host.Controllers
{
if (ApiHeaders == null)
{
Response.Headers.Add(HeaderNames.WWWAuthenticate, new StringValues("basic realm=\"Create TGS4 bearer token\""));
Response.Headers.Add(HeaderNames.WWWAuthenticate, new StringValues("basic realm=\"Create TGS bearer token\""));
return HeadersIssue(false);
}
@@ -167,7 +167,7 @@ namespace Tgstation.Server.Host.Controllers
if (job == default)
return NotFound();
var api = job.ToApi();
api.Progress = jobManager.JobProgress(job);
jobManager.SetJobProgress(api);
return Json(api);
}
@@ -178,7 +178,7 @@ namespace Tgstation.Server.Host.Controllers
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
private Task AddJobProgressResponseTransformer(JobResponse jobResponse)
{
jobResponse.Progress = jobManager.JobProgress(jobResponse);
jobManager.SetJobProgress(jobResponse);
return Task.CompletedTask;
}
}
@@ -454,7 +454,7 @@ namespace Tgstation.Server.Host.Controllers
async Task<IActionResult> RepositoryUpdateJobOhGodPleaseSomeoneRefactorThisItsTooFuckingBig(
IInstanceCore instance,
IDatabaseContextFactory databaseContextFactory,
Action<int> progressReporter,
JobProgressReporter progressReporter,
CancellationToken ct)
{
var repoManager = instance.RepositoryManager;
@@ -480,14 +480,14 @@ namespace Tgstation.Server.Host.Controllers
var numSteps = (model.NewTestMerges?.Count ?? 0) + (model.UpdateFromOrigin == true ? 1 : 0) + (!modelHasShaOrReference ? 2 : (hardResettingToOriginReference ? 3 : 1));
var doneSteps = 0;
Action<int> NextProgressReporter()
JobProgressReporter NextProgressReporter()
{
var tmpDoneSteps = doneSteps;
++doneSteps;
return progress => progressReporter((progress + (100 * tmpDoneSteps)) / numSteps);
return (status, progress) => progressReporter(status, (progress + (100 * tmpDoneSteps)) / numSteps);
}
progressReporter(0);
progressReporter(null, 0);
// get a base line for where we are
Models.RevisionInformation lastRevisionInfo = null;
@@ -579,7 +579,7 @@ namespace Tgstation.Server.Host.Controllers
postUpdateSha = repo.Head;
}
else
NextProgressReporter()(100);
NextProgressReporter()(null, 100);
}
}
@@ -614,7 +614,7 @@ namespace Tgstation.Server.Host.Controllers
await CallLoadRevInfo().ConfigureAwait(false); // we've either seen origin before or what we're checking out is on origin
}
else
NextProgressReporter()(100);
NextProgressReporter()(null, 100);
if (hardResettingToOriginReference)
{
@@ -884,7 +884,7 @@ namespace Tgstation.Server.Host.Controllers
if (startReference != null && repo.Head != startSha)
await repo.ResetToSha(startSha, NextProgressReporter(), default).ConfigureAwait(false);
else
progressReporter(100);
progressReporter(null, 100);
throw;
}
}
@@ -430,7 +430,7 @@ namespace Tgstation.Server.Host.Core
if (generalConfiguration.HostApiDocumentation)
{
applicationBuilder.UseSwagger();
applicationBuilder.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TGS API V4"));
applicationBuilder.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TGS API"));
logger.LogTrace("Swagger API generation enabled");
}
@@ -409,7 +409,7 @@ namespace Tgstation.Server.Host.Database
if (targetVersion == null)
throw new ArgumentNullException(nameof(targetVersion));
if (targetVersion < new Version(4, 0))
throw new ArgumentOutOfRangeException(nameof(targetVersion), targetVersion, "Not a valid V4 version!");
throw new ArgumentOutOfRangeException(nameof(targetVersion), targetVersion, "Cannot migrate below version 4.0.0!");
if (currentDatabaseType == DatabaseType.PostgresSql && targetVersion < new Version(4, 3, 0))
throw new NotSupportedException("Cannot migrate below version 4.3.0 with PostgresSql!");
@@ -104,7 +104,7 @@ namespace Tgstation.Server.Host.Extensions
CustomFormatter = new EcsTextFormatter(),
AutoRegisterTemplate = true,
AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv7,
IndexFormat = "tgs4-logs",
IndexFormat = "tgs-logs",
});
}
}
@@ -2,7 +2,7 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Jobs
@@ -13,11 +13,10 @@ namespace Tgstation.Server.Host.Jobs
public interface IJobManager : IHostedService
{
/// <summary>
/// Get the <see cref="Api.Models.Response.JobResponse.Progress"/> for a <paramref name="job"/>.
/// Set the <see cref="JobResponse.Progress"/> and <see cref="JobResponse.Stage"/> for a given <paramref name="apiResponse"/>.
/// </summary>
/// <param name="job">The <see cref="Api.Models.Internal.Job"/> to get <see cref="Api.Models.Response.JobResponse.Progress"/> for.</param>
/// <returns>The <see cref="Api.Models.Response.JobResponse.Progress"/> of <paramref name="job"/>.</returns>
int? JobProgress(Api.Models.Internal.Job job);
/// <param name="apiResponse">The <see cref="JobResponse"/> to update.</param>
void SetJobProgress(JobResponse apiResponse);
/// <summary>
/// Registers a given <see cref="Job"/> and begins running it.
@@ -1,5 +1,4 @@
using System;
using System.Threading;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Host.Components;
@@ -14,13 +13,13 @@ namespace Tgstation.Server.Host.Jobs
/// <param name="instance">The <see cref="IInstanceCore"/> the job is running on. <see langword="null"/> only when performing an instance move operation.</param>
/// <param name="databaseContextFactory">The <see cref="IDatabaseContextFactory"/> for the operation.</param>
/// <param name="job">The running <see cref="Job"/>.</param>
/// <param name="progressReporter">A <see cref="Action{T1}"/> that will update the progress of the job.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> for the job.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
public delegate Task JobEntrypoint(
IInstanceCore instance,
IDatabaseContextFactory databaseContextFactory,
Job job,
Action<int> progressReporter,
JobProgressReporter progressReporter,
CancellationToken cancellationToken);
}
@@ -44,6 +44,11 @@ namespace Tgstation.Server.Host.Jobs
/// </summary>
public int? Progress { get; set; }
/// <summary>
/// The stage of the job.
/// </summary>
public string Stage { get; set; }
/// <summary>
/// Wait for <see cref="task"/> to complete.
/// </summary>
+16 -8
View File
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Serilog.Context;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Host.Components;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Extensions;
@@ -221,15 +221,16 @@ namespace Tgstation.Server.Host.Jobs
}
/// <inheritdoc />
public int? JobProgress(Api.Models.Internal.Job job)
public void SetJobProgress(JobResponse apiResponse)
{
if (job == null)
throw new ArgumentNullException(nameof(job));
if (apiResponse == null)
throw new ArgumentNullException(nameof(apiResponse));
lock (synchronizationLock)
{
if (!jobs.TryGetValue(job.Id.Value, out var handler))
return null;
return handler.Progress;
if (!jobs.TryGetValue(apiResponse.Id.Value, out var handler))
return;
apiResponse.Progress = handler.Progress;
apiResponse.Stage = handler.Stage;
}
}
@@ -293,11 +294,18 @@ namespace Tgstation.Server.Host.Jobs
var oldJob = job;
job = new Job { Id = oldJob.Id };
void UpdateProgress(int progress)
void UpdateProgress(string stage, int? progress)
{
if (progress.HasValue
&& (progress.Value < 0 || progress.Value > 100))
throw new ArgumentOutOfRangeException(nameof(progress), "Progress must be a value from 0-100!");
lock (synchronizationLock)
if (jobs.TryGetValue(oldJob.Id.Value, out var handler))
{
handler.Stage = stage;
handler.Progress = progress;
}
}
await activationTcs.Task.WithToken(cancellationToken).ConfigureAwait(false);
@@ -0,0 +1,13 @@
using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Jobs
{
/// <summary>
/// Progress reporter for a <see cref="Job"/>.
/// </summary>
/// <param name="stage">A description of what the job is currently doing.</param>
/// <param name="progress">The progress of the job on a scale from 0-100.</param>
public delegate void JobProgressReporter(
string stage,
int? progress);
}
@@ -346,7 +346,7 @@ namespace Tgstation.Server.Host.Setup
cancellationToken)
.ConfigureAwait(false);
await console.WriteAsync(
"This means that you may not be able to update to the next minor version of TGS4 without a clean re-installation!",
"This means that you may not be able to update to the next minor version of TGS without a clean re-installation!",
true,
cancellationToken)
.ConfigureAwait(false);
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="../../build/Version.props" />
<PropertyGroup>
@@ -26,7 +26,7 @@
<DockerfileContext>..\..</DockerfileContext>
</PropertyGroup>
<Target Name="ClientInstall" Inputs="../../build/ControlPanelVersion.props" Outputs="$(NpmInstallStampFile)">
<Target Name="ClientInstall" BeforeTargets="ResolveAssemblyReferences" Inputs="../../build/ControlPanelVersion.props" Outputs="$(NpmInstallStampFile)">
<Message Text="Pulling web control panel..." Importance="high" />
<RemoveDir Directories="ClientApp" />
<Exec Command="git clone https://github.com/tgstation/tgstation-server-webpanel --branch v$(TgsControlPanelVersion) --depth 1 ClientApp" />
@@ -22,7 +22,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers.Tests
[TestInitialize]
public void Initialize()
{
var actualToken = Environment.GetEnvironmentVariable("TGS4_TEST_DISCORD_TOKEN");
var actualToken = Environment.GetEnvironmentVariable("TGS_TEST_DISCORD_TOKEN");
if (!String.IsNullOrWhiteSpace(actualToken))
testToken1 = new ChatBot
{
@@ -45,7 +45,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers.Tests
public async Task TestConstructionAndDisposal()
{
if (testToken1 == null)
Assert.Inconclusive("Required environment variable TGS4_TEST_DISCORD_TOKEN isn't set!");
Assert.Inconclusive("Required environment variable TGS_TEST_DISCORD_TOKEN isn't set!");
Assert.ThrowsException<ArgumentNullException>(() => new DiscordProvider(null, null, null, null));
Assert.ThrowsException<ArgumentNullException>(() => new DiscordProvider(mockJobManager, null, null, null));
@@ -75,7 +75,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers.Tests
public async Task TestConnectAndDisconnect()
{
if (testToken1 == null)
Assert.Inconclusive("Required environment variable TGS4_TEST_DISCORD_TOKEN isn't set!");
Assert.Inconclusive("Required environment variable TGS_TEST_DISCORD_TOKEN isn't set!");
var mockLogger = new Mock<ILogger<DiscordProvider>>();
await using var provider = new DiscordProvider(mockJobManager, Mock.Of<IAssemblyInformationProvider>(), mockLogger.Object, testToken1);
@@ -1,4 +1,4 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Linq;
using System.Threading;
@@ -60,9 +60,9 @@ namespace Tgstation.Server.Tests
}
catch (RateLimitException)
{
if (String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("TGS4_TEST_GITHUB_TOKEN")))
if (String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("TGS_TEST_GITHUB_TOKEN")))
{
Assert.Inconclusive("GitHub rate limit hit while testing administration endpoint. Set environment variable TGS4_TEST_GITHUB_TOKEN to fix this!");
Assert.Inconclusive("GitHub rate limit hit while testing administration endpoint. Set environment variable TGS_TEST_GITHUB_TOKEN to fix this!");
}
// CI fails all the time b/c of this, ignore it
@@ -70,7 +70,7 @@ namespace Tgstation.Server.Tests
}
//we've released a few 4.x versions now, check the release checker is at least somewhat functional
Assert.AreEqual(4, model.LatestVersion.Major);
Assert.IsTrue(4 <= model.LatestVersion.Major);
}
}
}
@@ -37,9 +37,9 @@ namespace Tgstation.Server.Tests.Instance
{
var firstBotReq = new ChatBotCreateRequest
{
ConnectionString = Environment.GetEnvironmentVariable("TGS4_TEST_IRC_CONNECTION_STRING"),
ConnectionString = Environment.GetEnvironmentVariable("TGS_TEST_IRC_CONNECTION_STRING"),
Enabled = false,
Name = "tgs4_integration_test",
Name = "tgs_integration_test",
Provider = ChatProvider.Irc,
ReconnectionInterval = 1,
ChannelLimit = 1
@@ -68,7 +68,7 @@ namespace Tgstation.Server.Tests.Instance
Assert.AreEqual(true, updatedBot.Enabled);
var channelId = Environment.GetEnvironmentVariable("TGS4_TEST_IRC_CHANNEL"); ;
var channelId = Environment.GetEnvironmentVariable("TGS_TEST_IRC_CHANNEL"); ;
updatedBot = await chatClient.Update(new ChatBotUpdateRequest
{
@@ -104,7 +104,7 @@ namespace Tgstation.Server.Tests.Instance
ConnectionString =
new DiscordConnectionStringBuilder
{
BotToken = Environment.GetEnvironmentVariable("TGS4_TEST_DISCORD_TOKEN"),
BotToken = Environment.GetEnvironmentVariable("TGS_TEST_DISCORD_TOKEN"),
DMOutputDisplay = DiscordDMOutputDisplayType.OnError
}.ToString(),
Enabled = false,
@@ -137,7 +137,7 @@ namespace Tgstation.Server.Tests.Instance
Assert.AreEqual(true, updatedBot.Enabled);
var channelId = UInt64.Parse(Environment.GetEnvironmentVariable("TGS4_TEST_DISCORD_CHANNEL"));
var channelId = UInt64.Parse(Environment.GetEnvironmentVariable("TGS_TEST_DISCORD_CHANNEL"));
firstBot.Channels = new List<ChatChannel>
{
new ChatChannel
@@ -25,7 +25,7 @@ namespace Tgstation.Server.Tests.Instance
public async Task RunPreWatchdog(CancellationToken cancellationToken)
{
const string TestRefEnvVar = "TGS4_GITHUB_REF";
const string TestRefEnvVar = "TGS_GITHUB_REF";
var envVar = Environment.GetEnvironmentVariable(TestRefEnvVar);
string workingBranch = null;
if (!String.IsNullOrWhiteSpace(envVar))
@@ -108,12 +108,12 @@ namespace Tgstation.Server.Tests.Instance
updated = await Checkout(new RepositoryUpdateRequest { CheckoutSha = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }, true, false, cancellationToken);
// Fake ref
updated = await Checkout(new RepositoryUpdateRequest { Reference = "Tgs4IntegrationTestFakeBranchNeverNameABranchThis" }, true, true, cancellationToken);
updated = await Checkout(new RepositoryUpdateRequest { Reference = "TgsIntegrationTestFakeBranchNeverNameABranchThis" }, true, true, cancellationToken);
// Back
updated = await Checkout(new RepositoryUpdateRequest { Reference = workingBranch }, false, true, cancellationToken);
var testPRString = Environment.GetEnvironmentVariable("TGS4_TEST_PULL_REQUEST_NUMBER");
var testPRString = Environment.GetEnvironmentVariable("TGS_TEST_PULL_REQUEST_NUMBER");
if (String.IsNullOrWhiteSpace(testPRString))
testPRString = Environment.GetEnvironmentVariable("APPVEYOR_PULL_REQUEST_NUMBER");
if (String.IsNullOrWhiteSpace(testPRString))
@@ -96,7 +96,7 @@ namespace Tgstation.Server.Tests
}
catch (RateLimitException ex)
{
if (String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("TGS4_TEST_GITHUB_TOKEN")))
if (String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("TGS_TEST_GITHUB_TOKEN")))
throw;
Assert.Inconclusive("GitHub rate limit hit: {0}", ex);
@@ -184,7 +184,7 @@ namespace Tgstation.Server.Tests
}
catch (RateLimitException ex)
{
if (String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("TGS4_TEST_GITHUB_TOKEN")))
if (String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("TGS_TEST_GITHUB_TOKEN")))
throw;
Assert.Inconclusive("GitHub rate limit hit: {0}", ex);
@@ -393,7 +393,7 @@ namespace Tgstation.Server.Tests
}
catch (RateLimitException ex)
{
if (String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("TGS4_TEST_GITHUB_TOKEN")))
if (String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("TGS_TEST_GITHUB_TOKEN")))
throw;
Assert.Inconclusive("GitHub rate limit hit: {0}", ex);
@@ -590,7 +590,7 @@ namespace Tgstation.Server.Tests
}
catch (RateLimitException ex)
{
if (String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("TGS4_TEST_GITHUB_TOKEN")))
if (String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("TGS_TEST_GITHUB_TOKEN")))
throw;
Assert.Inconclusive("GitHub rate limit hit: {0}", ex);
@@ -648,14 +648,14 @@ namespace Tgstation.Server.Tests
[TestMethod]
public async Task TestDownMigrations()
{
var connectionString = Environment.GetEnvironmentVariable("TGS4_TEST_CONNECTION_STRING");
var connectionString = Environment.GetEnvironmentVariable("TGS_TEST_CONNECTION_STRING");
if (String.IsNullOrEmpty(connectionString))
Assert.Inconclusive("No connection string configured in env var TGS4_TEST_CONNECTION_STRING!");
Assert.Inconclusive("No connection string configured in env var TGS_TEST_CONNECTION_STRING!");
var databaseTypeString = Environment.GetEnvironmentVariable("TGS4_TEST_DATABASE_TYPE");
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 TGS4_TEST_DATABASE_TYPE!");
Assert.Inconclusive("No/invalid database type configured in env var TGS_TEST_DATABASE_TYPE!");
string migrationName = null;
DatabaseContext CreateContext()
@@ -1061,7 +1061,7 @@ namespace Tgstation.Server.Tests
() => { });
const string StartSha = "af4da8beb9f9b374b04a3cc4d65acca662e8cc1a";
await repo.CheckoutObject(StartSha, null, null, true, progress => { }, default);
await repo.CheckoutObject(StartSha, null, null, true, (stage, progress) => { }, default);
var result = await repo.ShaIsParent("2f8588a3ca0f6b027704a2a04381215619de3412", default);
Assert.IsTrue(result);
Assert.AreEqual(StartSha, repo.Head);
@@ -35,10 +35,10 @@ namespace Tgstation.Server.Tests
public TestingServer(SwarmConfiguration swarmConfiguration, bool enableOAuth, ushort port = 5010)
{
Directory = Environment.GetEnvironmentVariable("TGS4_TEST_TEMP_DIRECTORY");
Directory = Environment.GetEnvironmentVariable("TGS_TEST_TEMP_DIRECTORY");
if (String.IsNullOrWhiteSpace(Directory))
{
Directory = Path.Combine(Path.GetTempPath(), "TGS4_INTEGRATION_TEST");
Directory = Path.Combine(Path.GetTempPath(), "TGS_INTEGRATION_TEST");
if (System.IO.Directory.Exists(Directory) && swarmConfiguration == null)
try
{
@@ -55,16 +55,16 @@ namespace Tgstation.Server.Tests
//so we need a db
//we have to rely on env vars
DatabaseType = Environment.GetEnvironmentVariable("TGS4_TEST_DATABASE_TYPE");
var connectionString = Environment.GetEnvironmentVariable("TGS4_TEST_CONNECTION_STRING");
var gitHubAccessToken = Environment.GetEnvironmentVariable("TGS4_TEST_GITHUB_TOKEN");
var dumpOpenAPISpecPathEnvVar = Environment.GetEnvironmentVariable("TGS4_TEST_DUMP_API_SPEC");
DatabaseType = Environment.GetEnvironmentVariable("TGS_TEST_DATABASE_TYPE");
var connectionString = Environment.GetEnvironmentVariable("TGS_TEST_CONNECTION_STRING");
var gitHubAccessToken = Environment.GetEnvironmentVariable("TGS_TEST_GITHUB_TOKEN");
var dumpOpenAPISpecPathEnvVar = Environment.GetEnvironmentVariable("TGS_TEST_DUMP_API_SPEC");
if (String.IsNullOrEmpty(DatabaseType))
Assert.Inconclusive("No database type configured in env var TGS4_TEST_DATABASE_TYPE!");
Assert.Inconclusive("No database type configured in env var TGS_TEST_DATABASE_TYPE!");
if (String.IsNullOrEmpty(connectionString))
Assert.Inconclusive("No connection string configured in env var TGS4_TEST_CONNECTION_STRING!");
Assert.Inconclusive("No connection string configured in env var TGS_TEST_CONNECTION_STRING!");
if (String.IsNullOrEmpty(gitHubAccessToken))
Console.WriteLine("WARNING: No GitHub access token configured, test may fail due to rate limits!");
+33 -46
View File
@@ -34,7 +34,7 @@ namespace ReleaseNotes
var doNotCloseMilestone = args.Length > 1 && args[1].ToUpperInvariant() == "--NO-CLOSE";
const string ReleaseNotesEnvVar = "TGS4_RELEASE_NOTES_TOKEN";
const string ReleaseNotesEnvVar = "TGS_RELEASE_NOTES_TOKEN";
var githubToken = Environment.GetEnvironmentVariable(ReleaseNotesEnvVar);
if (String.IsNullOrWhiteSpace(githubToken) && !doNotCloseMilestone)
{
@@ -201,8 +201,6 @@ namespace ReleaseNotes
var releases = await releasesTask.ConfigureAwait(false);
var releasingSuite = version.Major;
Version highestReleaseVersion = null;
Release highestRelease = null;
foreach (var I in releases)
@@ -213,7 +211,7 @@ namespace ReleaseNotes
continue;
}
if (currentReleaseVersion.Major == releasingSuite && (highestReleaseVersion == null || currentReleaseVersion > highestReleaseVersion) && version != currentReleaseVersion)
if (currentReleaseVersion.Major == version.Major && (highestReleaseVersion == null || currentReleaseVersion > highestReleaseVersion) && version != currentReleaseVersion)
{
highestReleaseVersion = currentReleaseVersion;
highestRelease = I;
@@ -222,7 +220,7 @@ namespace ReleaseNotes
if (highestReleaseVersion == null)
{
Console.WriteLine("Unable to determine highest release version for suite " + releasingSuite + "!");
Console.WriteLine("Unable to determine highest release version for major version " + version.Major + "!");
return 6;
}
@@ -247,49 +245,38 @@ namespace ReleaseNotes
oldNotes = String.Join('\n', splits);
string prefix;
switch (releasingSuite)
const string PropsPath = "build/Version.props";
const string ControlPanelPropsPath = "build/ControlPanelVersion.props";
var doc = XDocument.Load(PropsPath);
var project = doc.Root;
var xmlNamespace = project.GetDefaultNamespace();
var versionsPropertyGroup = project.Elements().First(x => x.Name == xmlNamespace + "PropertyGroup");
var doc2 = XDocument.Load(ControlPanelPropsPath);
var project2 = doc2.Root;
var controlPanelXmlNamespace = project2.GetDefaultNamespace();
var controlPanelVersionsPropertyGroup = project2.Elements().First(x => x.Name == controlPanelXmlNamespace + "PropertyGroup");
var coreVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsCoreVersion").Value);
if (coreVersion != version)
{
case 4:
const string PropsPath = "build/Version.props";
const string ControlPanelPropsPath = "build/ControlPanelVersion.props";
var doc = XDocument.Load(PropsPath);
var project = doc.Root;
var xmlNamespace = project.GetDefaultNamespace();
var versionsPropertyGroup = project.Elements().First(x => x.Name == xmlNamespace + "PropertyGroup");
var doc2 = XDocument.Load(ControlPanelPropsPath);
var project2 = doc2.Root;
var controlPanelXmlNamespace = project2.GetDefaultNamespace();
var controlPanelVersionsPropertyGroup = project2.Elements().First(x => x.Name == controlPanelXmlNamespace + "PropertyGroup");
var coreVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsCoreVersion").Value);
if (coreVersion != version)
{
Console.WriteLine("Received a different version on command line than in Version.props!");
return 10;
}
var apiVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsApiVersion").Value);
var configVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsConfigVersion").Value);
var dmApiVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsDmapiVersion").Value);
var interopVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsInteropVersion").Value);
var webControlVersion = Version.Parse(controlPanelVersionsPropertyGroup.Element(controlPanelXmlNamespace + "TgsControlPanelVersion").Value);
var hostWatchdogVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsHostWatchdogVersion").Value);
if (webControlVersion.Major == 0)
postControlPanelMessage = true;
prefix = $"Please refer to the [README](https://github.com/tgstation/tgstation-server#setup) for setup instructions.{Environment.NewLine}{Environment.NewLine}#### Component Versions\nCore: {coreVersion}\nConfiguration: {configVersion}\nHTTP API: {apiVersion}\nDreamMaker API: {dmApiVersion} (Interop: {interopVersion})\n[Web Control Panel](https://github.com/tgstation/tgstation-server-webpanel): {webControlVersion}\nHost Watchdog: {hostWatchdogVersion}";
break;
case 3:
prefix = "The /tg/station server suite";
break;
default:
prefix = "See https://tgstation.github.io/tgstation-server for installation instructions";
break;
Console.WriteLine("Received a different version on command line than in Version.props!");
return 10;
}
var apiVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsApiVersion").Value);
var configVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsConfigVersion").Value);
var dmApiVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsDmapiVersion").Value);
var interopVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsInteropVersion").Value);
var webControlVersion = Version.Parse(controlPanelVersionsPropertyGroup.Element(controlPanelXmlNamespace + "TgsControlPanelVersion").Value);
var hostWatchdogVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsHostWatchdogVersion").Value);
if (webControlVersion.Major == 0)
postControlPanelMessage = true;
prefix = $"Please refer to the [README](https://github.com/tgstation/tgstation-server#setup) for setup instructions.{Environment.NewLine}{Environment.NewLine}#### Component Versions\nCore: {coreVersion}\nConfiguration: {configVersion}\nHTTP API: {apiVersion}\nDreamMaker API: {dmApiVersion} (Interop: {interopVersion})\n[Web Control Panel](https://github.com/tgstation/tgstation-server-webpanel): {webControlVersion}\nHost Watchdog: {hostWatchdogVersion}";
var newNotes = new StringBuilder(prefix);
if (postControlPanelMessage)
{
@@ -453,7 +440,7 @@ namespace ReleaseNotes
newNotes.Append(Environment.NewLine);
if (version != new Version(4, 1, 0))
if (version.Minor != 0 && version.Build != 0)
newNotes.Append(oldNotes);
const string OutputPath = "release_notes.md";