commit c04bf3c03bb695758c15914040424ff941fa37d8
parent 6b1cc67dd752708e03a9ad810562ea6cdf2c092b
Author: triesap <tyson@radroots.org>
Date: Tue, 7 Apr 2026 01:42:57 +0000
cli: default to local env config
Diffstat:
6 files changed, 98 insertions(+), 44 deletions(-)
diff --git a/.env.example b/.env.example
@@ -1,4 +1,4 @@
-# copy to .env for local cli development
+# copy to .env for local cli development; the cli loads .env by default from the repo root
RADROOTS_CLI_LOGGING_FILTER=info
RADROOTS_CLI_LOGGING_OUTPUT_DIR=/absolute/path/to/radroots-platform-v1/logs/services/local/radroots-cli
RADROOTS_CLI_LOGGING_STDOUT=false
diff --git a/src/runtime/config.rs b/src/runtime/config.rs
@@ -7,6 +7,7 @@ use crate::cli::CliArgs;
use crate::runtime::RuntimeError;
const DEFAULT_LOG_FILTER: &str = "info";
+const DEFAULT_ENV_PATH: &str = ".env";
const ENV_FILE_PATH: &str = "RADROOTS_ENV_FILE";
const ENV_OUTPUT: &str = "RADROOTS_OUTPUT";
const ENV_CLI_LOG_FILTER: &str = "RADROOTS_CLI_LOGGING_FILTER";
@@ -161,6 +162,10 @@ fn resolve_env_file_path(args: &CliArgs, env: &dyn Environment) -> Option<PathBu
args.env_file
.clone()
.or_else(|| env.var(ENV_FILE_PATH).map(PathBuf::from))
+ .or_else(|| {
+ let default_path = PathBuf::from(DEFAULT_ENV_PATH);
+ default_path.exists().then_some(default_path)
+ })
}
fn resolve_output_format(
diff --git a/tests/identity_commands.rs b/tests/identity_commands.rs
@@ -1,16 +1,37 @@
+use std::path::Path;
use std::process::Command;
use assert_cmd::prelude::*;
use serde_json::Value;
use tempfile::tempdir;
+fn cli_command_in(workdir: &Path) -> Command {
+ let mut command = Command::cargo_bin("radroots").expect("binary");
+ command.current_dir(workdir);
+ for key in [
+ "RADROOTS_ENV_FILE",
+ "RADROOTS_OUTPUT",
+ "RADROOTS_CLI_LOGGING_FILTER",
+ "RADROOTS_CLI_LOGGING_OUTPUT_DIR",
+ "RADROOTS_CLI_LOGGING_STDOUT",
+ "RADROOTS_LOG_FILTER",
+ "RADROOTS_LOG_DIR",
+ "RADROOTS_LOG_STDOUT",
+ "RADROOTS_IDENTITY_PATH",
+ "RADROOTS_SIGNER_BACKEND",
+ "RADROOTS_MYC_EXECUTABLE",
+ ] {
+ command.env_remove(key);
+ }
+ command
+}
+
#[test]
fn identity_init_json_creates_identity_file() {
let dir = tempdir().expect("tempdir");
let identity_path = dir.path().join("identity.json");
- let output = Command::cargo_bin("radroots")
- .expect("binary")
+ let output = cli_command_in(dir.path())
.args([
"--json",
"--identity-path",
@@ -39,8 +60,7 @@ fn identity_show_json_reads_existing_public_identity() {
let dir = tempdir().expect("tempdir");
let identity_path = dir.path().join("identity.json");
- let init = Command::cargo_bin("radroots")
- .expect("binary")
+ let init = cli_command_in(dir.path())
.args([
"--json",
"--identity-path",
@@ -52,8 +72,7 @@ fn identity_show_json_reads_existing_public_identity() {
.expect("run identity init");
assert!(init.status.success());
- let output = Command::cargo_bin("radroots")
- .expect("binary")
+ let output = cli_command_in(dir.path())
.args([
"--json",
"--identity-path",
@@ -80,8 +99,7 @@ fn identity_show_json_reports_unconfigured_without_creating_identity() {
let dir = tempdir().expect("tempdir");
let identity_path = dir.path().join("missing-identity.json");
- let output = Command::cargo_bin("radroots")
- .expect("binary")
+ let output = cli_command_in(dir.path())
.args([
"--json",
"--identity-path",
diff --git a/tests/myc_status.rs b/tests/myc_status.rs
@@ -1,5 +1,6 @@
use std::fs;
use std::os::unix::fs::PermissionsExt;
+use std::path::Path;
use std::process::Command;
use std::sync::{Mutex, MutexGuard, OnceLock};
@@ -8,6 +9,27 @@ use radroots_identity::RadrootsIdentity;
use serde_json::{Value, json};
use tempfile::tempdir;
+fn cli_command_in(workdir: &Path) -> Command {
+ let mut command = Command::cargo_bin("radroots").expect("binary");
+ command.current_dir(workdir);
+ for key in [
+ "RADROOTS_ENV_FILE",
+ "RADROOTS_OUTPUT",
+ "RADROOTS_CLI_LOGGING_FILTER",
+ "RADROOTS_CLI_LOGGING_OUTPUT_DIR",
+ "RADROOTS_CLI_LOGGING_STDOUT",
+ "RADROOTS_LOG_FILTER",
+ "RADROOTS_LOG_DIR",
+ "RADROOTS_LOG_STDOUT",
+ "RADROOTS_IDENTITY_PATH",
+ "RADROOTS_SIGNER_BACKEND",
+ "RADROOTS_MYC_EXECUTABLE",
+ ] {
+ command.env_remove(key);
+ }
+ command
+}
+
#[test]
fn myc_status_reports_ready_for_valid_full_status_payload() {
let _guard = myc_test_guard();
@@ -17,8 +39,7 @@ fn myc_status_reports_ready_for_valid_full_status_payload() {
successful_status_script(sample_status_payload(true).to_string()).as_str(),
);
- let output = Command::cargo_bin("radroots")
- .expect("binary")
+ let output = cli_command_in(dir.path())
.args([
"--json",
"--myc-executable",
@@ -49,8 +70,7 @@ fn myc_status_reports_unavailable_for_invalid_status_payload() {
let dir = tempdir().expect("tempdir");
let executable = write_fake_myc(dir.path(), "#!/bin/sh\nprintf '%s\\n' 'this is not json'\n");
- let output = Command::cargo_bin("radroots")
- .expect("binary")
+ let output = cli_command_in(dir.path())
.args([
"--json",
"--myc-executable",
@@ -82,8 +102,7 @@ fn myc_status_reports_degraded_service_as_external_unavailable() {
successful_status_script(sample_status_payload(false).to_string()).as_str(),
);
- let output = Command::cargo_bin("radroots")
- .expect("binary")
+ let output = cli_command_in(dir.path())
.args([
"--json",
"--myc-executable",
@@ -116,8 +135,7 @@ fn signer_status_reports_degraded_myc_backend_as_external_unavailable() {
successful_status_script(sample_status_payload(false).to_string()).as_str(),
);
- let output = Command::cargo_bin("radroots")
- .expect("binary")
+ let output = cli_command_in(dir.path())
.args([
"--json",
"--signer-backend",
@@ -150,8 +168,7 @@ fn myc_status_reports_unavailable_when_executable_is_missing() {
let dir = tempdir().expect("tempdir");
let missing = dir.path().join("missing-myc");
- let output = Command::cargo_bin("radroots")
- .expect("binary")
+ let output = cli_command_in(dir.path())
.args([
"--json",
"--myc-executable",
@@ -182,8 +199,7 @@ fn myc_status_reports_unavailable_for_non_zero_exit() {
"#!/bin/sh\nprintf '%s\\n' 'transport unavailable' >&2\nexit 42\n",
);
- let output = Command::cargo_bin("radroots")
- .expect("binary")
+ let output = cli_command_in(dir.path())
.args([
"--json",
"--myc-executable",
@@ -211,8 +227,7 @@ fn myc_status_reports_unavailable_for_timeout() {
"#!/bin/sh\nif [ \"$1\" != \"status\" ] || [ \"$2\" != \"--view\" ] || [ \"$3\" != \"full\" ]; then\n echo \"unexpected args: $*\" >&2\n exit 64\nfi\nexec sleep 5\n",
);
- let output = Command::cargo_bin("radroots")
- .expect("binary")
+ let output = cli_command_in(dir.path())
.args([
"--json",
"--myc-executable",
diff --git a/tests/runtime_show.rs b/tests/runtime_show.rs
@@ -1,12 +1,14 @@
use std::fs;
+use std::path::Path;
use std::process::Command;
use assert_cmd::prelude::*;
use serde_json::Value;
use tempfile::tempdir;
-fn runtime_show_command() -> Command {
+fn runtime_show_command_in(workdir: &Path) -> Command {
let mut command = Command::cargo_bin("radroots").expect("binary");
+ command.current_dir(workdir);
for key in [
"RADROOTS_ENV_FILE",
"RADROOTS_OUTPUT",
@@ -27,7 +29,8 @@ fn runtime_show_command() -> Command {
#[test]
fn runtime_show_json_reports_default_bootstrap_state() {
- let output = runtime_show_command()
+ let dir = tempdir().expect("tempdir");
+ let output = runtime_show_command_in(dir.path())
.args(["--json", "runtime", "show"])
.output()
.expect("run runtime show");
@@ -46,7 +49,8 @@ fn runtime_show_json_reports_default_bootstrap_state() {
#[test]
fn runtime_show_json_reflects_environment_configuration() {
- let output = runtime_show_command()
+ let dir = tempdir().expect("tempdir");
+ let output = runtime_show_command_in(dir.path())
.env("RADROOTS_OUTPUT", "json")
.env("RADROOTS_LOG_FILTER", "debug")
.env("RADROOTS_LOG_DIR", "logs/runtime")
@@ -69,9 +73,9 @@ fn runtime_show_json_reflects_environment_configuration() {
}
#[test]
-fn runtime_show_json_reads_logging_from_env_file() {
+fn runtime_show_json_reads_logging_from_default_env_file() {
let temp = tempdir().expect("tempdir");
- let env_path = temp.path().join("radroots-cli.env");
+ let env_path = temp.path().join(".env");
let logs_dir = temp.path().join("logs").join("radroots-cli");
fs::write(
&env_path,
@@ -82,14 +86,8 @@ fn runtime_show_json_reads_logging_from_env_file() {
)
.expect("write env file");
- let output = runtime_show_command()
- .args([
- "--json",
- "--env-file",
- env_path.to_str().expect("env path"),
- "runtime",
- "show",
- ])
+ let output = runtime_show_command_in(temp.path())
+ .args(["--json", "runtime", "show"])
.output()
.expect("run runtime show");
diff --git a/tests/signer_status.rs b/tests/signer_status.rs
@@ -1,17 +1,38 @@
use std::fs;
+use std::path::Path;
use std::process::Command;
use assert_cmd::prelude::*;
use serde_json::Value;
use tempfile::tempdir;
+fn cli_command_in(workdir: &Path) -> Command {
+ let mut command = Command::cargo_bin("radroots").expect("binary");
+ command.current_dir(workdir);
+ for key in [
+ "RADROOTS_ENV_FILE",
+ "RADROOTS_OUTPUT",
+ "RADROOTS_CLI_LOGGING_FILTER",
+ "RADROOTS_CLI_LOGGING_OUTPUT_DIR",
+ "RADROOTS_CLI_LOGGING_STDOUT",
+ "RADROOTS_LOG_FILTER",
+ "RADROOTS_LOG_DIR",
+ "RADROOTS_LOG_STDOUT",
+ "RADROOTS_IDENTITY_PATH",
+ "RADROOTS_SIGNER_BACKEND",
+ "RADROOTS_MYC_EXECUTABLE",
+ ] {
+ command.env_remove(key);
+ }
+ command
+}
+
#[test]
fn signer_status_reports_local_ready_when_identity_exists() {
let dir = tempdir().expect("tempdir");
let identity_path = dir.path().join("identity.json");
- let init = Command::cargo_bin("radroots")
- .expect("binary")
+ let init = cli_command_in(dir.path())
.args([
"--json",
"--identity-path",
@@ -23,8 +44,7 @@ fn signer_status_reports_local_ready_when_identity_exists() {
.expect("run identity init");
assert!(init.status.success());
- let output = Command::cargo_bin("radroots")
- .expect("binary")
+ let output = cli_command_in(dir.path())
.args([
"--json",
"--identity-path",
@@ -52,8 +72,7 @@ fn signer_status_reports_local_unconfigured_when_identity_is_missing() {
let dir = tempdir().expect("tempdir");
let identity_path = dir.path().join("missing-identity.json");
- let output = Command::cargo_bin("radroots")
- .expect("binary")
+ let output = cli_command_in(dir.path())
.args([
"--json",
"--identity-path",
@@ -85,8 +104,7 @@ fn signer_status_reports_internal_error_for_invalid_identity_file() {
let identity_path = dir.path().join("invalid-identity.json");
fs::write(&identity_path, "{ not valid json").expect("write invalid identity");
- let output = Command::cargo_bin("radroots")
- .expect("binary")
+ let output = cli_command_in(dir.path())
.args([
"--json",
"--identity-path",