Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/lib/src/bootc_composefs/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ async fn boot_entry_from_composefs_deployment(
cached_update: None,
incompatible: false,
pinned: false,
download_only: false, // Not yet supported for composefs backend
store: None,
ostree: None,
composefs: Some(crate::spec::BootEntryComposefs {
Expand Down
52 changes: 48 additions & 4 deletions crates/lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@ pub(crate) struct UpgradeOpts {
#[clap(long = "soft-reboot", conflicts_with = "check")]
pub(crate) soft_reboot: Option<SoftRebootMode>,

/// Download and stage the update without applying it.
///
/// Download the update and ensure it's retained on disk for the lifetime of this system boot,
/// but it will not be applied on reboot. If the system is rebooted without applying the update,
/// the image will be eligible for garbage collection again.
#[clap(long, conflicts_with_all = ["check", "apply"])]
pub(crate) download_only: bool,

#[clap(flatten)]
pub(crate) progress: ProgressOptions,
}
Expand Down Expand Up @@ -956,7 +964,35 @@ async fn upgrade(
.map(|img| &img.manifest_digest == fetched_digest)
.unwrap_or_default();
if staged_unchanged {
println!("Staged update present, not changed.");
let staged_deployment = storage.get_ostree()?.staged_deployment();
let mut download_only_changed = false;

if let Some(staged) = staged_deployment {
// Handle download-only mode based on flags
if opts.download_only {
// --download-only: set download-only mode
if !staged.is_finalization_locked() {
storage.get_ostree()?.change_finalization(&staged)?;
println!("Image downloaded, but will not be applied on reboot");
download_only_changed = true;
}
} else if !opts.check {
// --apply or no flags: clear download-only mode
// (skip if --check, which is read-only)
if staged.is_finalization_locked() {
storage.get_ostree()?.change_finalization(&staged)?;
println!("Staged deployment will now be applied on reboot");
download_only_changed = true;
}
}
} else if opts.download_only || opts.apply {
anyhow::bail!("No staged deployment found");
}

if !download_only_changed {
println!("Staged update present, not changed");
}

handle_staged_soft_reboot(booted_ostree, opts.soft_reboot, &host)?;
if opts.apply {
crate::reboot::reboot()?;
Expand All @@ -966,7 +1002,15 @@ async fn upgrade(
} else {
let stateroot = booted_ostree.stateroot();
let from = MergeState::from_stateroot(storage, &stateroot)?;
crate::deploy::stage(storage, from, &fetched, &spec, prog.clone()).await?;
crate::deploy::stage(
storage,
from,
&fetched,
&spec,
prog.clone(),
opts.download_only,
)
.await?;
changed = true;
if let Some(prev) = booted_image.as_ref() {
if let Some(fetched_manifest) = fetched.get_manifest(repo)? {
Expand Down Expand Up @@ -1071,7 +1115,7 @@ async fn switch_ostree(

let stateroot = booted_ostree.stateroot();
let from = MergeState::from_stateroot(storage, &stateroot)?;
crate::deploy::stage(storage, from, &fetched, &new_spec, prog.clone()).await?;
crate::deploy::stage(storage, from, &fetched, &new_spec, prog.clone(), false).await?;

storage.update_mtime()?;

Expand Down Expand Up @@ -1200,7 +1244,7 @@ async fn edit_ostree(

let stateroot = booted_ostree.stateroot();
let from = MergeState::from_stateroot(storage, &stateroot)?;
crate::deploy::stage(storage, from, &fetched, &new_spec, prog.clone()).await?;
crate::deploy::stage(storage, from, &fetched, &new_spec, prog.clone(), false).await?;

storage.update_mtime()?;

Expand Down
8 changes: 7 additions & 1 deletion crates/lib/src/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,7 @@ async fn deploy(
from: MergeState,
image: &ImageState,
origin: &glib::KeyFile,
lock_finalization: bool,
) -> Result<Deployment> {
// Compute the kernel argument overrides. In practice today this API is always expecting
// a merge deployment. The kargs code also always looks at the booted root (which
Expand Down Expand Up @@ -608,6 +609,9 @@ async fn deploy(
let stateroot = Some(stateroot);
let mut opts = ostree::SysrootDeployTreeOpts::default();

// Set finalization lock if requested
opts.locked = lock_finalization;

// Because the C API expects a Vec<&str>, convert the Cmdline to string slices.
// The references borrow from the Cmdline, which outlives this usage.
let override_kargs_refs = override_kargs
Expand Down Expand Up @@ -691,6 +695,7 @@ pub(crate) async fn stage(
image: &ImageState,
spec: &RequiredHostSpec<'_>,
prog: ProgressWriter,
lock_finalization: bool,
) -> Result<()> {
// Log the staging operation to systemd journal with comprehensive upgrade information
const STAGE_JOURNAL_ID: &str = "8f7a2b1c3d4e5f6a7b8c9d0e1f2a3b4c";
Expand Down Expand Up @@ -748,7 +753,8 @@ pub(crate) async fn stage(
})
.await;
let origin = origin_from_imageref(spec.image)?;
let deployment = crate::deploy::deploy(sysroot, from, image, &origin).await?;
let deployment =
crate::deploy::deploy(sysroot, from, image, &origin, lock_finalization).await?;

subtask.completed = true;
subtasks.push(subtask.clone());
Expand Down
2 changes: 2 additions & 0 deletions crates/lib/src/fixtures/spec-booted-pinned.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ status:
cachedUpdate: null
incompatible: false
pinned: true
downloadOnly: false
ostree:
checksum: 439f6bd2e2361bee292c1f31840d798c5ac5ba76483b8021dc9f7b0164ac0f48
deploySerial: 0
Expand All @@ -37,6 +38,7 @@ status:
cachedUpdate: null
incompatible: false
pinned: true
downloadOnly: false
ostree:
checksum: 99b2cc3b6edce9ebaef6a6076effa5ee3e1dcff3523016ffc94a1b27c6c67e12
deploySerial: 0
Expand Down
1 change: 1 addition & 0 deletions crates/lib/src/fixtures/spec-only-booted.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ status:
cachedUpdate: null
incompatible: false
pinned: false
downloadOnly: false
ostree:
checksum: 439f6bd2e2361bee292c1f31840d798c5ac5ba76483b8021dc9f7b0164ac0f48
deploySerial: 0
Expand Down
1 change: 1 addition & 0 deletions crates/lib/src/fixtures/spec-ostree-remote.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ status:
imageDigest: sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c
incompatible: false
pinned: false
downloadOnly: false
ostree:
checksum: 41af286dc0b172ed2f1ca934fd2278de4a1192302ffa07087cea2682e7d372e3
deploySerial: 0
Expand Down
2 changes: 2 additions & 0 deletions crates/lib/src/fixtures/spec-ostree-to-bootc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ status:
cachedUpdate: null
incompatible: false
pinned: false
downloadOnly: false
store: ostreeContainer
ostree:
checksum: 05cbf6dcae32e7a1c5a0774a648a073a5834a305ca92204b53fb6c281fe49db1
Expand All @@ -30,6 +31,7 @@ status:
cachedUpdate: null
incompatible: false
pinned: false
downloadOnly: false
store: null
ostree:
checksum: f9fa3a553ceaaaf30cf85bfe7eed46a822f7b8fd7e14c1e3389cbc3f6d27f791
Expand Down
2 changes: 2 additions & 0 deletions crates/lib/src/fixtures/spec-rfe-ostree-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ status:
cachedUpdate: null
incompatible: true
pinned: false
downloadOnly: false
store: null
ostree:
checksum: 1c24260fdd1be20f72a4a97a75c582834ee3431fbb0fa8e4f482bb219d633a45
Expand All @@ -21,6 +22,7 @@ status:
cachedUpdate: null
incompatible: false
pinned: false
downloadOnly: false
store: null
ostree:
checksum: f9fa3a553ceaaaf30cf85bfe7eed46a822f7b8fd7e14c1e3389cbc3f6d27f791
Expand Down
2 changes: 2 additions & 0 deletions crates/lib/src/fixtures/spec-staged-booted.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ status:
imageDigest: sha256:16dc2b6256b4ff0d2ec18d2dbfb06d117904010c8cf9732cdb022818cf7a7566
incompatible: false
pinned: false
downloadOnly: false
ostree:
checksum: 3c6dad657109522e0b2e49bf44b5420f16f0b438b5b9357e5132211cfbad135d
deploySerial: 0
Expand All @@ -37,6 +38,7 @@ status:
imageDigest: sha256:736b359467c9437c1ac915acaae952aad854e07eb4a16a94999a48af08c83c34
incompatible: false
pinned: false
downloadOnly: false
ostree:
checksum: 26836632adf6228d64ef07a26fd3efaf177104efd1f341a2cf7909a3e4e2c72c
deploySerial: 0
Expand Down
45 changes: 45 additions & 0 deletions crates/lib/src/fixtures/spec-staged-download-only.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
apiVersion: org.containers.bootc/v1alpha1
kind: BootcHost
metadata:
name: host
spec:
image:
image: quay.io/example/someimage:latest
transport: registry
signature: insecure
status:
staged:
image:
image:
image: quay.io/example/someimage:latest
transport: registry
signature: insecure
architecture: arm64
version: nightly
timestamp: 2023-10-14T19:22:15.42Z
imageDigest: sha256:16dc2b6256b4ff0d2ec18d2dbfb06d117904010c8cf9732cdb022818cf7a7566
incompatible: false
pinned: false
downloadOnly: true
ostree:
checksum: 3c6dad657109522e0b2e49bf44b5420f16f0b438b5b9357e5132211cfbad135d
deploySerial: 0
stateroot: default
booted:
image:
image:
image: quay.io/example/someimage:latest
transport: registry
signature: insecure
architecture: arm64
version: nightly
timestamp: 2023-09-30T19:22:16Z
imageDigest: sha256:736b359467c9437c1ac915acaae952aad854e07eb4a16a94999a48af08c83c34
incompatible: false
pinned: false
ostree:
checksum: 26836632adf6228d64ef07a26fd3efaf177104efd1f341a2cf7909a3e4e2c72c
deploySerial: 0
stateroot: default
rollback: null
isContainer: false
2 changes: 2 additions & 0 deletions crates/lib/src/fixtures/spec-staged-rollback.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ status:
imageDigest: sha256:16dc2b6256b4ff0d2ec18d2dbfb06d117904010c8cf9732cdb022818cf7a7566
incompatible: false
pinned: false
downloadOnly: false
ostree:
checksum: 3c6dad657109522e0b2e49bf44b5420f16f0b438b5b9357e5132211cfbad135d
deploySerial: 0
Expand All @@ -37,6 +38,7 @@ status:
imageDigest: sha256:736b359467c9437c1ac915acaae952aad854e07eb4a16a94999a48af08c83c34
incompatible: false
pinned: false
downloadOnly: false
ostree:
checksum: 26836632adf6228d64ef07a26fd3efaf177104efd1f341a2cf7909a3e4e2c72c
deploySerial: 0
Expand Down
2 changes: 2 additions & 0 deletions crates/lib/src/fixtures/spec-v1a1-orig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ status:
architecture: amd64
incompatible: false
pinned: false
downloadOnly: false
ostree:
checksum: 3c6dad657109522e0b2e49bf44b5420f16f0b438b5b9357e5132211cfbad135d
deploySerial: 0
Expand All @@ -36,6 +37,7 @@ status:
architecture: amd64
incompatible: false
pinned: false
downloadOnly: false
ostree:
checksum: 26836632adf6228d64ef07a26fd3efaf177104efd1f341a2cf7909a3e4e2c72c
deploySerial: 0
Expand Down
1 change: 1 addition & 0 deletions crates/lib/src/fixtures/spec-v1a1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ status:
imageDigest: sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c
incompatible: false
pinned: false
downloadOnly: false
ostree:
checksum: 41af286dc0b172ed2f1ca934fd2278de4a1192302ffa07087cea2682e7d372e3
deploySerial: 0
Expand Down
1 change: 1 addition & 0 deletions crates/lib/src/fixtures/spec-via-local-oci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ status:
cachedUpdate: null
incompatible: false
pinned: false
downloadOnly: false
ostree:
checksum: 439f6bd2e2361bee292c1f31840d798c5ac5ba76483b8021dc9f7b0164ac0f48
deploySerial: 0
Expand Down
2 changes: 1 addition & 1 deletion crates/lib/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2383,7 +2383,7 @@ pub(crate) async fn install_reset(opts: InstallResetOpts) -> Result<()> {
stateroot: target_stateroot.clone(),
kargs,
};
crate::deploy::stage(sysroot, from, &fetched, &spec, prog.clone()).await?;
crate::deploy::stage(sysroot, from, &fetched, &spec, prog.clone(), false).await?;

// Copy /boot entry from /etc/fstab to the new stateroot if it exists
if let Some(boot_spec) = read_boot_fstab_entry(rootfs)? {
Expand Down
5 changes: 5 additions & 0 deletions crates/lib/src/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,10 @@ pub struct BootEntry {
/// This is true if (relative to the booted system) this is a possible target for a soft reboot
#[serde(default)]
pub soft_reboot_capable: bool,
/// Whether this deployment is in download-only mode (prevented from automatic finalization on shutdown).
/// This is set via --download-only on the CLI.
#[serde(default)]
pub download_only: bool,
/// The container storage backend
#[serde(default)]
pub store: Option<Store>,
Expand Down Expand Up @@ -628,6 +632,7 @@ mod tests {
incompatible: false,
soft_reboot_capable: false,
pinned: false,
download_only: false,
store: None,
ostree: None,
composefs: None,
Expand Down
Loading