mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-26 14:37:44 +01:00
Merge branch 'dev' into FixUpdates
This commit is contained in:
@@ -8,7 +8,7 @@ on:
|
||||
|
||||
env:
|
||||
TGS_DOTNET_VERSION: 8
|
||||
OD_MIN_COMPAT_DOTNET_VERSION: 7
|
||||
TGS_DOTNET_QUALITY: ga
|
||||
|
||||
jobs:
|
||||
master-merge:
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
name: 'Sync spacestation13/tgstation-server'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
tags:
|
||||
- '*'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
TGS_DOTNET_VERSION: 8
|
||||
TGS_DOTNET_QUALITY: ga
|
||||
|
||||
jobs:
|
||||
fork-sync:
|
||||
name: Fork Sync
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '${{ env.TGS_DOTNET_VERSION }}.0.x'
|
||||
dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }}
|
||||
|
||||
- name: Build Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: temp_workspace
|
||||
|
||||
- name: Restore
|
||||
run: |
|
||||
cd temp_workspace
|
||||
dotnet restore
|
||||
|
||||
- name: Build ReleaseNotes
|
||||
run: |
|
||||
cd temp_workspace
|
||||
dotnet build -c Release -p:TGS_HOST_NO_WEBPANEL=true tools/Tgstation.Server.ReleaseNotes/Tgstation.Server.ReleaseNotes.csproj
|
||||
|
||||
- name: Generate App Token
|
||||
run: |
|
||||
cd temp_workspace
|
||||
dotnet run -c Release --no-build --project tools/Tgstation.Server.ReleaseNotes --token-output-file ${{ runner.temp }}/installation_secret.txt ${{ secrets.TGS_CI_GITHUB_APP_TOKEN_BASE64 }} --spacestation13
|
||||
echo "INSTALLATION_TOKEN=$(cat ${{ runner.temp }}/installation_secret.txt)" >> $GITHUB_ENV
|
||||
rm ${{ runner.temp }}/installation_secret.txt
|
||||
env:
|
||||
TGS_RELEASE_NOTES_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
|
||||
|
||||
- name: Main Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
token: ${{ env.INSTALLATION_TOKEN }}
|
||||
|
||||
- name: Restore
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build ReleaseNotes
|
||||
run: dotnet build -c Release -p:TGS_HOST_NO_WEBPANEL=true tools/Tgstation.Server.ReleaseNotes/Tgstation.Server.ReleaseNotes.csproj
|
||||
|
||||
- name: Generate App Token
|
||||
run: |
|
||||
dotnet run -c Release --no-build --project tools/Tgstation.Server.ReleaseNotes --token-output-file ${{ runner.temp }}/installation_secret.txt ${{ secrets.TGS_CI_GITHUB_APP_TOKEN_BASE64 }}
|
||||
echo "INSTALLATION_TOKEN=$(cat ${{ runner.temp }}/installation_secret.txt)" >> $GITHUB_ENV
|
||||
rm ${{ runner.temp }}/installation_secret.txt
|
||||
env:
|
||||
TGS_RELEASE_NOTES_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
|
||||
|
||||
- name: Push to Spacestation13 Fork
|
||||
run: |
|
||||
git config user.name "tgstation-server-ci[bot]"
|
||||
git config user.email "161980869+tgstation-server-ci[bot]@users.noreply.github.com"
|
||||
git push "https://tgstation-server-ci:${{ env.INSTALLATION_TOKEN }}@github.com/spacestation13/tgstation-server"
|
||||
git push --tags "https://tgstation-server-ci:${{ env.INSTALLATION_TOKEN }}@github.com/spacestation13/tgstation-server"
|
||||
@@ -18,9 +18,9 @@
|
||||
<!-- Pinned: Be VERY careful about updating https://github.com/moq/moq/issues/1372 -->
|
||||
<PackageReference Include="Moq" Version="4.20.70" />
|
||||
<!-- Usage: MSTest execution -->
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.5.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.5.1" />
|
||||
<!-- Usage: MSTest asserts etc... -->
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.5.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.5.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
+8
-8
@@ -3,13 +3,13 @@
|
||||
<!-- Integration tests will ensure they match across the board -->
|
||||
<Import Project="WebpanelVersion.props" />
|
||||
<PropertyGroup>
|
||||
<TgsCoreVersion>6.8.0</TgsCoreVersion>
|
||||
<TgsConfigVersion>5.1.0</TgsConfigVersion>
|
||||
<TgsApiVersion>10.6.0</TgsApiVersion>
|
||||
<TgsCoreVersion>6.9.0</TgsCoreVersion>
|
||||
<TgsConfigVersion>5.2.0</TgsConfigVersion>
|
||||
<TgsApiVersion>10.7.0</TgsApiVersion>
|
||||
<TgsCommonLibraryVersion>7.0.0</TgsCommonLibraryVersion>
|
||||
<TgsApiLibraryVersion>13.6.0</TgsApiLibraryVersion>
|
||||
<TgsClientVersion>15.6.0</TgsClientVersion>
|
||||
<TgsDmapiVersion>7.1.3</TgsDmapiVersion>
|
||||
<TgsApiLibraryVersion>13.7.0</TgsApiLibraryVersion>
|
||||
<TgsClientVersion>16.0.0</TgsClientVersion>
|
||||
<TgsDmapiVersion>7.2.1</TgsDmapiVersion>
|
||||
<TgsInteropVersion>5.9.0</TgsInteropVersion>
|
||||
<TgsHostWatchdogVersion>1.4.1</TgsHostWatchdogVersion>
|
||||
<TgsContainerScriptVersion>1.2.1</TgsContainerScriptVersion>
|
||||
@@ -18,9 +18,9 @@
|
||||
<TgsNetMajorVersion>8</TgsNetMajorVersion>
|
||||
<!-- Update this frequently with dotnet runtime patches. MAJOR MUST MATCH ABOVE! -->
|
||||
<TgsDotnetRedistUrl>https://download.visualstudio.microsoft.com/download/pr/751d3fcd-72db-4da2-b8d0-709c19442225/33cc492bde704bfd6d70a2b9109005a0/dotnet-hosting-8.0.6-win.exe</TgsDotnetRedistUrl>
|
||||
<TgsMariaDBRedistVersion>10.11.8</TgsMariaDBRedistVersion>
|
||||
<TgsMariaDBRedistVersion>11.4.2</TgsMariaDBRedistVersion>
|
||||
<!-- Only have this uncommented if the mariadb servers are shitting the bed, update if the version updates -->
|
||||
<TgsMariaDBFallbackRedist>https://mirror.its.dal.ca/mariadb//mariadb-10.11.8/winx64-packages/mariadb-10.11.8-winx64.msi</TgsMariaDBFallbackRedist>
|
||||
<!--<TgsMariaDBFallbackRedist>https://mirror.its.dal.ca/mariadb//mariadb-10.11.8/winx64-packages/mariadb-10.11.8-winx64.msi</TgsMariaDBFallbackRedist>-->
|
||||
<TgsYarnVersion>1.22.21</TgsYarnVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
+73
-27
@@ -1,18 +1,19 @@
|
||||
// tgstation-server DMAPI
|
||||
// The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in IETF RFC 2119.
|
||||
|
||||
#define TGS_DMAPI_VERSION "7.1.3"
|
||||
#define TGS_DMAPI_VERSION "7.2.1"
|
||||
|
||||
// All functions and datums outside this document are subject to change with any version and should not be relied on.
|
||||
|
||||
// CONFIGURATION
|
||||
|
||||
/// Create this define if you want to do TGS configuration outside of this file.
|
||||
/// Consumers SHOULD create this define if you want to do TGS configuration outside of this file.
|
||||
#ifndef TGS_EXTERNAL_CONFIGURATION
|
||||
|
||||
// Comment this out once you've filled in the below.
|
||||
// Consumers MUST comment this out once you've filled in the below and are not using [TGS_EXTERNAL_CONFIGURATION].
|
||||
#error TGS API unconfigured
|
||||
|
||||
// Uncomment this if you wish to allow the game to interact with TGS 3..
|
||||
// Consumers MUST uncomment this if you wish to allow the game to interact with TGS version 3.
|
||||
// This will raise the minimum required security level of your game to TGS_SECURITY_TRUSTED due to it utilizing call()().
|
||||
//#define TGS_V3_API
|
||||
|
||||
@@ -52,7 +53,7 @@
|
||||
|
||||
#ifndef TGS_FILE2TEXT_NATIVE
|
||||
#ifdef file2text
|
||||
#error Your codebase is re-defining the BYOND proc file2text. The DMAPI requires the native version to read the result of world.Export(). You can fix this by adding "#define TGS_FILE2TEXT_NATIVE file2text" before your override of file2text to allow the DMAPI to use the native version. This will only be used for world.Export(), not regular file accesses
|
||||
#error Your codebase is re-defining the BYOND proc file2text. The DMAPI requires the native version to read the result of world.Export(). You SHOULD fix this by adding "#define TGS_FILE2TEXT_NATIVE file2text" before your override of file2text to allow the DMAPI to use the native version. This will only be used for world.Export(), not regular file accesses
|
||||
#endif
|
||||
#define TGS_FILE2TEXT_NATIVE file2text
|
||||
#endif
|
||||
@@ -152,16 +153,17 @@
|
||||
//REQUIRED HOOKS
|
||||
|
||||
/**
|
||||
* Call this somewhere in [/world/proc/New] that is always run. This function may sleep!
|
||||
* Consumers MUST call this somewhere in [/world/proc/New] that is always run. This function may sleep!
|
||||
*
|
||||
* * event_handler - Optional user defined [/datum/tgs_event_handler].
|
||||
* * minimum_required_security_level: The minimum required security level to run the game in which the DMAPI is integrated. Can be one of [TGS_SECURITY_ULTRASAFE], [TGS_SECURITY_SAFE], or [TGS_SECURITY_TRUSTED].
|
||||
* * http_handler - Optional user defined [/datum/tgs_http_handler].
|
||||
*/
|
||||
/world/proc/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE)
|
||||
/world/proc/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE, datum/tgs_http_handler/http_handler)
|
||||
return
|
||||
|
||||
/**
|
||||
* Call this when your initializations are complete and your game is ready to play before any player interactions happen.
|
||||
* Consumers MUST call this when world initializations are complete and the game is ready to play before any player interactions happen.
|
||||
*
|
||||
* This may use [/world/var/sleep_offline] to make this happen so ensure no changes are made to it while this call is running.
|
||||
* Afterwards, consider explicitly setting it to what you want to avoid this BYOND bug: http://www.byond.com/forum/post/2575184
|
||||
@@ -170,12 +172,10 @@
|
||||
/world/proc/TgsInitializationComplete()
|
||||
return
|
||||
|
||||
/// Put this at the start of [/world/proc/Topic].
|
||||
/// Consumers MUST run this macro at the start of [/world/proc/Topic].
|
||||
#define TGS_TOPIC var/tgs_topic_return = TgsTopic(args[1]); if(tgs_topic_return) return tgs_topic_return
|
||||
|
||||
/**
|
||||
* Call this as late as possible in [world/proc/Reboot] (BEFORE ..()).
|
||||
*/
|
||||
/// Consumers MUST call this as late as possible in [world/proc/Reboot] (BEFORE ..()).
|
||||
/world/proc/TgsReboot()
|
||||
return
|
||||
|
||||
@@ -269,7 +269,7 @@
|
||||
/// The [/datum/tgs_chat_channel] the user was from.
|
||||
var/datum/tgs_chat_channel/channel
|
||||
|
||||
/// User definable handler for TGS events.
|
||||
/// User definable handler for TGS events This abstract version SHOULD be overridden to be used.
|
||||
/datum/tgs_event_handler
|
||||
/// If the handler receieves [TGS_EVENT_HEALTH_CHECK] events.
|
||||
var/receive_health_checks = FALSE
|
||||
@@ -283,7 +283,41 @@
|
||||
set waitfor = FALSE
|
||||
return
|
||||
|
||||
/// User definable chat command.
|
||||
/// User definable handler for HTTP calls. This abstract version MUST be overridden to be used.
|
||||
/datum/tgs_http_handler
|
||||
|
||||
/**
|
||||
* User definable callback for executing HTTP GET requests.
|
||||
* MUST perform BYOND sleeps while the request is in flight.
|
||||
* MUST return a [/datum/tgs_http_result].
|
||||
* SHOULD log its own errors
|
||||
*
|
||||
* url - The full URL to execute the GET request for including query parameters.
|
||||
*/
|
||||
/datum/tgs_http_handler/proc/PerformGet(url)
|
||||
CRASH("[type]/PerformGet not implemented!")
|
||||
|
||||
/// Result of a [/datum/tgs_http_handler] call. MUST NOT be overridden.
|
||||
/datum/tgs_http_result
|
||||
/// HTTP response as text
|
||||
var/response_text
|
||||
/// Boolean request success flag. Set for any 2XX response code.
|
||||
var/success
|
||||
|
||||
/**
|
||||
* Create a [/datum/tgs_http_result].
|
||||
*
|
||||
* * response_text - HTTP response as text. Must be provided in New().
|
||||
* * success - Boolean request success flag. Set for any 2XX response code. Must be provided in New().
|
||||
*/
|
||||
/datum/tgs_http_result/New(response_text, success)
|
||||
if(response_text && !istext(response_text))
|
||||
CRASH("response_text was not text!")
|
||||
|
||||
src.response_text = response_text
|
||||
src.success = success
|
||||
|
||||
/// User definable chat command. This abstract version MUST be overridden to be used.
|
||||
/datum/tgs_chat_command
|
||||
/// The string to trigger this command on a chat bot. e.g `@bot name ...` or `!tgs name ...`.
|
||||
var/name = ""
|
||||
@@ -296,21 +330,27 @@
|
||||
|
||||
/**
|
||||
* Process command activation. Should return a [/datum/tgs_message_content] to respond to the issuer with.
|
||||
* MUST be implemented
|
||||
*
|
||||
* sender - The [/datum/tgs_chat_user] who issued the command.
|
||||
* params - The trimmed string following the command `/datum/tgs_chat_command/var/name].
|
||||
* * sender - The [/datum/tgs_chat_user] who issued the command.
|
||||
* * params - The trimmed string following the command `/datum/tgs_chat_command/var/name].
|
||||
*/
|
||||
/datum/tgs_chat_command/proc/Run(datum/tgs_chat_user/sender, params)
|
||||
CRASH("[type] has no implementation for Run()")
|
||||
|
||||
/// User definable chat message.
|
||||
/// User definable chat message. MUST NOT be overridden.
|
||||
/datum/tgs_message_content
|
||||
/// The tring content of the message. Must be provided in New().
|
||||
/// The string content of the message. Must be provided in New().
|
||||
var/text
|
||||
|
||||
/// The [/datum/tgs_chat_embed] to embed in the message. Not supported on all chat providers.
|
||||
var/datum/tgs_chat_embed/structure/embed
|
||||
|
||||
/**
|
||||
* Create a [/datum/tgs_message_content].
|
||||
*
|
||||
* * text - The string content of the message.
|
||||
*/
|
||||
/datum/tgs_message_content/New(text)
|
||||
..()
|
||||
if(!istext(text))
|
||||
@@ -319,7 +359,7 @@
|
||||
|
||||
src.text = text
|
||||
|
||||
/// User definable chat embed. Currently mirrors Discord chat embeds. See https://discord.com/developers/docs/resources/channel#embed-object-embed-structure for details.
|
||||
/// User definable chat embed. Currently mirrors Discord chat embeds. See https://discord.com/developers/docs/resources/message#embed-object for details.
|
||||
/datum/tgs_chat_embed/structure
|
||||
var/title
|
||||
var/description
|
||||
@@ -331,13 +371,13 @@
|
||||
/// Colour must be #AARRGGBB or #RRGGBB hex string.
|
||||
var/colour
|
||||
|
||||
/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-image-structure for details.
|
||||
/// See https://discord.com/developers/docs/resources/message#embed-object-embed-image-structure for details.
|
||||
var/datum/tgs_chat_embed/media/image
|
||||
|
||||
/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-thumbnail-structure for details.
|
||||
/// See https://discord.com/developers/docs/resources/message#embed-object-embed-thumbnail-structure for details.
|
||||
var/datum/tgs_chat_embed/media/thumbnail
|
||||
|
||||
/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-image-structure for details.
|
||||
/// See https://discord.com/developers/docs/resources/message#embed-object-embed-video-structure for details.
|
||||
var/datum/tgs_chat_embed/media/video
|
||||
|
||||
var/datum/tgs_chat_embed/footer/footer
|
||||
@@ -346,7 +386,7 @@
|
||||
|
||||
var/list/datum/tgs_chat_embed/field/fields
|
||||
|
||||
/// Common datum for similar discord embed medias.
|
||||
/// Common datum for similar Discord embed medias.
|
||||
/datum/tgs_chat_embed/media
|
||||
/// Must be set in New().
|
||||
var/url
|
||||
@@ -354,6 +394,7 @@
|
||||
var/height
|
||||
var/proxy_url
|
||||
|
||||
/// Create a [/datum/tgs_chat_embed].
|
||||
/datum/tgs_chat_embed/media/New(url)
|
||||
..()
|
||||
if(!istext(url))
|
||||
@@ -361,13 +402,14 @@
|
||||
|
||||
src.url = url
|
||||
|
||||
/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-footer-structure for details.
|
||||
/// See https://discord.com/developers/docs/resources/message#embed-object-embed-footer-structure for details.
|
||||
/datum/tgs_chat_embed/footer
|
||||
/// Must be set in New().
|
||||
var/text
|
||||
var/icon_url
|
||||
var/proxy_icon_url
|
||||
|
||||
/// Create a [/datum/tgs_chat_embed/footer].
|
||||
/datum/tgs_chat_embed/footer/New(text)
|
||||
..()
|
||||
if(!istext(text))
|
||||
@@ -375,16 +417,17 @@
|
||||
|
||||
src.text = text
|
||||
|
||||
/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-provider-structure for details.
|
||||
/// See https://discord.com/developers/docs/resources/message#embed-object-embed-provider-structure for details.
|
||||
/datum/tgs_chat_embed/provider
|
||||
var/name
|
||||
var/url
|
||||
|
||||
/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-author-structure for details. Must have name set in New().
|
||||
/// See https://discord.com/developers/docs/resources/message#embed-object-embed-author-structure for details. Must have name set in New().
|
||||
/datum/tgs_chat_embed/provider/author
|
||||
var/icon_url
|
||||
var/proxy_icon_url
|
||||
|
||||
/// Create a [/datum/tgs_chat_embed/footer].
|
||||
/datum/tgs_chat_embed/provider/author/New(name)
|
||||
..()
|
||||
if(!istext(name))
|
||||
@@ -392,12 +435,15 @@
|
||||
|
||||
src.name = name
|
||||
|
||||
/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-field-structure for details. Must have name and value set in New().
|
||||
/// See https://discord.com/developers/docs/resources/message#embed-object-embed-field-structure for details.
|
||||
/datum/tgs_chat_embed/field
|
||||
/// Must be set in New().
|
||||
var/name
|
||||
/// Must be set in New().
|
||||
var/value
|
||||
var/is_inline
|
||||
|
||||
/// Create a [/datum/tgs_chat_embed/field].
|
||||
/datum/tgs_chat_embed/field/New(name, value)
|
||||
..()
|
||||
if(!istext(name))
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
This folder contains all DMAPI code not directly involved in an API.
|
||||
|
||||
- [_definitions.dm](./definitions.dm) contains defines needed across DMAPI internals.
|
||||
- [byond_world_export.dm](./byond_world_export.dm) contains the default `/datum/tgs_http_handler` implementation which uses `world.Export()`.
|
||||
- [core.dm](./core.dm) contains the implementations of the `/world/proc/TgsXXX()` procs. Many map directly to the `/datum/tgs_api` functions. It also contains the /datum selection and setup code.
|
||||
- [datum.dm](./datum.dm) contains the `/datum/tgs_api` declarations that all APIs must implement.
|
||||
- [tgs_version.dm](./tgs_version.dm) contains the `/datum/tgs_version` definition
|
||||
-
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/datum/tgs_http_handler/byond_world_export
|
||||
|
||||
/datum/tgs_http_handler/byond_world_export/PerformGet(url)
|
||||
// This is an infinite sleep until we get a response
|
||||
var/export_response = world.Export(url)
|
||||
TGS_DEBUG_LOG("byond_world_export: Export complete")
|
||||
|
||||
if(!export_response)
|
||||
TGS_ERROR_LOG("byond_world_export: Failed request: [url]")
|
||||
return new /datum/tgs_http_result(null, FALSE)
|
||||
|
||||
var/content = export_response["CONTENT"]
|
||||
if(!content)
|
||||
TGS_ERROR_LOG("byond_world_export: Failed request, missing content!")
|
||||
return new /datum/tgs_http_result(null, FALSE)
|
||||
|
||||
var/response_json = TGS_FILE2TEXT_NATIVE(content)
|
||||
if(!response_json)
|
||||
TGS_ERROR_LOG("byond_world_export: Failed request, failed to load content!")
|
||||
return new /datum/tgs_http_result(null, FALSE)
|
||||
|
||||
return new /datum/tgs_http_result(response_json, TRUE)
|
||||
@@ -1,4 +1,4 @@
|
||||
/world/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE)
|
||||
/world/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE, datum/tgs_http_handler/http_handler = null)
|
||||
var/current_api = TGS_READ_GLOBAL(tgs)
|
||||
if(current_api)
|
||||
TGS_ERROR_LOG("API datum already set (\ref[current_api] ([current_api]))! Was TgsNew() called more than once?")
|
||||
@@ -55,7 +55,10 @@
|
||||
TGS_ERROR_LOG("Invalid parameter for event_handler: [event_handler]")
|
||||
event_handler = null
|
||||
|
||||
var/datum/tgs_api/new_api = new api_datum(event_handler, version)
|
||||
if(!http_handler)
|
||||
http_handler = new /datum/tgs_http_handler/byond_world_export
|
||||
|
||||
var/datum/tgs_api/new_api = new api_datum(event_handler, version, http_handler)
|
||||
|
||||
TGS_WRITE_GLOBAL(tgs, new_api)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ TGS_DEFINE_AND_SET_GLOBAL(tgs, null)
|
||||
|
||||
var/list/warned_deprecated_command_runs
|
||||
|
||||
/datum/tgs_api/New(datum/tgs_event_handler/event_handler, datum/tgs_version/version)
|
||||
/datum/tgs_api/New(datum/tgs_event_handler/event_handler, datum/tgs_version/version, datum/tgs_http_handler/http_handler)
|
||||
..()
|
||||
src.event_handler = event_handler
|
||||
src.version = version
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "core\_definitions.dm"
|
||||
#include "core\byond_world_export.dm"
|
||||
#include "core\core.dm"
|
||||
#include "core\datum.dm"
|
||||
#include "core\tgs_version.dm"
|
||||
|
||||
@@ -31,9 +31,12 @@
|
||||
|
||||
var/detached = FALSE
|
||||
|
||||
/datum/tgs_api/v5/New()
|
||||
var/datum/tgs_http_handler/http_handler
|
||||
|
||||
/datum/tgs_api/v5/New(datum/tgs_event_handler/event_handler, datum/tgs_version/version, datum/tgs_http_handler/http_handler)
|
||||
. = ..()
|
||||
interop_version = version
|
||||
src.http_handler = http_handler
|
||||
TGS_DEBUG_LOG("V5 API created: [json_encode(args)]")
|
||||
|
||||
/datum/tgs_api/v5/ApiVersion()
|
||||
|
||||
@@ -78,27 +78,24 @@
|
||||
WaitForReattach(FALSE)
|
||||
|
||||
TGS_DEBUG_LOG("Bridge request start")
|
||||
// This is an infinite sleep until we get a response
|
||||
var/export_response = world.Export(bridge_request)
|
||||
var/datum/tgs_http_result/result = http_handler.PerformGet(bridge_request)
|
||||
TGS_DEBUG_LOG("Bridge request complete")
|
||||
|
||||
if(!export_response)
|
||||
TGS_ERROR_LOG("Failed bridge request: [bridge_request]")
|
||||
if(isnull(result))
|
||||
TGS_ERROR_LOG("Failed bridge request, handler returned null!")
|
||||
return
|
||||
|
||||
var/content = export_response["CONTENT"]
|
||||
if(!content)
|
||||
TGS_ERROR_LOG("Failed bridge request, missing content!")
|
||||
if(!istype(result) || result.type != /datum/tgs_http_result)
|
||||
TGS_ERROR_LOG("Failed bridge request, handler returned non-[/datum/tgs_http_result]!")
|
||||
return
|
||||
|
||||
var/response_json = TGS_FILE2TEXT_NATIVE(content)
|
||||
if(!response_json)
|
||||
TGS_ERROR_LOG("Failed bridge request, failed to load content!")
|
||||
if(!result.success)
|
||||
TGS_DEBUG_LOG("Failed bridge request, HTTP request failed!")
|
||||
return
|
||||
|
||||
var/list/bridge_response = json_decode(response_json)
|
||||
var/list/bridge_response = json_decode(result.response_text)
|
||||
if(!bridge_response)
|
||||
TGS_ERROR_LOG("Failed bridge request, bad json: [response_json]")
|
||||
TGS_ERROR_LOG("Failed bridge request, bad json: [result.response_text]")
|
||||
return
|
||||
|
||||
var/error = bridge_response[DMAPI5_RESPONSE_ERROR_MESSAGE]
|
||||
|
||||
@@ -108,5 +108,11 @@ namespace Tgstation.Server.Api.Models
|
||||
/// </summary>
|
||||
[Description("Reconnect chat bot")]
|
||||
ReconnectChatBot,
|
||||
|
||||
/// <summary>
|
||||
/// When a repository is recloned.
|
||||
/// </summary>
|
||||
[Description("Reclone repository")]
|
||||
RepositoryReclone,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,5 +16,10 @@ namespace Tgstation.Server.Api.Models.Response
|
||||
/// The latest available version of the Tgstation.Server.Host assembly from the upstream repository. If <see cref="Version.Major"/> is not equal to 4 the update cannot be applied due to API changes.
|
||||
/// </summary>
|
||||
public Version? LatestVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This response is cached. This field indicates the <see cref="DateTimeOffset"/> when it was generated.
|
||||
/// </summary>
|
||||
public DateTimeOffset? GeneratedAt { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,5 +59,11 @@ namespace Tgstation.Server.Api.Models.Response
|
||||
/// </summary>
|
||||
[ResponseOptions]
|
||||
public bool? CurrentAllowWebclient { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The amount of RAM in use by the game server in bytes.
|
||||
/// </summary>
|
||||
[ResponseOptions]
|
||||
public long? ImmediateMemoryUsage { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,5 +82,10 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// User may change submodule update settings.
|
||||
/// </summary>
|
||||
ChangeSubmoduleUpdate = 1 << 13,
|
||||
|
||||
/// <summary>
|
||||
/// User may trigger repository recloning.
|
||||
/// </summary>
|
||||
Reclone = 1 << 14,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Tgstation.Server.Client
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask<AdministrationResponse> Read(CancellationToken cancellationToken) => ApiClient.Read<AdministrationResponse>(Routes.Administration, cancellationToken);
|
||||
public ValueTask<AdministrationResponse> Read(bool forceFresh, CancellationToken cancellationToken) => ApiClient.Read<AdministrationResponse>($"{Routes.Administration}?fresh={forceFresh}", cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask<ServerUpdateResponse> Update(
|
||||
|
||||
@@ -43,7 +43,10 @@ namespace Tgstation.Server.Client.Components
|
||||
public ValueTask<JobResponse> Restart(CancellationToken cancellationToken) => apiClient.Patch<JobResponse>(Routes.DreamDaemon, instance.Id!.Value, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask<DreamDaemonResponse> Read(CancellationToken cancellationToken) => apiClient.Read<DreamDaemonResponse>(Routes.DreamDaemon, instance.Id!.Value, cancellationToken);
|
||||
public ValueTask<DreamDaemonResponse> Read(CancellationToken cancellationToken) => apiClient.Read<DreamDaemonResponse>(
|
||||
Routes.DreamDaemon,
|
||||
instance.Id!.Value,
|
||||
cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask<DreamDaemonResponse> Update(DreamDaemonRequest dreamDaemon, CancellationToken cancellationToken) => apiClient.Update<DreamDaemonRequest, DreamDaemonResponse>(Routes.DreamDaemon, dreamDaemon ?? throw new ArgumentNullException(nameof(dreamDaemon)), instance.Id!.Value, cancellationToken);
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Tgstation.Server.Client.Components
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the <see cref="DreamDaemonResponse"/> information.</returns>
|
||||
ValueTask<DreamDaemonResponse> Read(CancellationToken cancellationToken);
|
||||
ValueTask<DreamDaemonResponse> Read(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Start <see cref="DreamDaemonResponse"/>.
|
||||
|
||||
@@ -40,5 +40,12 @@ namespace Tgstation.Server.Client.Components
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the <see cref="RepositoryResponse"/>.</returns>
|
||||
ValueTask<RepositoryResponse> Delete(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes and reclones the repository.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the <see cref="RepositoryResponse"/>.</returns>
|
||||
ValueTask<RepositoryResponse> Reclone(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,5 +44,8 @@ namespace Tgstation.Server.Client.Components
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask<RepositoryResponse> Update(RepositoryUpdateRequest repository, CancellationToken cancellationToken) => apiClient.Update<RepositoryUpdateRequest, RepositoryResponse>(Routes.Repository, repository ?? throw new ArgumentNullException(nameof(repository)), instance.Id!.Value, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask<RepositoryResponse> Reclone(CancellationToken cancellationToken) => apiClient.Patch<RepositoryResponse>(Routes.Repository, instance.Id!.Value, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,10 @@ namespace Tgstation.Server.Client
|
||||
/// <summary>
|
||||
/// Get the <see cref="AdministrationResponse"/> represented by the <see cref="IAdministrationClient"/>.
|
||||
/// </summary>
|
||||
/// <param name="forceFresh">If <see langword="true"/> the response will be forcefully regenerated.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the <see cref="AdministrationResponse"/> represented by the <see cref="IAdministrationClient"/>.</returns>
|
||||
ValueTask<AdministrationResponse> Read(CancellationToken cancellationToken);
|
||||
ValueTask<AdministrationResponse> Read(bool forceFresh = false, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the <see cref="AdministrationResponse"/> setttings.
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands
|
||||
public async ValueTask<MessageContent> Invoke(string arguments, ChatUser user, CancellationToken cancellationToken)
|
||||
{
|
||||
string result;
|
||||
if (arguments.Split(' ').Any(x => x.ToUpperInvariant() == "--REPO"))
|
||||
if (arguments.Split(' ').Any(x => x.Equals("--repo", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
if (repositoryManager.CloneInProgress || repositoryManager.InUse)
|
||||
return new MessageContent
|
||||
@@ -58,15 +58,13 @@ namespace Tgstation.Server.Host.Components.Chat.Commands
|
||||
Text = "Repository busy! Try again later",
|
||||
};
|
||||
|
||||
using (var repo = await repositoryManager.LoadRepository(cancellationToken))
|
||||
{
|
||||
if (repo == null)
|
||||
return new MessageContent
|
||||
{
|
||||
Text = "Repository unavailable!",
|
||||
};
|
||||
result = repo.Head;
|
||||
}
|
||||
using var repo = await repositoryManager.LoadRepository(cancellationToken);
|
||||
if (repo == null)
|
||||
return new MessageContent
|
||||
{
|
||||
Text = "Repository unavailable!",
|
||||
};
|
||||
result = repo.Head;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -157,6 +157,8 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
{
|
||||
Logger.LogTrace("OD repo seems to already exist, attempting load and fetch...");
|
||||
repo = await repositoryManager.LoadRepository(cancellationToken);
|
||||
if (repo == null)
|
||||
throw new JobException("Can't load OpenDream repository! Please delete cache from disk!");
|
||||
|
||||
await repo!.FetchOrigin(
|
||||
progressSection1,
|
||||
@@ -176,6 +178,7 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
progressSection2,
|
||||
cancellationToken);
|
||||
|
||||
|
||||
@@ -224,7 +224,7 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
/// <inheritdoc />
|
||||
protected override string GetDreamDaemonName(Version byondVersion, out bool supportsCli)
|
||||
{
|
||||
supportsCli = byondVersion >= DDExeVersion;
|
||||
supportsCli = byondVersion >= DDExeVersion && !sessionConfiguration.ForceUseDreamDaemonExe;
|
||||
return supportsCli ? "dd.exe" : "dreamdaemon.exe";
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
RepoResetOrigin,
|
||||
|
||||
/// <summary>
|
||||
/// Parameters: Checkout target.
|
||||
/// Parameters: Checkout target, hard reset flag (If "True", this is actually a hard reset, not a checkout).
|
||||
/// </summary>
|
||||
[EventScript("RepoCheckout")]
|
||||
RepoCheckout,
|
||||
|
||||
@@ -47,6 +47,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <param name="username">The optional username used for fetching from submodule repositories.</param>
|
||||
/// <param name="password">The optional password used for fetching from submodule repositories.</param>
|
||||
/// <param name="updateSubmodules">If a submodule update should be attempted after the merge.</param>
|
||||
/// <param name="moveCurrentReference">If a hard reset to the target committish should be performed instead of a checkout.</param>
|
||||
/// <param name="progressReporter">The optional <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="ValueTask"/> representing the running operation.</returns>
|
||||
@@ -55,6 +56,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
string? username,
|
||||
string? password,
|
||||
bool updateSubmodules,
|
||||
bool moveCurrentReference,
|
||||
JobProgressReporter? progressReporter,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
|
||||
@@ -295,7 +295,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
var revertTo = originalCommit.CanonicalName ?? originalCommit.Tip.Sha;
|
||||
logger.LogDebug("Merge conflict, aborting and reverting to {revertTarget}", revertTo);
|
||||
progressReporter.ReportProgress(0);
|
||||
RawCheckout(revertTo, progressReporter.CreateSection("Hard Reset to {revertTo}", 1.0), cancellationToken);
|
||||
RawCheckout(revertTo, false, progressReporter.CreateSection("Hard Reset to {revertTo}", 1.0), cancellationToken);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
@@ -376,19 +376,21 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
string? username,
|
||||
string? password,
|
||||
bool updateSubmodules,
|
||||
bool moveCurrentReference,
|
||||
JobProgressReporter? progressReporter,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(committish);
|
||||
|
||||
logger.LogDebug("Checkout object: {committish}...", committish);
|
||||
await eventConsumer.HandleEvent(EventType.RepoCheckout, new List<string> { committish }, false, cancellationToken);
|
||||
await eventConsumer.HandleEvent(EventType.RepoCheckout, new List<string> { committish, moveCurrentReference.ToString() }, false, cancellationToken);
|
||||
await Task.Factory.StartNew(
|
||||
() =>
|
||||
{
|
||||
libGitRepo.RemoveUntrackedFiles();
|
||||
RawCheckout(
|
||||
committish,
|
||||
moveCurrentReference,
|
||||
progressReporter?.CreateSection(null, updateSubmodules ? 2.0 / 3 : 1.0),
|
||||
cancellationToken);
|
||||
},
|
||||
@@ -879,9 +881,10 @@ 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="moveCurrentReference">If a hard reset should actually be performed.</param>
|
||||
/// <param name="progressReporter">The optional <see cref="JobProgressReporter"/> for the operation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
void RawCheckout(string committish, JobProgressReporter? progressReporter, CancellationToken cancellationToken)
|
||||
void RawCheckout(string committish, bool moveCurrentReference, JobProgressReporter? progressReporter, CancellationToken cancellationToken)
|
||||
{
|
||||
logger.LogTrace("Checkout: {committish}", committish);
|
||||
|
||||
@@ -900,34 +903,52 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
void RunCheckout() => commands.Checkout(
|
||||
libGitRepo,
|
||||
checkoutOptions,
|
||||
committish);
|
||||
|
||||
try
|
||||
if (moveCurrentReference)
|
||||
{
|
||||
RunCheckout();
|
||||
if (Reference == NoReference)
|
||||
throw new InvalidOperationException("Cannot move current reference when not on reference!");
|
||||
|
||||
var gitObject = libGitRepo.Lookup(committish);
|
||||
if (gitObject == null)
|
||||
throw new JobException($"Could not find committish: {committish}");
|
||||
|
||||
var commit = gitObject.Peel<Commit>();
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
libGitRepo.Reset(ResetMode.Hard, commit, checkoutOptions);
|
||||
}
|
||||
catch (NotFoundException)
|
||||
else
|
||||
{
|
||||
// Maybe (likely) a remote?
|
||||
var remoteName = $"origin/{committish}";
|
||||
var remoteBranch = libGitRepo.Branches.FirstOrDefault(
|
||||
branch => branch.FriendlyName.Equals(remoteName, StringComparison.Ordinal));
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
void RunCheckout() => commands.Checkout(
|
||||
libGitRepo,
|
||||
checkoutOptions,
|
||||
committish);
|
||||
|
||||
if (remoteBranch == default)
|
||||
throw;
|
||||
try
|
||||
{
|
||||
RunCheckout();
|
||||
}
|
||||
catch (NotFoundException)
|
||||
{
|
||||
// Maybe (likely) a remote?
|
||||
var remoteName = $"origin/{committish}";
|
||||
var remoteBranch = libGitRepo.Branches.FirstOrDefault(
|
||||
branch => branch.FriendlyName.Equals(remoteName, StringComparison.Ordinal));
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
logger.LogDebug("Creating local branch for {remoteBranchFriendlyName}...", remoteBranch.FriendlyName);
|
||||
var branch = libGitRepo.CreateBranch(committish, remoteBranch.Tip);
|
||||
if (remoteBranch == default)
|
||||
throw;
|
||||
|
||||
libGitRepo.Branches.Update(branch, branchUpdate => branchUpdate.TrackedBranch = remoteBranch.CanonicalName);
|
||||
logger.LogDebug("Creating local branch for {remoteBranchFriendlyName}...", remoteBranch.FriendlyName);
|
||||
var branch = libGitRepo.CreateBranch(committish, remoteBranch.Tip);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
libGitRepo.Branches.Update(branch, branchUpdate => branchUpdate.TrackedBranch = remoteBranch.CanonicalName);
|
||||
|
||||
RunCheckout();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
RunCheckout();
|
||||
}
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -22,11 +22,6 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// </summary>
|
||||
sealed class RepositoryUpdateService
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="RepositoryUpdateRequest"/> for the <see cref="RepositoryUpdateService"/>.
|
||||
/// </summary>
|
||||
readonly RepositoryUpdateRequest model;
|
||||
|
||||
/// <summary>
|
||||
/// The current <see cref="RepositorySettings"/> for the <see cref="RepositoryUpdateService"/>.
|
||||
/// </summary>
|
||||
@@ -50,19 +45,16 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepositoryUpdateService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="model">The value of <see cref="model"/>.</param>
|
||||
/// <param name="currentModel">The value of <see cref="currentModel"/>.</param>
|
||||
/// <param name="initiatingUser">The value of <see cref="initiatingUser"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="logger"/>.</param>
|
||||
/// <param name="instanceId">The value of <see cref="instanceId"/>.</param>
|
||||
public RepositoryUpdateService(
|
||||
RepositoryUpdateRequest model,
|
||||
RepositorySettings currentModel,
|
||||
User initiatingUser,
|
||||
ILogger<RepositoryUpdateService> logger,
|
||||
long instanceId)
|
||||
{
|
||||
this.model = model ?? throw new ArgumentNullException(nameof(model));
|
||||
this.currentModel = currentModel ?? throw new ArgumentNullException(nameof(currentModel));
|
||||
this.initiatingUser = initiatingUser ?? throw new ArgumentNullException(nameof(initiatingUser));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
@@ -139,24 +131,25 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <summary>
|
||||
/// The job entrypoint used by <see cref="Controllers.RepositoryController"/> to update the repository's current HEAD.
|
||||
/// </summary>
|
||||
/// <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="model">The <see cref="RepositoryUpdateRequest"/>.</param>
|
||||
/// <param name="instance">The <see cref="IInstanceCore"/> the job is running on.</param>
|
||||
/// <param name="databaseContextFactory">The <see cref="IDatabaseContextFactory"/> for the operation.</param>
|
||||
/// <param name="job">The running <see cref="Job"/>, ignored.</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="ValueTask"/> representing the running operation.</returns>
|
||||
#pragma warning disable CA1502, CA1506 // TODO: Decomplexify
|
||||
public async ValueTask RepositoryUpdateJob(
|
||||
RepositoryUpdateRequest model,
|
||||
IInstanceCore? instance,
|
||||
IDatabaseContextFactory databaseContextFactory,
|
||||
Job job,
|
||||
JobProgressReporter progressReporter,
|
||||
CancellationToken cancellationToken)
|
||||
#pragma warning restore CA1502, CA1506
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(model);
|
||||
ArgumentNullException.ThrowIfNull(instance);
|
||||
|
||||
_ = job; // shuts up an IDE warning
|
||||
ArgumentNullException.ThrowIfNull(databaseContextFactory);
|
||||
ArgumentNullException.ThrowIfNull(progressReporter);
|
||||
|
||||
var repoManager = instance.RepositoryManager;
|
||||
using var repo = await repoManager.LoadRepository(cancellationToken) ?? throw new JobException(ErrorCode.RepoMissing);
|
||||
@@ -315,6 +308,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
currentModel.AccessUser,
|
||||
currentModel.AccessToken,
|
||||
updateSubmodules,
|
||||
false,
|
||||
NextProgressReporter("Checkout"),
|
||||
cancellationToken);
|
||||
await CallLoadRevInfo(); // we've either seen origin before or what we're checking out is on origin
|
||||
@@ -579,6 +573,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
currentModel.AccessUser,
|
||||
currentModel.AccessToken,
|
||||
true,
|
||||
false,
|
||||
progressReporter.CreateSection($"Checkout {startReference ?? startSha[..7]}", secondStep ? 0.5 : 1.0),
|
||||
default);
|
||||
|
||||
@@ -588,5 +583,86 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The job entrypoint used by <see cref="Controllers.RepositoryController"/> to reclone a repository.
|
||||
/// </summary>
|
||||
/// <param name="instance">The <see cref="IInstanceCore"/> the job is running on.</param>
|
||||
/// <param name="databaseContextFactory">The <see cref="IDatabaseContextFactory"/> for the operation.</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="ValueTask"/> representing the running operation.</returns>
|
||||
public async ValueTask RepositoryRecloneJob(
|
||||
IInstanceCore? instance,
|
||||
IDatabaseContextFactory databaseContextFactory,
|
||||
JobProgressReporter progressReporter,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(instance);
|
||||
ArgumentNullException.ThrowIfNull(databaseContextFactory);
|
||||
ArgumentNullException.ThrowIfNull(progressReporter);
|
||||
|
||||
progressReporter.StageName = "Loading Old Repository";
|
||||
|
||||
Uri origin;
|
||||
string? oldReference;
|
||||
string oldSha;
|
||||
ValueTask deleteTask;
|
||||
using (var oldRepo = await instance.RepositoryManager.LoadRepository(cancellationToken))
|
||||
{
|
||||
if (oldRepo == null)
|
||||
throw new JobException(ErrorCode.RepoMissing);
|
||||
|
||||
origin = oldRepo.Origin;
|
||||
oldSha = oldRepo.Head;
|
||||
oldReference = oldRepo.Reference;
|
||||
if (oldReference == Repository.NoReference)
|
||||
oldReference = null;
|
||||
|
||||
progressReporter.StageName = "Deleting Old Repository";
|
||||
deleteTask = instance.RepositoryManager.DeleteRepository(cancellationToken);
|
||||
}
|
||||
|
||||
await deleteTask;
|
||||
progressReporter.ReportProgress(0.1);
|
||||
IRepository newRepo;
|
||||
try
|
||||
{
|
||||
newRepo = await instance.RepositoryManager.CloneRepository(
|
||||
origin,
|
||||
oldReference,
|
||||
currentModel.AccessUser,
|
||||
currentModel.AccessToken,
|
||||
progressReporter.CreateSection("Cloning New Repository", 0.8),
|
||||
true, // TODO: Make configurable maybe...
|
||||
cancellationToken)
|
||||
?? throw new JobException("A race condition occurred while recloning the repository. Somehow, it was fully cloned instantly after being deleted!"); // I'll take lines of code that should never be hit for $10k
|
||||
}
|
||||
catch (Exception ex) when (ex is not JobException)
|
||||
{
|
||||
logger.LogWarning("Reclone failed, clearing credentials!");
|
||||
|
||||
// need to clear credentials here
|
||||
await databaseContextFactory.UseContextTaskReturn(context =>
|
||||
{
|
||||
context.RepositorySettings.Attach(currentModel);
|
||||
currentModel.AccessUser = null;
|
||||
currentModel.AccessToken = null;
|
||||
return context.Save(CancellationToken.None); // DCT: Must always run
|
||||
});
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
using (newRepo)
|
||||
await newRepo.CheckoutObject(
|
||||
oldSha,
|
||||
currentModel.AccessUser,
|
||||
currentModel.AccessToken,
|
||||
false,
|
||||
oldReference != null,
|
||||
progressReporter.CreateSection("Checking out previous Detached Commit", 0.1),
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,6 +121,9 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
/// </summary>
|
||||
public FifoSemaphore TopicSendSemaphore { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public long MemoryUsage => process.MemoryUsage;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Byond.TopicSender.ITopicClient"/> for the <see cref="SessionController"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -24,6 +24,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// </summary>
|
||||
WatchdogStatus Status { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the memory usage of the game server in bytes.
|
||||
/// </summary>
|
||||
long? MemoryUsage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If the alpha server is the active server.
|
||||
/// </summary>
|
||||
|
||||
@@ -50,6 +50,9 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public long? MemoryUsage => GetActiveController()?.MemoryUsage;
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract bool AlphaIsActive { get; }
|
||||
|
||||
|
||||
@@ -29,5 +29,10 @@
|
||||
/// If <see langword="true"/>, deployments that fail will not be immediately cleaned up. They will be cleaned up the next time the instance is onlined.
|
||||
/// </summary>
|
||||
public bool DelayCleaningFailedDeployments { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If set dd.exe will not be used on Windows systems in versions where it is present. Instead dreamdaemon.exe will always be used.
|
||||
/// </summary>
|
||||
public bool ForceUseDreamDaemonExe { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
@@ -42,6 +43,11 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// </summary>
|
||||
const string OctokitException = "Bad GitHub API response, check configuration!";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IMemoryCache"/> key for <see cref="Read(bool?, CancellationToken)"/>.
|
||||
/// </summary>
|
||||
static readonly object ReadCacheKey = new();
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IGitHubServiceFactory"/> for the <see cref="AdministrationController"/>.
|
||||
/// </summary>
|
||||
@@ -77,6 +83,11 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// </summary>
|
||||
readonly IFileTransferTicketProvider fileTransferService;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IMemoryCache"/> for the <see cref="AdministrationController"/>.
|
||||
/// </summary>
|
||||
readonly IMemoryCache cacheService;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="FileLoggingConfiguration"/> for the <see cref="AdministrationController"/>.
|
||||
/// </summary>
|
||||
@@ -94,6 +105,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <param name="ioManager">The value of <see cref="ioManager"/>.</param>
|
||||
/// <param name="platformIdentifier">The value of <see cref="platformIdentifier"/>.</param>
|
||||
/// <param name="fileTransferService">The value of <see cref="fileTransferService"/>.</param>
|
||||
/// <param name="cacheService">The value of <see cref="cacheService"/>.</param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="ApiController"/>.</param>
|
||||
/// <param name="fileLoggingConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing value of <see cref="fileLoggingConfiguration"/>.</param>
|
||||
/// <param name="apiHeadersProvider">The <see cref="IApiHeadersProvider"/> for the <see cref="ApiController"/>.</param>
|
||||
@@ -107,6 +119,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
IIOManager ioManager,
|
||||
IPlatformIdentifier platformIdentifier,
|
||||
IFileTransferTicketProvider fileTransferService,
|
||||
IMemoryCache cacheService,
|
||||
ILogger<AdministrationController> logger,
|
||||
IOptions<FileLoggingConfiguration> fileLoggingConfigurationOptions,
|
||||
IApiHeadersProvider apiHeadersProvider)
|
||||
@@ -124,12 +137,14 @@ namespace Tgstation.Server.Host.Controllers
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
|
||||
this.fileTransferService = fileTransferService ?? throw new ArgumentNullException(nameof(fileTransferService));
|
||||
this.cacheService = cacheService ?? throw new ArgumentNullException(nameof(cacheService));
|
||||
fileLoggingConfiguration = fileLoggingConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(fileLoggingConfigurationOptions));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get <see cref="AdministrationResponse"/> server information.
|
||||
/// </summary>
|
||||
/// <param name="fresh">If <see langword="true"/>, the cache should be bypassed.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the <see cref="IActionResult"/> for the operation.</returns>
|
||||
/// <response code="200">Retrieved <see cref="AdministrationResponse"/> data successfully.</response>
|
||||
@@ -140,39 +155,56 @@ namespace Tgstation.Server.Host.Controllers
|
||||
[ProducesResponseType(typeof(AdministrationResponse), 200)]
|
||||
[ProducesResponseType(typeof(ErrorMessageResponse), 424)]
|
||||
[ProducesResponseType(typeof(ErrorMessageResponse), 429)]
|
||||
public async ValueTask<IActionResult> Read(CancellationToken cancellationToken)
|
||||
public async ValueTask<IActionResult> Read([FromQuery] bool? fresh, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
Version? greatestVersion = null;
|
||||
Uri? repoUrl = null;
|
||||
try
|
||||
async Task<JsonResult> CacheFactory()
|
||||
{
|
||||
var gitHubService = gitHubServiceFactory.CreateService();
|
||||
var repositoryUrlTask = gitHubService.GetUpdatesRepositoryUrl(cancellationToken);
|
||||
var releases = await gitHubService.GetTgsReleases(cancellationToken);
|
||||
|
||||
foreach (var kvp in releases)
|
||||
Version? greatestVersion = null;
|
||||
Uri? repoUrl = null;
|
||||
try
|
||||
{
|
||||
var version = kvp.Key;
|
||||
var release = kvp.Value;
|
||||
if (version.Major > 3 // Forward/backward compatible but not before TGS4
|
||||
&& (greatestVersion == null || version > greatestVersion))
|
||||
greatestVersion = version;
|
||||
var gitHubService = gitHubServiceFactory.CreateService();
|
||||
var repositoryUrlTask = gitHubService.GetUpdatesRepositoryUrl(cancellationToken);
|
||||
var releases = await gitHubService.GetTgsReleases(cancellationToken);
|
||||
|
||||
foreach (var kvp in releases)
|
||||
{
|
||||
var version = kvp.Key;
|
||||
var release = kvp.Value;
|
||||
if (version.Major > 3 // Forward/backward compatible but not before TGS4
|
||||
&& (greatestVersion == null || version > greatestVersion))
|
||||
greatestVersion = version;
|
||||
}
|
||||
|
||||
repoUrl = await repositoryUrlTask;
|
||||
}
|
||||
catch (NotFoundException e)
|
||||
{
|
||||
Logger.LogWarning(e, "Not found exception while retrieving upstream repository info!");
|
||||
}
|
||||
|
||||
repoUrl = await repositoryUrlTask;
|
||||
}
|
||||
catch (NotFoundException e)
|
||||
{
|
||||
Logger.LogWarning(e, "Not found exception while retrieving upstream repository info!");
|
||||
return Json(new AdministrationResponse
|
||||
{
|
||||
LatestVersion = greatestVersion,
|
||||
TrackedRepositoryUrl = repoUrl,
|
||||
GeneratedAt = DateTimeOffset.UtcNow,
|
||||
});
|
||||
}
|
||||
|
||||
return Json(new AdministrationResponse
|
||||
var ttl = TimeSpan.FromMinutes(30);
|
||||
Task<JsonResult> task;
|
||||
if (fresh == true || !cacheService.TryGetValue(ReadCacheKey, out var rawCacheObject))
|
||||
{
|
||||
LatestVersion = greatestVersion,
|
||||
TrackedRepositoryUrl = repoUrl,
|
||||
});
|
||||
using var entry = cacheService.CreateEntry(ReadCacheKey);
|
||||
entry.AbsoluteExpirationRelativeToNow = ttl;
|
||||
entry.Value = task = CacheFactory();
|
||||
}
|
||||
else
|
||||
task = (Task<JsonResult>)rawCacheObject!;
|
||||
|
||||
return await task;
|
||||
}
|
||||
catch (RateLimitExceededException e)
|
||||
{
|
||||
|
||||
@@ -317,11 +317,12 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <param name="knownForcedReboot">If there was a settings change made that forced a switch to <see cref="RebootState.Restart"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the <see cref="IActionResult"/> of the operation.</returns>
|
||||
#pragma warning disable CA1502 // TODO: Decomplexify
|
||||
ValueTask<IActionResult> ReadImpl(DreamDaemonSettings? settings, bool knownForcedReboot, CancellationToken cancellationToken)
|
||||
#pragma warning restore CA1502
|
||||
=> WithComponentInstance(async instance =>
|
||||
{
|
||||
var dd = instance.Watchdog;
|
||||
|
||||
var metadata = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (ulong)DreamDaemonRights.ReadMetadata) != 0;
|
||||
var revision = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (ulong)DreamDaemonRights.ReadRevision) != 0;
|
||||
|
||||
@@ -372,6 +373,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
result.Visibility = settings.Visibility!.Value;
|
||||
result.SoftRestart = rstate == RebootState.Restart;
|
||||
result.SoftShutdown = rstate == RebootState.Shutdown;
|
||||
result.ImmediateMemoryUsage = dd.MemoryUsage;
|
||||
|
||||
if (rstate == RebootState.Normal && knownForcedReboot)
|
||||
result.SoftRestart = true;
|
||||
|
||||
@@ -223,6 +223,42 @@ namespace Tgstation.Server.Host.Controllers
|
||||
return Accepted(api);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete the repository.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the <see cref="IActionResult"/> of the operation.</returns>
|
||||
/// <response code="202">Job to delete the repository created successfully.</response>
|
||||
/// <response code="410">The database entity for the requested instance could not be retrieved. The instance was likely detached.</response>
|
||||
[HttpPatch]
|
||||
[TgsAuthorize(RepositoryRights.Reclone)]
|
||||
[ProducesResponseType(typeof(RepositoryResponse), 202)]
|
||||
[ProducesResponseType(typeof(ErrorMessageResponse), 410)]
|
||||
public async ValueTask<IActionResult> Reclone(CancellationToken cancellationToken)
|
||||
{
|
||||
var currentModel = await DatabaseContext
|
||||
.RepositorySettings
|
||||
.AsQueryable()
|
||||
.Where(x => x.InstanceId == Instance.Id)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
if (currentModel == default)
|
||||
return this.Gone();
|
||||
|
||||
Logger.LogInformation("Instance {instanceId} repository reclone initiated by user {userId}", Instance.Id, AuthenticationContext.User.Require(x => x.Id));
|
||||
|
||||
var repositoryUpdater = CreateRepositoryUpdateService(currentModel);
|
||||
|
||||
var job = Job.Create(JobCode.RepositoryReclone, AuthenticationContext.User, Instance);
|
||||
var api = currentModel.ToApi();
|
||||
await jobManager.RegisterOperation(
|
||||
job,
|
||||
(core, databaseContextFactory, paramJob, progressReporter, ct) => repositoryUpdater.RepositoryRecloneJob(core, databaseContextFactory, progressReporter, ct),
|
||||
cancellationToken);
|
||||
api.ActiveJob = job.ToApi();
|
||||
return Accepted(api);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the repository's status.
|
||||
/// </summary>
|
||||
@@ -443,17 +479,12 @@ namespace Tgstation.Server.Host.Controllers
|
||||
var job = Job.Create(JobCode.RepositoryUpdate, AuthenticationContext.User, Instance, RepositoryRights.CancelPendingChanges);
|
||||
job.Description = description;
|
||||
|
||||
var repositoryUpdater = new RepositoryUpdateService(
|
||||
model,
|
||||
currentModel,
|
||||
AuthenticationContext.User,
|
||||
loggerFactory.CreateLogger<RepositoryUpdateService>(),
|
||||
Instance.Require(x => x.Id));
|
||||
var repositoryUpdater = CreateRepositoryUpdateService(currentModel);
|
||||
|
||||
// Time to access git, do it in a job
|
||||
await jobManager.RegisterOperation(
|
||||
job,
|
||||
repositoryUpdater.RepositoryUpdateJob,
|
||||
(instance, databaseContextFactory, _, progressReporter, jobToken) => repositoryUpdater.RepositoryUpdateJob(model, instance, databaseContextFactory, progressReporter, jobToken),
|
||||
cancellationToken);
|
||||
|
||||
api.ActiveJob = job.ToApi();
|
||||
@@ -494,5 +525,17 @@ namespace Tgstation.Server.Host.Controllers
|
||||
cancellationToken);
|
||||
return needsDbUpdate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="RepositoryUpdateService"/>.
|
||||
/// </summary>
|
||||
/// <param name="currentModel">The current <see cref="RepositorySettings"/>.</param>
|
||||
/// <returns>A new <see cref="RepositoryUpdateService"/>.</returns>
|
||||
RepositoryUpdateService CreateRepositoryUpdateService(RepositorySettings currentModel)
|
||||
=> new(
|
||||
currentModel,
|
||||
AuthenticationContext.User,
|
||||
loggerFactory.CreateLogger<RepositoryUpdateService>(),
|
||||
Instance.Require(x => x.Id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,11 @@ namespace Tgstation.Server.Host.System
|
||||
/// </summary>
|
||||
Task<int?> Lifetime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the process' memory usage in bytes.
|
||||
/// </summary>
|
||||
long MemoryUsage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Set's the owned <see cref="global::System.Diagnostics.Process.PriorityClass"/> to a non-normal value.
|
||||
/// </summary>
|
||||
|
||||
@@ -22,6 +22,9 @@ namespace Tgstation.Server.Host.System
|
||||
/// <inheritdoc />
|
||||
public Task<int?> Lifetime { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public long MemoryUsage => handle.VirtualMemorySize64;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IProcessFeatures"/> for the <see cref="Process"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -115,9 +115,9 @@
|
||||
<!-- Usage: File logging plugin -->
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<!-- Usage: OpenAPI spec generator -->
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.7.0" />
|
||||
<!-- Usage: Newtonsoft.Json plugin for OpenAPI spec generator -->
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="6.6.2" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="6.7.0" />
|
||||
<!-- Usage: Windows authentication plugin allowing searching for users by name -->
|
||||
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="8.0.0" />
|
||||
<!-- Usage: Identifying owning user of Windows Process objects -->
|
||||
|
||||
@@ -23,6 +23,7 @@ Session:
|
||||
HighPriorityLiveDreamDaemon: false # If DreamDaemon instances should run as higher priority processes
|
||||
LowPriorityDeploymentProcesses: true # If TGS Deployments should run as lower priority processes
|
||||
DelayCleaningFailedDeployments: false # If true, deployments that fail will not be immediately cleaned up. They will be cleaned up the next time the instance is onlined
|
||||
ForceUseDreamDaemonExe: false # If true, dd.exe will not be used on Windows systems in versions where it is present. Instead dreamdaemon.exe will always be used.
|
||||
FileLogging:
|
||||
Directory: # Directory in which log files are stored. Windows default: %PROGRAMDATA%/tgstation-server. Linux default: /var/log/tgstation-server
|
||||
Disable: true # Disable file logging entirely
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Models.Response;
|
||||
using Tgstation.Server.Client;
|
||||
@@ -54,10 +54,24 @@ namespace Tgstation.Server.Tests.Live
|
||||
|
||||
async Task TestRead(CancellationToken cancellationToken)
|
||||
{
|
||||
var model = await client.Read(cancellationToken);
|
||||
var model = await client.Read(false, cancellationToken);
|
||||
|
||||
//we've released a few 5.x versions now, check the release checker is at least somewhat functional
|
||||
Assert.IsTrue(4 < model.LatestVersion.Major);
|
||||
Assert.IsNotNull(model.TrackedRepositoryUrl);
|
||||
Assert.IsTrue(model.GeneratedAt.HasValue);
|
||||
Assert.IsTrue(model.GeneratedAt.Value <= DateTimeOffset.UtcNow);
|
||||
|
||||
// test the cache
|
||||
var newerModel = await client.Read(false, cancellationToken);
|
||||
Assert.AreEqual(model.GeneratedAt, newerModel.GeneratedAt);
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
|
||||
|
||||
var newestModel = await client.Read(true, cancellationToken);
|
||||
Assert.AreNotEqual(model.GeneratedAt, newestModel.GeneratedAt);
|
||||
Assert.IsNotNull(newestModel.GeneratedAt);
|
||||
Assert.IsTrue(model.GeneratedAt < newestModel.GeneratedAt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,7 +262,7 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
Assert.AreEqual(HubConnectionState.Disconnected, permlessConn.State);
|
||||
|
||||
// force token refreshs
|
||||
await Task.WhenAll(permedUser.Administration.Read(cancellationToken).AsTask(), permlessUser.Instances.List(null, cancellationToken).AsTask());
|
||||
await Task.WhenAll(permedUser.Administration.Read(false, cancellationToken).AsTask(), permlessUser.Instances.List(null, cancellationToken).AsTask());
|
||||
|
||||
if (!permlessPsId.HasValue)
|
||||
{
|
||||
|
||||
@@ -116,6 +116,8 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
await ApiAssert.ThrowsException<ApiConflictException, RepositoryResponse>(() => Checkout(new RepositoryUpdateRequest { Reference = "master", CheckoutSha = "286bb75" }, false, false, cancellationToken), ErrorCode.RepoMismatchShaAndReference);
|
||||
var updated = await Checkout(new RepositoryUpdateRequest { CheckoutSha = "286bb75" }, false, false, cancellationToken);
|
||||
|
||||
await RecloneTest(cancellationToken);
|
||||
|
||||
// Fake SHA
|
||||
updated = await Checkout(new RepositoryUpdateRequest { CheckoutSha = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }, true, false, cancellationToken);
|
||||
|
||||
@@ -142,6 +144,23 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
await TestMergeTests(updated, prNumber, cancellationToken);
|
||||
}
|
||||
|
||||
async ValueTask RecloneTest(CancellationToken cancellationToken)
|
||||
{
|
||||
var initialState = await repositoryClient.Read(cancellationToken);
|
||||
Assert.IsNotNull(initialState.Reference);
|
||||
Assert.IsNotNull(initialState.RevisionInformation);
|
||||
Assert.IsNotNull(initialState.RevisionInformation.CommitSha);
|
||||
Assert.IsNotNull(initialState.RevisionInformation.OriginCommitSha);
|
||||
|
||||
var reclone = await repositoryClient.Reclone(cancellationToken);
|
||||
await WaitForJob(reclone.ActiveJob, 70, false, null, cancellationToken);
|
||||
|
||||
var newState = await repositoryClient.Read(cancellationToken);
|
||||
Assert.AreEqual(initialState.Reference, newState.Reference);
|
||||
Assert.AreEqual(initialState.RevisionInformation.CommitSha, newState.RevisionInformation.CommitSha);
|
||||
Assert.AreEqual(initialState.RevisionInformation.OriginCommitSha, newState.RevisionInformation.OriginCommitSha);
|
||||
}
|
||||
|
||||
async ValueTask<RepositoryResponse> Checkout(RepositoryUpdateRequest updated, bool expectFailure, bool isRef, CancellationToken cancellationToken)
|
||||
{
|
||||
var newRef = isRef ? updated.Reference : updated.CheckoutSha;
|
||||
|
||||
@@ -150,6 +150,7 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
}, cancellationToken);
|
||||
|
||||
Assert.AreEqual<ushort?>(47, updated.OpenDreamTopicPort);
|
||||
Assert.IsFalse(updated.ImmediateMemoryUsage.HasValue);
|
||||
}
|
||||
catch (ConflictException ex) when (ex.ErrorCode == ErrorCode.PortNotAvailable)
|
||||
{
|
||||
@@ -721,6 +722,8 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
await CheckDDPriority();
|
||||
Assert.AreEqual(false, daemonStatus.SoftRestart);
|
||||
Assert.AreEqual(false, daemonStatus.SoftShutdown);
|
||||
Assert.IsTrue(daemonStatus.ImmediateMemoryUsage.HasValue);
|
||||
Assert.AreNotEqual(0, daemonStatus.ImmediateMemoryUsage.Value);
|
||||
|
||||
await GracefulWatchdogShutdown(cancellationToken);
|
||||
|
||||
@@ -1365,6 +1368,9 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
Assert.IsNotNull(daemonStatus.ActiveCompileJob);
|
||||
ValidateSessionId(daemonStatus, true);
|
||||
|
||||
Assert.IsTrue(daemonStatus.ImmediateMemoryUsage.HasValue);
|
||||
Assert.AreNotEqual(0, daemonStatus.ImmediateMemoryUsage.Value);
|
||||
|
||||
Assert.AreEqual(initialStatus.ActiveCompileJob.Id, daemonStatus.ActiveCompileJob.Id);
|
||||
var newerCompileJob = daemonStatus.StagedCompileJob;
|
||||
Assert.AreNotEqual(daemonStatus.ActiveCompileJob.EngineVersion, newerCompileJob.EngineVersion);
|
||||
|
||||
@@ -216,7 +216,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
};
|
||||
|
||||
var badClient = clientFactory.CreateFromToken(serverClient.Url, newToken);
|
||||
await ApiAssert.ThrowsException<UnauthorizedException, AdministrationResponse>(() => badClient.Administration.Read(cancellationToken));
|
||||
await ApiAssert.ThrowsException<UnauthorizedException, AdministrationResponse>(() => badClient.Administration.Read(false, cancellationToken));
|
||||
await ApiAssert.ThrowsException<UnauthorizedException, ServerInformationResponse>(() => badClient.ServerInformation(cancellationToken));
|
||||
}
|
||||
|
||||
|
||||
@@ -670,7 +670,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
"asdfasdfasdfasdf");
|
||||
|
||||
await using var node1BadClient = clientFactory.CreateFromToken(node1.RootUrl, controllerUserClient.Token);
|
||||
await ApiAssert.ThrowsException<UnauthorizedException, AdministrationResponse>(() => node1BadClient.Administration.Read(cancellationToken));
|
||||
await ApiAssert.ThrowsException<UnauthorizedException, AdministrationResponse>(() => node1BadClient.Administration.Read(false, cancellationToken));
|
||||
|
||||
// check instance info is not shared
|
||||
var controllerInstance = await controllerClient.Instances.CreateOrAttach(
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace Tgstation.Server.Tests
|
||||
() => { });
|
||||
|
||||
const string StartSha = "af4da8beb9f9b374b04a3cc4d65acca662e8cc1a";
|
||||
await repo.CheckoutObject(StartSha, null, null, true, new JobProgressReporter(Mock.Of<ILogger<JobProgressReporter>>(), null, (stage, progress) => { }), CancellationToken.None);
|
||||
await repo.CheckoutObject(StartSha, null, null, true, false, new JobProgressReporter(Mock.Of<ILogger<JobProgressReporter>>(), null, (stage, progress) => { }), CancellationToken.None);
|
||||
|
||||
Assert.AreEqual(Host.Components.Repository.Repository.NoReference, repo.Reference);
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ namespace Tgstation.Server.ReleaseNotes
|
||||
return 454233;
|
||||
}
|
||||
|
||||
await GenerateAppCredentials(client, args[1]);
|
||||
await GenerateAppCredentials(client, args[1], false);
|
||||
|
||||
return await EnsureRelease(client);
|
||||
}
|
||||
@@ -171,7 +171,8 @@ namespace Tgstation.Server.ReleaseNotes
|
||||
return 33847;
|
||||
}
|
||||
|
||||
await GenerateAppCredentials(client, args[2]);
|
||||
bool toSS13 = args.Length > 3 && args[3].Equals("--spacestation13", StringComparison.OrdinalIgnoreCase);
|
||||
await GenerateAppCredentials(client, args[2], toSS13);
|
||||
|
||||
var token = client.Credentials.GetToken();
|
||||
var destPath = args[1];
|
||||
@@ -1640,7 +1641,7 @@ package (version) distribution(s); urgency=urgency
|
||||
return 0;
|
||||
}
|
||||
|
||||
static async ValueTask GenerateAppCredentials(GitHubClient gitHubClient, string pemBase64)
|
||||
static async ValueTask GenerateAppCredentials(GitHubClient gitHubClient, string pemBase64, bool toSS13)
|
||||
{
|
||||
var pemBytes = Convert.FromBase64String(pemBase64);
|
||||
var pem = Encoding.UTF8.GetString(pemBytes);
|
||||
@@ -1665,7 +1666,11 @@ package (version) distribution(s); urgency=urgency
|
||||
|
||||
gitHubClient.Credentials = new Credentials(jwtStr, AuthenticationType.Bearer);
|
||||
|
||||
var installation = await gitHubClient.GitHubApps.GetRepositoryInstallationForCurrent(RepoOwner, RepoName);
|
||||
var installation = await gitHubClient.GitHubApps.GetRepositoryInstallationForCurrent(
|
||||
toSS13
|
||||
? "spacestation13"
|
||||
: RepoOwner,
|
||||
RepoName);
|
||||
var installToken = await gitHubClient.GitHubApps.CreateInstallationToken(installation.Id);
|
||||
|
||||
gitHubClient.Credentials = new Credentials(installToken.Token);
|
||||
@@ -1673,7 +1678,7 @@ package (version) distribution(s); urgency=urgency
|
||||
|
||||
static async ValueTask<int> CICompletionCheck(GitHubClient gitHubClient, string currentSha, string pemBase64)
|
||||
{
|
||||
await GenerateAppCredentials(gitHubClient, pemBase64);
|
||||
await GenerateAppCredentials(gitHubClient, pemBase64, false);
|
||||
|
||||
await gitHubClient.Check.Run.Create(RepoOwner, RepoName, new NewCheckRun("CI Completion", currentSha)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user