diff --git a/Cargo.lock b/Cargo.lock index 02b7d04..afd0625 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -319,6 +319,47 @@ dependencies = [ "strsim", ] +[[package]] +name = "clap_complete" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc443334c81a804575546c5a8a79b4913b50e28d69232903604cada1de817ce" +dependencies = [ + "clap", +] + +[[package]] +name = "clap_complete_command" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "183495371ea78d4c9ff638bfc6497d46fed2396e4f9c50aebc1278a4a9919a3d" +dependencies = [ + "clap", + "clap_complete", + "clap_complete_fig", + "clap_complete_nushell", +] + +[[package]] +name = "clap_complete_fig" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99fee1d30a51305a6c2ed3fc5709be3c8af626c9c958e04dd9ae94e27bcbce9f" +dependencies = [ + "clap", + "clap_complete", +] + +[[package]] +name = "clap_complete_nushell" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d02bc8b1a18ee47c4d2eec3fb5ac034dc68ebea6125b1509e9ccdffcddce66e" +dependencies = [ + "clap", + "clap_complete", +] + [[package]] name = "clap_derive" version = "4.3.2" @@ -1642,6 +1683,7 @@ dependencies = [ "axum-extra", "chrono", "clap", + "clap_complete_command", "rand", "reqwest", "serde", diff --git a/client/Cargo.toml b/client/Cargo.toml index bc7579f..53e3908 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -24,6 +24,7 @@ tracing = { version = "0.1.37", features = ["release_max_level_debug"] } tracing-subscriber = "0.3.16" url = { version = "2.3.1" } ssh-cert-dist-common = { path = "../common" } +clap_complete_command = "0.5.1" [dev-dependencies] tempfile = "3.3.0" diff --git a/client/src/client.rs b/client/src/client.rs index 20018e5..eda3eee 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -1,6 +1,7 @@ use anyhow::{bail, Context}; use axum_extra::routing::TypedPath; -use clap::{Parser, Subcommand}; +use clap::{CommandFactory, Parser, Subcommand, ValueHint}; +use clap_complete_command::Shell; use reqwest::{Client, StatusCode}; use ssh_key::Certificate; use std::path::PathBuf; @@ -17,7 +18,7 @@ use ssh_cert_dist_common::*; #[derive(Parser)] pub struct ClientArgs { /// Url for the API endpoint - #[clap(short = 'a', long = "api-endpoint", env = env_key!("API"))] + #[clap(short = 'a', long = "api-endpoint",value_hint = ValueHint::Url, env = env_key!("API"))] api: Url, /// Require interaction before writing certificates #[clap(short = 'i', long = "interactive", env = env_key!("INTERACTIVE"))] @@ -30,7 +31,7 @@ pub struct FetchArgs { args: ClientArgs, #[clap(short = 'k', long = "key-update", env = env_key!("KEY_UPDATE"))] prohibit_key_update: bool, - #[clap(short = 'c', long = "cert-dir", env = env_key!("CERT_DIR"))] + #[clap(short = 'c', long = "cert-dir",value_hint = ValueHint::DirPath, env = env_key!("CERT_DIR"))] cert_dir: PathBuf, /// minimum time in days between now and expiry to consider checking #[clap(short = 'd', long = "days", default_value = "60", env = env_key!("MIN_DELTA_DAYS"))] @@ -42,7 +43,7 @@ pub struct UploadArgs { #[clap(flatten)] args: ClientArgs, /// Certificates to be uploaded - #[clap(env = env_key!("FILES"))] + #[clap(value_hint = ValueHint::FilePath, env = env_key!("FILES"))] files: Vec, } @@ -52,10 +53,10 @@ pub struct RenewCommandArgs { #[clap(short = 'x')] execute: bool, /// Path to the CA private key - #[clap(long="ca", env = env_key!("CA_KEY"))] + #[clap(long="ca",value_hint = ValueHint::DirPath, env = env_key!("CA_KEY"))] ca_key: Option, /// Certificates to generate commands for - #[clap(env = env_key!("FILES"))] + #[clap(value_hint = ValueHint::FilePath,env = env_key!("FILES"))] files: Vec, } @@ -70,6 +71,11 @@ pub enum ClientCommands { Fetch(FetchArgs), Upload(UploadArgs), RenewCommand(RenewCommandArgs), + #[clap(hide = true)] + Completions { + #[arg(long = "shell", value_enum)] + shell: Shell, + } } pub async fn run(ClientCommand { cmd }: ClientCommand) -> anyhow::Result<()> { @@ -77,6 +83,10 @@ pub async fn run(ClientCommand { cmd }: ClientCommand) -> anyhow::Result<()> { ClientCommands::Fetch(args) => fetch(args).await, ClientCommands::Upload(args) => upload(args).await, ClientCommands::RenewCommand(args) => renew(args).await, + ClientCommands::Completions { shell } => { + shell.generate(&mut ClientCommand::command(), &mut std::io::stdout()); + Ok(()) + } } } diff --git a/flake.nix b/flake.nix index ad44ce3..d508642 100644 --- a/flake.nix +++ b/flake.nix @@ -122,18 +122,31 @@ openssl ]; nativeBuildInputs = with prev; [ - pkg-config + pkg-config installShellFiles ]; + installCompletions = '' + mkdir completions + for shell in bash zsh fish; do + $out/bin/sshcd completions --shell $shell > completions/$shell + installShellCompletion --$shell completions/$shell + done + ''; in { "${pname}-server" = naersk-lib.buildPackage { name = "${pname}-server"; inherit root buildInputs nativeBuildInputs; + # postInstall = '' + # ${installCompletions} + # ''; }; "${pname}-client" = naersk-lib.buildPackage { name = "${pname}-client"; + postInstall = '' + ${installCompletions} + ''; inherit root buildInputs nativeBuildInputs; }; };