Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions rsworkspace/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rsworkspace/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ all = "deny"
acp-nats = { path = "crates/acp-nats" }
acp-telemetry = { path = "crates/acp-telemetry" }
trogon-nats = { path = "crates/trogon-nats" }
trogon-service-config = { path = "crates/trogon-service-config" }
trogon-source-discord = { path = "crates/trogon-source-discord" }
trogon-source-github = { path = "crates/trogon-source-github" }
trogon-source-gitlab = { path = "crates/trogon-source-gitlab" }
Expand Down
1 change: 1 addition & 0 deletions rsworkspace/crates/trogon-gateway/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ serde = { workspace = true }
tokio = { workspace = true, features = ["full"] }
tracing = { workspace = true }
trogon-nats = { workspace = true }
trogon-service-config = { workspace = true }
Comment thread
yordis marked this conversation as resolved.
trogon-source-discord = { workspace = true }
trogon-source-github = { workspace = true }
trogon-source-gitlab = { workspace = true }
Expand Down
10 changes: 5 additions & 5 deletions rsworkspace/crates/trogon-gateway/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
use std::path::PathBuf;
use trogon_service_config::RuntimeConfigArgs;

#[derive(clap::Parser, Clone)]
#[command(name = "trogon-gateway", about = "Unified gateway ingestion binary")]
pub struct Cli {
#[command(flatten)]
pub runtime: RuntimeConfigArgs,

#[command(subcommand)]
pub command: Command,
}

#[derive(clap::Subcommand, Clone)]
pub enum Command {
Serve {
#[arg(long, short)]
config: Option<PathBuf>,
},
Serve,
}
123 changes: 54 additions & 69 deletions rsworkspace/crates/trogon-gateway/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ use std::fmt;
use std::path::Path;

use confique::Config;
#[cfg(test)]
use trogon_nats::NatsAuth;
use trogon_nats::jetstream::StreamMaxAge;
use trogon_nats::{NatsAuth, NatsToken, SubjectTokenViolation};
use trogon_nats::{NatsToken, SubjectTokenViolation};
use trogon_service_config::{NatsArgs, NatsConfigSection, load_config, resolve_nats};
use trogon_source_discord::config::DiscordBotToken;
use trogon_source_github::config::GitHubWebhookSecret;
use trogon_source_gitlab::config::GitLabWebhookSecret;
Expand Down Expand Up @@ -164,7 +167,7 @@ struct GatewayConfig {
#[config(nested)]
http_server: HttpServerConfig,
#[config(nested)]
nats: NatsConfig,
nats: NatsConfigSection,
#[config(nested)]
sources: SourcesConfig,
}
Expand All @@ -175,22 +178,6 @@ struct HttpServerConfig {
port: u16,
}

#[derive(Config)]
struct NatsConfig {
#[config(env = "NATS_URL", default = "localhost:4222")]
url: String,
#[config(env = "NATS_CREDS")]
creds: Option<String>,
#[config(env = "NATS_NKEY")]
nkey: Option<String>,
#[config(env = "NATS_USER")]
user: Option<String>,
#[config(env = "NATS_PASSWORD")]
password: Option<String>,
#[config(env = "NATS_TOKEN")]
token: Option<String>,
}

#[derive(Config)]
struct SourcesConfig {
#[config(nested)]
Expand Down Expand Up @@ -358,17 +345,21 @@ impl ResolvedConfig {
}
}

#[cfg(test)]
pub fn load(config_path: Option<&Path>) -> Result<ResolvedConfig, ConfigError> {
let mut builder = GatewayConfig::builder();
if let Some(path) = config_path {
builder = builder.file(path);
}
let cfg = builder.env().load().map_err(ConfigError::Load)?;
resolve(cfg)
load_with_overrides(config_path, &NatsArgs::default())
}

pub fn load_with_overrides(
config_path: Option<&Path>,
nats_overrides: &NatsArgs,
) -> Result<ResolvedConfig, ConfigError> {
let cfg = load_config::<GatewayConfig>(config_path).map_err(ConfigError::Load)?;
resolve(cfg, nats_overrides)
}

fn resolve(cfg: GatewayConfig) -> Result<ResolvedConfig, ConfigError> {
let nats = resolve_nats(&cfg.nats);
fn resolve(cfg: GatewayConfig, nats_overrides: &NatsArgs) -> Result<ResolvedConfig, ConfigError> {
let nats = resolve_nats(&cfg.nats, nats_overrides);
let mut errors = Vec::new();

let github = resolve_github(cfg.sources.github, &mut errors);
Expand Down Expand Up @@ -398,38 +389,6 @@ fn resolve(cfg: GatewayConfig) -> Result<ResolvedConfig, ConfigError> {
})
}

fn non_empty(opt: &Option<String>) -> Option<&String> {
opt.as_ref().filter(|s| !s.is_empty())
}

fn resolve_nats(section: &NatsConfig) -> trogon_nats::NatsConfig {
let auth = if let Some(creds) = non_empty(&section.creds) {
NatsAuth::Credentials(creds.clone().into())
} else if let Some(nkey) = non_empty(&section.nkey) {
NatsAuth::NKey(nkey.clone())
} else if let (Some(user), Some(password)) =
(non_empty(&section.user), non_empty(&section.password))
{
NatsAuth::UserPassword {
user: user.clone(),
password: password.clone(),
}
} else if let Some(token) = non_empty(&section.token) {
NatsAuth::Token(token.clone())
} else {
NatsAuth::None
};

let servers: Vec<String> = section
.url
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();

trogon_nats::NatsConfig::new(servers, auth)
}

fn resolve_github(
section: GithubConfig,
errors: &mut Vec<ConfigValidationError>,
Expand Down Expand Up @@ -1721,21 +1680,47 @@ token = "mytoken"
}

#[test]
fn non_empty_filters_none() {
let val: Option<String> = None;
assert!(non_empty(&val).is_none());
}
fn load_with_overrides_prefers_cli_nats_values() {
let toml = r#"
[nats]
url = "file1:4222,file2:4222"
token = "file-token"
"#;
let f = write_toml(toml);
let cfg = load_with_overrides(
Some(f.path()),
&NatsArgs {
nats_url: Some("override:4222".to_string()),
nats_token: Some("override-token".to_string()),
..Default::default()
},
)
.expect("load failed");

#[test]
fn non_empty_filters_empty_string() {
let val = Some(String::new());
assert!(non_empty(&val).is_none());
assert_eq!(cfg.nats.servers, vec!["override:4222"]);
assert!(matches!(cfg.nats.auth, NatsAuth::Token(ref token) if token == "override-token"));
}

#[test]
fn non_empty_passes_through_nonempty() {
let val = Some("hello".to_string());
assert_eq!(non_empty(&val), Some(&"hello".to_string()));
fn load_with_overrides_keeps_auth_priority() {
let toml = r#"
[nats]
token = "file-token"
"#;
let f = write_toml(toml);
let cfg = load_with_overrides(
Some(f.path()),
&NatsArgs {
nats_creds: Some("/path/to/override.creds".to_string()),
nats_token: Some("override-token".to_string()),
..Default::default()
},
)
.expect("load failed");

assert!(
matches!(cfg.nats.auth, NatsAuth::Credentials(ref path) if path == std::path::Path::new("/path/to/override.creds"))
);
}

#[test]
Expand Down
7 changes: 1 addition & 6 deletions rsworkspace/crates/trogon-gateway/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,7 @@ type SourceResult = (&'static str, Result<(), String>);
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let cli = CliArgs::<cli::Cli>::new().parse_args();

let config_path = match cli.command {
cli::Command::Serve { ref config } => config.as_deref(),
};

let resolved = config::load(config_path)?;
let resolved = config::load_with_overrides(cli.runtime.config.as_deref(), &cli.runtime.nats)?;

if !resolved.has_any_source() {
return Err("no sources configured — provide a config file or set source env vars".into());
Expand Down
15 changes: 15 additions & 0 deletions rsworkspace/crates/trogon-service-config/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "trogon-service-config"
version = "0.1.0"
edition = "2024"

[lints]
workspace = true

[dependencies]
clap = { workspace = true, features = ["derive"] }
confique = { workspace = true }
trogon-nats = { workspace = true }

[dev-dependencies]
tempfile = { workspace = true }
Loading
Loading