From 0793e71379aa5135f074698760b89992bdb5871d Mon Sep 17 00:00:00 2001 From: Brian Fjeldstad Date: Fri, 30 Oct 2026 16:57:37 +0000 Subject: [PATCH] telemetry: add agent configuration opt-in Rebase onto agent configuration read-error handling. Apply approved telemetry review fixes at their owning layer. Original layer head: 4a9a9c73d9fe445558aa7110630480a659739196 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 43cb811b-f191-4d90-852f-e3373041ca11 --- crates/trident/build.rs | 3 +- crates/trident/src/agentconfig.rs | 110 +++++++++++++++++++- crates/trident/src/lib.rs | 9 ++ crates/trident/src/subsystems/management.rs | 40 +++++++ docs/Reference/Agent-Configuration.md | 65 ++++++++---- 5 files changed, 201 insertions(+), 26 deletions(-) diff --git a/crates/trident/build.rs b/crates/trident/build.rs index 3e838be702..2d92b7b418 100644 --- a/crates/trident/build.rs +++ b/crates/trident/build.rs @@ -1,4 +1,5 @@ fn main() -> Result<(), Box> { println!("cargo:rerun-if-env-changed=TRIDENT_VERSION"); + println!("cargo:rerun-if-env-changed=AZURE_MONITOR_CONNECTION_STRING"); Ok(()) -} +} diff --git a/crates/trident/src/agentconfig.rs b/crates/trident/src/agentconfig.rs index e8d4cf206d..660f7ed971 100644 --- a/crates/trident/src/agentconfig.rs +++ b/crates/trident/src/agentconfig.rs @@ -10,8 +10,25 @@ use trident_api::{ error::{InvalidInputError, ReportError, TridentError}, }; +/// Whether Trident should attempt to send tracing data to Application +/// Insights (best-effort, and only when a connection string was compiled +/// into the binary -- see [`crate::AZURE_MONITOR_CONNECTION_STRING`]). +/// +/// Defaults to [`TelemetryPreference::OptOut`]: telemetry is disabled unless +/// a user has explicitly opted in via the Agent Configuration file. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum TelemetryPreference { + /// Sending telemetry to Application Insights is disabled. + #[default] + OptOut, + /// Telemetry is enabled, best-effort, provided a connection string was + /// compiled into this Trident binary. + OptIn, +} + pub struct AgentConfig { datastore: PathBuf, + telemetry: TelemetryPreference, } impl AgentConfig { @@ -24,6 +41,7 @@ impl AgentConfig { let path = path.as_ref(); let mut config = Self { datastore: TRIDENT_DATASTORE_PATH_DEFAULT.into(), + telemetry: TelemetryPreference::default(), }; let contents = match std::fs::read_to_string(path) { @@ -43,8 +61,20 @@ impl AgentConfig { }; for line in contents.lines() { - if let Some(path) = line.strip_prefix("DatastorePath=") { - config.datastore = path.trim().into(); + if let Some(value) = line.strip_prefix("DatastorePath=") { + config.datastore = value.trim().into(); + } else if let Some(value) = line.strip_prefix("Telemetry=") { + config.telemetry = match value.trim().to_ascii_lowercase().as_str() { + "optin" => TelemetryPreference::OptIn, + "optout" => TelemetryPreference::OptOut, + other => { + debug!( + "Unrecognized Telemetry setting '{other}' in agent \ + configuration file, defaulting to OptOut" + ); + TelemetryPreference::OptOut + } + }; } } @@ -55,6 +85,13 @@ impl AgentConfig { pub fn datastore_path(&self) -> &Path { &self.datastore } + + /// Whether telemetry (best-effort tracing to Application Insights) is + /// enabled per the agent configuration file. Defaults to `false` + /// (opt-out) when unset or unrecognized. + pub fn telemetry_enabled(&self) -> bool { + matches!(self.telemetry, TelemetryPreference::OptIn) + } } #[cfg(test)] @@ -129,4 +166,73 @@ mod tests { "got {error:?}" ); } + + #[test] + fn test_defaults_when_file_missing() { + let config = AgentConfig::load_from_path("/nonexistent/path/for/trident-tests.conf") + .expect("load_from_path should not fail even if the file is missing"); + assert_eq!( + config.datastore_path(), + Path::new(TRIDENT_DATASTORE_PATH_DEFAULT) + ); + assert!( + !config.telemetry_enabled(), + "telemetry must default to OptOut" + ); + } + + #[test] + fn test_telemetry_optin() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("trident.conf"); + std::fs::write(&path, "Telemetry=OptIn\n").unwrap(); + + let config = AgentConfig::load_from_path(path.to_str().unwrap()).unwrap(); + assert!(config.telemetry_enabled()); + } + + #[test] + fn test_telemetry_optout_explicit() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("trident.conf"); + std::fs::write(&path, "Telemetry=OptOut\n").unwrap(); + + let config = AgentConfig::load_from_path(path.to_str().unwrap()).unwrap(); + assert!(!config.telemetry_enabled()); + } + + #[test] + fn test_telemetry_is_case_insensitive() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("trident.conf"); + std::fs::write(&path, "Telemetry=OPTIN\n").unwrap(); + + let config = AgentConfig::load_from_path(path.to_str().unwrap()).unwrap(); + assert!(config.telemetry_enabled()); + } + + #[test] + fn test_telemetry_unrecognized_value_defaults_optout() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("trident.conf"); + std::fs::write(&path, "Telemetry=maybe\n").unwrap(); + + let config = AgentConfig::load_from_path(path.to_str().unwrap()).unwrap(); + assert!(!config.telemetry_enabled()); + } + + #[test] + fn test_datastore_and_telemetry_together() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("trident.conf"); + std::fs::write( + &path, + "DatastorePath=/custom/path.sqlite\nTelemetry=OptIn\n", + ) + .unwrap(); + + let config = AgentConfig::load_from_path(path.to_str().unwrap()).unwrap(); + assert!(config.telemetry_enabled()); + assert_eq!(config.datastore_path(), Path::new("/custom/path.sqlite")); + } } diff --git a/crates/trident/src/lib.rs b/crates/trident/src/lib.rs index a4f53747e7..ac6ff3aad5 100644 --- a/crates/trident/src/lib.rs +++ b/crates/trident/src/lib.rs @@ -95,6 +95,15 @@ lazy_static::lazy_static! { .expect("Failed to parse TRIDENT_VERSION as semver::Version"); } +/// Azure Monitor / Application Insights connection string, compiled in at +/// build time via the `AZURE_MONITOR_CONNECTION_STRING` environment +/// variable. Empty when the variable was not provided at build time. +pub const AZURE_MONITOR_CONNECTION_STRING: &str = + match option_env!("AZURE_MONITOR_CONNECTION_STRING") { + Some(v) => v, + None => "", + }; + /// Trident binary path. const TRIDENT_BINARY_PATH: &str = "/usr/bin/trident"; diff --git a/crates/trident/src/subsystems/management.rs b/crates/trident/src/subsystems/management.rs index dbe408d2f7..ecef48ca3d 100644 --- a/crates/trident/src/subsystems/management.rs +++ b/crates/trident/src/subsystems/management.rs @@ -271,4 +271,44 @@ mod tests { .unwrap_err(); } } + + #[test] + fn test_existing_symlinked_agent_config_is_not_modified() { + let custom = "/var/lib/trident/custom.sqlite"; + for (contents, expected_path, should_succeed) in [ + ( + "Telemetry=OptIn\n".to_string(), + TRIDENT_DATASTORE_PATH_DEFAULT, + true, + ), + ("Telemetry=OptIn\n".to_string(), custom, false), + ( + format!("Telemetry=OptIn\nDatastorePath={custom}\n"), + custom, + true, + ), + ] { + for verity in [false, true] { + let dir = tempfile::tempdir().unwrap(); + let target = dir.path().join("managed.conf"); + let config = dir.path().join("trident.conf"); + fs::write(&target, &contents).unwrap(); + std::os::unix::fs::symlink("managed.conf", &config).unwrap(); + + let result = configure_agent_config( + config.to_str().unwrap(), + Path::new(expected_path), + verity, + ); + + assert_eq!(result.is_ok(), should_succeed); + assert!(fs::symlink_metadata(&config) + .unwrap() + .file_type() + .is_symlink()); + assert_eq!(fs::read_link(&config).unwrap(), Path::new("managed.conf")); + assert_eq!(fs::read_to_string(&target).unwrap(), contents); + } + } + } } diff --git a/docs/Reference/Agent-Configuration.md b/docs/Reference/Agent-Configuration.md index a598c68b3d..afa12bfda5 100644 --- a/docs/Reference/Agent-Configuration.md +++ b/docs/Reference/Agent-Configuration.md @@ -1,23 +1,42 @@ ---- -sidebar_position: 5 ---- - -# Agent Configuration - -The Trident Agent Configuration file contains configuration details for Trident. It is used for all Trident commands. The Agent Configuration file path must be `/etc/trident/trident.conf`. - -> In most cases, the default values of Agent Configuration are sufficient and should not need to be changed. - -If the Agent Configuration file is not found, Trident uses the default settings. -Other read failures, including permission errors and invalid UTF-8, return an -error instead of silently falling back to defaults. - -## Setting Custom Datastore Path - -By default, Trident will use `/var/lib/trident/datastore.sqlite` as the path for the datastore. To configure a non-default path, the Agent Configuration file must contain a line defining the path like this: - -``` conf -DatastorePath=/special/path/to/my-datastore.sqlite -``` - -> The datastore path cannot be hosted on an [A/B volume pair](./Glossary#ab-volume-pair) and must be an absolute path. +--- +sidebar_position: 5 +--- + +# Agent Configuration + +The Trident Agent Configuration file contains configuration details for Trident. It is used for all Trident commands. The Agent Configuration file path must be `/etc/trident/trident.conf`. + +> In most cases, the default values of Agent Configuration are sufficient and should not need to be changed. + +If the Agent Configuration file is not found, Trident uses the default settings. +Other read failures, including permission errors and invalid UTF-8, return an +error instead of silently falling back to defaults. + +## Setting Custom Datastore Path + +By default, Trident will use `/var/lib/trident/datastore.sqlite` as the path for the datastore. To configure a non-default path, the Agent Configuration file must contain a line defining the path like this: + +``` conf +DatastorePath=/special/path/to/my-datastore.sqlite +``` + +> The datastore path cannot be hosted on an [A/B volume pair](./Glossary#ab-volume-pair) and must be an absolute path. + +## Telemetry + +The `Telemetry` setting records the preference for sending data to +Application Insights. Recording this preference alone does not upload +data: delivery also requires Application Insights runtime integration +and a connection string compiled into the binary using +`AZURE_MONITOR_CONNECTION_STRING`. + +The preference defaults to **OptOut**. To record an opt-in preference, +add a line to the Agent Configuration file: + +``` conf +Telemetry=OptIn +``` + +The value is case-insensitive (`OptIn`, `optin`, and `OPTIN` are all +equivalent); any value other than a case-insensitive match for `OptIn` +(including an absent `Telemetry` line) is treated as `OptOut`.