Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions 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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yml = "0.0.12"
toml = "0.8"
toml_edit = "0.22"
apollo-parser = "0.8.5"
tower-mcp-types = "0.12.0"
regex = "1"
Expand Down
6 changes: 6 additions & 0 deletions architecture/gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,12 @@ Driver implementation settings live in the TOML driver tables. See
`docs/reference/gateway-config.mdx` for worked per-driver examples and RFC
0003 for the full schema.

`openshell-gateway config set` updates a single resolved TOML file from
repeatable dotted `KEY=VALUE` assignments. It preserves comments, validates
the complete gateway schema, and replaces the file atomically. Local
development tasks can apply overrides to their generated configuration through
this command without changing gateway startup precedence.

`database_url` is env-only and rejected when present in the file
(`OPENSHELL_DB_URL` / `--db-url`).

Expand Down
1 change: 1 addition & 0 deletions crates/openshell-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ pin-project-lite = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
toml = { workspace = true }
toml_edit = { workspace = true }
tokio-stream = { workspace = true }
sqlx = { workspace = true }
reqwest = { workspace = true }
Expand Down
67 changes: 66 additions & 1 deletion crates/openshell-server/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ struct Cli {
enum Commands {
/// Generate mTLS PKI and write Kubernetes Secrets (Helm pre-install hook).
GenerateCerts(certgen::CertgenArgs),
/// Update the gateway TOML configuration.
Config(crate::config_command::ConfigArgs),
}

#[derive(clap::Args, Debug)]
Expand All @@ -49,7 +51,7 @@ struct RunArgs {
/// When set, gateway-wide settings and per-driver tables are read from
/// the file. Gateway command-line flags and `OPENSHELL_*` environment
/// variables continue to take precedence over gateway file values.
#[arg(long, env = "OPENSHELL_GATEWAY_CONFIG")]
#[arg(long, env = "OPENSHELL_GATEWAY_CONFIG", global = true)]
config: Option<PathBuf>,

/// IP address to bind the server, health, and metrics listeners to.
Expand Down Expand Up @@ -228,6 +230,7 @@ pub async fn run_cli() -> Result<()> {

match cli.command {
Some(Commands::GenerateCerts(args)) => certgen::run(args).await,
Some(Commands::Config(args)) => crate::config_command::run(args, cli.run.config),
None => Box::pin(run_from_args(cli.run, matches)).await,
}
}
Expand Down Expand Up @@ -1086,6 +1089,68 @@ mod tests {
));
}

#[test]
fn config_set_uses_explicit_config_path() {
let _lock = ENV_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let _guard = EnvVarGuard::remove("OPENSHELL_GATEWAY_CONFIG");
let temp = tempfile::tempdir().unwrap();
let path = temp.path().join("custom/gateway.toml");
let path_string = path.to_string_lossy().into_owned();

let cli = Cli::try_parse_from([
"openshell-gateway",
"config",
"set",
"--config",
&path_string,
"openshell.gateway.compute_drivers=[\"podman\"]",
"openshell.gateway.bind_address=0.0.0.0:17670",
])
.expect("config set should parse without runtime arguments");
let Cli { command, run } = cli;
let Some(super::Commands::Config(args)) = command else {
panic!("expected config subcommand");
};

crate::config_command::run(args, run.config).unwrap();

let loaded = crate::config_file::load(&path).unwrap();
assert_eq!(
loaded.openshell.gateway.compute_drivers,
Some(vec!["podman".to_string()])
);
assert_eq!(
loaded.openshell.gateway.bind_address,
Some("0.0.0.0:17670".parse().unwrap())
);
}

#[test]
fn config_set_requires_at_least_one_assignment() {
let error = Cli::try_parse_from(["openshell-gateway", "config", "set"])
.expect_err("config set without an assignment should fail");

assert_eq!(
error.kind(),
clap::error::ErrorKind::MissingRequiredArgument
);
}

#[test]
fn config_set_rejects_non_assignment_arguments() {
let error = Cli::try_parse_from([
"openshell-gateway",
"config",
"set",
"openshell.gateway.log_level",
])
.expect_err("config set arguments must use KEY=VALUE syntax");

assert_eq!(error.kind(), clap::error::ErrorKind::ValueValidation);
}

#[test]
fn bare_invocation_with_no_db_url_parses_for_runtime_defaults() {
// db_url is Option<String> at the clap level so subcommand parsing
Expand Down
Loading
Loading