Skip to content
Closed
2 changes: 1 addition & 1 deletion crates/scaffolder-core/src/telemetry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ fn build_user_properties(tools_version: &str, device_id: &str) -> serde_json::Va
"env": std::env::var("III_ENV").unwrap_or_else(|_| "unknown".to_string()),
"install_method": detect_install_method(),
"cli_version": tools_version,
"host_user_id": std::env::var("III_HOST_USER_ID").ok(),
"host_device_id": std::env::var("III_HOST_DEVICE_ID").ok(),
})
}

Expand Down
6 changes: 6 additions & 0 deletions engine/docker-compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ services:
- ./config.prod.yaml:/app/config.yaml:ro
environment:
- III_EXECUTION_CONTEXT=docker
# Pass the host's anonymous telemetry device_id into the container.
# Reported to Amplitude as `host_device_id` so container sessions can
# be attributed back to the host without requiring a mount (which
# breaks on the distroless nonroot uid). Populate from your shell or
# a .env file alongside this compose file.
- III_HOST_DEVICE_ID=${III_HOST_DEVICE_ID:-}
healthcheck:
test: ["CMD-SHELL", "nc -z 127.0.0.1 3111 || exit 1"]
interval: 10s
Expand Down
7 changes: 7 additions & 0 deletions engine/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ services:
environment:
- RUST_LOG=info
- III_EXECUTION_CONTEXT=docker
# Pass the host's anonymous telemetry device_id into the container.
# Amplitude reports it as `host_device_id` (not `device_id`) so
# container sessions can be attributed back to the host that
# launched them without requiring a bind-mount (which breaks on the
# distroless nonroot uid). Populate from your shell, e.g.:
# export III_HOST_DEVICE_ID=$(iii telemetry device-id)
- III_HOST_DEVICE_ID=${III_HOST_DEVICE_ID:-}
depends_on:
redis:
condition: service_healthy
Expand Down
85 changes: 84 additions & 1 deletion engine/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,27 @@ enum Commands {
#[arg(name = "command")]
target: Option<String>,
},

#[command(hide = true)]
Telemetry(TelemetryArgs),
}

#[derive(clap::Args, Debug)]
struct TelemetryArgs {
#[command(subcommand)]
command: TelemetryCommands,
}

#[derive(Subcommand, Debug)]
enum TelemetryCommands {
/// Print the current host device_id used for telemetry.
///
/// Intended for scripting: `export III_HOST_DEVICE_ID=$(iii telemetry device-id)`.
/// On first invocation this persists a new device_id to ~/.iii/telemetry.yaml
/// (same as a normal engine start). When run inside a container the printed
/// value will be a container-scoped id, so run it on the host.
#[command(hide = true)]
DeviceId,
}

fn should_init_logging_from_engine_config(cli: &Cli) -> bool {
Expand Down Expand Up @@ -190,14 +211,23 @@ async fn main() -> anyhow::Result<()> {
let exit_code = cli::handle_update(target.as_deref()).await;
std::process::exit(exit_code);
}
Some(Commands::Telemetry(args)) => {
match args.command {
TelemetryCommands::DeviceId => {
let id = iii::workers::telemetry::environment::get_or_create_device_id();
println!("{id}");
}
}
Ok(())
}
None => run_serve(&cli_args).await,
}
}

#[cfg(test)]
mod tests {
use super::*;
use clap::Parser;
use clap::{CommandFactory, Parser};
use iii::workers::worker::DEFAULT_PORT;

#[test]
Expand Down Expand Up @@ -471,6 +501,59 @@ mod tests {
);
}

#[test]
fn hidden_telemetry_device_id_parses() {
let cli = Cli::try_parse_from(["iii", "telemetry", "device-id"])
.expect("should parse hidden telemetry device-id subcommand");
match cli.command {
Some(Commands::Telemetry(args)) => match args.command {
TelemetryCommands::DeviceId => {}
},
_ => panic!("expected Telemetry(DeviceId) subcommand"),
}
}

#[test]
fn telemetry_without_subcommand_fails() {
let result = Cli::try_parse_from(["iii", "telemetry"]);
assert!(
result.is_err(),
"'iii telemetry' with no subcommand should fail"
);
}

#[test]
fn telemetry_unknown_subcommand_fails() {
let result = Cli::try_parse_from(["iii", "telemetry", "bogus"]);
assert!(
result.is_err(),
"'iii telemetry bogus' should fail — only device-id is valid"
);
}

#[test]
fn telemetry_is_hidden_from_top_level_help() {
let mut cmd = Cli::command();
let top_help = cmd.render_help().to_string();
assert!(
!top_help.contains("telemetry"),
"top-level help should not mention hidden 'telemetry' subcommand, got:\n{top_help}"
);
}

#[test]
fn device_id_is_hidden_from_telemetry_help() {
let mut cmd = Cli::command();
let telemetry = cmd
.find_subcommand_mut("telemetry")
.expect("telemetry subcommand should exist (hidden)");
let help = telemetry.render_help().to_string();
assert!(
!help.contains("device-id"),
"'iii telemetry --help' should not mention hidden 'device-id' subcommand, got:\n{help}"
);
}

#[test]
fn update_iii_cli_target_is_accepted() {
// Users with old iii-cli may type "iii update iii-cli" — this must
Expand Down
Loading
Loading