Skip to content

Commit 0fcdc63

Browse files
committed
cli: support the stateroot option also on switch
draft, still working out the details and testing, this doesn't work yet # Background <hash> added the `stateroot` option to the `install` subcommand # Issue The `stateroot` option is not available on the `switch` subcommand # Solution Add the `stateroot` option to the `switch` subcommand # Implementation * If the stateroot is different than the current, we should allow using the same image as the currently booted one * Stateroot has to be explicitly created (`init_osname` binding) if it doesn't exist. If it does, we still call `init_osname` and simply ignore the error (TODO: only ignore non-already-exists errors) Signed-off-by: Omer Tuchfeld <omer@tuchfeld.dev>
1 parent 1fd6c69 commit 0fcdc63

File tree

2 files changed

+38
-10
lines changed

2 files changed

+38
-10
lines changed

lib/src/cli.rs

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ pub(crate) struct SwitchOpts {
9999

100100
/// Target image to use for the next boot.
101101
pub(crate) target: String,
102+
103+
#[clap(long)]
104+
pub(crate) stateroot: Option<String>,
102105
}
103106

104107
/// Options controlling rollback
@@ -628,7 +631,7 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
628631
println!("No update available.")
629632
} else {
630633
let osname = booted_deployment.osname();
631-
crate::deploy::stage(sysroot, &osname, &fetched, &spec).await?;
634+
crate::deploy::stage(sysroot, &osname, &osname, &fetched, &spec).await?;
632635
changed = true;
633636
if let Some(prev) = booted_image.as_ref() {
634637
if let Some(fetched_manifest) = fetched.get_manifest(repo)? {
@@ -687,18 +690,42 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
687690
let (booted_deployment, _deployments, host) =
688691
crate::status::get_status_require_booted(sysroot)?;
689692

693+
let (old_stateroot, stateroot) = {
694+
let booted_osname = booted_deployment.osname();
695+
let stateroot = opts
696+
.stateroot
697+
.as_deref()
698+
.unwrap_or_else(|| booted_osname.as_str());
699+
700+
(booted_osname.to_owned(), stateroot.to_owned())
701+
};
702+
690703
let new_spec = {
691704
let mut new_spec = host.spec.clone();
692705
new_spec.image = Some(target.clone());
693706
new_spec
694707
};
695708

696-
if new_spec == host.spec {
697-
println!("Image specification is unchanged.");
709+
if new_spec == host.spec && old_stateroot == stateroot {
710+
// TODO: Should we really be confusing users with terms like "stateroot"?
711+
println!(
712+
"The currently running deployment in stateroot {stateroot} is already using this image"
713+
);
698714
return Ok(());
699715
}
700716
let new_spec = RequiredHostSpec::from_spec(&new_spec)?;
701717

718+
if old_stateroot != stateroot {
719+
let init_result = sysroot.init_osname(&stateroot, cancellable);
720+
match init_result {
721+
Ok(_) => {}
722+
Err(err) => {
723+
// TODO: Only ignore non already-exists errors
724+
println!("Ignoring error creating new stateroot: {err}");
725+
}
726+
}
727+
}
728+
702729
let fetched = crate::deploy::pull(repo, &target, None, opts.quiet).await?;
703730

704731
if !opts.retain {
@@ -712,8 +739,7 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
712739
}
713740
}
714741

715-
let stateroot = booted_deployment.osname();
716-
crate::deploy::stage(sysroot, &stateroot, &fetched, &new_spec).await?;
742+
crate::deploy::stage(sysroot, &old_stateroot, &stateroot, &fetched, &new_spec).await?;
717743

718744
if opts.apply {
719745
crate::reboot::reboot()?;
@@ -766,7 +792,7 @@ async fn edit(opts: EditOpts) -> Result<()> {
766792
// TODO gc old layers here
767793

768794
let stateroot = booted_deployment.osname();
769-
crate::deploy::stage(sysroot, &stateroot, &fetched, &new_spec).await?;
795+
crate::deploy::stage(sysroot, &stateroot, &stateroot, &fetched, &new_spec).await?;
770796

771797
Ok(())
772798
}

lib/src/deploy.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,6 @@ async fn deploy(
373373
image: &ImageState,
374374
origin: &glib::KeyFile,
375375
) -> Result<Deployment> {
376-
let stateroot = Some(stateroot);
377376
let mut opts = ostree::SysrootDeployTreeOpts::default();
378377
// Compute the kernel argument overrides. In practice today this API is always expecting
379378
// a merge deployment. The kargs code also always looks at the booted root (which
@@ -396,10 +395,12 @@ async fn deploy(
396395
let cancellable = gio::Cancellable::NONE;
397396
return sysroot
398397
.stage_tree_with_options(
399-
stateroot,
398+
Some(stateroot),
400399
image.ostree_commit.as_str(),
401400
Some(origin),
402-
merge_deployment,
401+
merge_deployment.filter(|merge_deployment| {
402+
stateroot == ostree::Deployment::osname(merge_deployment)
403+
}),
403404
&opts,
404405
cancellable,
405406
)
@@ -422,11 +423,12 @@ fn origin_from_imageref(imgref: &ImageReference) -> Result<glib::KeyFile> {
422423
#[context("Staging")]
423424
pub(crate) async fn stage(
424425
sysroot: &Storage,
426+
merge_stateroot: &str,
425427
stateroot: &str,
426428
image: &ImageState,
427429
spec: &RequiredHostSpec<'_>,
428430
) -> Result<()> {
429-
let merge_deployment = sysroot.merge_deployment(Some(stateroot));
431+
let merge_deployment = sysroot.merge_deployment(Some(merge_stateroot));
430432
let origin = origin_from_imageref(spec.image)?;
431433
let deployment = crate::deploy::deploy(
432434
sysroot,

0 commit comments

Comments
 (0)