Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion crates/trident/build.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("cargo:rerun-if-env-changed=TRIDENT_VERSION");
println!("cargo:rerun-if-env-changed=AZURE_MONITOR_CONNECTION_STRING");
Ok(())
}
}
110 changes: 108 additions & 2 deletions crates/trident/src/agentconfig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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) {
Expand All @@ -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
}
};
}
}

Expand All @@ -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)]
Expand Down Expand Up @@ -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"));
}
}
9 changes: 9 additions & 0 deletions crates/trident/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,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";

Expand Down
40 changes: 40 additions & 0 deletions crates/trident/src/subsystems/management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
}
65 changes: 42 additions & 23 deletions docs/Reference/Agent-Configuration.md
Original file line number Diff line number Diff line change
@@ -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`.
Loading