max / alloy
- Co-Authored-By
- Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 file changed,
+76 insertions,
-6 deletions
| @@ -589,12 +589,13 @@ | |||
| 589 | 589 | /// reached through `distrobox enter -- `, which is the documented host-side | |
| 590 | 590 | /// idiom for it. | |
| 591 | 591 | /// | |
| 592 | - | /// `/usr/bin/{bin}` is an assumption, and a flagged one: `--bin` wants an | |
| 593 | - | /// absolute path inside the container, the spec names a command rather than | |
| 594 | - | /// a path, and resolving it properly means asking the box where the command | |
| 595 | - | /// lives. Distrobox is not installed on the dev box, so this path is argv | |
| 596 | - | /// only — see the module docs on parsers checked against nothing but their | |
| 597 | - | /// own fixtures, which is the same trap. | |
| 592 | + | /// `/usr/bin/{bin}` because `--bin` wants an absolute path inside the | |
| 593 | + | /// container while the spec names a command. Verified against distrobox | |
| 594 | + | /// 1.8.2.5 — the version the Containerfile pins — on a real Fedora box: | |
| 595 | + | /// both this argv and `create`'s reach the tool and do what they claim. | |
| 596 | + | /// It remains a guess for an arbitrary image, which distrobox accepts and | |
| 597 | + | /// which need not put its commands in `/usr/bin`; resolving it properly | |
| 598 | + | /// means asking the box where the command lives. | |
| 598 | 599 | fn export(&self, name: &str, spec: &SpecBox) -> Result<Vec<Effect>> { | |
| 599 | 600 | let bins = spec.bins(name)?; | |
| 600 | 601 | let dir = export_dir()?; | |
| @@ -2765,6 +2766,75 @@ | |||
| 2765 | 2766 | std::fs::remove_dir_all(&mount).ok(); | |
| 2766 | 2767 | } | |
| 2767 | 2768 | ||
| 2769 | + | /// Create and export a real `host` box through distrobox. | |
| 2770 | + | /// | |
| 2771 | + | /// The `host` path is the one Alloy hands entirely to someone else's tool, | |
| 2772 | + | /// so what needs checking is not the behavior but the argv: that | |
| 2773 | + | /// `distrobox create --yes` really is non-interactive, and that | |
| 2774 | + | /// `distrobox enter -- distrobox-export` is the shape that reaches the | |
| 2775 | + | /// export tool from the host. Both were assumptions until this ran. | |
| 2776 | + | /// | |
| 2777 | + | /// It also settles `/usr/bin/{bin}`. `--bin` wants an absolute path inside | |
| 2778 | + | /// the container while the spec names a command, and Fedora images put | |
| 2779 | + | /// commands in `/usr/bin`, so the guess holds for the image Alloy ships. | |
| 2780 | + | /// It is still a guess for an arbitrary image, which distrobox accepts. | |
| 2781 | + | /// | |
| 2782 | + | /// Exports `dnf`, deliberately: an exported wrapper takes the binary's | |
| 2783 | + | /// bare name, so exporting something the host already has shadows it on the | |
| 2784 | + | /// PATH. `dnf` is in every Fedora image and on no Pop!_OS host, which is | |
| 2785 | + | /// also the real use case — you export a tool from a box because the host | |
| 2786 | + | /// does not have it. | |
| 2787 | + | /// | |
| 2788 | + | /// Ignored by default: it needs distrobox, and the first `enter` runs | |
| 2789 | + | /// distrobox's full container setup, which pulls packages and takes minutes. | |
| 2790 | + | #[test] | |
| 2791 | + | #[ignore = "requires distrobox, creates a container, and takes minutes"] | |
| 2792 | + | fn a_real_host_box_is_created_and_exported_through_distrobox() { | |
| 2793 | + | let name = "alloy-test-host"; | |
| 2794 | + | let spec = Spec::parse(&format!( | |
| 2795 | + | r#" | |
| 2796 | + | [box.{name}] | |
| 2797 | + | level = "host" | |
| 2798 | + | image = "registry.fedoraproject.org/fedora:42" | |
| 2799 | + | export = {{ bin = ["dnf"] }} | |
| 2800 | + | "# | |
| 2801 | + | )) | |
| 2802 | + | .unwrap(); | |
| 2803 | + | ||
| 2804 | + | let (_, entry) = spec.resolve(name).unwrap(); | |
| 2805 | + | let mut log = CommandLog::new(); | |
| 2806 | + | let exported = export_dir().unwrap().join("dnf"); | |
| 2807 | + | let cleanup = || { | |
| 2808 | + | let _ = Invocation::new("distrobox").args(["rm", "--force", name]).probe(); | |
| 2809 | + | let _ = Invocation::new("podman").args(["rm", "--force", name]).probe(); | |
| 2810 | + | std::fs::remove_file(&exported).ok(); | |
| 2811 | + | }; | |
| 2812 | + | cleanup(); | |
| 2813 | + | assert!(!exported.exists(), "the export lands somewhere empty"); | |
| 2814 | + | ||
| 2815 | + | Podman.create(name, entry).unwrap().run(&mut log).unwrap(); | |
| 2816 | + | ||
| 2817 | + | for effect in Podman.export(name, entry).unwrap() { | |
| 2818 | + | let Effect::Run(_) = &effect else { | |
| 2819 | + | panic!("host exports go through distrobox-export, not a written file"); | |
| 2820 | + | }; | |
| 2821 | + | effect.apply(&mut log).expect("distrobox accepts the argv Alloy builds"); | |
| 2822 | + | } | |
| 2823 | + | ||
| 2824 | + | assert!( | |
| 2825 | + | exported.is_file(), | |
| 2826 | + | "distrobox-export wrote the wrapper where Alloy asked it to" | |
| 2827 | + | ); | |
| 2828 | + | let wrapper = std::fs::read_to_string(&exported).unwrap(); | |
| 2829 | + | assert!( | |
| 2830 | + | wrapper.contains(name) && wrapper.contains("/usr/bin/dnf"), | |
| 2831 | + | "the wrapper points back at this box's binary:\n{wrapper}" | |
| 2832 | + | ); | |
| 2833 | + | ||
| 2834 | + | cleanup(); | |
| 2835 | + | assert!(!exported.exists(), "and the test leaves nothing on the PATH"); | |
| 2836 | + | } | |
| 2837 | + | ||
| 2768 | 2838 | /// Parse this machine's real podman and flatpak output. | |
| 2769 | 2839 | /// | |
| 2770 | 2840 | /// Ignored by default: needs both installed, and what it finds depends on |