Skip to content

Commit a578483

Browse files
shi2wei3cgwalters
authored andcommitted
lib: Add --from-downloaded flag for bootc upgrade
Add a new --from-downloaded flag to bootc upgrade that allows users to unlock a staged deployment created with --download-only without fetching updates from the container image source. This provides a way to apply already-downloaded updates without triggering a fetch operation, which is useful for scheduled maintenance workflows where the update was downloaded earlier and should now be applied at a scheduled time. Usage: # Download update without applying bootc upgrade --download-only # Later: Apply the staged update (without fetching from image source) bootc upgrade --from-downloaded # Or: Apply staged update and reboot immediately bootc upgrade --from-downloaded --apply The flag conflicts with --check and --download-only as those operations have different purposes. It can be combined with --apply to immediately reboot after unlocking the staged deployment. This commit also updates the documentation (upgrades.md) to describe all three ways to apply a download-only update, and updates the download-only test case (test-25) to use --from-downloaded instead of plain 'bootc upgrade' when clearing the download-only flag. Assisted-by: Claude Code (Sonnet 4.5) Signed-off-by: Wei Shi <wshi@redhat.com>
1 parent bf08c17 commit a578483

File tree

7 files changed

+79
-22
lines changed

7 files changed

+79
-22
lines changed

crates/lib/src/bootc_composefs/update.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,9 @@ pub(crate) async fn upgrade_composefs(
276276
if opts.download_only {
277277
anyhow::bail!("--download-only is not yet supported for composefs backend");
278278
}
279+
if opts.from_downloaded {
280+
anyhow::bail!("--from-downloaded is not yet supported for composefs backend");
281+
}
279282

280283
let host = get_composefs_status(storage, composefs)
281284
.await

crates/lib/src/cli.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,14 @@ pub(crate) struct UpgradeOpts {
108108
#[clap(long, conflicts_with_all = ["check", "apply"])]
109109
pub(crate) download_only: bool,
110110

111+
/// Apply a staged deployment that was previously downloaded with --download-only.
112+
///
113+
/// This unlocks the staged deployment without fetching updates from the container image source.
114+
/// The deployment will be applied on the next shutdown or reboot. Use with --apply to
115+
/// reboot immediately.
116+
#[clap(long, conflicts_with_all = ["check", "download_only"])]
117+
pub(crate) from_downloaded: bool,
118+
111119
#[clap(flatten)]
112120
pub(crate) progress: ProgressOptions,
113121
}
@@ -933,6 +941,28 @@ async fn upgrade(
933941
let staged = host.status.staged.as_ref();
934942
let staged_image = staged.as_ref().and_then(|s| s.image.as_ref());
935943
let mut changed = false;
944+
945+
// Handle --from-downloaded: unlock existing staged deployment without fetching from image source
946+
if opts.from_downloaded {
947+
let ostree = storage.get_ostree()?;
948+
let staged_deployment = ostree
949+
.staged_deployment()
950+
.ok_or_else(|| anyhow::anyhow!("No staged deployment found"))?;
951+
952+
if staged_deployment.is_finalization_locked() {
953+
ostree.change_finalization(&staged_deployment)?;
954+
println!("Staged deployment will now be applied on reboot");
955+
} else {
956+
println!("Staged deployment is already set to apply on reboot");
957+
}
958+
959+
handle_staged_soft_reboot(booted_ostree, opts.soft_reboot, &host)?;
960+
if opts.apply {
961+
crate::reboot::reboot()?;
962+
}
963+
return Ok(());
964+
}
965+
936966
if opts.check {
937967
let imgref = imgref.clone().into();
938968
let mut imp = crate::deploy::new_importer(repo, &imgref).await?;

docs/src/man/bootc-upgrade.8.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ Soft reboot allows faster system restart by avoiding full hardware reboot when p
6565

6666
Download and stage the update without applying it
6767

68+
**--from-downloaded**
69+
70+
Apply a staged deployment that was previously downloaded with --download-only
71+
6872
<!-- END GENERATED OPTIONS -->
6973

7074
# EXAMPLES

docs/src/upgrades.md

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ updates from a registry and booting into them, while supporting rollback.
66

77
## The `bootc upgrade` verb
88

9-
This will query the registry and queue an updated container image for the next boot.
9+
This will query the container image source and queue an updated container image for the next boot.
1010

1111
This is backed today by ostree, implementing an A/B style upgrade system.
1212
Changes to the base image are staged, and the running system is not
@@ -23,7 +23,7 @@ them on the next reboot:
2323
bootc upgrade --download-only
2424
```
2525

26-
This will pull the new container image from the registry and create a staged deployment
26+
This will pull the new container image from the container image source and create a staged deployment
2727
in download-only mode. The deployment will not be applied on shutdown or reboot until
2828
you explicitly apply it.
2929

@@ -40,24 +40,36 @@ In the output, you'll see `Download-only: yes` for deployments in download-only
4040

4141
#### Applying download-only updates
4242

43-
There are two ways to apply a staged update that is in download-only mode:
43+
There are three ways to apply a staged update that is in download-only mode:
4444

45-
**Option 1: Apply immediately with reboot**
45+
**Option 1: Apply the staged update without checking for newer updates**
4646

4747
```shell
48-
bootc upgrade --apply
48+
bootc upgrade --from-downloaded
4949
```
5050

51-
This will clear the download-only flag and immediately reboot into the staged deployment.
51+
This unlocks the staged deployment for automatic application on the next shutdown or reboot,
52+
without fetching updates from the container image source. This is useful when you want to apply the
53+
already-downloaded update at a scheduled time.
5254

53-
**Option 2: Clear download-only for automatic application**
55+
**Option 2: Apply the staged update and reboot immediately**
56+
57+
```shell
58+
bootc upgrade --from-downloaded --apply
59+
```
60+
61+
This unlocks the staged deployment and immediately reboots into it, without checking for
62+
newer updates.
63+
64+
**Option 3: Check for newer updates and apply**
5465

5566
```shell
5667
bootc upgrade
5768
```
5869

59-
Running `bootc upgrade` without flags on a staged deployment in download-only mode will
60-
clear the flag. The deployment will then be applied automatically on the next shutdown or reboot.
70+
Running `bootc upgrade` without flags will pull from the container image source to check for updates.
71+
If the staged deployment matches the latest available update, it will be unlocked. If a newer update is
72+
available, the staged deployment will be replaced with the newer version.
6173

6274
#### Checking for updates without side effects
6375

@@ -84,15 +96,23 @@ bootc status --verbose
8496
# 3. Test or wait for maintenance window...
8597

8698
# 4. Apply the update (choose one):
87-
# Option A: Clear download-only flag and let it apply on next shutdown
88-
bootc upgrade
99+
# Option A: Apply staged update without fetching from image source
100+
bootc upgrade --from-downloaded
101+
102+
# Option B: Apply staged update and reboot immediately (without fetching from image source)
103+
bootc upgrade --from-downloaded --apply
89104

90-
# Option B: Apply immediately with reboot
91-
bootc upgrade --apply
105+
# Option C: Check for newer updates first, then apply
106+
bootc upgrade
92107
```
93108

94109
**Important notes**:
95110

111+
- **Image source check difference**: `bootc upgrade --from-downloaded` does NOT fetch from the
112+
container image source to check for newer updates, while `bootc upgrade` always does.
113+
Use `--from-downloaded` when you want to apply the specific version you already downloaded,
114+
regardless of whether newer updates are available.
115+
96116
- If you reboot before applying a download-only update, the system will boot into the
97117
current deployment and the staged deployment will be discarded. However, the downloaded image
98118
data remains cached, so re-running `bootc upgrade --download-only` will be fast and won't

tmt/plans/integration.fmf

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,12 @@ execute:
9595
test:
9696
- /tmt/tests/tests/test-25-soft-reboot
9797

98-
/plan-25-download-only-upgrade:
98+
/plan-26-download-only-upgrade:
9999
summary: Execute download-only upgrade tests
100100
discover:
101101
how: fmf
102102
test:
103-
- /tmt/tests/tests/test-25-download-only-upgrade
103+
- /tmt/tests/tests/test-26-download-only-upgrade
104104

105105
/plan-27-custom-selinux-policy:
106106
summary: Execute custom selinux policy test

tmt/tests/booted/test-25-download-only-upgrade.nu renamed to tmt/tests/booted/test-download-only-upgrade.nu

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# number: 25
1+
# number: 26
22
# tmt:
33
# summary: Execute download-only upgrade tests
44
# duration: 40m
@@ -13,7 +13,7 @@
1313
# reboot (should still boot into v1, staged deployment discarded)
1414
# verify staged deployment is null (discarded on reboot)
1515
# bootc upgrade --download-only (re-stage v2 in download-only mode)
16-
# bootc upgrade (clear download-only mode)
16+
# bootc upgrade --from-downloaded (clear download-only mode without fetching from image source)
1717
# reboot (should boot into v2)
1818
#
1919
use std assert
@@ -113,9 +113,9 @@ def third_boot [] {
113113
assert ($status_json.status.staged != null) "Staged deployment should exist"
114114
assert ($status_json.status.staged.downloadOnly) "Staged deployment should be in download-only mode"
115115

116-
# Now clear download-only mode by running upgrade without flags
117-
print "Clearing download-only mode with bootc upgrade"
118-
bootc upgrade
116+
# Now clear download-only mode by running upgrade --from-downloaded (without fetching from image source)
117+
print "Clearing download-only mode with bootc upgrade --from-downloaded"
118+
bootc upgrade --from-downloaded
119119

120120
# Verify via JSON that deployment is not in download-only mode
121121
let status_after_json = bootc status --json | from json

tmt/tests/tests.fmf

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@
4141
duration: 30m
4242
test: nu booted/test-soft-reboot.nu
4343

44-
/test-25-download-only-upgrade:
44+
/test-26-download-only-upgrade:
4545
summary: Execute download-only upgrade tests
4646
duration: 40m
47-
test: nu booted/test-25-download-only-upgrade.nu
47+
test: nu booted/test-download-only-upgrade.nu
4848

4949
/test-27-custom-selinux-policy:
5050
summary: Execute custom selinux policy test

0 commit comments

Comments
 (0)