Skip to content

Commit 6ff1a80

Browse files
Allow specifying mutability in avocadoctl config
1 parent de3dc45 commit 6ff1a80

5 files changed

Lines changed: 274 additions & 39 deletions

File tree

example-config.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Example avocadoctl configuration file
2+
# This file demonstrates the available configuration options
3+
4+
[avocado.ext]
5+
# Directory where extensions are stored
6+
dir = "/var/lib/avocado/extensions"
7+
8+
# Mutability mode for extensions (systemd --mutable option)
9+
# Valid values: no, auto, yes, import, ephemeral, ephemeral-import
10+
# Default: ephemeral
11+
mutable = "ephemeral"
12+
13+
# Examples of other valid values:
14+
# mutable = "no" # Force immutable mode
15+
# mutable = "auto" # Enable mutable mode if write routing directories present
16+
# mutable = "yes" # Force mutable mode, create write routing directories
17+
# mutable = "import" # Immutable mode but merge write routing contents
18+
# mutable = "ephemeral-import" # Mutable mode with write routing merged but changes discarded after unmerge

src/commands/ext.rs

Lines changed: 75 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -182,14 +182,14 @@ pub fn handle_command(matches: &ArgMatches, config: &Config, output: &OutputMana
182182
list_extensions(config, output);
183183
}
184184
Some(("merge", _)) => {
185-
merge_extensions(output);
185+
merge_extensions(config, output);
186186
}
187187
Some(("unmerge", unmerge_matches)) => {
188188
let unmount = unmerge_matches.get_flag("unmount");
189189
unmerge_extensions(unmount, output);
190190
}
191191
Some(("refresh", _)) => {
192-
refresh_extensions(output);
192+
refresh_extensions(config, output);
193193
}
194194
Some(("status", _)) => {
195195
status_extensions(output);
@@ -251,8 +251,8 @@ fn list_extensions(config: &Config, output: &OutputManager) {
251251
}
252252

253253
/// Merge extensions using systemd-sysext and systemd-confext
254-
pub fn merge_extensions(output: &OutputManager) {
255-
match merge_extensions_internal(output) {
254+
pub fn merge_extensions(config: &Config, output: &OutputManager) {
255+
match merge_extensions_internal(config, output) {
256256
Ok(_) => {
257257
output.success("Extension Merge", "Extensions merged successfully");
258258
}
@@ -267,7 +267,7 @@ pub fn merge_extensions(output: &OutputManager) {
267267
}
268268

269269
/// Internal merge function that returns a Result
270-
fn merge_extensions_internal(output: &OutputManager) -> Result<(), SystemdError> {
270+
fn merge_extensions_internal(config: &Config, output: &OutputManager) -> Result<(), SystemdError> {
271271
let environment_info = if is_running_in_initrd() {
272272
"initrd environment"
273273
} else {
@@ -281,18 +281,29 @@ fn merge_extensions_internal(output: &OutputManager) -> Result<(), SystemdError>
281281
// Prepare the environment by setting up symlinks and get the list of enabled extensions
282282
let enabled_extensions = prepare_extension_environment_with_output(output)?;
283283

284+
// Get the mutability setting from config
285+
let mutability = match config.get_extension_mutable() {
286+
Ok(value) => value,
287+
Err(e) => {
288+
output.error(
289+
"Configuration Error",
290+
&format!("Invalid mutable configuration: {e}"),
291+
);
292+
return Err(SystemdError::ConfigurationError {
293+
message: e.to_string(),
294+
});
295+
}
296+
};
297+
let mutable_arg = format!("--mutable={mutability}");
298+
284299
// Merge system extensions
285-
let sysext_result = run_systemd_command(
286-
"systemd-sysext",
287-
&["merge", "--mutable=ephemeral", "--json=short"],
288-
)?;
300+
let sysext_result =
301+
run_systemd_command("systemd-sysext", &["merge", &mutable_arg, "--json=short"])?;
289302
handle_systemd_output("systemd-sysext merge", &sysext_result, output)?;
290303

291304
// Merge configuration extensions
292-
let confext_result = run_systemd_command(
293-
"systemd-confext",
294-
&["merge", "--mutable=ephemeral", "--json=short"],
295-
)?;
305+
let confext_result =
306+
run_systemd_command("systemd-confext", &["merge", &mutable_arg, "--json=short"])?;
296307
handle_systemd_output("systemd-confext merge", &confext_result, output)?;
297308

298309
// Process post-merge tasks only for enabled extensions
@@ -378,7 +389,9 @@ fn unmerge_extensions_internal_with_options(
378389
///
379390
/// Merge extensions - direct access for top-level alias
380391
pub fn merge_extensions_direct(output: &OutputManager) {
381-
merge_extensions(output);
392+
// Use default config for direct access
393+
let config = Config::default();
394+
merge_extensions(&config, output);
382395
}
383396

384397
/// Unmerge extensions - direct access for top-level alias
@@ -388,7 +401,9 @@ pub fn unmerge_extensions_direct(unmount: bool, output: &OutputManager) {
388401

389402
/// Refresh extensions - direct access for top-level alias
390403
pub fn refresh_extensions_direct(output: &OutputManager) {
391-
refresh_extensions(output);
404+
// Use default config for direct access
405+
let config = Config::default();
406+
refresh_extensions(&config, output);
392407
}
393408

394409
/// Enable extensions for a specific OS release version
@@ -450,8 +465,8 @@ pub fn enable_extensions(
450465

451466
for ext_name in extensions {
452467
// Check if extension exists - try both directory and .raw file
453-
let ext_dir_path = format!("{}/{}", extensions_dir, ext_name);
454-
let ext_raw_path = format!("{}/{}.raw", extensions_dir, ext_name);
468+
let ext_dir_path = format!("{extensions_dir}/{ext_name}");
469+
let ext_raw_path = format!("{extensions_dir}/{ext_name}.raw");
455470

456471
let source_path = if Path::new(&ext_dir_path).exists() {
457472
ext_dir_path
@@ -643,8 +658,8 @@ pub fn disable_extensions(
643658
// Disable specific extensions
644659
for ext_name in ext_names {
645660
// Check for both directory and .raw file symlinks
646-
let symlink_dir = format!("{}/{}", os_releases_dir, ext_name);
647-
let symlink_raw = format!("{}/{}.raw", os_releases_dir, ext_name);
661+
let symlink_dir = format!("{os_releases_dir}/{ext_name}");
662+
let symlink_raw = format!("{os_releases_dir}/{ext_name}.raw");
648663

649664
let mut found = false;
650665

@@ -735,7 +750,7 @@ pub fn disable_extensions(
735750
}
736751

737752
/// Refresh extensions (unmerge then merge)
738-
pub fn refresh_extensions(output: &OutputManager) {
753+
pub fn refresh_extensions(config: &Config, output: &OutputManager) {
739754
let environment_info = if is_running_in_initrd() {
740755
"initrd environment"
741756
} else {
@@ -757,7 +772,7 @@ pub fn refresh_extensions(output: &OutputManager) {
757772
output.step("Refresh", "Extensions unmerged");
758773

759774
// Then merge (this will call depmod via post-merge processing)
760-
if let Err(e) = merge_extensions_internal(output) {
775+
if let Err(e) = merge_extensions_internal(config, output) {
761776
output.error(
762777
"Extension Refresh",
763778
&format!("Failed to merge extensions: {e}"),
@@ -1225,13 +1240,11 @@ fn cleanup_stale_extension_symlinks(
12251240
if should_remove {
12261241
if let Err(e) = fs::remove_file(&path) {
12271242
output.progress(&format!(
1228-
"Warning: Failed to remove stale sysext symlink {}: {}",
1229-
file_name, e
1230-
));
1243+
"Warning: Failed to remove stale sysext symlink {file_name}: {e}"
1244+
));
12311245
} else {
12321246
output.progress(&format!(
1233-
"Removed stale sysext symlink: {}",
1234-
file_name
1247+
"Removed stale sysext symlink: {file_name}"
12351248
));
12361249
}
12371250
}
@@ -1281,13 +1294,11 @@ fn cleanup_stale_extension_symlinks(
12811294
if should_remove {
12821295
if let Err(e) = fs::remove_file(&path) {
12831296
output.progress(&format!(
1284-
"Warning: Failed to remove stale confext symlink {}: {}",
1285-
file_name, e
1286-
));
1297+
"Warning: Failed to remove stale confext symlink {file_name}: {e}"
1298+
));
12871299
} else {
12881300
output.progress(&format!(
1289-
"Removed stale confext symlink: {}",
1290-
file_name
1301+
"Removed stale confext symlink: {file_name}"
12911302
));
12921303
}
12931304
}
@@ -1437,9 +1448,8 @@ fn scan_extensions_from_all_sources_with_verbosity(
14371448
Entry::Occupied(_) => {
14381449
if verbose {
14391450
println!(
1440-
"Skipping OS release raw extension {} (higher priority version preferred)",
1441-
ext_name
1442-
);
1451+
"Skipping OS release raw extension {ext_name} (higher priority version preferred)"
1452+
);
14431453
}
14441454
}
14451455
}
@@ -1512,7 +1522,7 @@ fn scan_extensions_from_all_sources_with_verbosity(
15121522
// Add versioned names for raw files we're about to process
15131523
for (name, version, _path) in &raw_files {
15141524
if let Some(ver) = version {
1515-
available_loop_names.push(format!("{}-{}", name, ver));
1525+
available_loop_names.push(format!("{name}-{ver}"));
15161526
} else {
15171527
available_loop_names.push(name.clone());
15181528
}
@@ -2769,6 +2779,9 @@ pub enum SystemdError {
27692779
exit_code: Option<i32>,
27702780
stderr: String,
27712781
},
2782+
2783+
#[error("Configuration error: {message}")]
2784+
ConfigurationError { message: String },
27722785
}
27732786

27742787
#[cfg(test)]
@@ -3246,4 +3259,31 @@ OTHER_KEY=value
32463259
let result = is_confext_enabled_for_current_environment(&ext_path, "test_ext");
32473260
assert!(result);
32483261
}
3262+
3263+
#[test]
3264+
fn test_config_mutable_integration() {
3265+
// Test that the config mutable option is properly used
3266+
let mut config = Config::default();
3267+
3268+
// Test with default value (ephemeral)
3269+
assert_eq!(config.get_extension_mutable().unwrap(), "ephemeral");
3270+
3271+
// Test with custom value
3272+
config.avocado.ext.mutable = Some("yes".to_string());
3273+
assert_eq!(config.get_extension_mutable().unwrap(), "yes");
3274+
3275+
// Test with another valid value
3276+
config.avocado.ext.mutable = Some("auto".to_string());
3277+
assert_eq!(config.get_extension_mutable().unwrap(), "auto");
3278+
3279+
// Test error handling for invalid value
3280+
config.avocado.ext.mutable = Some("invalid".to_string());
3281+
let result = config.get_extension_mutable();
3282+
assert!(result.is_err());
3283+
3284+
let error = result.unwrap_err();
3285+
assert!(error
3286+
.to_string()
3287+
.contains("Invalid mutable value 'invalid'"));
3288+
}
32493289
}

src/commands/hitl.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ fn mount_extensions(matches: &ArgMatches, output: &OutputManager) {
136136
"HITL Mount",
137137
"Refreshing extensions to apply mounted changes",
138138
);
139-
ext::refresh_extensions(output);
139+
let config = crate::config::Config::default();
140+
ext::refresh_extensions(&config, output);
140141
} else {
141142
output.error("HITL Mount", "Some extensions failed to mount");
142143
std::process::exit(1);
@@ -263,7 +264,8 @@ fn unmount_extensions(matches: &ArgMatches, output: &OutputManager) {
263264
output.success("HITL Unmount", "All extensions unmounted successfully");
264265
output.info("HITL Unmount", "Refreshing extensions to apply changes");
265266
// Step 3: Merge remaining extensions
266-
ext::merge_extensions(output);
267+
let config = crate::config::Config::default();
268+
ext::merge_extensions(&config, output);
267269
} else {
268270
output.error("HITL Unmount", "Some extensions failed to unmount");
269271
std::process::exit(1);

0 commit comments

Comments
 (0)