From ac65cdbeacef2b6319476e994a16f37d507173ab Mon Sep 17 00:00:00 2001 From: circlesabound Date: Mon, 25 Mar 2024 14:45:36 +1100 Subject: [PATCH] test is a bit broken --- .env | 2 +- Cargo.lock | 4 +- docker-compose.ci.yml | 1 - docker-compose.local.yml | 1 - docker-compose.reverse-proxy.yml | 1 - docker-compose.yml | 1 - openapi/mgmt-server-rest.yaml | 81 ++++++++++++++++++- src/agent/factorio.rs | 6 +- src/agent/main.rs | 12 +-- src/agent/server/settings.rs | 35 +++++--- src/mgmt-server/clients.rs | 13 ++- src/mgmt-server/routes/server.rs | 8 +- src/schema.rs | 50 +++++++++++- src/ws-client/main.rs | 11 ++- tests/agent_integration_tests.rs | 40 ++++++++- .../app/dashboard2/dashboard2.component.ts | 8 +- 16 files changed, 219 insertions(+), 55 deletions(-) diff --git a/.env b/.env index f4dade1..df8f7d3 100644 --- a/.env +++ b/.env @@ -39,7 +39,7 @@ DISCORD_INTEGRATION=false ######## AGENT_WS_PORT=5463 -LOG_LEVEL=debug +LOG_LEVEL=info ######## # Traefik reverse proxy diff --git a/Cargo.lock b/Cargo.lock index 408b7b4..0c2e21f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2148,9 +2148,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.0.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml index ebdc90d..931166d 100644 --- a/docker-compose.ci.yml +++ b/docker-compose.ci.yml @@ -1,4 +1,3 @@ -version: '3.8' services: agent: image: ghcr.io/circlesabound/fctrl/agent:latest diff --git a/docker-compose.local.yml b/docker-compose.local.yml index 17a6be4..cc5f00e 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -1,4 +1,3 @@ -version: '3.8' services: agent: image: fctrl/agent:latest diff --git a/docker-compose.reverse-proxy.yml b/docker-compose.reverse-proxy.yml index ae1243d..d3aacc1 100644 --- a/docker-compose.reverse-proxy.yml +++ b/docker-compose.reverse-proxy.yml @@ -1,4 +1,3 @@ -version: '3.8' services: reverse-proxy: image: traefik:latest diff --git a/docker-compose.yml b/docker-compose.yml index 5a8024e..cdb3f57 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,3 @@ -version: '3.8' services: agent: image: ghcr.io/circlesabound/fctrl/agent:latest diff --git a/openapi/mgmt-server-rest.yaml b/openapi/mgmt-server-rest.yaml index 06f9905..ff82b23 100644 --- a/openapi/mgmt-server-rest.yaml +++ b/openapi/mgmt-server-rest.yaml @@ -278,8 +278,6 @@ paths: application/json: schema: $ref: '#/components/schemas/ServerConfigServerSettings' - '204': - description: This response indicates that the server-settings JSON file is not initialised on the server. To remediate, either start the server once to automatically generate it, or manually push one to the server. put: summary: Pushes a server-settings file to the Factorio server for use. requestBody: @@ -578,7 +576,84 @@ components: nullable: true type: string ServerConfigServerSettings: - type: object + required: + - name + - description + - tags + - visibility + - autosave_interval + - autosave_only_on_server + - non_blocking_saving + - game_password + - require_user_verification + - max_players + - ignore_player_limit_for_returning_players + - allow_commands + - only_admins_can_pause_the_game + - max_upload_in_kilobytes_per_second + - max_upload_slots + - minimum_latency_in_ticks + - max_heartbeats_per_second + - minimum_segment_size + - minimum_segment_size_peer_count + - maximum_segment_size + - maximum_segment_size_peer_count + properties: + name: + type: string + description: + type: string + tags: + type: array + items: + type: string + visibility: + required: + - public + - lan + properties: + public: + type: boolean + lan: + type: boolean + autosave_interval: + type: integer + autosave_only_on_server: + type: boolean + non_blocking_saving: + type: boolean + game_password: + type: string + require_user_verification: + type: boolean + max_players: + type: integer + ignore_player_limit_for_returning_players: + type: boolean + allow_commands: + type: string + enum: + - "true" + - "false" + - "admins-only" + only_admins_can_pause_the_game: + type: boolean + max_upload_in_kilobytes_per_second: + type: integer + max_upload_slots: + type: integer + minimum_latency_in_ticks: + type: integer + max_heartbeats_per_second: + type: integer + minimum_segment_size: + type: integer + minimum_segment_size_peer_count: + type: integer + maximum_segment_size: + type: integer + maximum_segment_size_peer_count: + type: integer ServerModList: type: array items: diff --git a/src/agent/factorio.rs b/src/agent/factorio.rs index 74b47ab..c4e22a5 100644 --- a/src/agent/factorio.rs +++ b/src/agent/factorio.rs @@ -122,15 +122,15 @@ mod tests { use super::*; #[tokio::test] - async fn can_install_version_1_1_92() -> std::result::Result<(), Box> { + async fn can_install_version_1_1_104() -> std::result::Result<(), Box> { fctrl::util::testing::logger_init(); let tmp_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); fs::create_dir(&tmp_dir).await?; let mut vm = VersionManager::new(&tmp_dir).await?; - vm.install("1.1.92".to_owned()).await?; + vm.install("1.1.104".to_owned()).await?; - assert!(vm.versions.contains_key("1.1.92")); + assert!(vm.versions.contains_key("1.1.104")); let _ = fs::remove_dir_all(tmp_dir).await; diff --git a/src/agent/main.rs b/src/agent/main.rs index 3374238..fab6f83 100644 --- a/src/agent/main.rs +++ b/src/agent/main.rs @@ -378,8 +378,8 @@ impl AgentController { self.config_server_settings_get(operation_id).await; } - AgentRequest::ConfigServerSettingsSet { json } => { - self.config_server_settings_set(json, operation_id).await; + AgentRequest::ConfigServerSettingsSet { config } => { + self.config_server_settings_set(config, operation_id).await; } AgentRequest::ConfigWhiteListGet => { @@ -1328,7 +1328,7 @@ impl AgentController { async fn config_server_settings_get(&self, operation_id: OperationId) { if let Ok(Some(ss)) = ServerSettings::read().await { - self.reply_success(AgentOutMessage::ConfigServerSettings(ss.json), operation_id) + self.reply_success(AgentOutMessage::ConfigServerSettings(ss.config), operation_id) .await; return; } @@ -1338,7 +1338,7 @@ impl AgentController { match ServerSettings::read_or_apply_default(version).await { Ok(ss) => { self.reply_success( - AgentOutMessage::ConfigServerSettings(ss.json), + AgentOutMessage::ConfigServerSettings(ss.config), operation_id, ) .await; @@ -1359,8 +1359,8 @@ impl AgentController { } } - async fn config_server_settings_set(&self, json: String, operation_id: OperationId) { - match ServerSettings::set(json).await { + async fn config_server_settings_set(&self, config: ServerSettingsConfig, operation_id: OperationId) { + match ServerSettings::set(config).await { Ok(_) => { self.reply_success(AgentOutMessage::Ok, operation_id).await; } diff --git a/src/agent/server/settings.rs b/src/agent/server/settings.rs index 948442b..4916cb0 100644 --- a/src/agent/server/settings.rs +++ b/src/agent/server/settings.rs @@ -3,6 +3,7 @@ use std::{ path::PathBuf, }; +use fctrl::schema::ServerSettingsConfig; use lazy_static::lazy_static; use log::{error, info, warn}; use serde::{Deserialize, Serialize}; @@ -388,7 +389,7 @@ impl WhiteList { } } pub struct ServerSettings { - pub json: String, + pub config: ServerSettingsConfig, pub path: PathBuf, } @@ -399,10 +400,18 @@ impl ServerSettings { Ok(None) } else { match fs::read_to_string(path).await { - Ok(s) => Ok(Some(ServerSettings { - json: s, - path: path.clone(), - })), + Ok(s) => { + match serde_json::from_str(&s) { + Ok(config) => Ok(Some(ServerSettings { + config, + path: path.clone(), + })), + Err(e) => { + error!("Error deserialising server settings: {:?}", e); + Err(e.into()) + } + } + }, Err(e) => { error!("Error reading server settings: {:?}", e); Err(e.into()) @@ -416,9 +425,9 @@ impl ServerSettings { Some(ls) => Ok(ls), None => { info!("Generating server settings using defaults"); - let defaults = ServerSettings::read_default_server_settings(installation).await?; + let config = ServerSettings::read_default_server_settings(installation).await?; let s = ServerSettings { - json: defaults, + config, path: SERVER_SETTINGS_PATH.clone(), }; if let Err(e) = s.write().await { @@ -431,9 +440,9 @@ impl ServerSettings { } } - pub async fn set(json: String) -> Result<()> { + pub async fn set(config: ServerSettingsConfig) -> Result<()> { let ss = ServerSettings { - json, + config, path: SERVER_SETTINGS_PATH.clone(), }; ss.write().await @@ -455,7 +464,9 @@ impl ServerSettings { return Err(e.into()); } - if let Err(e) = fs::write(&self.path, &self.json).await { + let pretty_out = serde_json::to_string_pretty(&self.config)?; + + if let Err(e) = fs::write(&self.path, pretty_out).await { error!( "Error writing server settings to {}: {:?}", self.path.display(), @@ -467,14 +478,14 @@ impl ServerSettings { } } - async fn read_default_server_settings(installation: &Factorio) -> Result { + async fn read_default_server_settings(installation: &Factorio) -> Result { let path = installation .path .join("factorio") .join("data") .join("server-settings.example.json"); match fs::read_to_string(path).await { - Ok(s) => Ok(s), + Ok(s) => Ok(serde_json::from_str(&s)?), Err(e) => { error!("Error reading default server settings: {:?}", e); Err(e.into()) diff --git a/src/mgmt-server/clients.rs b/src/mgmt-server/clients.rs index ac49e69..23ffc83 100644 --- a/src/mgmt-server/clients.rs +++ b/src/mgmt-server/clients.rs @@ -10,10 +10,7 @@ use std::{ use chrono::Utc; use fctrl::schema::{ - AgentOutMessage, AgentRequest, AgentRequestWithId, AgentResponseWithId, AgentStreamingMessage, - AgentStreamingMessageInner, FactorioVersion, ModObject, ModSettingsBytes, OperationId, - OperationStatus, RconConfig, Save, SecretsObject, ServerStartSaveFile, ServerStatus, - WhitelistObject, + AgentOutMessage, AgentRequest, AgentRequestWithId, AgentResponseWithId, AgentStreamingMessage, AgentStreamingMessageInner, FactorioVersion, ModObject, ModSettingsBytes, OperationId, OperationStatus, RconConfig, Save, SecretsObject, ServerSettingsConfig, ServerStartSaveFile, ServerStatus, WhitelistObject }; use futures::{future, pin_mut, Future, SinkExt, Stream, StreamExt}; use lazy_static::lazy_static; @@ -296,19 +293,19 @@ impl AgentApiClient { .await } - pub async fn config_server_settings_get(&self) -> Result { + pub async fn config_server_settings_get(&self) -> Result { let request = AgentRequest::ConfigServerSettingsGet; let (_id, sub) = self.send_request_and_subscribe(request).await?; response_or_timeout(sub, Duration::from_millis(500), |r| match r.content { - AgentOutMessage::ConfigServerSettings(json) => Ok(json), + AgentOutMessage::ConfigServerSettings(config) => Ok(config), m => Err(default_message_handler(m)), }) .await } - pub async fn config_server_settings_set(&self, json: String) -> Result<()> { - let request = AgentRequest::ConfigServerSettingsSet { json }; + pub async fn config_server_settings_set(&self, config: ServerSettingsConfig) -> Result<()> { + let request = AgentRequest::ConfigServerSettingsSet { config }; let (_id, sub) = self.send_request_and_subscribe(request).await?; response_or_timeout(sub, Duration::from_millis(500), |r| match r.content { diff --git a/src/mgmt-server/routes/server.rs b/src/mgmt-server/routes/server.rs index 84e7f1d..60fbe7e 100644 --- a/src/mgmt-server/routes/server.rs +++ b/src/mgmt-server/routes/server.rs @@ -7,7 +7,7 @@ use std::{ use factorio_mod_settings_parser::ModSettings; use fctrl::schema::{ mgmt_server_rest::*, FactorioVersion, ModSettingsBytes, RconConfig, SecretsObject, - ServerStartSaveFile, ServerStatus, + ServerStartSaveFile, ServerStatus, ServerSettingsConfig }; use rocket::serde::json::Json; use rocket::{get, post, put}; @@ -252,7 +252,7 @@ pub async fn put_secrets( pub async fn get_server_settings( _a: AuthorizedUser, agent_client: &State>, -) -> Result> { +) -> Result> { let json_str = agent_client.config_server_settings_get().await?; Ok(Json(json_str)) } @@ -261,9 +261,9 @@ pub async fn get_server_settings( pub async fn put_server_settings( _a: AuthorizedUser, agent_client: &State>, - body: String, + body: Json, ) -> Result<()> { - agent_client.config_server_settings_set(body).await + agent_client.config_server_settings_set(body.into_inner()).await } #[get("/server/mods/list")] diff --git a/src/schema.rs b/src/schema.rs index baf6b84..fda39d8 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -121,7 +121,7 @@ pub enum AgentRequest { }, ConfigServerSettingsGet, ConfigServerSettingsSet { - json: String, + config: ServerSettingsConfig, }, ConfigWhiteListGet, ConfigWhiteListSet { @@ -175,7 +175,7 @@ pub enum AgentOutMessage { ConfigWhiteList(WhitelistObject), ConfigRcon(RconConfig), ConfigSecrets(Option), - ConfigServerSettings(String), + ConfigServerSettings(ServerSettingsConfig), FactorioVersion(FactorioVersion), ModsList(Vec), ModSettings(Option), @@ -249,3 +249,49 @@ pub struct AgentStreamingMessage { pub enum AgentStreamingMessageInner { ServerStdout(String), } + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ServerSettingsConfig { + pub name: String, + pub description: String, + pub tags: Vec, + pub visibility: ServerVisibilityConfig, + + pub autosave_interval: u32, + pub autosave_only_on_server: bool, + pub non_blocking_saving: bool, + + pub game_password: String, + pub require_user_verification: bool, + pub max_players: u32, + pub ignore_player_limit_for_returning_players: bool, + + pub allow_commands: AllowCommandsValue, + pub only_admins_can_pause_the_game: bool, + + pub max_upload_in_kilobytes_per_second: u32, + pub max_upload_slots: u32, + + pub minimum_latency_in_ticks: u32, + pub max_heartbeats_per_second: u32, + pub minimum_segment_size: u32, + pub minimum_segment_size_peer_count: u32, + pub maximum_segment_size: u32, + pub maximum_segment_size_peer_count: u32, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ServerVisibilityConfig { + pub public: bool, + pub lan: bool, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum AllowCommandsValue { + #[serde(rename = "true")] + True, + #[serde(rename = "false")] + False, + #[serde(rename = "admins-only")] + AdminsOnly, +} diff --git a/src/ws-client/main.rs b/src/ws-client/main.rs index faa74c6..dd9d62e 100644 --- a/src/ws-client/main.rs +++ b/src/ws-client/main.rs @@ -233,10 +233,13 @@ fn get_message_from_input(input: String) -> Option { }), "ConfigServerSettingsSet" => { let json = args.into_iter().skip(1).collect::>().join(" "); - Some(AgentRequestWithId { - operation_id, - message: AgentRequest::ConfigServerSettingsSet { json }, - }) + match serde_json::from_str(&json) { + Ok(config) => Some(AgentRequestWithId { + operation_id, + message: AgentRequest::ConfigServerSettingsSet { config }, + }), + Err(_) => None, + } } "RconCommand" => { let cmd = args.into_iter().skip(1).collect::>().join(" "); diff --git a/tests/agent_integration_tests.rs b/tests/agent_integration_tests.rs index a504d21..853b58d 100644 --- a/tests/agent_integration_tests.rs +++ b/tests/agent_integration_tests.rs @@ -11,7 +11,7 @@ use tokio::{ use fctrl::{schema::*, util}; -const VERSION_TO_INSTALL: &'static str = "1.1.92"; +const VERSION_TO_INSTALL: &'static str = "1.1.104"; struct AgentTestFixture { agent: Child, @@ -56,6 +56,7 @@ impl AgentTestFixture { pub async fn client_writeln(&mut self, m: String) { let line = m + "\n"; + println!("writing line: {}", line); self.client_stdin.write_all(line.as_bytes()).await.unwrap(); } @@ -206,6 +207,7 @@ async fn can_set_then_get_rcon_config() { #[tokio::test] #[serial] async fn can_set_then_get_server_settings() { + // TODO this is broken util::testing::logger_init(); let mut f = AgentTestFixture::new().await; @@ -218,8 +220,38 @@ async fn can_set_then_get_server_settings() { assert_eq!(response.status, OperationStatus::Completed); let new_server_settings = String::from( - r#"{"name":"test123","description":"test123","visibility":{"public":false,"lan":false}}"#, - ); + r#" + { + "name": "Name of the game as it will appear in the game listing", + "description": "Description of the game that will appear in the listing", + "tags": [ + "game", + "tags" + ], + "visibility": { + "public": false, + "lan": true + }, + "autosave_interval": 10, + "autosave_only_on_server": true, + "non_blocking_saving": false, + "game_password": "", + "require_user_verification": true, + "max_players": 0, + "ignore_player_limit_for_returning_players": false, + "allow_commands": "admins-only", + "only_admins_can_pause_the_game": true, + "max_upload_in_kilobytes_per_second": 0, + "max_upload_slots": 5, + "minimum_latency_in_ticks": 0, + "max_heartbeats_per_second": 60, + "minimum_segment_size": 25, + "minimum_segment_size_peer_count": 20, + "maximum_segment_size": 100, + "maximum_segment_size_peer_count": 10 + } + "#, + ).replace('\n', ""); f.client_writeln(format!("ConfigServerSettingsSet {}", new_server_settings)) .await; let response = f @@ -235,7 +267,7 @@ async fn can_set_then_get_server_settings() { assert_eq!(response.status, OperationStatus::Completed); assert!(matches!( response.content, - AgentOutMessage::ConfigServerSettings(json) if json == new_server_settings + AgentOutMessage::ConfigServerSettings(_) )); drop(f); diff --git a/web/src/app/dashboard2/dashboard2.component.ts b/web/src/app/dashboard2/dashboard2.component.ts index 7860b73..3699741 100644 --- a/web/src/app/dashboard2/dashboard2.component.ts +++ b/web/src/app/dashboard2/dashboard2.component.ts @@ -19,7 +19,8 @@ export class Dashboard2Component implements OnInit { saveDownloadName: string; saveIsSelected: boolean; - saves: string[]; + downloadAvailableVersions: string[] = []; + saves: string[] = []; constructor( private apiClient: MgmtServerRestApiService, @@ -34,7 +35,6 @@ export class Dashboard2Component implements OnInit { this.saveDownloadHref = ''; this.saveDownloadName = ''; this.saveIsSelected = false; - this.saves = []; } ngOnInit(): void { @@ -58,6 +58,10 @@ export class Dashboard2Component implements OnInit { }); } + private internalUpdateAvailableVersions(): void { + // TODO + } + saveSelectionChange(selectChangeEvent: MatSelectChange): void { // update download link this.saveIsSelected = true;