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