-
Notifications
You must be signed in to change notification settings - Fork 86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bootc switch --stateroot
flag
#617
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,6 +25,7 @@ use crate::deploy::RequiredHostSpec; | |
use crate::lints; | ||
use crate::spec::Host; | ||
use crate::spec::ImageReference; | ||
use crate::task::Task; | ||
use crate::utils::sigpolicy_from_opts; | ||
|
||
include!(concat!(env!("OUT_DIR"), "/version.rs")); | ||
|
@@ -99,6 +100,11 @@ pub(crate) struct SwitchOpts { | |
|
||
/// Target image to use for the next boot. | ||
pub(crate) target: String, | ||
|
||
/// Make the switch into a custom stateroot. If the stateroot doesn't exist, it will be created | ||
/// and `/var` of the current stateroot will be copied (copy-on-write) into it. | ||
#[clap(long)] | ||
pub(crate) stateroot: Option<String>, | ||
} | ||
|
||
/// Options controlling rollback | ||
|
@@ -628,7 +634,7 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> { | |
println!("No update available.") | ||
} else { | ||
let osname = booted_deployment.osname(); | ||
crate::deploy::stage(sysroot, &osname, &fetched, &spec).await?; | ||
crate::deploy::stage(sysroot, &osname, &osname, &fetched, &spec).await?; | ||
changed = true; | ||
if let Some(prev) = booted_image.as_ref() { | ||
if let Some(fetched_manifest) = fetched.get_manifest(repo)? { | ||
|
@@ -664,13 +670,13 @@ async fn switch(opts: SwitchOpts) -> Result<()> { | |
); | ||
let target = ostree_container::OstreeImageReference { sigverify, imgref }; | ||
let target = ImageReference::from(target); | ||
let root = cap_std::fs::Dir::open_ambient_dir("/", cap_std::ambient_authority())?; | ||
|
||
// If we're doing an in-place mutation, we shortcut most of the rest of the work here | ||
if opts.mutate_in_place { | ||
let deployid = { | ||
// Clone to pass into helper thread | ||
let target = target.clone(); | ||
let root = cap_std::fs::Dir::open_ambient_dir("/", cap_std::ambient_authority())?; | ||
tokio::task::spawn_blocking(move || { | ||
crate::deploy::switch_origin_inplace(&root, &target) | ||
}) | ||
|
@@ -687,18 +693,52 @@ async fn switch(opts: SwitchOpts) -> Result<()> { | |
let (booted_deployment, _deployments, host) = | ||
crate::status::get_status_require_booted(sysroot)?; | ||
|
||
let (old_stateroot, stateroot) = { | ||
let booted_osname = booted_deployment.osname(); | ||
let stateroot = opts | ||
.stateroot | ||
.as_deref() | ||
.unwrap_or_else(|| booted_osname.as_str()); | ||
|
||
(booted_osname.to_owned(), stateroot.to_owned()) | ||
}; | ||
|
||
let new_spec = { | ||
let mut new_spec = host.spec.clone(); | ||
new_spec.image = Some(target.clone()); | ||
new_spec | ||
}; | ||
|
||
if new_spec == host.spec { | ||
println!("Image specification is unchanged."); | ||
if new_spec == host.spec && old_stateroot == stateroot { | ||
// TODO: Should we really be confusing users with terms like "stateroot"? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, it's a good question. My thinking here is it's going to be hard to hide...and we should lean into exposing it as a more first class verb. |
||
println!( | ||
"The currently running deployment in stateroot {stateroot} is already using this image" | ||
); | ||
return Ok(()); | ||
} | ||
let new_spec = RequiredHostSpec::from_spec(&new_spec)?; | ||
|
||
if old_stateroot != stateroot { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Related to the above I'm uncertain about doing this by default. It may make sense to have a Definitely in some cases we explicitly want to ignore previous state. |
||
let init_result = sysroot.init_osname(&stateroot, cancellable); | ||
match init_result { | ||
Ok(_) => { | ||
Task::new("Copying /var to new stateroot", "cp") | ||
.args([ | ||
"--recursive", | ||
"--reflink=auto", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some discussion about hard requiring reflinks by default There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One subtle thing about this implementation is that as it is today because we're operating on the stateroot view of Speaking of, we almost certainly want to pass (Also this relates a bit to ostreedev/ostree#3292 ) |
||
"--archive", | ||
format!("/sysroot/ostree/deploy/{old_stateroot}/var").as_str(), | ||
format!("/sysroot/ostree/deploy/{stateroot}/").as_str(), | ||
]) | ||
.run()?; | ||
} | ||
Err(err) => { | ||
// TODO: Only ignore non already-exists errors | ||
println!("Ignoring error creating new stateroot: {err}"); | ||
} | ||
} | ||
} | ||
|
||
let fetched = crate::deploy::pull(repo, &target, None, opts.quiet).await?; | ||
|
||
if !opts.retain { | ||
|
@@ -712,8 +752,7 @@ async fn switch(opts: SwitchOpts) -> Result<()> { | |
} | ||
} | ||
|
||
let stateroot = booted_deployment.osname(); | ||
crate::deploy::stage(sysroot, &stateroot, &fetched, &new_spec).await?; | ||
crate::deploy::stage(sysroot, &old_stateroot, &stateroot, &fetched, &new_spec).await?; | ||
|
||
if opts.apply { | ||
crate::reboot::reboot()?; | ||
|
@@ -766,7 +805,7 @@ async fn edit(opts: EditOpts) -> Result<()> { | |
// TODO gc old layers here | ||
|
||
let stateroot = booted_deployment.osname(); | ||
crate::deploy::stage(sysroot, &stateroot, &fetched, &new_spec).await?; | ||
crate::deploy::stage(sysroot, &stateroot, &stateroot, &fetched, &new_spec).await?; | ||
|
||
Ok(()) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the interest of forward progress, how about adding
#[clap(hide = true))]
and calling thisexperimental_clone_into_stateroot
so it can be an experimental feature for now while we decide on how we stabilize this?