Skip to content

Commit 1416c9c

Browse files
author
faukah
committed
nixos: refactor OsRebuildArgs impl
1 parent 62b0c6f commit 1416c9c

File tree

1 file changed

+155
-78
lines changed

1 file changed

+155
-78
lines changed

src/nixos.rs

Lines changed: 155 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -98,21 +98,9 @@ impl OsRebuildArgs {
9898
final_attr: Option<String>,
9999
elevation: ElevationStrategy,
100100
) -> Result<()> {
101-
use OsRebuildVariant::{Boot, Build, BuildVm, Switch, Test};
101+
use OsRebuildVariant::{Build, BuildVm};
102102

103-
if self.build_host.is_some() || self.target_host.is_some() {
104-
// This can fail, we only care about prompting the user
105-
// for ssh key login beforehand.
106-
let _ = ensure_ssh_key_login();
107-
}
108-
109-
let elevate = check_and_get_elevation_status(self.bypass_root_check)?;
110-
111-
if self.update_args.update_all || self.update_args.update_input.is_some() {
112-
update(&self.common.installable, self.update_args.update_input)?;
113-
}
114-
115-
let target_hostname = get_resolved_hostname(self.hostname.clone())?;
103+
let (elevate, target_hostname) = self.setup_build_context()?;
116104

117105
// Only show the warning if we're explicitly building a VM
118106
// and no hostname was explicitly provided (--hostname was None)
@@ -129,49 +117,138 @@ impl OsRebuildArgs {
129117
);
130118
}
131119

132-
let (out_path, _tempdir_guard) = match self.common.out_link {
133-
Some(p) => (p, None),
120+
let (out_path, _tempdir_guard) = self.determine_output_path(variant)?;
121+
122+
let toplevel =
123+
self.resolve_installable_and_toplevel(&target_hostname, final_attr)?;
124+
125+
let message = match variant {
126+
BuildVm => "Building NixOS VM image",
127+
_ => "Building NixOS configuration",
128+
};
129+
130+
self.execute_build_command(toplevel, &out_path, message)?;
131+
132+
let target_profile = self.resolve_specialisation_and_profile(&out_path)?;
133+
134+
self.handle_dix_diff(&target_profile)?;
135+
136+
if self.common.dry || matches!(variant, Build | BuildVm) {
137+
if self.common.ask {
138+
warn!("--ask has no effect as dry run was requested");
139+
}
140+
141+
// For VM builds, print instructions on how to run the VM
142+
if matches!(variant, BuildVm) && !self.common.dry {
143+
print_vm_instructions(&out_path)?;
144+
}
145+
146+
return Ok(());
147+
}
148+
149+
self.activate_rebuilt_config(
150+
variant,
151+
&out_path,
152+
&target_profile,
153+
elevate,
154+
elevation,
155+
)?;
156+
157+
Ok(())
158+
}
159+
160+
/// Performs initial setup and gathers context for an OS rebuild operation.
161+
///
162+
/// This includes:
163+
/// - Ensuring SSH key login if a remote build/target host is involved.
164+
/// - Checking and determining elevation status.
165+
/// - Performing updates to Nix inputs if specified.
166+
/// - Resolving the target hostname for the build.
167+
///
168+
/// # Returns
169+
/// A `Result` containing a tuple:
170+
/// - `bool`: `true` if elevation is required, `false` otherwise.
171+
/// - `String`: The resolved target hostname.
172+
fn setup_build_context(&self) -> Result<(bool, String)> {
173+
if self.build_host.is_some() || self.target_host.is_some() {
174+
// This can fail, we only care about prompting the user
175+
// for ssh key login beforehand.
176+
let _ = ensure_ssh_key_login();
177+
}
178+
179+
let elevate = check_and_get_elevation_status(self.bypass_root_check)?;
180+
181+
if self.update_args.update_all || self.update_args.update_input.is_some() {
182+
update(
183+
&self.common.installable,
184+
self.update_args.update_input.clone(),
185+
)?;
186+
}
187+
188+
let target_hostname = get_resolved_hostname(self.hostname.clone())?;
189+
Ok((elevate, target_hostname))
190+
}
191+
192+
fn determine_output_path(
193+
&self,
194+
variant: &OsRebuildVariant,
195+
) -> Result<(PathBuf, Option<tempfile::TempDir>)> {
196+
use OsRebuildVariant::{Build, BuildVm};
197+
match self.common.out_link.clone() {
198+
Some(p) => Ok((p, None)),
134199
None => {
135200
let (path, guard) = if matches!(variant, BuildVm | Build) {
136201
(PathBuf::from("result"), None)
137202
} else {
138203
let dir = tempfile::Builder::new().prefix("nh-os").tempdir()?;
139204
(dir.as_ref().join("result"), Some(dir))
140205
};
141-
(path, guard)
206+
Ok((path, guard))
142207
},
143-
};
208+
}
209+
}
144210

145-
// Use NH_OS_FLAKE if available, otherwise use the provided installable
211+
fn resolve_installable_and_toplevel(
212+
&self,
213+
target_hostname: &str,
214+
final_attr: Option<String>,
215+
) -> Result<Installable> {
146216
let installable = if let Some(flake_installable) = parse_nh_os_flake_env()?
147217
{
148218
flake_installable
149219
} else {
150220
self.common.installable.clone()
151221
};
152222

153-
let toplevel = toplevel_for(
154-
&target_hostname,
223+
Ok(toplevel_for(
224+
target_hostname,
155225
installable,
156226
final_attr.as_deref().unwrap_or("toplevel"),
157-
);
158-
159-
let message = match variant {
160-
BuildVm => "Building NixOS VM image",
161-
_ => "Building NixOS configuration",
162-
};
227+
))
228+
}
163229

230+
fn execute_build_command(
231+
&self,
232+
toplevel: Installable,
233+
out_path: &Path,
234+
message: &str,
235+
) -> Result<()> {
164236
commands::Build::new(toplevel)
165237
.extra_arg("--out-link")
166-
.extra_arg(&out_path)
238+
.extra_arg(out_path)
167239
.extra_args(&self.extra_args)
168240
.passthrough(&self.common.passthrough)
169241
.builder(self.build_host.clone())
170242
.message(message)
171243
.nom(!self.common.no_nom)
172244
.run()
173-
.wrap_err("Failed to build configuration")?;
245+
.wrap_err("Failed to build configuration")
246+
}
174247

248+
fn resolve_specialisation_and_profile(
249+
&self,
250+
out_path: &Path,
251+
) -> Result<PathBuf> {
175252
let current_specialisation = std::fs::read_to_string(SPEC_LOCATION).ok();
176253

177254
let target_specialisation = if self.no_specialisation {
@@ -183,7 +260,7 @@ impl OsRebuildArgs {
183260
debug!("Target specialisation: {target_specialisation:?}");
184261

185262
let target_profile = target_specialisation.as_ref().map_or_else(
186-
|| out_path.clone(),
263+
|| out_path.to_path_buf(),
187264
|spec| out_path.join("specialisation").join(spec),
188265
);
189266

@@ -201,10 +278,13 @@ impl OsRebuildArgs {
201278
));
202279
}
203280

281+
Ok(target_profile)
282+
}
283+
284+
fn handle_dix_diff(&self, target_profile: &Path) -> Result<()> {
204285
match self.common.diff {
205286
DiffType::Always => {
206-
let _ =
207-
print_dix_diff(&PathBuf::from(CURRENT_PROFILE), &target_profile);
287+
let _ = print_dix_diff(&PathBuf::from(CURRENT_PROFILE), target_profile);
208288
},
209289
DiffType::Never => {
210290
debug!("Not running dix as the --diff flag is set to never.");
@@ -221,7 +301,7 @@ impl OsRebuildArgs {
221301
target_profile.display()
222302
);
223303
let _ =
224-
print_dix_diff(&PathBuf::from(CURRENT_PROFILE), &target_profile);
304+
print_dix_diff(&PathBuf::from(CURRENT_PROFILE), target_profile);
225305
} else {
226306
debug!(
227307
"Not running dix as a remote host is involved or an explicit \
@@ -230,19 +310,19 @@ impl OsRebuildArgs {
230310
}
231311
},
232312
}
313+
Ok(())
314+
}
233315

234-
if self.common.dry || matches!(variant, Build | BuildVm) {
235-
if self.common.ask {
236-
warn!("--ask has no effect as dry run was requested");
237-
}
238-
239-
// For VM builds, print instructions on how to run the VM
240-
if matches!(variant, BuildVm) && !self.common.dry {
241-
print_vm_instructions(&out_path)?;
242-
}
243-
244-
return Ok(());
245-
}
316+
#[expect(clippy::too_many_arguments)]
317+
fn activate_rebuilt_config(
318+
&self,
319+
variant: &OsRebuildVariant,
320+
out_path: &Path,
321+
target_profile: &Path,
322+
elevate: bool,
323+
elevation: ElevationStrategy,
324+
) -> Result<()> {
325+
use OsRebuildVariant::{Boot, Switch, Test};
246326

247327
if self.common.ask {
248328
let confirmation = inquire::Confirm::new("Apply the config?")
@@ -315,8 +395,8 @@ impl OsRebuildArgs {
315395

316396
let mut cmd = Command::new(switch_to_configuration)
317397
.arg("boot")
318-
.ssh(self.target_host)
319-
.elevate(elevate.then_some(elevation))
398+
.ssh(self.target_host.clone())
399+
.elevate(elevate.then_some(elevation.clone()))
320400
.message("Adding configuration to bootloader")
321401
.preserve_envs(["NIXOS_INSTALL_BOOTLOADER"]);
322402

@@ -331,7 +411,6 @@ impl OsRebuildArgs {
331411
}
332412

333413
debug!("Completed {variant:?} operation with output path: {out_path:?}");
334-
335414
Ok(())
336415
}
337416
}
@@ -517,42 +596,40 @@ impl OsRollbackArgs {
517596
fn find_vm_script(out_path: &Path) -> Result<PathBuf> {
518597
let bin_dir = out_path.join("bin");
519598

520-
if !bin_dir.exists() {
599+
if !bin_dir.is_dir() {
521600
bail!(
522601
"VM build output missing bin directory at {}",
523602
bin_dir.display()
524603
);
525604
}
526605

527-
let entries = fs::read_dir(&bin_dir).wrap_err_with(|| {
528-
format!("Failed to read bin directory at {}", bin_dir.display())
529-
})?;
530-
531-
let mut vm_script: Option<PathBuf> = None;
532-
for entry_result in entries {
533-
match entry_result {
534-
Ok(entry) => {
535-
let fname = entry.file_name();
536-
if fname
537-
.to_str()
538-
.is_some_and(|name| name.starts_with("run-") && name.ends_with("-vm"))
539-
{
540-
vm_script = Some(entry.path());
541-
break;
542-
}
543-
},
544-
Err(e) => {
545-
warn!(
546-
"Error reading entry in VM bin directory ({}): {}",
547-
bin_dir.display(),
548-
e
549-
);
550-
},
551-
}
552-
}
553-
let vm_script = vm_script.ok_or_else(|| {
554-
eyre!("Could not find VM runner script in {}", bin_dir.display())
555-
})?;
606+
let vm_script = fs::read_dir(&bin_dir)
607+
.wrap_err_with(|| {
608+
format!("Failed to read directory {}", bin_dir.display())
609+
})?
610+
.filter_map(|entry_result| {
611+
match entry_result {
612+
Ok(entry) => Some(entry),
613+
Err(e) => {
614+
warn!("Error reading entry in {}: {}", bin_dir.display(), e);
615+
None
616+
},
617+
}
618+
})
619+
.find_map(|entry| {
620+
let fname = entry.file_name();
621+
if fname
622+
.to_str()
623+
.is_some_and(|name| name.starts_with("run-") && name.ends_with("-vm"))
624+
{
625+
Some(entry.path())
626+
} else {
627+
None
628+
}
629+
})
630+
.ok_or_else(|| {
631+
eyre!("Could not find VM runner script in {}", bin_dir.display())
632+
})?;
556633

557634
Ok(vm_script)
558635
}

0 commit comments

Comments
 (0)