|
2 | 2 | //!
|
3 | 3 | //! APIs for operating on container images in the bootc storage.
|
4 | 4 |
|
5 |
| -use anyhow::{Context, Result}; |
| 5 | +use anyhow::{bail, ensure, Context, Result}; |
6 | 6 | use bootc_utils::CommandRunExt;
|
| 7 | +use cap_std_ext::cap_std::{self, fs::Dir}; |
| 8 | +use clap::ValueEnum; |
| 9 | +use comfy_table::{presets::NOTHING, Table}; |
7 | 10 | use fn_error_context::context;
|
8 | 11 | use ostree_ext::container::{ImageReference, Transport};
|
| 12 | +use serde::Serialize; |
9 | 13 |
|
10 |
| -use crate::imgstorage::Storage; |
| 14 | +use crate::{ |
| 15 | + boundimage::query_bound_images, |
| 16 | + cli::{ImageListFormat, ImageListType}, |
| 17 | +}; |
11 | 18 |
|
12 | 19 | /// The name of the image we push to containers-storage if nothing is specified.
|
13 | 20 | const IMAGE_DEFAULT: &str = "localhost/bootc";
|
14 | 21 |
|
| 22 | +#[derive(Clone, Serialize, ValueEnum)] |
| 23 | +enum ImageListTypeColumn { |
| 24 | + Host, |
| 25 | + Logical, |
| 26 | +} |
| 27 | + |
| 28 | +impl std::fmt::Display for ImageListTypeColumn { |
| 29 | + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 30 | + self.to_possible_value().unwrap().get_name().fmt(f) |
| 31 | + } |
| 32 | +} |
| 33 | + |
| 34 | +#[derive(Serialize)] |
| 35 | +struct ImageOutput { |
| 36 | + image_type: ImageListTypeColumn, |
| 37 | + image: String, |
| 38 | + // TODO: Add hash, size, etc? Difficult because [`ostree_ext::container::store::list_images`] |
| 39 | + // only gives us the pullspec. |
| 40 | +} |
| 41 | + |
| 42 | +#[context("Listing host images")] |
| 43 | +fn list_host_images(sysroot: &crate::store::Storage) -> Result<Vec<ImageOutput>> { |
| 44 | + let repo = sysroot.repo(); |
| 45 | + let images = ostree_ext::container::store::list_images(&repo).context("Querying images")?; |
| 46 | + |
| 47 | + Ok(images |
| 48 | + .iter() |
| 49 | + .map(|x| ImageOutput { |
| 50 | + image: x.to_string(), |
| 51 | + image_type: ImageListTypeColumn::Host, |
| 52 | + }) |
| 53 | + .collect()) |
| 54 | +} |
| 55 | + |
| 56 | +#[context("Listing logical images")] |
| 57 | +fn list_logical_images(root: &Dir) -> Result<Vec<ImageOutput>> { |
| 58 | + let bound = query_bound_images(root)?; |
| 59 | + |
| 60 | + Ok(bound |
| 61 | + .iter() |
| 62 | + .map(|x| ImageOutput { |
| 63 | + image: x.image.clone(), |
| 64 | + image_type: ImageListTypeColumn::Logical, |
| 65 | + }) |
| 66 | + .collect()) |
| 67 | +} |
| 68 | + |
| 69 | +async fn list_images(list_type: ImageListType) -> Result<Vec<ImageOutput>> { |
| 70 | + let rootfs = cap_std::fs::Dir::open_ambient_dir("/", cap_std::ambient_authority()) |
| 71 | + .context("Opening /")?; |
| 72 | + |
| 73 | + let sysroot: Option<crate::store::Storage> = |
| 74 | + if ostree_ext::container_utils::running_in_container() { |
| 75 | + None |
| 76 | + } else { |
| 77 | + Some(crate::cli::get_storage().await?) |
| 78 | + }; |
| 79 | + |
| 80 | + Ok(match (list_type, sysroot) { |
| 81 | + // TODO: Should we list just logical images silently here, or error? |
| 82 | + (ImageListType::All, None) => list_logical_images(&rootfs)?, |
| 83 | + (ImageListType::All, Some(sysroot)) => list_host_images(&sysroot)? |
| 84 | + .into_iter() |
| 85 | + .chain(list_logical_images(&rootfs)?) |
| 86 | + .collect(), |
| 87 | + (ImageListType::Logical, _) => list_logical_images(&rootfs)?, |
| 88 | + (ImageListType::Host, None) => { |
| 89 | + bail!("Listing host images requires a booted bootc system") |
| 90 | + } |
| 91 | + (ImageListType::Host, Some(sysroot)) => list_host_images(&sysroot)?, |
| 92 | + }) |
| 93 | +} |
| 94 | + |
15 | 95 | #[context("Listing images")]
|
16 |
| -pub(crate) async fn list_entrypoint() -> Result<()> { |
17 |
| - let sysroot = crate::cli::get_storage().await?; |
18 |
| - let repo = &sysroot.repo(); |
| 96 | +pub(crate) async fn list_entrypoint( |
| 97 | + list_type: ImageListType, |
| 98 | + list_format: ImageListFormat, |
| 99 | +) -> Result<()> { |
| 100 | + let images = list_images(list_type).await?; |
19 | 101 |
|
20 |
| - let images = ostree_ext::container::store::list_images(repo).context("Querying images")?; |
| 102 | + match list_format { |
| 103 | + ImageListFormat::Table => { |
| 104 | + let mut table = Table::new(); |
21 | 105 |
|
22 |
| - println!("# Host images"); |
23 |
| - for image in images { |
24 |
| - println!("{image}"); |
25 |
| - } |
26 |
| - println!(); |
| 106 | + table |
| 107 | + .load_preset(NOTHING) |
| 108 | + .set_header(vec!["REPOSITORY", "TYPE"]); |
| 109 | + |
| 110 | + for image in images { |
| 111 | + table.add_row(vec![image.image, image.image_type.to_string()]); |
| 112 | + } |
27 | 113 |
|
28 |
| - println!("# Logically bound images"); |
29 |
| - let mut listcmd = sysroot.get_ensure_imgstore()?.new_image_cmd()?; |
30 |
| - listcmd.arg("list"); |
31 |
| - listcmd.run()?; |
| 114 | + println!("{table}"); |
| 115 | + } |
| 116 | + ImageListFormat::Json => { |
| 117 | + let mut stdout = std::io::stdout(); |
| 118 | + serde_json::to_writer_pretty(&mut stdout, &images)?; |
| 119 | + } |
| 120 | + } |
32 | 121 |
|
33 | 122 | Ok(())
|
34 | 123 | }
|
@@ -79,7 +168,7 @@ pub(crate) async fn push_entrypoint(source: Option<&str>, target: Option<&str>)
|
79 | 168 | /// Thin wrapper for invoking `podman image <X>` but set up for our internal
|
80 | 169 | /// image store (as distinct from /var/lib/containers default).
|
81 | 170 | pub(crate) async fn imgcmd_entrypoint(
|
82 |
| - storage: &Storage, |
| 171 | + storage: &crate::imgstorage::Storage, |
83 | 172 | arg: &str,
|
84 | 173 | args: &[std::ffi::OsString],
|
85 | 174 | ) -> std::result::Result<(), anyhow::Error> {
|
|
0 commit comments