diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 6608edab..07b851dd 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -1,6 +1,7 @@ -use crate::config::config::ConfigOptionCli; use clap::{Args, Parser, Subcommand, ValueEnum}; +use crate::commands::config::config_option::ConfigOptionCli; + #[derive(Parser)] #[command(version, about, long_about = None)] pub struct Cli { @@ -19,7 +20,7 @@ pub enum Commands { #[arg(short, long)] name: String, #[command(subcommand)] - option: ConfigOptionCli + option: ConfigOptionCli, }, /// Uploads new game version to depot Upload(UploadInfo), diff --git a/cli/src/config/config.rs b/cli/src/commands/config/config.rs similarity index 87% rename from cli/src/config/config.rs rename to cli/src/commands/config/config.rs index 440e2ed3..abc042cd 100644 --- a/cli/src/config/config.rs +++ b/cli/src/commands/config/config.rs @@ -1,8 +1,4 @@ -use crate::config::{ - s3::{S3Config, S3ConfigCli}, - server::ServerConfig, -}; -use clap::Subcommand; +use crate::commands::config::{config_option::ConfigOption, s3::S3Config}; use log::warn; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, fs}; @@ -15,6 +11,12 @@ pub struct Config { active_s3: Option, } impl Config { + pub fn new() -> Self { + Self { + items: HashMap::new(), + active_s3: None, + } + } pub fn save(&self) -> anyhow::Result<()> { let json = serde_json::to_string(self)?; let save_path = dirs::config_dir() @@ -41,26 +43,7 @@ impl Config { self.items.insert(name, object); self.save().expect("Failed to save config"); } -} -#[derive(Subcommand, Clone)] -pub enum ConfigOptionCli { - Server(ServerConfig), - S3(S3ConfigCli), -} -#[derive(Serialize, Deserialize, Clone)] -pub enum ConfigOption { - Server(ServerConfig), - S3(S3Config), -} - -impl Config { - pub fn new() -> Self { - Self { - items: HashMap::new(), - active_s3: None, - } - } pub fn get_active_s3(&self) -> Option { if let Some(active_s3) = &self.active_s3 { self.items diff --git a/cli/src/commands/config/config_option.rs b/cli/src/commands/config/config_option.rs new file mode 100644 index 00000000..8e61f237 --- /dev/null +++ b/cli/src/commands/config/config_option.rs @@ -0,0 +1,18 @@ +use clap::Subcommand; +use serde::{Deserialize, Serialize}; + +use crate::commands::config::{ + s3::{S3Config, S3ConfigCli}, + server::{ServerConfig, ServerConfigCli}, +}; + +#[derive(Subcommand, Clone)] +pub enum ConfigOptionCli { + Server(ServerConfigCli), + S3(S3ConfigCli), +} +#[derive(Serialize, Deserialize, Clone)] +pub enum ConfigOption { + Server(ServerConfig), + S3(S3Config), +} diff --git a/cli/src/commands/config/configure.rs b/cli/src/commands/config/configure.rs new file mode 100644 index 00000000..736aa5e9 --- /dev/null +++ b/cli/src/commands/config/configure.rs @@ -0,0 +1,5 @@ +use crate::commands::config::config_option::ConfigOption; + +pub trait Configurable { + async fn configure(self) -> anyhow::Result; +} diff --git a/cli/src/config/interactive.rs b/cli/src/commands/config/interactive.rs similarity index 86% rename from cli/src/config/interactive.rs rename to cli/src/commands/config/interactive.rs index 0c72ca03..f1bd1e31 100644 --- a/cli/src/config/interactive.rs +++ b/cli/src/commands/config/interactive.rs @@ -8,7 +8,7 @@ macro_rules! interactive_variable { let $var = if let Some($var) = $value.$var { $var } else { - crate::config::interactive::query_variable($prompt).unwrap() + crate::commands::config::interactive::query_variable($prompt).unwrap() }; }; } @@ -18,7 +18,7 @@ macro_rules! interactive_optional_variable { let $var = if let Some($var) = $value.$var { Some($var) } else { - crate::config::interactive::query_optional_variable($prompt).unwrap() + crate::commands::config::interactive::query_optional_variable($prompt).unwrap() }; }; } @@ -44,4 +44,4 @@ where return Ok(None); } Ok(Some(input)) -} \ No newline at end of file +} diff --git a/cli/src/config/mod.rs b/cli/src/commands/config/mod.rs similarity index 80% rename from cli/src/config/mod.rs rename to cli/src/commands/config/mod.rs index 00b59511..a18d788f 100644 --- a/cli/src/config/mod.rs +++ b/cli/src/commands/config/mod.rs @@ -1,6 +1,7 @@ pub mod config; +pub mod configure; pub mod s3; pub mod server; -pub mod configure; #[macro_use] pub mod interactive; +pub mod config_option; diff --git a/cli/src/config/s3.rs b/cli/src/commands/config/s3.rs similarity index 84% rename from cli/src/config/s3.rs rename to cli/src/commands/config/s3.rs index d2c11f78..6ee48304 100644 --- a/cli/src/config/s3.rs +++ b/cli/src/commands/config/s3.rs @@ -4,10 +4,12 @@ use clap::Args; use s3::{Bucket, Region, creds::Credentials}; use serde::{Deserialize, Serialize}; -use crate::{config::configure::Configurable, interactive_optional_variable, interactive_variable}; +use crate::{ + commands::config::{config_option::ConfigOption, configure::Configurable}, + interactive_optional_variable, interactive_variable, +}; - -#[derive(Serialize, Deserialize, Args, Clone)] +#[derive(Args, Clone)] pub struct S3ConfigCli { secret_key: Option, key_id: Option, @@ -16,25 +18,6 @@ pub struct S3ConfigCli { endpoint: Option, } -impl Configurable for S3ConfigCli { - type Out = S3Config; - - fn configure(self) -> Self::Out { - interactive_variable!(self, secret_key, "S3 Secret Key"); - interactive_variable!(self, key_id, "S3 Key ID"); - interactive_variable!(self, region, "S3 Region"); - interactive_variable!(self, bucket_name, "S3 Bucket Name"); - interactive_optional_variable!(self, endpoint, "S3 Endpoint (leave blank for none"); - Self::Out { - secret_key, - key_id, - region, - bucket_name, - endpoint, - } - } -} - #[derive(Serialize, Deserialize, Debug, Clone)] pub struct S3Config { secret_key: String, @@ -44,6 +27,23 @@ pub struct S3Config { endpoint: Option, } +impl Configurable for S3ConfigCli { + async fn configure(self) -> anyhow::Result { + interactive_variable!(self, secret_key, "S3 Secret Key"); + interactive_variable!(self, key_id, "S3 Key ID"); + interactive_variable!(self, region, "S3 Region"); + interactive_variable!(self, bucket_name, "S3 Bucket Name"); + interactive_optional_variable!(self, endpoint, "S3 Endpoint (leave blank for none"); + Ok(ConfigOption::S3(S3Config { + secret_key, + key_id, + region, + bucket_name, + endpoint, + })) + } +} + impl S3Config { pub fn generate_bucket(&self) -> anyhow::Result { let credentials = @@ -62,4 +62,4 @@ impl S3Config { Ok(*bucket) } -} \ No newline at end of file +} diff --git a/cli/src/commands/config/server.rs b/cli/src/commands/config/server.rs new file mode 100644 index 00000000..6ba3f181 --- /dev/null +++ b/cli/src/commands/config/server.rs @@ -0,0 +1,92 @@ +use clap::Args; +use serde::{Deserialize, Serialize}; +use std::sync::LazyLock; + +use anyhow::{Result, anyhow}; +use dialoguer::{Confirm, Input, theme::ColorfulTheme}; +use reqwest::Client; +use url::Url; + +use crate::commands::config::{config_option::ConfigOption, configure::Configurable}; + +#[derive(Serialize, Deserialize, Clone)] +pub struct ServerConfig { + url: String, + token: String, +} +#[derive(Args, Clone)] +pub struct ServerConfigCli { + /// Endpoint of the Drop server + url: String, + #[arg(short, long)] + token: Option, +} + +const TOKEN_CREATE_PAYLOAD: &str = + "eyJuYW1lIjoiZG93bnBvdXIgKGNsaSkiLCJhY2xzIjpbImRlcG90Om5ldyJdfQ=="; + +impl Configurable for ServerConfigCli { + async fn configure(self) -> anyhow::Result { + let base_url = Url::parse(&self.url)?; + let mut token_create_url = base_url.join("/admin/settings/tokens")?; + { + let mut query = token_create_url.query_pairs_mut(); + query.append_pair("payload", TOKEN_CREATE_PAYLOAD); + }; + + let confirm = Confirm::with_theme(&ColorfulTheme::default()) + .with_prompt(format!( + "Open \"{}\" in your default browser?", + token_create_url.as_str() + )) + .interact()?; + + if !confirm { + return Err(anyhow!("User cancelled action")); + } + + webbrowser::open(token_create_url.as_str())?; + + let token: String = Input::with_theme(&ColorfulTheme::default()) + .with_prompt("API token") + .interact_text()?; + + validate_configuration(&self.url, &token).await?; + + Ok(ConfigOption::Server(ServerConfig { + url: self.url, + token, + })) + } +} + +static CLIENT: LazyLock = LazyLock::new(|| reqwest::Client::new()); +const REQUIRED_ACLS: [&str; 1] = ["depot:new"]; + +pub async fn validate_configuration(url: &str, token: &str) -> Result<()> { + let base_url = Url::parse(&url)?; + let token_check_url = base_url.join("/api/v1/token")?; + + let acl_check = CLIENT + .get(token_check_url) + .bearer_auth(token) + .send() + .await?; + + if !acl_check.status().is_success() { + return Err(anyhow!( + "ACL check failed with response code: {}", + acl_check.status() + )); + } + + let acls: Vec = acl_check.json().await?; + + for acl in REQUIRED_ACLS { + if !acls.contains(&acl.to_string()) { + return Err(anyhow!("Token missing {} acl", acl)); + } + } + + Ok(()) +} diff --git a/cli/src/commands/configure.rs b/cli/src/commands/configure.rs deleted file mode 100644 index c4a2ed1c..00000000 --- a/cli/src/commands/configure.rs +++ /dev/null @@ -1,77 +0,0 @@ -use std::sync::LazyLock; - -use anyhow::{Result, anyhow}; -use dialoguer::{Confirm, Input, theme::ColorfulTheme}; -use reqwest::Client; -use serde::{Deserialize, Serialize}; -use url::Url; - -const TOKEN_CREATE_PAYLOAD: &str = - "eyJuYW1lIjoiZG93bnBvdXIgKGNsaSkiLCJhY2xzIjpbImRlcG90Om5ldyJdfQ=="; - -static CLIENT: LazyLock = LazyLock::new(|| reqwest::Client::new()); -const REQUIRED_ACLS: [&str; 1] = ["depot:new"]; - -pub async fn interactive_configure(url: String) -> Result<()> { - let base_url = Url::parse(&url)?; - let mut token_create_url = base_url.join("/admin/settings/tokens")?; - { - let mut query = token_create_url.query_pairs_mut(); - query.append_pair("payload", TOKEN_CREATE_PAYLOAD); - }; - - let confirm = Confirm::with_theme(&ColorfulTheme::default()) - .with_prompt(format!( - "Open \"{}\" in your default browser?", - token_create_url.as_str() - )) - .interact()?; - - if !confirm { - return Err(anyhow!("User cancelled action")); - } - - webbrowser::open(token_create_url.as_str())?; - - let token: String = Input::with_theme(&ColorfulTheme::default()) - .with_prompt("API token") - .interact_text()?; - - validate_configuration(url, token).await?; - - Ok(()) -} - -pub async fn validate_configuration(url: String, token: String) -> Result<()> { - let base_url = Url::parse(&url)?; - let token_check_url = base_url.join("/api/v1/token")?; - - let acl_check = CLIENT - .get(token_check_url) - .bearer_auth(token) - .send() - .await?; - - if !acl_check.status().is_success() { - return Err(anyhow!( - "ACL check failed with response code: {}", - acl_check.status() - )); - } - - let acls: Vec = acl_check.json().await?; - - for acl in REQUIRED_ACLS { - if !acls.contains(&acl.to_string()) { - return Err(anyhow!("Token missing {} acl", acl)); - } - } - - Ok(()) -} - -#[derive(Serialize, Deserialize)] -pub struct ServerConfiguration { - pub endpoint: String, - pub token: String, -} diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs index db8ad986..1acc25f6 100644 --- a/cli/src/commands/mod.rs +++ b/cli/src/commands/mod.rs @@ -1,2 +1,2 @@ -pub mod configure; +pub mod config; pub mod upload; diff --git a/cli/src/commands/upload/interface.rs b/cli/src/commands/upload/interface.rs index dbcbd988..c5a5ece3 100644 --- a/cli/src/commands/upload/interface.rs +++ b/cli/src/commands/upload/interface.rs @@ -1,7 +1,10 @@ use std::path::Path; use crate::{ - cli::UploadInfo, commands::upload::{s3::S3, uploadable::Uploadable}, config::config::Config, manifest::generate_manifest + cli::UploadInfo, + commands::config::config::Config, + commands::upload::{s3::S3, uploadable::Uploadable}, + manifest::generate_manifest, }; use log::info; diff --git a/cli/src/commands/upload/s3.rs b/cli/src/commands/upload/s3.rs index 2de7c614..f3ca3d79 100644 --- a/cli/src/commands/upload/s3.rs +++ b/cli/src/commands/upload/s3.rs @@ -1,8 +1,9 @@ use crate::{ + commands::config::s3::S3Config, commands::upload::{ speedtest::{SPEEDTEST_PATH, Speedtest}, uploadable::Uploadable, - }, config::s3::S3Config, + }, }; use async_trait::async_trait; use droplet_rs::manifest::{ChunkData, Manifest}; diff --git a/cli/src/commands/upload/speedtest.rs b/cli/src/commands/upload/speedtest.rs index 5cde5efa..15a16bb9 100644 --- a/cli/src/commands/upload/speedtest.rs +++ b/cli/src/commands/upload/speedtest.rs @@ -30,4 +30,4 @@ impl Speedtest { to_write: SPEEDTEST_BYTES, } } -} \ No newline at end of file +} diff --git a/cli/src/commands/upload/uploadable.rs b/cli/src/commands/upload/uploadable.rs index f8f40544..0bba1054 100644 --- a/cli/src/commands/upload/uploadable.rs +++ b/cli/src/commands/upload/uploadable.rs @@ -11,5 +11,10 @@ pub trait Uploadable { chunk: &ChunkData, ) -> anyhow::Result<()>; async fn upload_speedtest(&mut self) -> anyhow::Result<()>; - async fn upload_manifest(&mut self, manifest: Manifest, game_id: &String, version_id: &String) -> anyhow::Result<()>; + async fn upload_manifest( + &mut self, + manifest: Manifest, + game_id: &String, + version_id: &String, + ) -> anyhow::Result<()>; } diff --git a/cli/src/config/configure.rs b/cli/src/config/configure.rs deleted file mode 100644 index 5c684cfe..00000000 --- a/cli/src/config/configure.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub trait Configurable { - type Out; - fn configure(self) -> Self::Out; -} \ No newline at end of file diff --git a/cli/src/config/server.rs b/cli/src/config/server.rs deleted file mode 100644 index 5ecf6757..00000000 --- a/cli/src/config/server.rs +++ /dev/null @@ -1,10 +0,0 @@ -use clap::Args; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Args, Clone)] -pub struct ServerConfig { - /// Endpoint of the Drop server - url: String, - #[arg(short, long)] - token: String, -} \ No newline at end of file diff --git a/cli/src/logging.rs b/cli/src/logging.rs index 8f64982f..6a2abef1 100644 --- a/cli/src/logging.rs +++ b/cli/src/logging.rs @@ -4,7 +4,6 @@ use std::env; use std::fs; use std::io; - pub fn configure_logging() -> anyhow::Result<()> { let log_level = env::var("RUST_LOG") .unwrap_or_else(|_| "info".to_string()) diff --git a/cli/src/main.rs b/cli/src/main.rs index 5d0d9774..21d74d78 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,13 +1,13 @@ -use crate::config::configure::Configurable; +use crate::commands::config::config_option::ConfigOptionCli; +use crate::commands::config::configure::Configurable; use crate::{ cli::{Cli, Commands}, + commands::config::config::Config, commands::upload, - config::config::Config, }; use clap::Parser; mod cli; mod commands; -mod config; mod logging; mod manifest; @@ -19,13 +19,13 @@ async fn main() -> anyhow::Result<()> { let mut config = Config::read(); match &cli.command { - Commands::Configure { name, option } => match option { - config::config::ConfigOptionCli::Server(server_config) => todo!(), - config::config::ConfigOptionCli::S3(s3_config_cli) => config.add_item( - name.clone(), - config::config::ConfigOption::S3(s3_config_cli.clone().configure()), - ), - }, + Commands::Configure { name, option } => config.add_item( + name.clone(), + match option { + ConfigOptionCli::Server(server_config) => server_config.clone().configure().await?, + ConfigOptionCli::S3(s3_config_cli) => s3_config_cli.clone().configure().await?, + }, + ), Commands::Upload(info) => { upload::interface::upload(info, config).await?; } diff --git a/cli/src/manifest.rs b/cli/src/manifest.rs index 0953fb43..ebc539e4 100644 --- a/cli/src/manifest.rs +++ b/cli/src/manifest.rs @@ -1,6 +1,4 @@ -use std::{ - path::Path, -}; +use std::path::Path; use droplet_rs::manifest::{Manifest, generate_manifest_rusty}; use indicatif::{ProgressBar, ProgressStyle};