max / alloy
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
1 file changed,
+87 insertions,
-1 deletion
| @@ -904,6 +904,36 @@ | |||
| 904 | 904 | /// working the day that stops being true. | |
| 905 | 905 | const LIVE_SOURCE_TAG: &str = "local"; | |
| 906 | 906 | ||
| 907 | + | /// What the installed machine fetches for every update after this one. | |
| 908 | + | /// | |
| 909 | + | /// **Where the image is installed *from* and where updates come *from* are two | |
| 910 | + | /// different places, and omitting this conflated them.** bootc defaults | |
| 911 | + | /// `--target-imgref` to the source, and `--target-transport` separately defaults | |
| 912 | + | /// to `registry`, so leaving it out did not produce a sane fallback: it paired | |
| 913 | + | /// the *source path* with the *registry transport* and wrote | |
| 914 | + | /// | |
| 915 | + | /// ```text | |
| 916 | + | /// ostree-unverified-registry:/run/initramfs/live/source/alloy:local | |
| 917 | + | /// ``` | |
| 918 | + | /// | |
| 919 | + | /// into the deployment origin. A registry reference whose name is a filesystem | |
| 920 | + | /// path. Measured on the one installed machine, where that path is necessarily | |
| 921 | + | /// absent because it is the live medium's mount point, and `skopeo inspect` on | |
| 922 | + | /// the equivalent reference answers `invalid reference format`. So every machine | |
| 923 | + | /// installed before this constant existed cannot take an update, and fails at | |
| 924 | + | /// reference parsing rather than with anything that names the real problem. | |
| 925 | + | /// | |
| 926 | + | /// `:43` and not `:latest`, matching docs/IMAGE.md: a Fedora major bump is a | |
| 927 | + | /// deliberate act that Alloy tests and ships, so it must not arrive through the | |
| 928 | + | /// same channel as ordinary updates. `:latest` is for casual users who opt into | |
| 929 | + | /// it; a fresh install follows the version it was built against. | |
| 930 | + | /// | |
| 931 | + | /// The registry does not exist yet (GO task 1372b159 provisions it with cosign | |
| 932 | + | /// signing). Naming it anyway is deliberate: an unpublished reference degrades to | |
| 933 | + | /// "no update found", which is recoverable and legible, while the alternative | |
| 934 | + | /// degrades to "updates can never work". | |
| 935 | + | const UPDATE_IMAGE: &str = "quay.io/alloy/alloy:43"; | |
| 936 | + | ||
| 907 | 937 | /// The `--source-imgref` for this run, or `None` when bootc's own default | |
| 908 | 938 | /// applies. | |
| 909 | 939 | /// | |
| @@ -970,6 +1000,12 @@ | |||
| 970 | 1000 | if let Some(source) = live_source_image() { | |
| 971 | 1001 | install = install.args(["--source-imgref", &source]); | |
| 972 | 1002 | } | |
| 1003 | + | // Unconditional, unlike --source-imgref above. That one is detected | |
| 1004 | + | // because the source genuinely differs between the ISO and a dev | |
| 1005 | + | // container; where updates come from does not, and a machine | |
| 1006 | + | // installed during development wanting the same update stream as | |
| 1007 | + | // everyone else is correct rather than a side effect. | |
| 1008 | + | install = install.args(["--target-imgref", UPDATE_IMAGE]); | |
| 973 | 1009 | install.arg(disk) | |
| 974 | 1010 | }), | |
| 975 | 1011 | // bootc returns when the install is done, not when the kernel and | |
| @@ -2738,7 +2774,13 @@ | |||
| 2738 | 2774 | let shown = view.plan_display(); | |
| 2739 | 2775 | ||
| 2740 | 2776 | assert_eq!(shown.len(), 4, "{shown:#?}"); | |
| 2741 | - | assert_eq!(shown[0], "bootc install to-disk --wipe /dev/sda"); | |
| 2777 | + | // The update stream is on the line the confirm screen shows, which is | |
| 2778 | + | // where a flag that decides how the machine behaves for its whole life | |
| 2779 | + | // belongs. | |
| 2780 | + | assert_eq!( | |
| 2781 | + | shown[0], | |
| 2782 | + | format!("bootc install to-disk --wipe --target-imgref {UPDATE_IMAGE} /dev/sda"), | |
| 2783 | + | ); | |
| 2742 | 2784 | assert_eq!(shown[1], "udevadm settle"); | |
| 2743 | 2785 | assert_eq!(shown[2], "mkdir -p /mnt/alloy-target"); | |
| 2744 | 2786 | assert!(shown[3].starts_with("lsblk"), "{}", shown[3]); | |
| @@ -3441,6 +3483,50 @@ | |||
| 3441 | 3483 | ); | |
| 3442 | 3484 | } | |
| 3443 | 3485 | ||
| 3486 | + | // The install names where updates come from, and this is the only place that | |
| 3487 | + | // can. Its absence is invisible at install time and total afterwards: bootc | |
| 3488 | + | // succeeds, the machine boots, and `bootc upgrade` fails months later on a | |
| 3489 | + | // reference nobody wrote deliberately. It was | |
| 3490 | + | // `ostree-unverified-registry:/run/initramfs/live/source/alloy:local` on the | |
| 3491 | + | // one machine installed without it, which is a registry reference naming a | |
| 3492 | + | // path that only exists while the installer stick is plugged in. | |
| 3493 | + | #[test] | |
| 3494 | + | fn the_plan_names_where_updates_come_from() { | |
| 3495 | + | let plan = install_plan("/dev/sda", "host", "user", "pw", false); | |
| 3496 | + | let install = plan | |
| 3497 | + | .iter() | |
| 3498 | + | .map(Stage::display) | |
| 3499 | + | .find(|line| line.contains("install to-disk")) | |
| 3500 | + | .expect("the plan installs"); | |
| 3501 | + | ||
| 3502 | + | assert!( | |
| 3503 | + | install.contains("--target-imgref"), | |
| 3504 | + | "without this the origin records the install medium: {install}", | |
| 3505 | + | ); | |
| 3506 | + | assert!(install.contains(UPDATE_IMAGE), "{install}"); | |
| 3507 | + | } | |
| 3508 | + | ||
| 3509 | + | // A registry reference, not a path and not a transport prefix. The bug this | |
| 3510 | + | // guards against was bootc pairing a source *path* with the default | |
| 3511 | + | // *registry* transport, so a value that still looks like a path would | |
| 3512 | + | // reproduce it exactly while passing the test above. | |
| 3513 | + | #[test] | |
| 3514 | + | fn the_update_image_is_a_registry_reference() { | |
| 3515 | + | assert!( | |
| 3516 | + | !UPDATE_IMAGE.starts_with('/'), | |
| 3517 | + | "a path is what the bug wrote: {UPDATE_IMAGE}", | |
| 3518 | + | ); | |
| 3519 | + | assert!( | |
| 3520 | + | !UPDATE_IMAGE.contains(':') || UPDATE_IMAGE.split(':').count() == 2, | |
| 3521 | + | "one colon, separating image from tag: {UPDATE_IMAGE}", | |
| 3522 | + | ); | |
| 3523 | + | let (image, tag) = UPDATE_IMAGE.split_once(':').expect("a tag is pinned"); | |
| 3524 | + | assert!(image.contains('.'), "a registry host: {image}"); | |
| 3525 | + | // Not `latest`: docs/IMAGE.md makes a Fedora major bump a deliberate act, | |
| 3526 | + | // and `latest` would deliver one through the ordinary update channel. | |
| 3527 | + | assert_ne!(tag, "latest", "a fresh install follows its own version"); | |
| 3528 | + | } | |
| 3529 | + | ||
| 3444 | 3530 | // Missing answers cannot happen from the summary — every step gates on its | |
| 3445 | 3531 | // own validation — but a partial plan would render a command with a hole in | |
| 3446 | 3532 | // it, so it returns nothing instead. |