mirror of
https://github.com/containers/bootc.git
synced 2026-02-05 06:45:13 +01:00
composefs/update: Handle --download-only flag
When `--download-only` is passed, only download the image into the composefs repository but don't finalize it. Conver the /run/composefs/staged-deployment to a JSON file and Add a finalization_locked field depending upon which the finalize service will either finalize the staged deployment or leave it as is for garbage collection (even though GC is not fully implemented right now). Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
This commit is contained in:
committed by
Colin Walters
parent
653a1da6ca
commit
d8347297bf
@@ -1277,7 +1277,7 @@ pub(crate) async fn setup_composefs_boot(
|
||||
&root_setup.physical_root_path,
|
||||
&id,
|
||||
&crate::spec::ImageReference::from(state.target_imgref.clone()),
|
||||
false,
|
||||
None,
|
||||
boot_type,
|
||||
boot_digest,
|
||||
&get_container_manifest_and_config(&get_imgref(
|
||||
|
||||
@@ -57,6 +57,11 @@ pub(crate) async fn composefs_backend_finalize(
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if staged_depl.download_only {
|
||||
tracing::debug!("Staged deployment is marked download only. Won't finalize");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let staged_composefs = staged_depl.composefs.as_ref().ok_or(anyhow::anyhow!(
|
||||
"Staged deployment is not a composefs deployment"
|
||||
))?;
|
||||
|
||||
@@ -9,6 +9,7 @@ use bootc_kernel_cmdline::utf8::Cmdline;
|
||||
use bootc_mount::tempmount::TempMount;
|
||||
use bootc_utils::CommandRunExt;
|
||||
use camino::Utf8PathBuf;
|
||||
use canon_json::CanonJsonSerialize;
|
||||
use cap_std_ext::cap_std::ambient_authority;
|
||||
use cap_std_ext::cap_std::fs::{Dir, Permissions, PermissionsExt};
|
||||
use cap_std_ext::dirext::CapStdExtDirExt;
|
||||
@@ -23,7 +24,9 @@ use rustix::{
|
||||
|
||||
use crate::bootc_composefs::boot::BootType;
|
||||
use crate::bootc_composefs::repo::get_imgref;
|
||||
use crate::bootc_composefs::status::{ImgConfigManifest, get_sorted_type1_boot_entries};
|
||||
use crate::bootc_composefs::status::{
|
||||
ImgConfigManifest, StagedDeployment, get_sorted_type1_boot_entries,
|
||||
};
|
||||
use crate::parsers::bls_config::BLSConfigType;
|
||||
use crate::store::{BootedComposefs, Storage};
|
||||
use crate::{
|
||||
@@ -227,7 +230,7 @@ pub(crate) async fn write_composefs_state(
|
||||
root_path: &Utf8PathBuf,
|
||||
deployment_id: &Sha512HashValue,
|
||||
target_imgref: &ImageReference,
|
||||
staged: bool,
|
||||
staged: Option<StagedDeployment>,
|
||||
boot_type: BootType,
|
||||
boot_digest: String,
|
||||
container_details: &ImgConfigManifest,
|
||||
@@ -248,7 +251,12 @@ pub(crate) async fn write_composefs_state(
|
||||
)
|
||||
.context("Failed to create symlink for /var")?;
|
||||
|
||||
initialize_state(&root_path, &deployment_id.to_hex(), &state_path, !staged)?;
|
||||
initialize_state(
|
||||
&root_path,
|
||||
&deployment_id.to_hex(),
|
||||
&state_path,
|
||||
staged.is_none(),
|
||||
)?;
|
||||
|
||||
let ImageReference {
|
||||
image: image_name,
|
||||
@@ -291,7 +299,7 @@ pub(crate) async fn write_composefs_state(
|
||||
)
|
||||
.context("Failed to write to .origin file")?;
|
||||
|
||||
if staged {
|
||||
if let Some(staged) = staged {
|
||||
std::fs::create_dir_all(COMPOSEFS_TRANSIENT_STATE_DIR)
|
||||
.with_context(|| format!("Creating {COMPOSEFS_TRANSIENT_STATE_DIR}"))?;
|
||||
|
||||
@@ -302,7 +310,9 @@ pub(crate) async fn write_composefs_state(
|
||||
staged_depl_dir
|
||||
.atomic_write(
|
||||
COMPOSEFS_STAGED_DEPLOYMENT_FNAME,
|
||||
deployment_id.to_hex().as_bytes(),
|
||||
staged
|
||||
.to_canon_json_vec()
|
||||
.context("Failed to serialize staged deployment JSON")?,
|
||||
)
|
||||
.with_context(|| format!("Writing to {COMPOSEFS_STAGED_DEPLOYMENT_FNAME}"))?;
|
||||
}
|
||||
|
||||
@@ -79,6 +79,12 @@ impl std::fmt::Display for ComposefsCmdline {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub(crate) struct StagedDeployment {
|
||||
pub(crate) depl_id: String,
|
||||
pub(crate) finalization_locked: bool,
|
||||
}
|
||||
|
||||
/// Detect if we have composefs=<digest> in /proc/cmdline
|
||||
pub(crate) fn composefs_booted() -> Result<Option<&'static ComposefsCmdline>> {
|
||||
static CACHED_DIGEST_VALUE: OnceLock<Option<ComposefsCmdline>> = OnceLock::new();
|
||||
@@ -554,7 +560,7 @@ pub(crate) async fn composefs_deployment_status_from(
|
||||
|
||||
let mut host = Host::new(host_spec);
|
||||
|
||||
let staged_deployment_id = match std::fs::File::open(format!(
|
||||
let staged_deployment = match std::fs::File::open(format!(
|
||||
"{COMPOSEFS_TRANSIENT_STATE_DIR}/{COMPOSEFS_STAGED_DEPLOYMENT_FNAME}"
|
||||
)) {
|
||||
Ok(mut f) => {
|
||||
@@ -590,7 +596,7 @@ pub(crate) async fn composefs_deployment_status_from(
|
||||
let ini = tini::Ini::from_string(&config)
|
||||
.with_context(|| format!("Failed to parse file {depl_file_name}.origin as ini"))?;
|
||||
|
||||
let boot_entry =
|
||||
let mut boot_entry =
|
||||
boot_entry_from_composefs_deployment(storage, ini, depl_file_name.to_string()).await?;
|
||||
|
||||
// SAFETY: boot_entry.composefs will always be present
|
||||
@@ -614,8 +620,11 @@ pub(crate) async fn composefs_deployment_status_from(
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(staged_deployment_id) = &staged_deployment_id {
|
||||
if depl_file_name == staged_deployment_id.trim() {
|
||||
if let Some(staged_deployment) = &staged_deployment {
|
||||
let staged_depl = serde_json::from_str::<StagedDeployment>(&staged_deployment)?;
|
||||
|
||||
if depl_file_name == staged_depl.depl_id {
|
||||
boot_entry.download_only = staged_depl.finalization_locked;
|
||||
host.status.staged = Some(boot_entry);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ pub(crate) async fn switch_composefs(
|
||||
let do_upgrade_opts = DoUpgradeOpts {
|
||||
soft_reboot: opts.soft_reboot,
|
||||
apply: opts.apply,
|
||||
download_only: false,
|
||||
};
|
||||
|
||||
if let Some(cfg_verity) = image {
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
use std::io::Write;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use camino::Utf8PathBuf;
|
||||
use cap_std_ext::cap_std::fs::Dir;
|
||||
use canon_json::CanonJsonSerialize;
|
||||
use cap_std_ext::{cap_std::fs::Dir, dirext::CapStdExtDirExt};
|
||||
use composefs::fsverity::{FsVerityHashValue, Sha512HashValue};
|
||||
use composefs_boot::BootOps;
|
||||
use composefs_oci::image::create_filesystem;
|
||||
use fn_error_context::context;
|
||||
use ocidir::cap_std::ambient_authority;
|
||||
use ostree_ext::container::ManifestDiff;
|
||||
|
||||
use crate::{
|
||||
@@ -15,12 +19,15 @@ use crate::{
|
||||
soft_reboot::prepare_soft_reboot_composefs,
|
||||
state::write_composefs_state,
|
||||
status::{
|
||||
ImgConfigManifest, get_bootloader, get_composefs_status,
|
||||
ImgConfigManifest, StagedDeployment, get_bootloader, get_composefs_status,
|
||||
get_container_manifest_and_config, get_imginfo,
|
||||
},
|
||||
},
|
||||
cli::{SoftRebootMode, UpgradeOpts},
|
||||
composefs_consts::{STATE_DIR_RELATIVE, TYPE1_ENT_PATH_STAGED, USER_CFG_STAGED},
|
||||
composefs_consts::{
|
||||
COMPOSEFS_STAGED_DEPLOYMENT_FNAME, COMPOSEFS_TRANSIENT_STATE_DIR, STATE_DIR_RELATIVE,
|
||||
TYPE1_ENT_PATH_STAGED, USER_CFG_STAGED,
|
||||
},
|
||||
spec::{Bootloader, Host, ImageReference},
|
||||
store::{BootedComposefs, ComposefsRepository, Storage},
|
||||
};
|
||||
@@ -206,6 +213,31 @@ pub(crate) fn validate_update(
|
||||
pub(crate) struct DoUpgradeOpts {
|
||||
pub(crate) apply: bool,
|
||||
pub(crate) soft_reboot: Option<SoftRebootMode>,
|
||||
pub(crate) download_only: bool,
|
||||
}
|
||||
|
||||
async fn apply_upgrade(
|
||||
storage: &Storage,
|
||||
booted_cfs: &BootedComposefs,
|
||||
depl_id: &String,
|
||||
opts: &DoUpgradeOpts,
|
||||
) -> Result<()> {
|
||||
if let Some(soft_reboot_mode) = opts.soft_reboot {
|
||||
return prepare_soft_reboot_composefs(
|
||||
storage,
|
||||
booted_cfs,
|
||||
Some(depl_id),
|
||||
soft_reboot_mode,
|
||||
opts.apply,
|
||||
)
|
||||
.await;
|
||||
};
|
||||
|
||||
if opts.apply {
|
||||
return crate::reboot::reboot();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Performs the Update or Switch operation
|
||||
@@ -255,29 +287,17 @@ pub(crate) async fn do_upgrade(
|
||||
&Utf8PathBuf::from("/sysroot"),
|
||||
&id,
|
||||
imgref,
|
||||
true,
|
||||
Some(StagedDeployment {
|
||||
depl_id: id.to_hex(),
|
||||
finalization_locked: opts.download_only,
|
||||
}),
|
||||
boot_type,
|
||||
boot_digest,
|
||||
img_manifest_config,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(soft_reboot_mode) = opts.soft_reboot {
|
||||
return prepare_soft_reboot_composefs(
|
||||
storage,
|
||||
booted_cfs,
|
||||
Some(&id.to_hex()),
|
||||
soft_reboot_mode,
|
||||
opts.apply,
|
||||
)
|
||||
.await;
|
||||
};
|
||||
|
||||
if opts.apply {
|
||||
return crate::reboot::reboot();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
apply_upgrade(storage, booted_cfs, &id.to_hex(), opts).await
|
||||
}
|
||||
|
||||
#[context("Upgrading composefs")]
|
||||
@@ -286,18 +306,60 @@ pub(crate) async fn upgrade_composefs(
|
||||
storage: &Storage,
|
||||
composefs: &BootedComposefs,
|
||||
) -> Result<()> {
|
||||
// Download-only mode is not yet supported for composefs backend
|
||||
if opts.download_only {
|
||||
anyhow::bail!("--download-only is not yet supported for composefs backend");
|
||||
}
|
||||
if opts.from_downloaded {
|
||||
anyhow::bail!("--from-downloaded is not yet supported for composefs backend");
|
||||
}
|
||||
|
||||
let host = get_composefs_status(storage, composefs)
|
||||
.await
|
||||
.context("Getting composefs deployment status")?;
|
||||
|
||||
let do_upgrade_opts = DoUpgradeOpts {
|
||||
soft_reboot: opts.soft_reboot,
|
||||
apply: opts.apply,
|
||||
download_only: opts.download_only,
|
||||
};
|
||||
|
||||
if opts.from_downloaded {
|
||||
let staged = host
|
||||
.status
|
||||
.staged
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow::anyhow!("No staged deployment found"))?;
|
||||
|
||||
// Staged deployment exists, but it will be finalized
|
||||
if !staged.download_only {
|
||||
println!("Staged deployment is present and not in download only mode.");
|
||||
println!("Use `bootc update --apply` to apply the update.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
start_finalize_stated_svc()?;
|
||||
|
||||
// Make the staged deployment not download_only
|
||||
let new_staged = StagedDeployment {
|
||||
depl_id: staged.require_composefs()?.verity.clone(),
|
||||
finalization_locked: false,
|
||||
};
|
||||
|
||||
let staged_depl_dir =
|
||||
Dir::open_ambient_dir(COMPOSEFS_TRANSIENT_STATE_DIR, ambient_authority())
|
||||
.context("Opening transient state directory")?;
|
||||
|
||||
staged_depl_dir
|
||||
.atomic_replace_with(
|
||||
COMPOSEFS_STAGED_DEPLOYMENT_FNAME,
|
||||
|f| -> std::io::Result<()> {
|
||||
f.write_all(new_staged.to_canon_json_string()?.as_bytes())
|
||||
},
|
||||
)
|
||||
.context("Writing staged file")?;
|
||||
|
||||
return apply_upgrade(
|
||||
storage,
|
||||
composefs,
|
||||
&staged.require_composefs()?.verity,
|
||||
&do_upgrade_opts,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
let mut booted_imgref = host
|
||||
.spec
|
||||
.image
|
||||
@@ -313,11 +375,6 @@ pub(crate) async fn upgrade_composefs(
|
||||
// Or if we have another staged deployment with a different image
|
||||
let staged_image = host.status.staged.as_ref().and_then(|i| i.image.as_ref());
|
||||
|
||||
let do_upgrade_opts = DoUpgradeOpts {
|
||||
soft_reboot: opts.soft_reboot,
|
||||
apply: opts.apply,
|
||||
};
|
||||
|
||||
if let Some(staged_image) = staged_image {
|
||||
// We have a staged image and it has the same digest as the currently booted image's latest
|
||||
// digest
|
||||
|
||||
Reference in New Issue
Block a user