use std::sync::Arc;
use clap::Subcommand;
use crate::cli::CliError;
use crate::cli::render;
use acme_proxy_admin::admin;
use acme_proxy_admin::admin::ProfileSummary;
use acme_proxy_core::config::Config;
use acme_proxy_core::palette::Palette;
#[derive(Subcommand)]
pub enum ProfileCommand {
List {
#[arg(long)]
json: bool,
},
}
pub async fn run_profile_command(
command: ProfileCommand,
palette: Palette,
config: &Arc<Config>,
) -> Result<(), CliError> {
match command {
ProfileCommand::List { json } => {
let resolved = config
.resolve_profiles()
.map_err(|error| CliError::failed(format!("configuration error: {error}")))?;
let profiles: Vec<ProfileSummary> = resolved
.iter()
.map(|profile| ProfileSummary::configured(&config.server.base_url, profile))
.collect();
if json {
let rendered: Vec<_> = profiles.iter().map(admin::render_profile_json).collect();
println!("{}", serde_json::Value::Array(rendered));
} else {
for profile in &profiles {
println!("{}", render::render_profile_line(profile, palette));
}
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use acme_proxy_core::config::ENV_LOCK;
fn config_from(body: &str) -> Arc<Config> {
let _lock = ENV_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let dir = acme_proxy_core::testutil::TempDir::new("profile");
std::fs::write(dir.join("config.toml"), body).unwrap();
unsafe {
std::env::set_var("ACME_PROXY_CONFIG", dir.join("config").to_str().unwrap());
}
let config = Config::load().expect("the configuration must load");
unsafe {
std::env::remove_var("ACME_PROXY_CONFIG");
}
Arc::new(config)
}
#[test]
fn the_listing_is_the_profiles_this_configuration_would_mount() {
let config = config_from(
r#"
[server]
base_url = "https://ca.example.com"
[challenge]
bypass = false
[profiles.staging]
challenge.bypass = true
[profiles.le]
eab.enabled = true
[profiles.parked]
enabled = false
"#,
);
let resolved = config.resolve_profiles().unwrap();
let summaries: Vec<ProfileSummary> = resolved
.iter()
.map(|profile| ProfileSummary::configured(&config.server.base_url, profile))
.collect();
let names: Vec<&str> = summaries.iter().map(|p| p.name.as_str()).collect();
assert_eq!(names, ["le", "staging"], "parked is not mounted");
let le = &summaries[0];
assert_eq!(le.base_url, "https://ca.example.com/profile/le");
assert_eq!(
le.directory_url(),
"https://ca.example.com/profile/le/directory"
);
assert!(le.eab_enabled);
assert!(!le.challenge_bypass);
assert!(summaries[1].challenge_bypass, "staging overrode it");
}
#[tokio::test]
async fn both_shapes_render_and_a_profileless_configuration_is_refused() {
let config = config_from("[profiles.default]\n");
for json in [false, true] {
run_profile_command(ProfileCommand::List { json }, Palette::plain(), &config)
.await
.unwrap();
}
let empty = Arc::new(Config::default());
let error = run_profile_command(
ProfileCommand::List { json: false },
Palette::plain(),
&empty,
)
.await
.expect_err("a configuration mounting nothing is not a listing of nothing");
assert!(
error.to_string().starts_with("configuration error: "),
"{error}"
);
}
}