diff --git a/Cargo.toml b/Cargo.toml index d1f6e398..4d57dc9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,13 @@ anyhow = "1.0.81" tokio = { version = "1", features = ["full"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } tracing = "0.1.40" +# We work fine with any version of 4, but 4.5 bumped MSRV to 1.74 +clap = { version = "<=4.4", features = ["derive", "cargo", "env"] } + +[dev-dependencies] +# Purely for the MSRV requirement. +assert_cmd = "<=2.0.13" +predicates = "3" [dependencies.libazureinit] path = "libazureinit" diff --git a/src/main.rs b/src/main.rs index 757d5d36..f33df1fa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ use std::process::ExitCode; use anyhow::Context; +use clap::Parser; use libazureinit::imds::InstanceMetadata; use libazureinit::User; use libazureinit::{ @@ -20,6 +21,27 @@ use tracing_subscriber::EnvFilter; const VERSION: &str = env!("CARGO_PKG_VERSION"); +/// Minimal provisioning agent for Azure +/// +/// Create a user, add SSH public keys, and set the hostname. +/// +/// Arguments provided via command-line arguments override any arguments provided +/// via environment variables. +#[derive(Parser, Debug)] +struct Cli { + /// List of supplementary groups of the provisioned user account. + /// + /// Values can be comma-separated and the argument can be provided multiple times. + #[arg( + long, + short, + env = "AZURE_INIT_USER_GROUPS", + value_delimiter = ',', + default_value = "wheel" + )] + groups: Vec, +} + #[instrument] fn get_environment() -> Result { let ovf_devices = get_mount_device(None)?; @@ -96,6 +118,8 @@ async fn main() -> ExitCode { #[instrument] async fn provision() -> Result<(), anyhow::Error> { + let opts = Cli::parse(); + let mut default_headers = header::HeaderMap::new(); let user_agent = header::HeaderValue::from_str( format!("azure-init v{VERSION}").as_str(), @@ -124,7 +148,8 @@ async fn provision() -> Result<(), anyhow::Error> { .clone() .ok_or::(LibError::InstanceMetadataFailure)?; - let user = User::new(username, im.compute.public_keys); + let user = + User::new(username, im.compute.public_keys).with_groups(opts.groups); Provision::new(im.compute.os_profile.computer_name, user) .hostname_provisioners([ diff --git a/tests/cli.rs b/tests/cli.rs new file mode 100644 index 00000000..72b333a7 --- /dev/null +++ b/tests/cli.rs @@ -0,0 +1,17 @@ +use std::process::Command; + +use assert_cmd::prelude::*; +use predicates::prelude::*; + +// Assert help text includes the --groups flag +#[test] +fn help_groups() -> Result<(), Box> { + let mut command = Command::cargo_bin("azure-init")?; + command.arg("--help"); + command + .assert() + .success() + .stdout(predicate::str::contains("-g, --groups ")); + + Ok(()) +}